use super::{BackbackSwapEvent, Backpack, UiHeadState}; use crate::{ GameState, global_observer, heads::HeadsImages, loading_assets::UIAssets, sounds::PlaySound, }; use bevy::prelude::*; static HEAD_SLOTS: usize = 5; #[derive(Event, Clone, Copy, Reflect, PartialEq)] pub enum BackpackAction { Left, Right, Swap, OpenClose, } #[derive(Component, Default)] struct BackpackMarker; #[derive(Component, Default)] struct BackpackCountText; #[derive(Component, Default)] struct HeadSelector(pub usize); #[derive(Component, Default)] struct HeadImage(pub usize); #[derive(Component, Default)] struct HeadDamage(pub usize); #[derive(Resource, Default, Debug)] struct BackpackUiState { heads: [Option; 5], scroll: usize, count: usize, current_slot: usize, open: bool, } impl BackpackUiState { fn relative_current_slot(&self) -> usize { self.current_slot.saturating_sub(self.scroll) } } pub fn plugin(app: &mut App) { app.init_resource::(); app.add_systems(OnEnter(GameState::Playing), setup); app.add_systems( Update, (update, sync_on_change, update_visibility, update_count) .run_if(in_state(GameState::Playing)), ); global_observer!(app, swap_head_inputs); } fn setup(mut commands: Commands, assets: Res) { commands .spawn(( Name::new("backpack-ui"), BackpackMarker, Visibility::Hidden, Node { position_type: PositionType::Absolute, top: Val::Px(20.0), right: Val::Px(20.0), height: Val::Px(74.0), ..default() }, )) .with_children(|parent| { for i in 0..HEAD_SLOTS { spawn_head_ui( parent, assets.head_bg.clone(), assets.head_regular.clone(), assets.head_selector.clone(), assets.head_damage.clone(), i, ); } }); commands.spawn(( Text::new("0"), BackpackCountText, TextFont { font: assets.font.clone(), font_size: 34.0, ..default() }, TextColor(Color::Srgba(Srgba::rgb(0., 1., 0.))), TextLayout::new_with_justify(JustifyText::Center), Node { position_type: PositionType::Absolute, top: Val::Px(20.0), right: Val::Px(20.0), ..default() }, )); } fn spawn_head_ui( parent: &mut ChildBuilder, bg: Handle, regular: Handle, selector: Handle, damage: Handle, head_slot: usize, ) { const SIZE: f32 = 90.0; const DAMAGE_SIZE: f32 = 74.0; parent .spawn((Node { position_type: PositionType::Relative, justify_content: JustifyContent::Center, align_items: AlignItems::Center, width: Val::Px(SIZE), ..default() },)) .with_children(|parent| { parent.spawn(( Name::new("selector"), Node { position_type: PositionType::Absolute, bottom: Val::Px(-30.0), ..default() }, Visibility::Hidden, ImageNode::new(selector).with_flip_y(), HeadSelector(head_slot), )); parent.spawn(( Name::new("bg"), Node { position_type: PositionType::Absolute, ..default() }, ImageNode::new(bg), )); parent.spawn(( Name::new("head"), Node { position_type: PositionType::Absolute, ..default() }, ImageNode::default(), Visibility::Hidden, HeadImage(head_slot), )); parent.spawn(( Name::new("rings"), Node { position_type: PositionType::Absolute, ..default() }, ImageNode::new(regular), )); parent .spawn(( Name::new("health"), Node { height: Val::Px(DAMAGE_SIZE), width: Val::Px(DAMAGE_SIZE), ..default() }, )) .with_children(|parent| { parent .spawn(( Name::new("damage_ring"), HeadDamage(head_slot), Node { position_type: PositionType::Absolute, display: Display::Block, overflow: Overflow::clip(), top: Val::Px(0.), left: Val::Px(0.), right: Val::Px(0.), height: Val::Percent(25.), ..default() }, )) .with_child(ImageNode::new(damage)); }); }); } fn update_visibility( state: Res, mut backpack: Query<&mut Visibility, (With, Without)>, mut count: Query<&mut Visibility, (Without, With)>, ) { if state.is_changed() { for mut vis in backpack.iter_mut() { *vis = if state.open { Visibility::Visible } else { Visibility::Hidden }; } for mut vis in count.iter_mut() { *vis = if !state.open { Visibility::Visible } else { Visibility::Hidden }; } } } fn update_count( state: Res, text: Query>, mut writer: TextUiWriter, ) { if state.is_changed() { let Some(text) = text.iter().next() else { return; }; *writer.text(text, 0) = state.count.to_string(); } } fn update( state: Res, heads_images: Res, mut head_image: Query<(&HeadImage, &mut Visibility, &mut ImageNode), Without>, mut head_damage: Query<(&HeadDamage, &mut Node), Without>, mut head_selector: Query<(&HeadSelector, &mut Visibility), Without>, ) { if state.is_changed() { for (HeadImage(head), mut vis, mut image) in head_image.iter_mut() { if let Some(head) = &state.heads[*head] { *vis = Visibility::Inherited; image.image = heads_images.heads[head.head].clone(); } else { *vis = Visibility::Hidden; } } for (HeadDamage(head), mut node) in head_damage.iter_mut() { if let Some(head) = &state.heads[*head] { node.height = Val::Percent(head.damage() * 100.0); } } for (HeadSelector(head), mut vis) in head_selector.iter_mut() { *vis = if *head == state.relative_current_slot() { Visibility::Inherited } else { Visibility::Hidden }; } } } fn swap_head_inputs( trigger: Trigger, backpack: Res, mut commands: Commands, mut state: ResMut, time: Res