From e7fce4596536bee0147799c931fe8f1edd055f2d Mon Sep 17 00:00:00 2001 From: Louis Capitanchik <contact@louiscap.co> Date: Thu, 8 Dec 2022 16:34:39 +0000 Subject: [PATCH] Create panel stats, shop scrolling, swap panels in town menu --- game_core/src/ui/components/mod.rs | 2 + game_core/src/ui/components/panel.rs | 1 + game_core/src/ui/components/panel_stat.rs | 104 +++++++++++++++++ game_core/src/ui/screens/in_game.rs | 4 +- game_core/src/ui/utilities.rs | 50 ++++++++ game_core/src/ui/widgets/mod.rs | 1 + game_core/src/ui/widgets/shop_panel.rs | 134 +++++++++++++--------- game_core/src/ui/widgets/stats_panel.rs | 63 +++------- game_core/src/ui/widgets/town_menu.rs | 35 ++++-- game_core/src/ui/widgets/transit_panel.rs | 91 ++++++++------- 10 files changed, 332 insertions(+), 153 deletions(-) create mode 100644 game_core/src/ui/components/panel_stat.rs diff --git a/game_core/src/ui/components/mod.rs b/game_core/src/ui/components/mod.rs index 31e028e..1a21bf9 100644 --- a/game_core/src/ui/components/mod.rs +++ b/game_core/src/ui/components/mod.rs @@ -7,6 +7,7 @@ mod h_divider; mod image_button; mod inset_icon; mod panel; +mod panel_stat; mod v_divider; pub use self::a_text_box::{ @@ -24,4 +25,5 @@ pub use self::inset_icon::{ render_inset_icon_widget, IconContent, InsetIconProps, InsetIconWidget, }; pub use self::panel::{render_panel_widget, PanelProps, PanelVariant, PanelWidget}; +pub use self::panel_stat::{render_panel_stat_widget, PanelStatProps, PanelStatWidget}; pub use self::v_divider::{render_v_divider, VDividerWidget, VDividerWidgetProps}; diff --git a/game_core/src/ui/components/panel.rs b/game_core/src/ui/components/panel.rs index ac1f19e..87d70b5 100644 --- a/game_core/src/ui/components/panel.rs +++ b/game_core/src/ui/components/panel.rs @@ -36,6 +36,7 @@ pub fn render_panel_widget( }; *computed = KStyle { + render_command: value(RenderCommand::Quad), min_height: px(edge_size * 2.0 + 8.0), min_width: px(edge_size * 2.0 + 8.0), padding: value(Edge::all(Units::Stretch(0.0))), diff --git a/game_core/src/ui/components/panel_stat.rs b/game_core/src/ui/components/panel_stat.rs new file mode 100644 index 0000000..8764d43 --- /dev/null +++ b/game_core/src/ui/components/panel_stat.rs @@ -0,0 +1,104 @@ +use bevy::prelude::*; +use kayak_ui::prelude::*; +use kayak_ui::widgets::{BackgroundBundle, TextProps, TextWidgetBundle}; + +use crate::ui::components::*; +use crate::ui::prelude::{px, stretch, value}; +use crate::ui::utilities::TextSizer; +use crate::ui::widgets::*; +use crate::{basic_widget, empty_props}; + +#[derive(Component, Debug, Clone, PartialEq)] +pub struct PanelStatProps { + pub icon: IconContent, + pub text: String, + pub font_size: f32, + pub color: Color, + pub fill: bool, +} + +impl Default for PanelStatProps { + fn default() -> Self { + Self { + icon: IconContent::None, + text: String::new(), + font_size: 18.0, + color: Color::BLACK, + fill: false, + } + } +} + +impl Widget for PanelStatProps {} + +basic_widget!(PanelStatProps => PanelStatWidget); + +pub fn render_panel_stat_widget( + In((mut widget_context, entity)): In<(KayakWidgetContext, Entity)>, + mut commands: Commands, + mut query: Query<(&PanelStatProps, &mut ComputedStyles, &KStyle)>, + text_sizer: TextSizer, +) -> bool { + let parent_id = Some(entity); + + if let Ok((props, mut computed, style)) = query.get_mut(entity) { + let (text_width, text_height) = + text_sizer.measure_text(props.text.as_str(), props.font_size); + + let between = 10.0; + let image_size = props.font_size; + + let total_width = text_width + between + image_size; + + *computed = if props.fill { + KStyle { + render_command: value(RenderCommand::Quad), + min_width: px(total_width), + width: stretch(1.0), + height: px(text_height), + ..Default::default() + } + .with_style(style) + .into() + } else { + KStyle { + render_command: value(RenderCommand::Quad), + width: px(total_width), + height: px(text_height), + ..Default::default() + } + .with_style(style) + .into() + }; + + let wrapper_style = KStyle { + layout_type: value(LayoutType::Row), + col_between: if props.fill { + stretch(1.0) + } else { + px(between) + }, + ..Default::default() + }; + + rsx! { + <BackgroundBundle styles={wrapper_style}> + <InsetIconWidget props={InsetIconProps { image: props.icon.clone(), size: image_size }} /> + <TextWidgetBundle + text={TextProps { + content: props.text.clone(), + size: props.font_size, + line_height: Some(props.font_size + 2.0), + ..Default::default() + }} + styles={KStyle { + color: value(props.color), + ..Default::default() + }} + /> + </BackgroundBundle> + }; + } + + true +} diff --git a/game_core/src/ui/screens/in_game.rs b/game_core/src/ui/screens/in_game.rs index e152050..94e051a 100644 --- a/game_core/src/ui/screens/in_game.rs +++ b/game_core/src/ui/screens/in_game.rs @@ -76,7 +76,9 @@ pub fn render_game_panels( }} { if show_distance_panel { - constructor! { <StatsPanel /> } + constructor! { + <StatsPanel /> + } }} </ElementBundle> }; diff --git a/game_core/src/ui/utilities.rs b/game_core/src/ui/utilities.rs index 8a4e3fb..f8607ff 100644 --- a/game_core/src/ui/utilities.rs +++ b/game_core/src/ui/utilities.rs @@ -1,10 +1,17 @@ +use std::marker::PhantomData; + +use bevy::asset::Assets; +use bevy::ecs::system::SystemParam; use bevy::prelude::{ Commands, Component, DespawnRecursiveExt, Entity, In, Query, Res, Resource, With, }; +use kayak_font::{Alignment, KayakFont, TextProperties}; use kayak_ui::prelude::{ Event, EventDispatcherContext, KayakRootContext, KayakWidgetContext, WidgetParam, WidgetState, }; +use crate::assets::AssetHandles; + #[derive(bevy::prelude::Component, Clone, PartialEq, Default)] pub struct EmptyProps; @@ -214,6 +221,12 @@ pub mod context { EmptyState, render_inset_icon_widget ); + register_widget!( + widget_context, + PanelStatProps, + EmptyState, + render_panel_stat_widget + ); register_widget_with_resource!( widget_context, TownMenuPanelProps, @@ -228,6 +241,13 @@ pub mod context { UITravelInfo, render_transit_panel ); + register_widget_with_resource!( + widget_context, + ShopPanelProps, + EmptyState, + UITravelInfo, + render_shop_panel + ); register_widget_with_resource!( widget_context, StatsPanelProps, @@ -276,3 +296,33 @@ macro_rules! on_button_click { ) }; } + +#[derive(SystemParam)] +pub struct TextSizer<'w, 's> { + pub assets: Res<'w, AssetHandles>, + pub fonts: Res<'w, Assets<KayakFont>>, + #[system_param(ignore)] + _phantom: PhantomData<&'s ()>, +} + +impl<'w, 's> TextSizer<'w, 's> { + pub fn measure_text(&self, content: &str, font_size: f32) -> (f32, f32) { + let font_data = self + .fonts + .get(&self.assets.kayak_font("equipment_pro")) + .unwrap(); + + font_data + .measure( + content, + TextProperties { + font_size, + line_height: font_size + 2.0, + alignment: Alignment::Start, + tab_size: 4, + max_size: (100000.0, font_size + 2.0), + }, + ) + .size() + } +} diff --git a/game_core/src/ui/widgets/mod.rs b/game_core/src/ui/widgets/mod.rs index c706e56..8c6515b 100644 --- a/game_core/src/ui/widgets/mod.rs +++ b/game_core/src/ui/widgets/mod.rs @@ -5,6 +5,7 @@ mod town_menu; mod transit_panel; pub use encounter_panel::{render_encounter_panel, EncounterPanel, EncounterPanelProps}; +pub use shop_panel::{render_shop_panel, ShopPanel, ShopPanelProps}; pub use stats_panel::{render_stats_panel, StatsPanel, StatsPanelProps}; pub use town_menu::{ render_town_menu_panel, TownMenuPanel, TownMenuPanelProps, TownMenuPanelState, diff --git a/game_core/src/ui/widgets/shop_panel.rs b/game_core/src/ui/widgets/shop_panel.rs index b008d44..f3d95d1 100644 --- a/game_core/src/ui/widgets/shop_panel.rs +++ b/game_core/src/ui/widgets/shop_panel.rs @@ -1,22 +1,19 @@ use bevy::prelude::*; use kayak_ui::prelude::*; use kayak_ui::widgets::{ - ElementBundle, ScrollBoxBundle, ScrollBoxProps, ScrollContextProviderBundle, TextProps, - TextWidgetBundle, + BackgroundBundle, ElementBundle, ScrollBoxBundle, ScrollBoxProps, ScrollContextProviderBundle, + TextProps, TextWidgetBundle, }; -use crate::assets::AssetHandles; use crate::states::Player; -use crate::system::utilities::format_ui_distance; use crate::ui::components::*; use crate::ui::prelude::*; -use crate::ui::sync::UITravelInfo; use crate::ui::widgets::*; use crate::world::{CurrentResidence, MapQuery, TownPaths}; use crate::{basic_widget, empty_props, on_button_click}; -empty_props!(TransitPanelProps); -basic_widget!(TransitPanelProps => TransitPanel); +empty_props!(ShopPanelProps); +basic_widget!(ShopPanelProps => ShopPanel); pub fn buysell_button_factory(target: String) -> OnEvent { let target = target.clone(); @@ -56,59 +53,90 @@ pub fn buysell_button_factory(target: String) -> OnEvent { ) } -pub fn render_transit_panel( +pub fn render_shop_panel( In((widget_context, entity)): In<(KayakWidgetContext, Entity)>, mut commands: Commands, - ui_data: Res<UITravelInfo>, + query: Query<&ShopPanelProps>, ) -> bool { let parent_id = Some(entity); - rsx! { - <ElementBundle> - <TextWidgetBundle - text={TextProps { - content: format!("Set off for:"), - size: 32.0, - ..Default::default() - }} - styles={KStyle { - color: value(Color::BLACK), - padding: edge_px(20.0), - bottom: px(15.0), - left: stretch(1.0), - right: stretch(1.0), - ..Default::default() - }} - /> - <ScrollContextProviderBundle> - <ScrollBoxBundle> - { - for (place, distance) in ui_data.travel_options.iter() { - constructor! { - <ButtonWidget - styles={ - KStyle { - left: stretch(1.0), - right: stretch(1.0), - width: pct(70.0), - min_width: px(300.0), - max_width: px(600.0), - bottom: px(10.0), - padding_left: px(20.0), - padding_right: px(20.0), + let items = vec![ + ("Armour", 12usize, 12, 14, 2), + ("Weapons", 1, 13, 14, 2), + ("Luxury Goods", 13, 27, 14, 2), + ("Ale", 11, 6, 14, 2), + ("Milk", 2, 5, 14, 2), + ("Cheese", 3, 6, 14, 2), + ("Armour", 12usize, 12, 14, 2), + ("Weapons", 1, 13, 14, 2), + ("Luxury Goods", 13, 27, 14, 2), + ("Ale", 11, 6, 14, 2), + ("Milk", 2, 5, 14, 2), + ("Cheese", 3, 6, 14, 2), + ("Armour", 12usize, 12, 14, 2), + ("Weapons", 1, 13, 14, 2), + ("Luxury Goods", 13, 27, 14, 2), + ("Ale", 11, 6, 14, 2), + ("Milk", 2, 5, 14, 2), + ("Cheese", 3, 6, 14, 2), + ]; + + log::info!("{:?}", items); + + if let Ok(props) = query.get(entity) { + rsx! { + <ElementBundle> + <TextWidgetBundle + text={TextProps { + content: format!("You find an array of goods to buy:"), + size: 32.0, + ..Default::default() + }} + styles={KStyle { + color: value(Color::BLACK), + padding: edge_px(20.0), + bottom: px(15.0), + left: stretch(1.0), + right: stretch(1.0), + ..Default::default() + }} + /> + <ScrollContextProviderBundle> + <ScrollBoxBundle + scroll_box_props={ScrollBoxProps { + track_color: Some(Color::rgb(0.827, 0.482, 0.353)), + thumb_color: Some(Color::rgb(0.451, 0.224, 0.063)), + ..Default::default() + } } styles={KStyle { padding: edge_px(5.0), ..Default::default() }}> + { + for (name, idx, cost, shop_amount, player_amount) in items.iter() { + constructor! { + <BackgroundBundle + styles={KStyle { + layout_type: value(LayoutType::Row), + col_between: stretch(0.5), + height: px(40.0), + bottom: px(5.0), + padding_top: px(4.0), + padding_bottom: px(4.0), + color: value(Color::BLACK), ..Default::default() - } - } - props={ButtonWidgetProps::text(format!("{}: {}", &place, format_ui_distance(*distance)), 28.0)} - on_event={buysell_button_factory(place.clone())} - /> - }; + }} + > + <InsetIconWidget props={InsetIconProps { image: IconContent::Atlas(String::from("icons"), *idx), size: 32.0}} /> + <TextWidgetBundle styles={KStyle { width: stretch(1.0), ..Default::default() }} text={TextProps { content: String::from(*name), size: 30.0, line_height: Some(32.0), ..Default::default() } }/> + <TextWidgetBundle styles={KStyle { width: stretch(1.0), ..Default::default() }} text={TextProps { content: format!("{}g", cost), size: 30.0, line_height: Some(32.0), ..Default::default() } }/> + <TextWidgetBundle styles={KStyle { width: stretch(1.0), ..Default::default() }} text={TextProps { content: format!("{}", shop_amount), size: 30.0, line_height: Some(32.0), ..Default::default() } }/> + <TextWidgetBundle styles={KStyle { width: stretch(1.0), ..Default::default() }} text={TextProps { content: format!("{}", player_amount), size: 30.0, line_height: Some(32.0), ..Default::default() } }/> + </BackgroundBundle> + }; + } } - } - </ScrollBoxBundle> - </ScrollContextProviderBundle> - </ElementBundle> - }; + </ScrollBoxBundle> + </ScrollContextProviderBundle> + </ElementBundle> + }; + } true } diff --git a/game_core/src/ui/widgets/stats_panel.rs b/game_core/src/ui/widgets/stats_panel.rs index 65f5660..7e85abf 100644 --- a/game_core/src/ui/widgets/stats_panel.rs +++ b/game_core/src/ui/widgets/stats_panel.rs @@ -18,13 +18,14 @@ pub fn render_stats_panel( let parent_id = Some(entity); let root_style = KStyle { - position_type: value(KPositionType::SelfDirected), - top: px(15.0), - left: stretch(1.0), + top: px(20.0), + left: px(20.0), right: stretch(1.0), bottom: stretch(1.0), - height: px(70.0), + height: px(160.0), + width: px(160.0), layout_type: value(LayoutType::Column), + position_type: value(KPositionType::SelfDirected), ..Default::default() }; // @@ -40,51 +41,19 @@ pub fn render_stats_panel( ..Default::default() }; + let pad_style = KStyle { + render_command: value(RenderCommand::Quad), + height: px(20.0), + ..Default::default() + }; + rsx! { <PanelWidget styles={root_style}> - <ElementBundle styles={inner_container_style}> - <InsetIconWidget props={InsetIconProps { size: 28.0, image: IconContent::Atlas(String::from("icons"), 14)}} /> - <TextWidgetBundle - text={TextProps { - content: distance_travelled, - size: 28.0, - ..Default::default() - }} - styles={KStyle { - color: value(Color::BLACK), - bottom: px(6.0), - ..Default::default() - }} - /> - - <InsetIconWidget props={InsetIconProps { size: 28.0, image: IconContent::Atlas(String::from("icons"), 8)}} /> - <TextWidgetBundle - text={TextProps { - content: format!("{}g", ui_data.money), - size: 28.0, - ..Default::default() - }} - styles={KStyle { - color: value(Color::BLACK), - bottom: px(6.0), - ..Default::default() - }} - /> - - <InsetIconWidget props={InsetIconProps { size: 28.0, image: IconContent::Atlas(String::from("icons"), 15)}} /> - <TextWidgetBundle - text={TextProps { - content: format!("{}", ui_data.sustenance), - size: 28.0, - ..Default::default() - }} - styles={KStyle { - color: value(Color::BLACK), - bottom: px(6.0), - ..Default::default() - }} - /> - </ElementBundle> + <PanelStatWidget props={PanelStatProps { fill: true, text: distance_travelled, font_size: 24.0, icon: IconContent::Atlas(String::from("icons"), 14), color: Color::BLACK }} /> + <ElementBundle styles={pad_style.clone()} /> + <PanelStatWidget props={PanelStatProps { fill: true, text: format!("{}g", ui_data.money), font_size: 24.0, icon: IconContent::Atlas(String::from("icons"), 8), color: Color::BLACK }} /> + <ElementBundle styles={pad_style} /> + <PanelStatWidget props={PanelStatProps { fill: true, text: format!("{}", ui_data.sustenance), font_size: 24.0, icon: IconContent::Atlas(String::from("icons"), 15), color: Color::BLACK }} /> </PanelWidget> }; diff --git a/game_core/src/ui/widgets/town_menu.rs b/game_core/src/ui/widgets/town_menu.rs index 95e5024..0e2c7d7 100644 --- a/game_core/src/ui/widgets/town_menu.rs +++ b/game_core/src/ui/widgets/town_menu.rs @@ -23,19 +23,22 @@ pub enum TownMenuTab { Travel, Merchant, Tavern, + None, } #[derive(Component, Clone, PartialEq, Default)] pub struct TownMenuPanelState { pub tab: TownMenuTab, + pub next_tab: Option<TownMenuTab>, } pub fn render_town_menu_panel( In((widget_context, entity)): In<(KayakWidgetContext, Entity)>, mut commands: Commands, - state_query: Query<&TownMenuPanelState>, + mut state_query: Query<&mut TownMenuPanelState>, ui_data: Res<UITravelInfo>, places: Res<TownPaths>, + mut next_state: Local<TownMenuTab>, ) -> bool { let parent_id = Some(entity); let state_entity = @@ -71,27 +74,31 @@ pub fn render_town_menu_panel( &mut TownMenuPanelState, >| { if let Ok(mut state) = q.get_mut(state_entity) { - state.tab = TownMenuTab::Travel; + state.tab = TownMenuTab::None; + state.next_tab = Some(TownMenuTab::Travel); } }); let click_tab_merchant = on_button_click!(Query<&mut TownMenuPanelState>, |mut q: Query< &mut TownMenuPanelState, >| { if let Ok(mut state) = q.get_mut(state_entity) { - state.tab = TownMenuTab::Merchant; + state.tab = TownMenuTab::None; + state.next_tab = Some(TownMenuTab::Merchant); } }); let click_tab_tavern = on_button_click!(Query<&mut TownMenuPanelState>, |mut q: Query< &mut TownMenuPanelState, >| { if let Ok(mut state) = q.get_mut(state_entity) { - state.tab = TownMenuTab::Tavern; + state.tab = TownMenuTab::None; + state.next_tab = Some(TownMenuTab::Tavern); } }); - if let (Some(ref place), Some(state)) = - (&ui_data.current_town, state_query.get(state_entity).ok()) - { + if let (Some(ref place), Some(mut state)) = ( + &ui_data.current_town, + state_query.get_mut(state_entity).ok(), + ) { rsx! { <PanelWidget styles={panel_style} @@ -151,18 +158,30 @@ pub fn render_town_menu_panel( { + log::info!("Town menu is showing: {:?}", &state.tab); match state.tab { TownMenuTab::Travel => { constructor! { <TransitPanel /> }; }, - TownMenuTab::Merchant => {} + TownMenuTab::Merchant => { + constructor! { + <ShopPanel /> + } + } TownMenuTab::Tavern => {} + TownMenuTab::None => {} } } </PanelWidget> }; + + if let Some(next) = state.next_tab { + log::info!("SWAPPGIN {:?} ;; {:?}", next, state.tab); + state.tab = next; + state.next_tab = None; + } } true diff --git a/game_core/src/ui/widgets/transit_panel.rs b/game_core/src/ui/widgets/transit_panel.rs index 4015846..e26787f 100644 --- a/game_core/src/ui/widgets/transit_panel.rs +++ b/game_core/src/ui/widgets/transit_panel.rs @@ -60,55 +60,58 @@ pub fn render_transit_panel( In((widget_context, entity)): In<(KayakWidgetContext, Entity)>, mut commands: Commands, ui_data: Res<UITravelInfo>, + query: Query<&TransitPanelProps>, ) -> bool { let parent_id = Some(entity); - rsx! { - <ElementBundle> - <TextWidgetBundle - text={TextProps { - content: format!("Set off for:"), - size: 32.0, - ..Default::default() - }} - styles={KStyle { - color: value(Color::BLACK), - padding: edge_px(20.0), - bottom: px(15.0), - left: stretch(1.0), - right: stretch(1.0), - ..Default::default() - }} - /> - <ScrollContextProviderBundle> - <ScrollBoxBundle> - { - for (place, distance) in ui_data.travel_options.iter() { - constructor! { - <ButtonWidget - styles={ - KStyle { - left: stretch(1.0), - right: stretch(1.0), - width: pct(70.0), - min_width: px(300.0), - max_width: px(600.0), - bottom: px(10.0), - padding_left: px(20.0), - padding_right: px(20.0), - ..Default::default() + if let Ok(props) = query.get(entity) { + rsx! { + <ElementBundle> + <TextWidgetBundle + text={TextProps { + content: format!("Set off for:"), + size: 32.0, + ..Default::default() + }} + styles={KStyle { + color: value(Color::BLACK), + padding: edge_px(20.0), + bottom: px(15.0), + left: stretch(1.0), + right: stretch(1.0), + ..Default::default() + }} + /> + <ScrollContextProviderBundle> + <ScrollBoxBundle> + { + for (place, distance) in ui_data.travel_options.iter() { + constructor! { + <ButtonWidget + styles={ + KStyle { + left: stretch(1.0), + right: stretch(1.0), + width: pct(70.0), + min_width: px(300.0), + max_width: px(600.0), + bottom: px(10.0), + padding_left: px(20.0), + padding_right: px(20.0), + ..Default::default() + } } - } - props={ButtonWidgetProps::text(format!("{}: {}", &place, format_ui_distance(*distance)), 28.0)} - on_event={transit_button_factory(place.clone())} - /> - }; + props={ButtonWidgetProps::text(format!("{}: {}", &place, format_ui_distance(*distance)), 28.0)} + on_event={transit_button_factory(place.clone())} + /> + }; + } } - } - </ScrollBoxBundle> - </ScrollContextProviderBundle> - </ElementBundle> - }; + </ScrollBoxBundle> + </ScrollContextProviderBundle> + </ElementBundle> + }; + } true } -- GitLab