use bevy::{input_focus::{AutoFocus, InputFocus}, ui::RelativeCursorPosition}; use crate::{attachment::PartInShip, client::colors, ecs::{CanCraft, CraftingUi, Drill, MainCamera, Me, Part, SingleStorage, ToggleDrillEvent}, prelude::*}; pub fn crafting_ui_plugin(app: &mut App) { app.add_systems(Update, (close_button, drill_button, drill_state_change, single_storage_display)); } #[derive(Component)] struct CloseButton(Entity); // stores corresponding menu entity #[derive(Component)] struct PreviousInteraction(Interaction); #[derive(Component)] struct DrillButton(Entity); // stores corresponding part #[derive(Component)] struct SingleStorageDisplay(Entity); // stores corresponding part pub fn open_crafting_ui( ev: On>, crafting_parts: Query<(Entity, &Transform, Option<&Drill>, Option<&SingleStorage>), (With, With)>, hearty: Query<(Entity, &Transform, Option<&Drill>, Option<&SingleStorage>), (With, With)>, camera: Single<(Entity, &Camera, &GlobalTransform), (With, Without)>, commands: Commands, ) { if matches!(ev.button, PointerButton::Secondary) { let (entity, transform, drill, single_storage) = if let Ok(part) = crafting_parts.get(ev.entity) { part } else if let Ok(part) = hearty.get(ev.entity) { part } else { return }; // we have our crafting entity! // now make the ui setup_ui(entity, transform, commands, camera, drill, single_storage); } } fn setup_ui( parent_part: Entity, parent_transform: &Transform, mut commands: Commands, camera: Single<(Entity, &Camera, &GlobalTransform), (With, Without)>, drill: Option<&Drill>, single_storage: Option<&SingleStorage>, ) { let parent_pos = camera.1.world_to_viewport(camera.2, parent_transform.translation).unwrap(); let entity = commands.spawn(( UiTargetCamera(camera.0), Node { position_type: PositionType::Absolute, left: Val::Px(parent_pos.x), top: Val::Px(parent_pos.y), width: Val::Px(100.0), height: Val::Px(100.0), display: Display::Flex, flex_direction: FlexDirection::Column, ..default() }, AutoFocus, CraftingUi, BackgroundColor(colors::MANTLE), RelativeCursorPosition::default(), )) .with_children(|parent| { parent.spawn(( Node { width: Val::Px(25.0), height: Val::Px(25.0), justify_content: JustifyContent::Center, align_content: AlignContent::Center, ..Default::default() }, Button, BackgroundColor(colors::RED), CloseButton(parent.target_entity()), PreviousInteraction(Interaction::None), )) .with_children(|parent| { parent.spawn(( Node { ..Default::default() }, Text::new("x"), )); }); // only add the drill button if the part is a drill if let Some(drill) = drill { parent.spawn(( Node { width: Val::Px(100.0), height: Val::Px(30.0), ..Default::default() }, Button, DrillButton(parent_part), BackgroundColor(colors::CRUST), PreviousInteraction(Interaction::None), )) .with_children(|parent| { parent.spawn(( Node { ..Default::default() }, TextLayout::new(Justify::Center, LineBreak::WordBoundary), TextFont { font_size: 10.0, ..Default::default() }, Text::new(get_drill_text(drill)), )); }); } // only add storage if the part has single storage if let Some(single_storage) = single_storage { parent.spawn(( Node { ..Default::default() }, TextFont { font_size: 10.0, ..Default::default() }, Text::new(format!("{}: {}", single_storage.resource_name, single_storage.stored)), SingleStorageDisplay(parent_part), )); } }); } fn drill_button( mut interaction_query: Query< ( &Interaction, &mut PreviousInteraction, &mut BackgroundColor, &DrillButton, &mut Button, &Children, ), Changed, >, mut toggle_drill_writer: MessageWriter, mut text_query: Query<&mut Text>, drills: Query<&Drill>, ) { for (interaction, mut previous_interaction, mut color, drill_button, mut button, children) in &mut interaction_query { match *interaction { Interaction::Pressed => { *color = colors::SURFACE_1.into(); } Interaction::Hovered => { *color = colors::SURFACE_0.into(); if previous_interaction.0 == Interaction::Pressed { // released let mut text = text_query.get_mut(children[0]).unwrap(); let Ok(drill) = drills.get(drill_button.0) else { error!("A former drill is now not a drill, causing a problem in the drill button"); previous_interaction.0 = *interaction; return }; // don't allow drill toggling while not on a planet if drill.on_planet.is_none() { return } // the text is flipped because drill.drilling is an old value, // which was now toggled if drill.drilling { **text = "Start Drill".to_string(); } else { **text = "Stop Drill".to_string(); } toggle_drill_writer.write(ToggleDrillEvent { drill_entity: drill_button.0 }); } } Interaction::None => { *color = colors::CRUST.into(); } } previous_interaction.0 = *interaction; } } fn drill_state_change( drills: Query<&Drill, Changed>, drill_buttons: Query<(&DrillButton, &Children)>, mut text_query: Query<&mut Text>, ) { for (drill_button, children) in &drill_buttons { let Ok(drill) = drills.get(drill_button.0) else { continue }; let mut text = text_query.get_mut(children[0]).unwrap(); **text = get_drill_text(drill); } } fn get_drill_text(drill: &Drill) -> String { if drill.on_planet.is_some() { if drill.drilling { "Stop Drill".to_string() } else { "Start Drill".to_string() } } else { "Drill not on planet".to_string() } } fn single_storage_display( mut single_storage_display_query: Query<(&mut Text, &SingleStorageDisplay)>, part_query: Query<&SingleStorage, With>, ) { for (mut text, single_storage_display) in &mut single_storage_display_query { let single_storage = part_query.get(single_storage_display.0).expect("In single_storage_display, the entity didn't match a storage."); **text = format!("{}: {}", single_storage.resource_name, single_storage.stored); } } fn close_button( mut commands: Commands, mut interaction_query: Query< (&Interaction, &mut PreviousInteraction, &mut BackgroundColor, &CloseButton, &mut Button), Changed, >, mouse: Res>, ) { for (interaction, mut previous_interaction, mut color, close_button, mut button) in &mut interaction_query { match *interaction { Interaction::Pressed => { *color = colors::MAROON.into(); } Interaction::Hovered => { *color = colors::PINK.into(); if previous_interaction.0 == Interaction::Pressed { commands.entity(close_button.0).despawn(); } } Interaction::None => { *color = colors::RED.into(); } } previous_interaction.0 = *interaction; } }