diff --git a/src/gameplay/hud/inspect/info.rs b/src/gameplay/hud/inspect/info.rs deleted file mode 100644 index 4efe163..0000000 --- a/src/gameplay/hud/inspect/info.rs +++ /dev/null @@ -1,221 +0,0 @@ -use bevy::{prelude::*, ui_widgets::observe}; - -use crate::{ - assets::indexing::IndexMap, - gameplay::{ - hud::inspect::{InspectedEntity, InspectionMenuState}, - recipe::{Input, Output, assets::RecipeDef, select::SelectedRecipe}, - storage::Storage, - }, - widgets::{ - self, - item::StackIcon, - slot::{AddedToSlot, RemovedFromSlot}, - }, -}; - -pub fn plugin(app: &mut App) { - app.add_systems( - OnEnter(InspectionMenuState::RecipeInspect), - open_recipe_menu, - ); -} - -#[derive(Component, Reflect)] -#[reflect(Component)] -pub struct HeldRelic(Entity); - -pub fn open_recipe_menu( - mut commands: Commands, - inspected_entity: Res, - structure_query: Query<(&SelectedRecipe, &Storage)>, - input_query: Query<(), With>, - output_query: Query<(), With>, - recipes: Res>, - recipe_index: Res>, - held_relics: Query<&HeldRelic>, -) { - let Ok((selected_recipe, storage)) = structure_query.get(inspected_entity.0) else { - return; - }; - - let Some(recipe) = recipe_index - .get(selected_recipe.0.as_str()) - .and_then(|asset_id| recipes.get(*asset_id)) - else { - return; - }; - - let container_id = commands - .spawn(( - Name::new("Recipe Menu"), - DespawnOnExit(InspectionMenuState::RecipeInspect), - Pickable::IGNORE, - widgets::container(), - )) - .id(); - - let menu_id = commands - .spawn(( - ChildOf(container_id), - Node { - width: percent(70.0), - height: percent(70.0), - display: Display::Flex, - flex_direction: FlexDirection::Column, - padding: px(32.0).all(), - ..default() - }, - BackgroundColor(Color::WHITE.with_alpha(0.5)), - )) - .id(); - - commands.spawn(( - ChildOf(menu_id), - Node { - width: percent(100.0), - display: Display::Flex, - flex_direction: FlexDirection::Row, - justify_content: JustifyContent::SpaceBetween, - align_items: AlignItems::Center, - ..default() - }, - children![ - ( - Text::new("Deselect"), - TextColor(Color::BLACK), - observe(on_deselect_recipe), - ), - ( - Text::new("Close"), - TextColor(Color::BLACK), - observe(on_close_menu), - ) - ], - )); - - let recipe_view_id = commands - .spawn(( - ChildOf(menu_id), - Node { - width: percent(100.0), - height: percent(100.0), - display: Display::Flex, - flex_direction: FlexDirection::Row, - justify_content: JustifyContent::Center, - align_items: AlignItems::Center, - ..default() - }, - )) - .id(); - - let input_list_id = commands - .spawn(( - ChildOf(recipe_view_id), - Node { - flex_direction: FlexDirection::Column, - ..default() - }, - )) - .id(); - - for input in storage.iter().filter(|s| input_query.contains(*s)) { - let slot = commands - .spawn((ChildOf(input_list_id), widgets::slot::slot_container())) - .id(); - commands.spawn(widgets::slot::slotted_stack(slot, input)); - } - - let middle_column_id = commands - .spawn(( - ChildOf(recipe_view_id), - Node { - flex_direction: FlexDirection::Column, - justify_content: JustifyContent::Center, - align_items: AlignItems::Center, - ..default() - }, - )) - .id(); - - commands.spawn(( - ChildOf(middle_column_id), - TextLayout::new_with_justify(Justify::Center), - Text::new(format!("{} seconds", recipe.duration.as_secs_f32())), - TextColor(Color::BLACK), - )); - - let relic_slot_id = commands - .spawn(( - ChildOf(middle_column_id), - widgets::slot::slot_container(), - observe( - |added_to_slot: On, - inspected_entity: Res, - mut commands: Commands, - slot_occupant_query: Query<&Children>, - stack_icon_query: Query<&StackIcon>| { - let Ok(children) = slot_occupant_query.get(added_to_slot.item) else { - return; - }; - - let Ok(stack_icon) = stack_icon_query.get(*children.first().unwrap()) else { - return; - }; - - commands - .entity(inspected_entity.0) - .insert(HeldRelic(stack_icon.0)); - }, - ), - observe( - |_removed_from_slot: On, - inspected_entity: Res, - mut commands: Commands| { - commands.entity(inspected_entity.0).remove::(); - }, - ), - )) - .id(); - - if let Ok(relic) = held_relics.get(inspected_entity.0) { - commands.spawn(widgets::slot::slotted_stack(relic_slot_id, relic.0)); - } - - let output_list_id = commands - .spawn(( - ChildOf(recipe_view_id), - Node { - flex_direction: FlexDirection::Column, - ..default() - }, - )) - .id(); - - for output in storage.iter().filter(|s| output_query.contains(*s)) { - let slot = commands - .spawn((ChildOf(output_list_id), widgets::slot::slot_container())) - .id(); - commands.spawn(widgets::slot::slotted_stack(slot, output)); - } -} - -fn on_deselect_recipe( - _pointer_click: On>, - mut next_state: ResMut>, - inspected_entity: Res, - mut commands: Commands, -) { - commands - .entity(inspected_entity.0) - .remove::(); - - next_state.set(InspectionMenuState::RecipeSelect); -} - -fn on_close_menu( - _pointer_click: On>, - mut next_state: ResMut>, -) { - next_state.set(InspectionMenuState::Closed); -} diff --git a/src/gameplay/hud/inspect/mod.rs b/src/gameplay/hud/inspect/mod.rs deleted file mode 100644 index 9d03a5a..0000000 --- a/src/gameplay/hud/inspect/mod.rs +++ /dev/null @@ -1,65 +0,0 @@ -use bevy::prelude::*; - -use crate::{ - gameplay::recipe::select::SelectedRecipe, - input::input_map::{Action, action_just_pressed}, -}; - -mod info; -mod select; - -pub fn plugin(app: &mut App) { - app.init_state::(); - app.init_resource::(); - - app.add_plugins((info::plugin, select::plugin)); - - app.add_observer(on_inspect); - - app.add_systems( - Update, - close_menu.run_if(action_just_pressed(Action::Dismiss)), - ); -} - -#[derive(States, Reflect, Default, Debug, Hash, PartialEq, Eq, Clone, Copy)] -pub enum InspectionMenuState { - #[default] - Closed, - RecipeSelect, - RecipeInspect, -} - -#[derive(Resource, Reflect)] -#[reflect(Resource)] -pub struct InspectedEntity(Entity); - -impl Default for InspectedEntity { - fn default() -> Self { - Self(Entity::PLACEHOLDER) - } -} - -#[derive(EntityEvent, Reflect)] -pub struct Inspect { - pub entity: Entity, -} - -fn on_inspect( - inspect: On, - mut next_state: ResMut>, - mut inspected_entity: ResMut, - selected_recipes: Query<&SelectedRecipe>, -) { - inspected_entity.0 = inspect.entity; - - if selected_recipes.contains(inspected_entity.0) { - next_state.set(InspectionMenuState::RecipeInspect); - } else { - next_state.set(InspectionMenuState::RecipeSelect); - } -} - -fn close_menu(mut next_state: ResMut>) { - next_state.set(InspectionMenuState::Closed); -} diff --git a/src/gameplay/hud/inspect/select.rs b/src/gameplay/hud/inspect/select.rs deleted file mode 100644 index 77db3ef..0000000 --- a/src/gameplay/hud/inspect/select.rs +++ /dev/null @@ -1,128 +0,0 @@ -use bevy::{prelude::*, ui_widgets::observe}; - -use crate::{ - gameplay::{ - hud::inspect::{InspectedEntity, InspectionMenuState}, - recipe::{assets::RecipeDef, select::SelectRecipe}, - }, - widgets, -}; - -pub fn plugin(app: &mut App) { - app.add_systems( - OnEnter(InspectionMenuState::RecipeSelect), - recipe_select_menu, - ); -} - -#[derive(Component, Reflect)] -#[reflect(Component)] -struct SelectRecipeButton(String); - -pub fn recipe_select_menu(mut commands: Commands, recipes: Res>) { - let recipes = recipes - .iter() - .map(|(_, recipe)| (recipe.id.to_owned(), recipe.name.to_owned())) - .collect::>(); - - commands.spawn(( - Name::new("Recipe Selection Menu Container"), - DespawnOnExit(InspectionMenuState::RecipeSelect), - widgets::container(), - Children::spawn_one(( - Node { - width: percent(70.0), - height: percent(70.0), - display: Display::Flex, - flex_direction: FlexDirection::Column, - padding: px(32.0).all(), - ..default() - }, - BackgroundColor(Color::WHITE.with_alpha(0.5)), - Children::spawn(( - Spawn(( - Node { - width: percent(100.0), - display: Display::Flex, - flex_direction: FlexDirection::Row, - justify_content: JustifyContent::SpaceBetween, - align_items: AlignItems::Center, - ..default() - }, - children![( - Text::new("Close"), - TextColor(Color::BLACK), - Node { - align_self: AlignSelf::End, - ..default() - }, - observe(on_close_menu), - )], - )), - Spawn(( - Node { - display: Display::Flex, - flex_direction: FlexDirection::Row, - justify_content: JustifyContent::Start, - align_items: AlignItems::Start, - ..default() - }, - Children::spawn(SpawnIter(recipes.into_iter().map( - |(recipe_id, recipe_name)| { - ( - Node { - padding: px(32.0).all(), - ..default() - }, - BackgroundColor(Color::BLACK.with_alpha(0.5)), - SelectRecipeButton(recipe_id), - children![Text::new(recipe_name),], - observe(on_recipe_hover), - observe(on_recipe_click), - observe(on_recipe_out), - ) - }, - ))), - )), - )), - )), - )); -} - -fn on_recipe_hover(pointer_over: On>, mut commands: Commands) { - commands - .entity(pointer_over.entity) - .insert(BackgroundColor(Color::hsl(120.0, 1.0, 0.5))); -} - -fn on_recipe_out(pointer_out: On>, mut commands: Commands) { - commands - .entity(pointer_out.entity) - .insert(BackgroundColor(Color::BLACK.with_alpha(0.5))); -} - -fn on_recipe_click( - pointer_clicks: On>, - buttons: Query<&SelectRecipeButton>, - inspected_entity: Res, - mut next_state: ResMut>, - mut commands: Commands, -) { - let Ok(button) = buttons.get(pointer_clicks.entity) else { - return; - }; - - commands.trigger(SelectRecipe { - entity: inspected_entity.0, - recipe_id: button.0.clone(), - }); - - next_state.set(InspectionMenuState::RecipeInspect); -} - -fn on_close_menu( - _pointer_click: On>, - mut next_state: ResMut>, -) { - next_state.set(InspectionMenuState::Closed); -} diff --git a/src/gameplay/hud/mod.rs b/src/gameplay/hud/mod.rs index 3eb9e1e..4b67739 100644 --- a/src/gameplay/hud/mod.rs +++ b/src/gameplay/hud/mod.rs @@ -8,13 +8,12 @@ use bevy_aseprite_ultra::prelude::*; use crate::screens::Screen; pub mod hotbar; -pub mod inspect; pub mod tome; pub fn plugin(app: &mut App) { app.add_plugins((UiWidgetsPlugins, InputDispatchPlugin, TabNavigationPlugin)); - app.add_plugins((hotbar::plugin, inspect::plugin, tome::plugin)); + app.add_plugins((hotbar::plugin, tome::plugin)); app.add_systems(OnEnter(Screen::Gameplay), (setup_portrait, setup_relic)); } diff --git a/src/gameplay/hud/tome/mod.rs b/src/gameplay/hud/tome/mod.rs index 6b70779..a29eff1 100644 --- a/src/gameplay/hud/tome/mod.rs +++ b/src/gameplay/hud/tome/mod.rs @@ -13,13 +13,19 @@ use crate::{ screens::Screen, }; +pub mod tab_inspect; pub mod tab_items; pub mod tab_people; pub mod tab_recipes; pub mod widgets; pub(super) fn plugin(app: &mut App) { - app.add_plugins((tab_items::plugin, tab_people::plugin, tab_recipes::plugin)); + app.add_plugins(( + tab_inspect::plugin, + tab_items::plugin, + tab_people::plugin, + tab_recipes::plugin, + )); app.add_sub_state::(); app.add_sub_state::(); @@ -59,6 +65,7 @@ pub enum TomeTab { Items, People, Recipes, + Inspect, } #[derive(Resource, Reflect, Debug, Default)] diff --git a/src/gameplay/hud/tome/tab_inspect.rs b/src/gameplay/hud/tome/tab_inspect.rs new file mode 100644 index 0000000..ca78ac9 --- /dev/null +++ b/src/gameplay/hud/tome/tab_inspect.rs @@ -0,0 +1,146 @@ +use bevy::{prelude::*, ui_widgets::observe}; + +use crate::{ + gameplay::{ + hud::tome::{TomeOpen, TomeTab, UITomeLeftPageRoot, UITomeRightPageRoot}, + recipe::{ + assets::RecipeDef, + select::{RecipeChanged, SelectRecipe}, + }, + storage::Storage, + }, + widgets, +}; + +pub(super) fn plugin(app: &mut App) { + app.add_systems( + OnEnter(TomeTab::Inspect), + (spawn_inspect_recipes, spawn_right_page), + ); + + app.add_systems( + Update, + update_right_page_on_recipe_changed + .run_if(in_state(TomeTab::Inspect).and(on_message::)), + ); + + app.add_observer(on_inspect); +} + +#[derive(Resource, Reflect, Debug)] +#[reflect(Resource)] +pub struct Inspected(pub Entity); + +#[derive(EntityEvent, Reflect)] +pub struct Inspect { + pub entity: Entity, +} + +#[derive(Component, Reflect, Debug)] +#[reflect(Component)] +pub struct Recipe(pub AssetId); + +fn on_inspect( + inspect: On, + mut commands: Commands, + mut next_tome_open: ResMut>, + mut next_tome_tab: ResMut>, +) { + commands.insert_resource(Inspected(inspect.entity)); + next_tome_open.set(TomeOpen(true)); + next_tome_tab.set(TomeTab::Inspect); +} + +fn spawn_inspect_recipes( + mut commands: Commands, + left_page: Single>, + recipes: Res>, +) { + let id = commands + .spawn(( + super::widgets::list_page(), + DespawnOnExit(TomeTab::Inspect), + ChildOf(*left_page), + )) + .id(); + + for (asset_id, _) in recipes.iter() { + commands.spawn(( + widgets::recipe_plate(asset_id), + ChildOf(id), + Recipe(asset_id), + observe(on_recipe_select), + )); + } +} + +fn on_recipe_select( + click: On>, + mut commands: Commands, + inspected: Res, + recipes: Res>, + query: Query<&Recipe>, +) { + let Some(recipe_id) = query + .get(click.entity) + .ok() + .and_then(|r| recipes.get(r.0)) + .map(|r| r.id.clone()) + else { + return; + }; + + commands.trigger(SelectRecipe { + entity: inspected.0, + recipe_id, + }); +} + +fn spawn_right_page( + inspected: Res, + storage: Query<&Storage>, + right_page: Single>, + mut commands: Commands, +) { + let Ok(storage) = storage.get(inspected.0) else { + return; + }; + + render_right_page(&mut commands, *right_page, storage); +} + +fn update_right_page_on_recipe_changed( + mut events: MessageReader, + inspected: Res, + storage: Query<&Storage>, + right_page: Single>, + mut commands: Commands, +) { + for event in events.read() { + if event.0 != inspected.0 { + continue; + } + + let Ok(storage) = storage.get(event.0) else { + continue; + }; + + render_right_page(&mut commands, *right_page, storage); + } +} + +fn render_right_page(commands: &mut Commands, right_page: Entity, storage: &Storage) { + commands.entity(right_page).despawn_children(); + + let id = commands + .spawn(( + super::widgets::list_page(), + DespawnOnExit(TomeTab::Inspect), + ChildOf(right_page), + )) + .id(); + + for stored in storage.iter() { + commands.spawn((widgets::resource_plate(stored), ChildOf(id))); + } +} diff --git a/src/gameplay/hud/tome/tab_items.rs b/src/gameplay/hud/tome/tab_items.rs index 2122a9c..7425084 100644 --- a/src/gameplay/hud/tome/tab_items.rs +++ b/src/gameplay/hud/tome/tab_items.rs @@ -1,37 +1,19 @@ -use bevy::{prelude::*, ui_widgets::RadioButton}; - -use crate::gameplay::{ - hud::tome::{TomeTab, UIEntry, UITomeLeftPageRoot, widgets}, - item::{assets::ItemDef, stack::Stack}, - player::Player, - storage::Storage, +use bevy::prelude::*; + +use crate::{ + gameplay::{ + hud::tome::{TomeTab, UIEntry, UITomeLeftPageRoot}, + item::stack::Stack, + player::Player, + storage::Storage, + }, + widgets, }; pub(super) fn plugin(app: &mut App) { app.add_systems(OnEnter(TomeTab::Items), spawn_item_list); - - app.add_systems( - Update, - (refresh_ui_item_stacks).run_if(in_state(TomeTab::Items)), - ); } -#[derive(Component, Reflect, Debug)] -#[reflect(Component)] -pub struct ItemPlate(pub Entity); - -#[derive(Component, Reflect, Debug)] -#[reflect(Component)] -pub struct ItemPortrait; - -#[derive(Component, Reflect, Debug)] -#[reflect(Component)] -pub struct ItemName; - -#[derive(Component, Reflect, Debug)] -#[reflect(Component)] -pub struct ItemQuantity; - fn spawn_item_list( mut commands: Commands, left_page: Single>, @@ -40,99 +22,13 @@ fn spawn_item_list( ) { let item_list = commands .spawn(( - widgets::list_page(), + super::widgets::list_page(), ChildOf(*left_page), DespawnOnExit(TomeTab::Items), )) .id(); for stack in q_player.iter().flat_map(|e| q_items.get(e)) { - commands.spawn((ui_item_stack(stack), ChildOf(item_list))); - } -} - -fn ui_item_stack(stack: Entity) -> impl Bundle { - ( - Node { - column_gap: px(4.0), - justify_content: JustifyContent::SpaceBetween, - align_items: AlignItems::Center, - ..default() - }, - UIEntry, - RadioButton, - ItemPlate(stack), - children![ - ( - Node { - align_items: AlignItems::Center, - ..default() - }, - children![ - ( - ItemPortrait, - ImageNode::default(), - Node { - width: px(64.0), - height: px(64.0), - ..default() - } - ), - ( - ItemName, - Text::default(), - TextFont::default().with_font_size(32.0), - ), - ], - ), - ( - Node { - align_items: AlignItems::Center, - ..default() - }, - children![( - ItemQuantity, - Text::default(), - TextFont::default().with_font_size(24.0), - ),], - ), - ], - ) -} - -fn refresh_ui_item_stacks( - q_item_plates: Query<(Entity, &ItemPlate)>, - q_item_stacks: Query<&Stack>, - item_defs: Res>, - children: Query<&Children>, - mut q_item_plate_components: ParamSet<( - Query<&mut ImageNode, With>, - Query<&mut Text, With>, - Query<&mut Text, With>, - )>, - asset_server: Res, -) { - for (item_plate, ItemPlate(stack_entity)) in q_item_plates { - let Ok(stack) = q_item_stacks.get(*stack_entity) else { - continue; - }; - - let Some(item_def) = item_defs.get(&stack.item) else { - return; - }; - - for child in children.iter_descendants(item_plate) { - if let Ok(mut image) = q_item_plate_components.p0().get_mut(child) { - image.image = asset_server.load(item_def.sprite.clone()); - } - - if let Ok(mut text) = q_item_plate_components.p1().get_mut(child) { - text.0 = item_def.name.clone(); - } - - if let Ok(mut text) = q_item_plate_components.p2().get_mut(child) { - text.0 = stack.quantity.to_string(); - } - } + commands.spawn((widgets::resource_plate(stack), UIEntry, ChildOf(item_list))); } } diff --git a/src/gameplay/hud/tome/tab_people.rs b/src/gameplay/hud/tome/tab_people.rs index 9cee595..8ff5f35 100644 --- a/src/gameplay/hud/tome/tab_people.rs +++ b/src/gameplay/hud/tome/tab_people.rs @@ -1,10 +1,13 @@ use bevy::prelude::*; -use crate::gameplay::{ - hud::tome::{TomeTab, UIEntryList, UITomeLeftPageRoot, widgets}, - people::Person, - player::Player, - storage::{Storage, StoredBy}, +use crate::{ + gameplay::{ + hud::tome::{TomeTab, UIEntryList, UITomeLeftPageRoot}, + people::Person, + player::Player, + storage::{Storage, StoredBy}, + }, + widgets, }; pub(super) fn plugin(app: &mut App) { @@ -12,45 +15,10 @@ pub(super) fn plugin(app: &mut App) { app.add_systems( Update, - (backfill_people_grid, refresh_person_badges).run_if(in_state(TomeTab::People)), + backfill_people_grid.run_if(in_state(TomeTab::People)), ); } -#[derive(Component, Reflect, Debug)] -#[reflect(Component)] -pub struct PersonBadge(pub Entity); - -#[derive(Component, Reflect, Debug)] -#[reflect(Component)] -pub struct PersonName; - -fn person_badge(person: Entity) -> impl Bundle { - ( - PersonBadge(person), - Node::default(), - children![(Text::default(), PersonName,)], - ) -} - -fn refresh_person_badges( - badges: Query<(Entity, &PersonBadge)>, - children: Query<&Children>, - people: Query<&Name, With>, - mut components: ParamSet<(Query<&mut Text, With>,)>, -) { - for (badge, PersonBadge(person)) in badges { - let Ok(name) = people.get(*person) else { - continue; - }; - - for child in children.iter_descendants(badge) { - if let Ok(mut text) = components.p0().get_mut(child) { - text.0 = name.to_string(); - } - } - } -} - fn spawn_people_grid( mut commands: Commands, left_page: Single>, @@ -60,7 +28,7 @@ fn spawn_people_grid( ) { let people_grid = commands .spawn(( - widgets::list_page(), + super::widgets::list_page(), ChildOf(*left_page), DespawnOnExit(TomeTab::People), )) @@ -70,7 +38,7 @@ fn spawn_people_grid( .iter_descendants(*player) .filter(|s| people.contains(*s)) { - commands.spawn((person_badge(person), ChildOf(people_grid))); + commands.spawn((widgets::person_badge(person), ChildOf(people_grid))); } } @@ -85,6 +53,6 @@ fn backfill_people_grid( continue; } - commands.spawn((person_badge(person), ChildOf(*people_grid))); + commands.spawn((widgets::person_badge(person), ChildOf(*people_grid))); } } diff --git a/src/gameplay/hud/tome/tab_recipes.rs b/src/gameplay/hud/tome/tab_recipes.rs index eec7e6a..53cde66 100644 --- a/src/gameplay/hud/tome/tab_recipes.rs +++ b/src/gameplay/hud/tome/tab_recipes.rs @@ -1,25 +1,15 @@ use bevy::prelude::*; -use crate::gameplay::{ - hud::tome::{TomeTab, UITomeLeftPageRoot, widgets}, - recipe::assets::RecipeDef, +use crate::{ + gameplay::{ + hud::tome::{TomeTab, UITomeLeftPageRoot}, + recipe::assets::RecipeDef, + }, + widgets, }; pub(super) fn plugin(app: &mut App) { app.add_systems(OnEnter(TomeTab::Recipes), spawn_recipe_list); - - app.add_systems( - Update, - refresh_recipe_plates.run_if(in_state(TomeTab::Recipes)), - ); -} - -#[derive(Component, Reflect, Debug)] -#[reflect(Component)] -pub struct RecipePlate(pub AssetId); - -fn recipe_plate(recipe: AssetId) -> impl Bundle { - (RecipePlate(recipe), Text::default()) } fn spawn_recipe_list( @@ -29,24 +19,13 @@ fn spawn_recipe_list( ) { let recipe_list = commands .spawn(( - widgets::list_page(), + super::widgets::list_page(), ChildOf(*left_page), DespawnOnExit(TomeTab::Recipes), )) .id(); for (asset_id, _) in recipe_defs.iter() { - commands.spawn((recipe_plate(asset_id), ChildOf(recipe_list))); - } -} - -fn refresh_recipe_plates( - plates: Query<(&RecipePlate, &mut Text)>, - recipe_defs: Res>, -) { - for (plate, mut text) in plates { - if let Some(recipe) = recipe_defs.get(plate.0) { - text.0 = recipe.name.clone(); - } + commands.spawn((widgets::recipe_plate(asset_id), ChildOf(recipe_list))); } } diff --git a/src/gameplay/structure/default_recipe.rs b/src/gameplay/structure/default_recipe.rs index b430a03..90d2ae2 100644 --- a/src/gameplay/structure/default_recipe.rs +++ b/src/gameplay/structure/default_recipe.rs @@ -1,7 +1,7 @@ use bevy::prelude::*; use crate::gameplay::{ - hud::inspect::Inspect, + hud::tome::tab_inspect::Inspect, recipe::select::SelectRecipe, structure::{Structure, assets::StructureDef, interactable::Interact}, world::construction::StructureConstructed, diff --git a/src/widgets/item.rs b/src/widgets/item.rs deleted file mode 100644 index 9bf729c..0000000 --- a/src/widgets/item.rs +++ /dev/null @@ -1,107 +0,0 @@ -use bevy::{prelude::*, ui_widgets::observe}; - -use crate::{ - gameplay::item::{assets::ItemDef, stack::Stack}, - widgets::tooltip::{HideTooltip, ShowTooltip}, -}; - -pub(super) fn plugin(app: &mut App) { - app.add_systems(Update, update_item_icons); -} - -#[derive(Component, Reflect, Debug)] -#[reflect(Component)] -pub struct StackIcon(pub Entity); - -pub fn stack_icon(stack: Entity) -> impl Bundle { - ( - Name::new("Item Icon"), - StackIcon(stack), - Node { - width: percent(100.0), - height: percent(100.0), - position_type: PositionType::Relative, - ..default() - }, - Pickable { - is_hoverable: true, - should_block_lower: false, - }, - children![ - ( - Node { - width: percent(100.0), - height: percent(100.0), - ..default() - }, - ImageNode::default(), - Pickable::IGNORE, - ), - ( - Node { - position_type: PositionType::Absolute, - right: Val::ZERO, - bottom: Val::ZERO, - ..default() - }, - BackgroundColor(Color::WHITE), - TextColor(Color::BLACK), - Text::default(), - Pickable::IGNORE, - ), - ], - observe( - |pointer_over: On>, - stack_icon_query: Query<&StackIcon>, - stack_query: Query<&Stack>, - items: Res>, - mut commands: Commands| { - let Ok(stack_icon) = stack_icon_query.get(pointer_over.entity) else { - return; - }; - - let Ok(stack) = stack_query.get(stack_icon.0) else { - return; - }; - - let Some(item_def) = items.get(&stack.item) else { - return; - }; - - commands.trigger(ShowTooltip(item_def.name.clone())); - }, - ), - observe(|_pointer_out: On>, mut commands: Commands| { - commands.trigger(HideTooltip); - }), - ) -} - -fn update_item_icons( - stack_icon_query: Query<(&StackIcon, &Children)>, - stack_query: Query<&Stack>, - mut image_node_query: Query<&mut ImageNode>, - mut quantity_text_query: Query<&mut Text>, - item_defs: Res>, - asset_server: Res, -) { - for (StackIcon(stack), children) in stack_icon_query { - let Ok(stack) = stack_query.get(*stack) else { - continue; - }; - - let Some(item_def) = item_defs.get(&stack.item) else { - continue; - }; - - let mut children_iter = children.iter(); - - if let Ok(mut image_node) = image_node_query.get_mut(children_iter.next().unwrap()) { - image_node.image = asset_server.load(item_def.sprite.to_owned()); - } - - if let Ok(mut text) = quantity_text_query.get_mut(children_iter.next().unwrap()) { - text.0 = stack.quantity.to_string(); - } - } -} diff --git a/src/widgets/mod.rs b/src/widgets/mod.rs index f252766..abf64be 100644 --- a/src/widgets/mod.rs +++ b/src/widgets/mod.rs @@ -1,20 +1,19 @@ use bevy::prelude::*; -pub mod item; -pub mod slot; +pub mod person_badge; +pub mod recipe_plate; +pub mod resource_plate; pub mod tooltip; -pub(super) fn plugin(app: &mut App) { - app.add_plugins((item::plugin, slot::plugin, tooltip::plugin)); -} +pub use person_badge::person_badge; +pub use recipe_plate::recipe_plate; +pub use resource_plate::resource_plate; -pub fn container() -> impl Bundle { - Node { - width: percent(100.0), - height: percent(100.0), - display: Display::Flex, - justify_content: JustifyContent::Center, - align_items: AlignItems::Center, - ..default() - } +pub(super) fn plugin(app: &mut App) { + app.add_plugins(( + person_badge::plugin, + recipe_plate::plugin, + resource_plate::plugin, + tooltip::plugin, + )); } diff --git a/src/widgets/person_badge.rs b/src/widgets/person_badge.rs new file mode 100644 index 0000000..af67d25 --- /dev/null +++ b/src/widgets/person_badge.rs @@ -0,0 +1,42 @@ +use bevy::prelude::*; + +use crate::gameplay::people::Person; + +pub(super) fn plugin(app: &mut App) { + app.add_systems(Update, refresh_person_badges); +} + +#[derive(Component, Reflect, Debug)] +#[reflect(Component)] +struct PersonBadge(pub Entity); + +#[derive(Component, Reflect, Debug)] +#[reflect(Component)] +struct PersonName; + +pub fn person_badge(person: Entity) -> impl Bundle { + ( + PersonBadge(person), + Node::default(), + children![(Text::default(), PersonName,)], + ) +} + +fn refresh_person_badges( + badges: Query<(Entity, &PersonBadge)>, + children: Query<&Children>, + people: Query<&Name, With>, + mut components: ParamSet<(Query<&mut Text, With>,)>, +) { + for (badge, PersonBadge(person)) in badges { + let Ok(name) = people.get(*person) else { + continue; + }; + + for child in children.iter_descendants(badge) { + if let Ok(mut text) = components.p0().get_mut(child) { + text.0 = name.to_string(); + } + } + } +} diff --git a/src/widgets/recipe_plate.rs b/src/widgets/recipe_plate.rs new file mode 100644 index 0000000..640454a --- /dev/null +++ b/src/widgets/recipe_plate.rs @@ -0,0 +1,26 @@ +use bevy::prelude::*; + +use crate::gameplay::recipe::assets::RecipeDef; + +pub(super) fn plugin(app: &mut App) { + app.add_systems(Update, refresh_recipe_plates); +} + +#[derive(Component, Reflect, Debug)] +#[reflect(Component)] +struct RecipePlate(pub AssetId); + +pub fn recipe_plate(recipe: AssetId) -> impl Bundle { + (RecipePlate(recipe), Text::default()) +} + +fn refresh_recipe_plates( + plates: Query<(&RecipePlate, &mut Text)>, + recipe_defs: Res>, +) { + for (plate, mut text) in plates { + if let Some(recipe) = recipe_defs.get(plate.0) { + text.0 = recipe.name.clone(); + } + } +} diff --git a/src/widgets/resource_plate.rs b/src/widgets/resource_plate.rs new file mode 100644 index 0000000..1a1f3a0 --- /dev/null +++ b/src/widgets/resource_plate.rs @@ -0,0 +1,108 @@ +use bevy::{prelude::*, ui_widgets::RadioButton}; + +use crate::gameplay::item::{assets::ItemDef, stack::Stack}; + +pub(super) fn plugin(app: &mut App) { + app.add_systems(Update, refresh_resource_plates); +} + +#[derive(Component, Reflect, Debug)] +#[reflect(Component)] +struct ResourcePlate(pub Entity); + +#[derive(Component, Reflect, Debug)] +#[reflect(Component)] +struct ResourcePortrait; + +#[derive(Component, Reflect, Debug)] +#[reflect(Component)] +struct ResourceName; + +#[derive(Component, Reflect, Debug)] +#[reflect(Component)] +struct ResourceQuantity; + +pub fn resource_plate(resource: Entity) -> impl Bundle { + ( + Node { + column_gap: px(4.0), + justify_content: JustifyContent::SpaceBetween, + align_items: AlignItems::Center, + ..default() + }, + RadioButton, + ResourcePlate(resource), + children![ + ( + Node { + align_items: AlignItems::Center, + ..default() + }, + children![ + ( + ResourcePortrait, + ImageNode::default(), + Node { + width: px(64.0), + height: px(64.0), + ..default() + } + ), + ( + ResourceName, + Text::default(), + TextFont::default().with_font_size(32.0), + ), + ], + ), + ( + Node { + align_items: AlignItems::Center, + ..default() + }, + children![( + ResourceQuantity, + Text::default(), + TextFont::default().with_font_size(24.0), + ),], + ), + ], + ) +} + +fn refresh_resource_plates( + q_item_plates: Query<(Entity, &ResourcePlate)>, + q_item_stacks: Query<&Stack>, + item_defs: Res>, + children: Query<&Children>, + mut q_item_plate_components: ParamSet<( + Query<&mut ImageNode, With>, + Query<&mut Text, With>, + Query<&mut Text, With>, + )>, + asset_server: Res, +) { + for (item_plate, ResourcePlate(stack_entity)) in q_item_plates { + let Ok(stack) = q_item_stacks.get(*stack_entity) else { + continue; + }; + + let Some(item_def) = item_defs.get(&stack.item) else { + return; + }; + + for child in children.iter_descendants(item_plate) { + if let Ok(mut image) = q_item_plate_components.p0().get_mut(child) { + image.image = asset_server.load(item_def.sprite.clone()); + } + + if let Ok(mut text) = q_item_plate_components.p1().get_mut(child) { + text.0 = item_def.name.clone(); + } + + if let Ok(mut text) = q_item_plate_components.p2().get_mut(child) { + text.0 = stack.quantity.to_string(); + } + } + } +} diff --git a/src/widgets/slot.rs b/src/widgets/slot.rs deleted file mode 100644 index 9b48c6d..0000000 --- a/src/widgets/slot.rs +++ /dev/null @@ -1,169 +0,0 @@ -use bevy::{prelude::*, ui_widgets::observe}; - -use crate::{screens::Screen, widgets::item::stack_icon}; - -const SLOTTED_OBJECT_Z_INDEX: i32 = 10; -const HELD_OBJECT_Z_INDEX: i32 = 15; -const HOVERED_SLOT_LIGHTEN_FACTOR: f32 = 0.05; - -pub fn plugin(app: &mut App) { - app.add_systems(Update, sync_slot_child.run_if(in_state(Screen::Gameplay))); -} - -pub fn slot_container() -> impl Bundle { - ( - Name::new("Slot"), - Node { - width: px(64.0), - height: px(64.0), - margin: px(4.0).all(), - justify_content: JustifyContent::Center, - align_items: AlignItems::Center, - ..default() - }, - BackgroundColor(Color::hsl(188.0, 0.94, 0.06)), - Pickable::default(), - observe( - |pointer_over: On>, mut query: Query<&mut BackgroundColor>| { - if let Ok(mut color) = query.get_mut(pointer_over.entity) { - color.0 = color.0.lighter(HOVERED_SLOT_LIGHTEN_FACTOR); - } - }, - ), - observe( - |pointer_out: On>, mut query: Query<&mut BackgroundColor>| { - if let Ok(mut color) = query.get_mut(pointer_out.entity) { - color.0 = color.0.darker(HOVERED_SLOT_LIGHTEN_FACTOR); - } - }, - ), - observe(on_slot_drag_and_drop), - ) -} - -pub fn slotted_stack(slot: Entity, stack: Entity) -> impl Bundle { - ( - Name::new("Slotted Item"), - Node { - width: percent(100.0), - height: percent(100.0), - ..default() - }, - GlobalZIndex(SLOTTED_OBJECT_Z_INDEX), - InSlot(slot), - ChildOf(slot), - Pickable { - is_hoverable: true, - should_block_lower: false, - }, - children![stack_icon(stack)], - observe( - |pointer_drag_start: On>, mut query: Query<&mut GlobalZIndex>| { - if let Ok(mut z_index) = query.get_mut(pointer_drag_start.entity) { - z_index.0 = HELD_OBJECT_Z_INDEX; - } - }, - ), - observe( - |pointer_drag: On>, mut query: Query<&mut UiTransform>| { - if let Ok(mut transform) = query.get_mut(pointer_drag.entity) { - transform.translation.x = px(pointer_drag.distance.x); - transform.translation.y = px(pointer_drag.distance.y); - } - }, - ), - observe( - |pointer_drag_end: On>, - mut query: Query<(&mut UiTransform, &mut GlobalZIndex)>| { - if let Ok((mut transform, mut z_index)) = query.get_mut(pointer_drag_end.entity) { - transform.translation = Val2::default(); - z_index.0 = SLOTTED_OBJECT_Z_INDEX; - } - }, - ), - ) -} - -#[derive(EntityEvent, Reflect)] -pub struct AddedToSlot { - pub entity: Entity, - pub item: Entity, -} - -#[derive(EntityEvent, Reflect)] -pub struct RemovedFromSlot { - pub entity: Entity, - pub item: Entity, -} - -#[derive(Component, Reflect, Deref)] -#[reflect(Component)] -#[relationship_target(relationship = InSlot, linked_spawn)] -pub struct SlotOccupant(Entity); - -#[derive(Component, Reflect, Deref)] -#[reflect(Component)] -#[relationship(relationship_target = SlotOccupant)] -pub struct InSlot(pub Entity); - -fn sync_slot_child(query: Query<(Entity, &InSlot), Changed>, mut commands: Commands) { - for (entity, InSlot(slot)) in query { - commands.entity(entity).insert(ChildOf(*slot)); - } -} - -fn on_slot_drag_and_drop( - pointer_drag_drop: On>, - mut commands: Commands, - item_query: Query<&InSlot>, - slot_query: Query<&SlotOccupant>, -) { - let destination_slot = pointer_drag_drop.entity; - let source_item = pointer_drag_drop.dropped; - - let Ok(source_slot) = item_query.get(source_item).map(|slot| slot.0) else { - return; - }; - - if source_slot == destination_slot { - return; - } - - let destination_item = slot_query - .get(destination_slot) - .map(|slotted_item| slotted_item.0); - - if let Ok(destination_item) = destination_item { - commands.entity(destination_item).remove::(); - } - - commands - .entity(source_item) - .insert(InSlot(destination_slot)); - - commands.trigger(RemovedFromSlot { - item: source_item, - entity: source_slot, - }); - - if let Ok(destination_item) = destination_item { - commands - .entity(destination_item) - .insert(InSlot(source_slot)); - - commands.trigger(RemovedFromSlot { - item: destination_item, - entity: destination_slot, - }); - - commands.trigger(AddedToSlot { - item: destination_item, - entity: source_slot, - }); - } - - commands.trigger(AddedToSlot { - item: source_item, - entity: destination_slot, - }); -}