From 9b22b4e7359e1584aa810ff4d0559ea3224ad299 Mon Sep 17 00:00:00 2001 From: Louis Capitanchik <contact@louiscap.co> Date: Sat, 10 Dec 2022 18:32:14 +0000 Subject: [PATCH] Set up tips panel --- game_core/src/system/flow.rs | 2 + game_core/src/ui/context/main_menu_context.rs | 68 +++++++ game_core/src/ui/context/mod.rs | 6 + game_core/src/ui/mod.rs | 4 + game_core/src/ui/screens/main_menu.rs | 184 +++++++---------- game_core/src/ui/screens/main_menu_tips.rs | 129 ++++++++++++ game_core/src/ui/screens/mod.rs | 2 + game_core/src/ui/utilities.rs | 91 +++++++++ game_core/src/ui/widgets/main_menu_panel.rs | 126 ++++++++++++ game_core/src/ui/widgets/mod.rs | 4 + game_core/src/ui/widgets/tips_panel.rs | 185 ++++++++++++++++++ 11 files changed, 693 insertions(+), 108 deletions(-) create mode 100644 game_core/src/ui/context/main_menu_context.rs create mode 100644 game_core/src/ui/context/mod.rs create mode 100644 game_core/src/ui/screens/main_menu_tips.rs create mode 100644 game_core/src/ui/widgets/main_menu_panel.rs create mode 100644 game_core/src/ui/widgets/tips_panel.rs diff --git a/game_core/src/system/flow.rs b/game_core/src/system/flow.rs index 0f565c7..e0b98c7 100644 --- a/game_core/src/system/flow.rs +++ b/game_core/src/system/flow.rs @@ -18,6 +18,8 @@ pub enum AppState { Splash, /// An initial landing page that will present players with options Menu, + Tips, + Settings, /// The in game state runs all of the actual gameplay logic. Most of the runtime /// will be spent here. InGame, diff --git a/game_core/src/ui/context/main_menu_context.rs b/game_core/src/ui/context/main_menu_context.rs new file mode 100644 index 0000000..30bc667 --- /dev/null +++ b/game_core/src/ui/context/main_menu_context.rs @@ -0,0 +1,68 @@ +use bevy::prelude::*; +use kayak_ui::prelude::*; + +use crate::{empty_props, parent_widget}; + +#[derive(Copy, Clone, Default, Debug, Eq, PartialEq)] +pub enum MainMenuView { + MenuPanel, + TipsPanel, + SettingsPanel, + #[default] + None, +} +impl MainMenuView { + pub fn is_none(&self) -> bool { + self == &MainMenuView::None + } +} + +#[derive(Copy, Clone, Default, Debug, Component, Eq, PartialEq)] +pub struct MainMenuContext { + pub view: MainMenuView, + pub next: Option<MainMenuView>, +} + +impl MainMenuContext { + pub fn new() -> Self { + Self { + view: MainMenuView::MenuPanel, + next: None, + } + } + + pub fn next(&mut self, next: MainMenuView) { + self.view = next; + // self.view = MainMenuView::None; + // self.next = Some(next); + } + + fn cycle(&mut self) { + if let Some(next) = self.next { + self.view = next; + self.next = None; + } + } +} + +empty_props!(MainMenuContextProps); +parent_widget!(MainMenuContextProps => MainMenuContextProvider); + +pub fn render_main_menu_context_provider( + In((mut widget_context, entity)): In<(KayakWidgetContext, Entity)>, + mut commands: Commands, + mut children_query: Query<(&KStyle, &mut ComputedStyles, &KChildren)>, + mut context_query: Query<&mut MainMenuContext>, +) -> bool { + if let Ok((style, mut computed, children)) = children_query.get_mut(entity) { + *computed = ComputedStyles(style.clone()); + if let Some(context_entity) = widget_context.get_context_entity::<MainMenuContext>(entity) { + children.process(&mut widget_context, Some(entity)); + } else { + let context_entity = commands.spawn(MainMenuContext::new()).id(); + widget_context.set_context_entity::<MainMenuContext>(Some(entity), context_entity); + } + } + + true +} diff --git a/game_core/src/ui/context/mod.rs b/game_core/src/ui/context/mod.rs new file mode 100644 index 0000000..7e91498 --- /dev/null +++ b/game_core/src/ui/context/mod.rs @@ -0,0 +1,6 @@ +mod main_menu_context; + +pub use main_menu_context::{ + render_main_menu_context_provider, MainMenuContext, MainMenuContextProps, + MainMenuContextProvider, MainMenuView, +}; diff --git a/game_core/src/ui/mod.rs b/game_core/src/ui/mod.rs index c9ea135..9fde625 100644 --- a/game_core/src/ui/mod.rs +++ b/game_core/src/ui/mod.rs @@ -3,6 +3,7 @@ use kayak_ui::prelude::KayakContextPlugin; use kayak_ui::widgets::KayakWidgets; pub mod components; +pub mod context; pub mod screens; pub mod sync; pub mod utilities; @@ -91,7 +92,10 @@ mod _config { app.add_exit_system(AppState::Setup, configure_kayak_ui) .add_enter_system(AppState::Menu, super::screens::render_menu_ui) .add_enter_system(AppState::InGame, super::screens::render_in_game_ui) + .add_enter_system(AppState::Tips, super::screens::render_menu_tips_ui) .add_exit_system(AppState::InGame, remove_ui) + .add_exit_system(AppState::Tips, remove_ui) + .add_exit_system(AppState::Settings, remove_ui) .add_exit_system(AppState::Menu, remove_ui); } } diff --git a/game_core/src/ui/screens/main_menu.rs b/game_core/src/ui/screens/main_menu.rs index 7e81556..201fed4 100644 --- a/game_core/src/ui/screens/main_menu.rs +++ b/game_core/src/ui/screens/main_menu.rs @@ -9,6 +9,7 @@ use crate::assets::AssetHandles; use crate::persistance::{fs_utils, LoadFileEvent}; use crate::system::flow::AppState; use crate::ui::components::*; +use crate::ui::context::*; use crate::ui::prelude::{pct, px, stretch, value}; use crate::ui::sync::UITravelInfo; use crate::ui::utilities::context::create_root_context; @@ -16,128 +17,86 @@ use crate::ui::utilities::StateUIRoot; use crate::ui::widgets::*; use crate::world::EncounterState; use crate::{ - empty_props, on_button_click, parent_widget, register_widget, + empty_props, on_button_click, parent_widget, register_widget, register_widget_with_context, register_widget_with_many_resources, register_widget_with_resource, }; empty_props!(MainMenuProps); parent_widget!(MainMenuProps => MainMenuLayout); +#[derive(Component, PartialEq, Eq, Clone, Default)] +pub struct MainMenuState { + bg_idx: usize, +} + pub fn render_main_menu_layout( In((widget_context, entity)): In<(KayakWidgetContext, Entity)>, mut commands: Commands, assets: Res<AssetHandles>, + state_query: Query<&MainMenuState>, ) -> bool { let parent_id = Some(entity); let has_autosave = fs_utils::has_auto_save(); - let root_styles = KStyle { - ..Default::default() - }; - - let image_styles = KStyle { - position_type: value(KPositionType::SelfDirected), - ..Default::default() - }; - - let contents_container_style = KStyle { - position_type: value(KPositionType::SelfDirected), - width: pct(65.0), - height: pct(80.0), - min_width: px(400.0), - min_height: px(300.0), - max_width: px(500.0), - max_height: px(400.0), - top: stretch(1.0), - left: stretch(1.0), - right: stretch(1.0), - bottom: stretch(1.0), - layout_type: value(LayoutType::Column), - row_between: px(20.0), - ..Default::default() - }; - - let header_style = KStyle { - left: stretch(1.0), - right: stretch(1.0), - ..Default::default() - }; - - let background_idx = fastrand::usize(1..6); - let image = KImage(assets.image(format!("menu_background_{}", background_idx))); - - let button_style = KStyle { - width: px(300.0), - left: stretch(1.0), - right: stretch(1.0), - ..Default::default() - }; - - let on_new = on_button_click!(Commands, |mut commands: Commands| { - commands.insert_resource(NextState(AppState::InGame)); - }); - - let on_continue = on_button_click!(EventWriter<LoadFileEvent>, |mut events: EventWriter< - LoadFileEvent, - >| { - events.send(LoadFileEvent::autosave()); - }); + let state_entity = widget_context.use_state( + &mut commands, + entity, + MainMenuState { + bg_idx: fastrand::usize(1..6), + }, + ); - rsx! { - <ElementBundle styles={root_styles}> - <KImageBundle styles={image_styles} image={image} /> - <ElementBundle styles={contents_container_style}> - <TextWidgetBundle - text={TextProps { - content: String::from("Trader Tales"), - font: Some(String::from("header")), - size: 72.0, - ..Default::default() - }} - styles={header_style} - /> - - <ButtonWidget - styles={button_style.clone()} - props={ButtonWidgetProps { - text: String::from("New Game"), - font_size: 32.0, - ..Default::default() - }} - on_event={on_new} - /> - - <ButtonWidget - styles={button_style.clone()} - props={ButtonWidgetProps { - text: String::from("Continue Game"), - font_size: 32.0, - is_disabled: !has_autosave, - ..Default::default() - }} - on_event={on_continue} - /> - <ButtonWidget - styles={button_style.clone()} - props={ButtonWidgetProps { - text: String::from("Tips"), - font_size: 32.0, - is_disabled: true, - ..Default::default() - }} - /> - <ButtonWidget - styles={button_style.clone()} - props={ButtonWidgetProps { - text: String::from("Settings"), - font_size: 32.0, - is_disabled: true, - ..Default::default() - }} - /> + if let Ok(state) = state_query.get(state_entity) { + log::info!("Rendering main menu"); + + let root_styles = KStyle { + ..Default::default() + }; + + let image_styles = KStyle { + position_type: value(KPositionType::SelfDirected), + ..Default::default() + }; + + let contents_container_style = KStyle { + position_type: value(KPositionType::SelfDirected), + width: pct(65.0), + height: pct(80.0), + min_width: px(400.0), + min_height: px(300.0), + max_width: px(500.0), + max_height: px(400.0), + top: stretch(1.0), + left: stretch(1.0), + right: stretch(1.0), + bottom: stretch(1.0), + layout_type: value(LayoutType::Column), + row_between: px(20.0), + ..Default::default() + }; + + let header_style = KStyle { + left: stretch(1.0), + right: stretch(1.0), + ..Default::default() + }; + + let image = KImage(assets.image(format!("menu_background_{}", state.bg_idx))); + + let button_style = KStyle { + width: px(300.0), + left: stretch(1.0), + right: stretch(1.0), + ..Default::default() + }; + + rsx! { + <ElementBundle styles={root_styles}> + <KImageBundle styles={image_styles} image={image} /> + <MainMenuPanel /> </ElementBundle> - </ElementBundle> - }; + }; + } true } @@ -158,12 +117,21 @@ pub fn render_menu_ui(mut commands: Commands) { fn create_main_menu_context() -> KayakRootContext { let mut widget_context = create_root_context(); - register_widget!( + register_widget_with_context!( widget_context, MainMenuProps, - EmptyState, + MainMenuState, + MainMenuContext, render_main_menu_layout ); + register_widget_with_context!( + widget_context, + MainMenuContextProps, + EmptyState, + MainMenuContext, + render_main_menu_context_provider + ); + widget_context } diff --git a/game_core/src/ui/screens/main_menu_tips.rs b/game_core/src/ui/screens/main_menu_tips.rs new file mode 100644 index 0000000..5c63931 --- /dev/null +++ b/game_core/src/ui/screens/main_menu_tips.rs @@ -0,0 +1,129 @@ +use bevy::prelude::*; +use iyes_loopless::prelude::NextState; +use kayak_ui::prelude::*; +use kayak_ui::widgets::{ + ElementBundle, KImage, KImageBundle, KayakAppBundle, TextProps, TextWidgetBundle, +}; + +use crate::assets::AssetHandles; +use crate::persistance::{fs_utils, LoadFileEvent}; +use crate::system::flow::AppState; +use crate::ui::components::*; +use crate::ui::context::*; +use crate::ui::prelude::{pct, px, stretch, value}; +use crate::ui::sync::UITravelInfo; +use crate::ui::utilities::context::create_root_context; +use crate::ui::utilities::StateUIRoot; +use crate::ui::widgets::*; +use crate::world::EncounterState; +use crate::{ + empty_props, on_button_click, parent_widget, register_widget, register_widget_with_context, + register_widget_with_many_resources, register_widget_with_resource, +}; + +empty_props!(MainMenuTipsProps); +parent_widget!(MainMenuTipsProps => MainMenuTipsLayout); + +#[derive(Component, PartialEq, Eq, Clone, Default)] +pub struct MainMenuState { + bg_idx: usize, +} + +pub fn render_main_menu_tips_layout( + In((widget_context, entity)): In<(KayakWidgetContext, Entity)>, + mut commands: Commands, + assets: Res<AssetHandles>, + context_query: Query<&MainMenuContext>, + state_query: Query<&MainMenuState>, +) -> bool { + let parent_id = Some(entity); + let has_autosave = fs_utils::has_auto_save(); + + let state_entity = widget_context.use_state( + &mut commands, + entity, + MainMenuState { + bg_idx: fastrand::usize(1..6), + }, + ); + + if let Ok(state) = state_query.get(state_entity) { + log::info!("Rendering main menu"); + + let root_styles = KStyle { + ..Default::default() + }; + + let image_styles = KStyle { + position_type: value(KPositionType::SelfDirected), + ..Default::default() + }; + + let contents_container_style = KStyle { + position_type: value(KPositionType::SelfDirected), + width: pct(65.0), + height: pct(80.0), + min_width: px(400.0), + min_height: px(300.0), + max_width: px(500.0), + max_height: px(400.0), + top: stretch(1.0), + left: stretch(1.0), + right: stretch(1.0), + bottom: stretch(1.0), + layout_type: value(LayoutType::Column), + row_between: px(20.0), + ..Default::default() + }; + + let header_style = KStyle { + left: stretch(1.0), + right: stretch(1.0), + ..Default::default() + }; + + let image = KImage(assets.image(format!("menu_background_{}", state.bg_idx))); + + let button_style = KStyle { + width: px(300.0), + left: stretch(1.0), + right: stretch(1.0), + ..Default::default() + }; + + rsx! { + <ElementBundle styles={root_styles}> + <KImageBundle styles={image_styles} image={image} /> + <TipsPanel /> + </ElementBundle> + }; + } + + true +} + +pub fn render_menu_tips_ui(mut commands: Commands) { + let parent_id = None; + let mut widget_context = create_main_menu_context(); + + rsx! { + <KayakAppBundle> + <MainMenuTipsLayout /> + </KayakAppBundle> + }; + + commands.spawn((UICameraBundle::new(widget_context), StateUIRoot)); +} + +fn create_main_menu_context() -> KayakRootContext { + let mut widget_context = create_root_context(); + + register_widget!( + widget_context, + MainMenuTipsProps, + MainMenuState, + render_main_menu_tips_layout + ); + + widget_context +} diff --git a/game_core/src/ui/screens/mod.rs b/game_core/src/ui/screens/mod.rs index 9f3b28a..520c779 100644 --- a/game_core/src/ui/screens/mod.rs +++ b/game_core/src/ui/screens/mod.rs @@ -1,5 +1,7 @@ mod in_game; mod main_menu; +mod main_menu_tips; pub use in_game::render_in_game_ui; pub use main_menu::render_menu_ui; +pub use main_menu_tips::render_menu_tips_ui; diff --git a/game_core/src/ui/utilities.rs b/game_core/src/ui/utilities.rs index 41e6f28..9cbcae4 100644 --- a/game_core/src/ui/utilities.rs +++ b/game_core/src/ui/utilities.rs @@ -114,6 +114,18 @@ macro_rules! register_widget_with_resource { }}; } +#[macro_export] +macro_rules! register_widget_with_context { + ($ctx: expr, $props: ident, $state: path, $context: ident, $system: ident) => {{ + $ctx.add_widget_data::<$props, $state>(); + $ctx.add_widget_system( + ::kayak_ui::prelude::Widget::get_name(&$props::default()), + ::kayak_ui::prelude::widget_update_with_context::<$props, $state, $context>, + $system, + ); + }}; +} + #[macro_export] macro_rules! register_widget_with_many_resources { ($ctx: expr, $props: ident, $state: ident, $system: ident, $($name: ident: $resource: ident),+) => {{ @@ -133,6 +145,59 @@ macro_rules! register_widget_with_many_resources { }}; } +// pub fn widget_update_with_context< +// Props: PartialEq + Component + Clone, +// State: PartialEq + Component + Clone, +// Context: PartialEq + Component + Clone + Default, +// >( +// In((widget_context, entity, previous_entity)): In<(KayakWidgetContext, Entity, Entity)>, +// widget_param: WidgetParam<Props, State>, +// context_query: Query<Entity, Changed<Context>>, +// ) -> bool { +// // Uses bevy state changes to see if context has changed. +// if let Some(context_entity) = widget_context.get_context_entity::<Context>(entity) { +// if context_query.contains(context_entity) { +// log::trace!( +// "Entity context: {} has changed! {}-{}", +// std::any::type_name::<Context>(), +// widget_param.widget_names.get(entity).unwrap().0, +// entity.index() +// ); +// return true; +// } +// } +// +// widget_param.has_changed(&widget_context, entity, previous_entity) +// } + +#[macro_export] +macro_rules! register_complex_widget { + ($ctx: expr, $props: ident, $state: ident, $system: ident, R($($r_name: ident: $resource: ident),*), C($($c_name: ident: $context: ident),*)) => {{ + $ctx.add_widget_data::<$props, $state>(); + $ctx.add_widget_system( + ::kayak_ui::prelude::Widget::get_name(&$props::default()), + | + ::bevy::prelude::In((widget_context, entity, previous_entity)): ::bevy::prelude::In<(::kayak_ui::prelude::KayakWidgetContext, ::bevy::prelude::Entity, ::bevy::prelude::Entity)>, + $( $r_name: ::bevy::prelude::Res<$resource> ),*, + $( $c_name: ::bevy::prelude::Query<::bevy::prelude::Entity, ::bevy::prelude::Changed<$context>> ),*, + widget_param: ::kayak_ui::prelude::WidgetParam<$props, $state>, + | { + $( + if let Some(context_entity) = widget_context.get_context_entity::<$context>(entity) { + if $c_name.contains(context_entity) { + return true; + } + } + )* + + widget_param.has_changed(&widget_context, entity, previous_entity) + $( || $r_name.is_changed() )* + }, + $system, + ); + }}; +} + pub fn widget_update_with_resource< Props: PartialEq + Component + Clone, State: PartialEq + Component + Clone, @@ -162,8 +227,10 @@ pub mod context { use kayak_ui::widgets::KayakWidgetsContextPlugin; use kayak_ui::KayakUIPlugin; + use crate::assets::AssetHandles; use crate::register_widget; use crate::ui::components::*; + use crate::ui::context::*; use crate::ui::sync::{UIStatsData, UITradeData, UITravelInfo}; use crate::ui::widgets::*; use crate::world::EncounterState; @@ -263,6 +330,30 @@ pub mod context { UIStatsData, render_stats_panel ); + register_widget_with_context!( + widget_context, + MainMenuPanelProps, + EmptyState, + MainMenuContext, + render_main_menu_panel + ); + // register_widget_with_context!( + // widget_context, + // TipsPanelProps, + // TipsPanelState, + // MainMenuContext, + // render_tips_panel + // ); + + register_complex_widget! { + widget_context, + TipsPanelProps, + TipsPanelState, + render_tips_panel, + R(ass: AssetHandles), + C(mainmenucontext: MainMenuContext) + }; + register_widget_with_resource!( widget_context, EncounterPanelProps, diff --git a/game_core/src/ui/widgets/main_menu_panel.rs b/game_core/src/ui/widgets/main_menu_panel.rs new file mode 100644 index 0000000..4dba389 --- /dev/null +++ b/game_core/src/ui/widgets/main_menu_panel.rs @@ -0,0 +1,126 @@ +use bevy::prelude::*; +use iyes_loopless::prelude::NextState; +use kayak_ui::prelude::*; +use kayak_ui::widgets::{ElementBundle, KImage, KImageBundle, TextProps, TextWidgetBundle}; + +use crate::assets::AssetHandles; +use crate::persistance::{fs_utils, LoadFileEvent}; +use crate::system::flow::AppState; +use crate::ui::components::*; +use crate::ui::context::{MainMenuContext, MainMenuView}; +use crate::ui::prelude::{pct, px, stretch, value}; +use crate::ui::widgets::*; +use crate::{basic_widget, empty_props, on_button_click}; + +empty_props!(MainMenuPanelProps); +basic_widget!(MainMenuPanelProps => MainMenuPanel); + +pub fn render_main_menu_panel( + In((widget_context, entity)): In<(KayakWidgetContext, Entity)>, + mut commands: Commands, + mut props_query: Query<&mut ComputedStyles>, + assets: Res<AssetHandles>, +) -> bool { + let parent_id = Some(entity); + let has_autosave = fs_utils::has_auto_save(); + + let contents_container_style = KStyle { + position_type: value(KPositionType::SelfDirected), + width: pct(65.0), + height: pct(80.0), + min_width: px(400.0), + min_height: px(300.0), + max_width: px(500.0), + max_height: px(400.0), + top: stretch(1.0), + left: stretch(1.0), + right: stretch(1.0), + bottom: stretch(1.0), + layout_type: value(LayoutType::Column), + row_between: px(20.0), + ..Default::default() + }; + + let header_style = KStyle { + left: stretch(1.0), + right: stretch(1.0), + ..Default::default() + }; + + let button_style = KStyle { + width: px(300.0), + left: stretch(1.0), + right: stretch(1.0), + ..Default::default() + }; + + let on_new = on_button_click!(Commands, |mut commands: Commands| { + commands.insert_resource(NextState(AppState::InGame)); + }); + let on_tips = on_button_click!(Commands, |mut commands: Commands| { + commands.insert_resource(NextState(AppState::Tips)); + }); + let on_settings = on_button_click!(Commands, |mut commands: Commands| { + commands.insert_resource(NextState(AppState::Settings)); + }); + let on_continue = on_button_click!(EventWriter<LoadFileEvent>, |mut events: EventWriter< + LoadFileEvent, + >| { + events.send(LoadFileEvent::autosave()); + }); + + rsx! { + <ElementBundle styles={contents_container_style}> + <TextWidgetBundle + text={TextProps { + content: String::from("Trader Tales"), + font: Some(String::from("header")), + size: 72.0, + ..Default::default() + }} + styles={header_style} + /> + + <ButtonWidget + styles={button_style.clone()} + props={ButtonWidgetProps { + text: String::from("New Game"), + font_size: 32.0, + ..Default::default() + }} + on_event={on_new} + /> + + <ButtonWidget + styles={button_style.clone()} + props={ButtonWidgetProps { + text: String::from("Continue Game"), + font_size: 32.0, + is_disabled: !has_autosave, + ..Default::default() + }} + on_event={on_continue} + /> + <ButtonWidget + styles={button_style.clone()} + props={ButtonWidgetProps { + text: String::from("Tips"), + font_size: 32.0, + ..Default::default() + }} + on_event={on_tips} + /> + <ButtonWidget + styles={button_style.clone()} + props={ButtonWidgetProps { + text: String::from("Settings"), + font_size: 32.0, + is_disabled: true, + ..Default::default() + }} + /> + </ElementBundle> + }; + + true +} diff --git a/game_core/src/ui/widgets/mod.rs b/game_core/src/ui/widgets/mod.rs index d4bd4a6..058f709 100644 --- a/game_core/src/ui/widgets/mod.rs +++ b/game_core/src/ui/widgets/mod.rs @@ -1,14 +1,18 @@ mod encounter_panel; +mod main_menu_panel; mod shop_panel; mod stats_panel; mod tavern_panel; +mod tips_panel; mod town_menu; mod transit_panel; pub use encounter_panel::{render_encounter_panel, EncounterPanel, EncounterPanelProps}; +pub use main_menu_panel::{render_main_menu_panel, MainMenuPanel, MainMenuPanelProps}; pub use shop_panel::{render_shop_panel, ShopPanel, ShopPanelProps}; pub use stats_panel::{render_stats_panel, StatsPanel, StatsPanelProps}; pub use tavern_panel::{render_tavern_panel, TavernPanel, TavernPanelProps}; +pub use tips_panel::{render_tips_panel, TipsPanel, TipsPanelProps, TipsPanelState}; pub use town_menu::{ render_town_menu_panel, TownMenuPanel, TownMenuPanelProps, TownMenuPanelState, }; diff --git a/game_core/src/ui/widgets/tips_panel.rs b/game_core/src/ui/widgets/tips_panel.rs new file mode 100644 index 0000000..2095181 --- /dev/null +++ b/game_core/src/ui/widgets/tips_panel.rs @@ -0,0 +1,185 @@ +use bevy::prelude::*; +use iyes_loopless::prelude::NextState; +use kayak_ui::prelude::*; +use kayak_ui::widgets::{ + BackgroundBundle, ElementBundle, KImage, KImageBundle, TextProps, TextWidgetBundle, +}; + +use crate::assets::AssetHandles; +use crate::persistance::{fs_utils, LoadFileEvent}; +use crate::system::flow::AppState; +use crate::ui::components::*; +use crate::ui::context::{MainMenuContext, MainMenuView}; +use crate::ui::prelude::{edge_px, pct, px, stretch, value}; +use crate::ui::widgets::*; +use crate::{basic_widget, empty_props, on_button_click}; + +empty_props!(TipsPanelProps); +basic_widget!(TipsPanelProps => TipsPanel); + +#[derive(Default, Component, Clone, Copy, Eq, PartialEq)] +pub struct TipsPanelState { + tip_index: usize, +} + +pub fn render_tips_panel( + In((widget_context, entity)): In<(KayakWidgetContext, Entity)>, + mut commands: Commands, + assets: Res<AssetHandles>, + mut props_query: Query<&mut ComputedStyles>, + state_query: Query<&TipsPanelState>, +) -> bool { + let parent_id = Some(entity); + + let state_entity = widget_context.use_state(&mut commands, entity, TipsPanelState::default()); + + if let Ok(mut computed) = props_query.get_mut(entity) { + if let Ok(state) = state_query.get(state_entity) { + let tips = vec![ + "Town merchants will only pay 80% of their sell price when buying goods from you", + "Be sure to tip your barkeep, they usually have great information on the various towns", + "Your game will be saved when you enter a town - make sure you reach your next destination to save your progress!", + "Without a regular supply of foods, towns might starve. Visit the tavern to check up on the locals.", + ]; + + let tips_len = tips.len(); + + let contents_container_style = KStyle { + position_type: value(KPositionType::SelfDirected), + width: pct(65.0), + height: pct(80.0), + min_width: px(400.0), + min_height: px(300.0), + max_width: px(500.0), + max_height: px(400.0), + top: stretch(1.0), + left: stretch(1.0), + right: stretch(1.0), + bottom: stretch(1.0), + layout_type: value(LayoutType::Column), + row_between: px(20.0), + color: value(Color::BLACK), + ..Default::default() + }; + + let header_style = KStyle { + left: stretch(1.0), + right: stretch(1.0), + ..Default::default() + }; + + let tip_style = KStyle { + height: stretch(1.0), + padding_left: px(20.0), + padding_right: px(20.0), + ..Default::default() + }; + + let button_style = KStyle { + ..Default::default() + }; + + let controls_style = KStyle { + top: stretch(1.0), + layout_type: value(LayoutType::Row), + col_between: stretch(1.0), + ..Default::default() + }; + + let on_next_tip = on_button_click!(Query<&mut TipsPanelState>, |mut query: Query< + &mut TipsPanelState, + >| { + if let Ok(mut state) = query.get_mut(state_entity) { + state.tip_index = if state.tip_index == tips_len - 1 { + 0 + } else { + state.tip_index + 1 + }; + } + }); + let on_previous_tip = + on_button_click!(Query<&mut TipsPanelState>, |mut query: Query< + &mut TipsPanelState, + >| { + if let Ok(mut state) = query.get_mut(state_entity) { + state.tip_index = if state.tip_index == 0 { + tips_len - 1 + } else { + state.tip_index - 1 + }; + } + }); + + let on_back = on_button_click!(Commands, |mut commands: Commands| commands + .insert_resource(NextState(AppState::Menu))); + + rsx! { + <ElementBundle> + <PanelWidget styles={contents_container_style}> + <TextWidgetBundle + text={TextProps { + content: format!("Tip #{}", state.tip_index + 1), + font: Some(String::from("header")), + size: 52.0, + ..Default::default() + }} + styles={header_style} + /> + <BackgroundBundle + styles={KStyle { + height: stretch(1.0), + width: pct(80.0), + left: stretch(1.0), + right: stretch(1.0), + top: px(30.0), + bottom: px(30.0), + ..Default::default() + }} + > + <TextWidgetBundle + text={TextProps { + content: tips[state.tip_index].to_string(), + size: 32.0, + ..Default::default() + }} + /> + </BackgroundBundle> + <BackgroundBundle styles={controls_style}> + <ButtonWidget + styles={button_style.clone()} + props={ButtonWidgetProps { + text: String::from("<"), + font_size: 32.0, + ..Default::default() + }} + on_event={on_previous_tip} + /> + + <ButtonWidget + styles={button_style.clone()} + props={ButtonWidgetProps { + text: String::from("back"), + font_size: 32.0, + ..Default::default() + }} + on_event={on_back} + /> + + <ButtonWidget + styles={button_style.clone()} + props={ButtonWidgetProps { + text: String::from(">"), + font_size: 32.0, + ..Default::default() + }} + on_event={on_next_tip} + /> + </BackgroundBundle> + </PanelWidget> + </ElementBundle> + + }; + } + } + true +} -- GitLab