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