From bfdb44dbcfc51edba3bb402bee3e68e999d85300 Mon Sep 17 00:00:00 2001
From: Louis Capitanchik <contact@louiscap.co>
Date: Sat, 10 Dec 2022 19:16:36 +0000
Subject: [PATCH] Implement settings menu

---
 game_core/src/ui/components/button.rs         |   4 +-
 game_core/src/ui/mod.rs                       |   1 +
 .../src/ui/screens/main_menu_settings.rs      | 129 ++++++++
 game_core/src/ui/screens/mod.rs               |   2 +
 game_core/src/ui/utilities.rs                 |   8 +
 game_core/src/ui/widgets/main_menu_panel.rs   |   2 +-
 game_core/src/ui/widgets/mod.rs               |   2 +
 game_core/src/ui/widgets/settings_panel.rs    | 278 ++++++++++++++++++
 game_core/src/ui/widgets/tips_panel.rs        |  58 ++--
 9 files changed, 453 insertions(+), 31 deletions(-)
 create mode 100644 game_core/src/ui/screens/main_menu_settings.rs
 create mode 100644 game_core/src/ui/widgets/settings_panel.rs

diff --git a/game_core/src/ui/components/button.rs b/game_core/src/ui/components/button.rs
index 8d4947e..213d45e 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 9fde625..36f6dc9 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 0000000..378f59b
--- /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 520c779..f057158 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 9cbcae4..62c0237 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 4dba389..7e181fa 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 058f709..7f9232c 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 0000000..56caf57
--- /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 2095181..3e85e27 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>
 
 			};
 		}
-- 
GitLab