diff --git a/game_core/src/ui/components/button.rs b/game_core/src/ui/components/button.rs index 8d4947eac9ec509d0f18a14ec3f64177007ab0c2..213d45e0da98417a0dbe7f9a36a47a58121336e9 100644 --- a/game_core/src/ui/components/button.rs +++ b/game_core/src/ui/components/button.rs @@ -124,10 +124,10 @@ pub fn render_button_widget( } if should_click { - params.p2().play_sfx("ui_ping"); + params.p2().play_ui_sfx("ui_ping"); } if should_proing { - params.p2().play_sfx("ui_confirm"); + params.p2().play_ui_sfx("ui_confirm"); } (event_dispatcher_context, event) diff --git a/game_core/src/ui/mod.rs b/game_core/src/ui/mod.rs index 9fde625f7ba32063ee9c1a07b140860146576844..36f6dc9051a1d32b9c92bab8b29968fbdb8c5f27 100644 --- a/game_core/src/ui/mod.rs +++ b/game_core/src/ui/mod.rs @@ -93,6 +93,7 @@ mod _config { .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_enter_system(AppState::Settings, super::screens::render_menu_settings_ui) .add_exit_system(AppState::InGame, remove_ui) .add_exit_system(AppState::Tips, remove_ui) .add_exit_system(AppState::Settings, remove_ui) diff --git a/game_core/src/ui/screens/main_menu_settings.rs b/game_core/src/ui/screens/main_menu_settings.rs new file mode 100644 index 0000000000000000000000000000000000000000..378f59b4237f60439dba5d45216c03a3de36d90b --- /dev/null +++ b/game_core/src/ui/screens/main_menu_settings.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!(MainMenuSettingsProps); +parent_widget!(MainMenuSettingsProps => MainMenuSettingsLayout); + +#[derive(Component, PartialEq, Eq, Clone, Default)] +pub struct MainMenuState { + bg_idx: usize, +} + +pub fn render_main_menu_settings_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} /> + <SettingsPanel /> + </ElementBundle> + }; + } + + true +} + +pub fn render_menu_settings_ui(mut commands: Commands) { + let parent_id = None; + let mut widget_context = create_main_menu_context(); + + rsx! { + <KayakAppBundle> + <MainMenuSettingsLayout /> + </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, + MainMenuSettingsProps, + MainMenuState, + render_main_menu_settings_layout + ); + + widget_context +} diff --git a/game_core/src/ui/screens/mod.rs b/game_core/src/ui/screens/mod.rs index 520c779611e7a63744da8d0b6851a30bb09b1c0d..f057158ca3dd350cd5137069462ca9e1c47489b3 100644 --- a/game_core/src/ui/screens/mod.rs +++ b/game_core/src/ui/screens/mod.rs @@ -1,7 +1,9 @@ mod in_game; mod main_menu; +mod main_menu_settings; mod main_menu_tips; pub use in_game::render_in_game_ui; pub use main_menu::render_menu_ui; +pub use main_menu_settings::render_menu_settings_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 9cbcae43e238f957f507c5bbf261994439107716..62c0237dba8923aaf5f4cf5d90508d53427e47aa 100644 --- a/game_core/src/ui/utilities.rs +++ b/game_core/src/ui/utilities.rs @@ -226,6 +226,7 @@ pub mod context { use kayak_ui::prelude::{widget_update, EmptyState, KayakRootContext}; use kayak_ui::widgets::KayakWidgetsContextPlugin; use kayak_ui::KayakUIPlugin; + use micro_musicbox::prelude::AudioSettings; use crate::assets::AssetHandles; use crate::register_widget; @@ -361,6 +362,13 @@ pub mod context { EncounterState, render_encounter_panel ); + register_widget_with_resource!( + widget_context, + SettingsPanelProps, + EmptyState, + AudioSettings, + render_settings_panel + ); } } } diff --git a/game_core/src/ui/widgets/main_menu_panel.rs b/game_core/src/ui/widgets/main_menu_panel.rs index 4dba38978af9928371f9ae22e48f5b20de1f7c65..7e181facf589591ae48ba541d58e7d26e355a0cc 100644 --- a/game_core/src/ui/widgets/main_menu_panel.rs +++ b/game_core/src/ui/widgets/main_menu_panel.rs @@ -115,9 +115,9 @@ pub fn render_main_menu_panel( props={ButtonWidgetProps { text: String::from("Settings"), font_size: 32.0, - is_disabled: true, ..Default::default() }} + on_event={on_settings} /> </ElementBundle> }; diff --git a/game_core/src/ui/widgets/mod.rs b/game_core/src/ui/widgets/mod.rs index 058f709a3baa17daaf0af6446989a150b8ca544b..7f9232cb050bf7bebf21bbab33e1e9649a226068 100644 --- a/game_core/src/ui/widgets/mod.rs +++ b/game_core/src/ui/widgets/mod.rs @@ -1,5 +1,6 @@ mod encounter_panel; mod main_menu_panel; +mod settings_panel; mod shop_panel; mod stats_panel; mod tavern_panel; @@ -9,6 +10,7 @@ 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 settings_panel::{render_settings_panel, SettingsPanel, SettingsPanelProps}; 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}; diff --git a/game_core/src/ui/widgets/settings_panel.rs b/game_core/src/ui/widgets/settings_panel.rs new file mode 100644 index 0000000000000000000000000000000000000000..56caf573aa9acefbe0dc8e5c94e8fdcac8cf592f --- /dev/null +++ b/game_core/src/ui/widgets/settings_panel.rs @@ -0,0 +1,278 @@ +use bevy::prelude::*; +use iyes_loopless::prelude::NextState; +use kayak_ui::prelude::*; +use kayak_ui::widgets::{ + BackgroundBundle, ElementBundle, KImage, KImageBundle, TextProps, TextWidgetBundle, +}; +use micro_musicbox::prelude::AudioSettings; + +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!(SettingsPanelProps); +basic_widget!(SettingsPanelProps => SettingsPanel); + +#[derive(Default, Component, Clone, Copy, Eq, PartialEq)] +pub struct SettingsPanelState { + tip_index: usize, +} + +pub fn render_settings_panel( + In((widget_context, entity)): In<(KayakWidgetContext, Entity)>, + mut commands: Commands, + assets: Res<AssetHandles>, + audio: Res<AudioSettings>, +) -> bool { + let parent_id = Some(entity); + + let state_entity = + widget_context.use_state(&mut commands, entity, SettingsPanelState::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), + color: value(Color::BLACK), + ..Default::default() + }; + + let header_style = KStyle { + left: stretch(1.0), + right: stretch(1.0), + bottom: px(30.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 row_style = KStyle { + layout_type: value(LayoutType::Row), + col_between: px(10.0), + height: px(60.0), + padding_top: stretch(1.0), + padding_bottom: stretch(1.0), + ..Default::default() + }; + + rsx! { + <ElementBundle styles={contents_container_style}> + <PanelWidget> + <TextWidgetBundle + text={TextProps { + content: String::from("Settings"), + font: Some(String::from("header")), + size: 52.0, + ..Default::default() + }} + styles={header_style} + /> + + <BackgroundBundle + styles={row_style.clone()} + > + <TextWidgetBundle + text={TextProps { + content: String::from("Master Volume"), + size: 32.0, + ..Default::default() + }} + /> + <BackgroundBundle styles={KStyle { width: stretch(1.0), ..Default::default() }} /> + <ButtonWidget + props={ButtonWidgetProps { + text: String::from("-"), + font_size: 24.0, + ..Default::default() + }} + on_event={on_button_click!(ResMut<AudioSettings>, |mut settings: ResMut< + AudioSettings, + >| { + settings.master_volume = (settings.master_volume - 0.05).max(0.0); + })} + /> + <BackgroundBundle + styles={KStyle { + padding_left: stretch(1.0), + padding_right: stretch(1.0), + padding_top: stretch(1.0), + padding_bottom: stretch(1.0), + width: px(100.0), + ..Default::default() + }} + > + <TextWidgetBundle + text={TextProps { + content: format!("{:.0}", audio.master_volume * 100.0), + size: 32.0, + ..Default::default() + }} + /> + </BackgroundBundle> + <ButtonWidget + props={ButtonWidgetProps { + text: String::from("+"), + font_size: 24.0, + ..Default::default() + }} + on_event={on_button_click!(ResMut<AudioSettings>, |mut settings: ResMut< + AudioSettings, + >| { + settings.master_volume = (settings.master_volume + 0.05).min(1.0); + })} + /> + </BackgroundBundle> + <BackgroundBundle + styles={row_style.clone()} + > + <TextWidgetBundle + text={TextProps { + content: String::from("Music Volume"), + size: 32.0, + ..Default::default() + }} + /> + <BackgroundBundle styles={KStyle { width: stretch(1.0), ..Default::default() }} /> + <ButtonWidget + props={ButtonWidgetProps { + text: String::from("-"), + font_size: 24.0, + ..Default::default() + }} + on_event={on_button_click!(ResMut<AudioSettings>, |mut settings: ResMut< + AudioSettings, + >| { + settings.music_volume = (settings.music_volume - 0.05).max(0.0); + })} + /> + <BackgroundBundle + styles={KStyle { + padding_left: stretch(1.0), + padding_right: stretch(1.0), + padding_top: stretch(1.0), + padding_bottom: stretch(1.0), + width: px(100.0), + ..Default::default() + }} + > + <TextWidgetBundle + text={TextProps { + content: format!("{:.0}", audio.music_volume * 100.0), + size: 32.0, + ..Default::default() + }} + /> + </BackgroundBundle> + <ButtonWidget + props={ButtonWidgetProps { + text: String::from("+"), + font_size: 24.0, + ..Default::default() + }} + on_event={on_button_click!(ResMut<AudioSettings>, |mut settings: ResMut< + AudioSettings, + >| { + settings.music_volume = (settings.music_volume + 0.05).min(1.0); + })} + /> + </BackgroundBundle> + + <BackgroundBundle + styles={row_style} + > + <TextWidgetBundle + text={TextProps { + content: String::from("UI Volume"), + size: 32.0, + ..Default::default() + }} + /> + <BackgroundBundle styles={KStyle { width: stretch(1.0), ..Default::default() }} /> + <ButtonWidget + props={ButtonWidgetProps { + text: String::from("-"), + font_size: 24.0, + ..Default::default() + }} + on_event={on_button_click!(ResMut<AudioSettings>, |mut settings: ResMut< + AudioSettings, + >| { + settings.ui_volume = (settings.ui_volume - 0.05).max(0.0); + })} + /> + <BackgroundBundle + styles={KStyle { + padding_left: stretch(1.0), + padding_right: stretch(1.0), + padding_top: stretch(1.0), + padding_bottom: stretch(1.0), + width: px(100.0), + ..Default::default() + }} + > + <TextWidgetBundle + text={TextProps { + content: format!("{:.0}", audio.ui_volume * 100.0), + size: 32.0, + ..Default::default() + }} + /> + </BackgroundBundle> + <ButtonWidget + props={ButtonWidgetProps { + text: String::from("+"), + font_size: 24.0, + ..Default::default() + }} + on_event={on_button_click!(ResMut<AudioSettings>, |mut settings: ResMut< + AudioSettings, + >| { + settings.ui_volume = (settings.ui_volume + 0.05).min(1.0); + })} + /> + </BackgroundBundle> + </PanelWidget> + + <ButtonWidget + props={ButtonWidgetProps { text: String::from("Back"), font_size: 32.0, ..Default::default() }} + on_event={on_button_click!(Commands, |mut commands: Commands| { + commands.insert_resource(NextState(AppState::Menu)); + })} + /> + </ElementBundle> + + }; + + true +} diff --git a/game_core/src/ui/widgets/tips_panel.rs b/game_core/src/ui/widgets/tips_panel.rs index 2095181cc36ed88c6f35efe68fe21e0bd6e79eef..3e85e276b897fcf0477e37f387e55eb0ba1d07e0 100644 --- a/game_core/src/ui/widgets/tips_panel.rs +++ b/game_core/src/ui/widgets/tips_panel.rs @@ -80,7 +80,10 @@ pub fn render_tips_panel( }; let controls_style = KStyle { - top: stretch(1.0), + // top: stretch(1.0), + height: px(60.0), + padding_top: stretch(1.0), + padding_bottom: stretch(1.0), layout_type: value(LayoutType::Row), col_between: stretch(1.0), ..Default::default() @@ -114,36 +117,36 @@ pub fn render_tips_panel( .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() - }} - > + <ElementBundle styles={contents_container_style}> + <PanelWidget> <TextWidgetBundle text={TextProps { - content: tips[state.tip_index].to_string(), - size: 32.0, + content: format!("Tip #{}", state.tip_index + 1), + font: Some(String::from("header")), + size: 52.0, ..Default::default() }} + styles={header_style} /> - </BackgroundBundle> + <BackgroundBundle + styles={KStyle { + 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> + </PanelWidget> <BackgroundBundle styles={controls_style}> <ButtonWidget styles={button_style.clone()} @@ -175,8 +178,7 @@ pub fn render_tips_panel( on_event={on_next_tip} /> </BackgroundBundle> - </PanelWidget> - </ElementBundle> + </ElementBundle> }; }