From 339f822a8087e827a5c2855d6fdad8fa9b080ebb Mon Sep 17 00:00:00 2001 From: dawnDus <96957561+dawndus@users.noreply.github.com> Date: Sun, 10 Apr 2022 15:57:19 -0400 Subject: [PATCH] Better Save Menu UX (fixes #111) --- src/menu/mod.rs | 105 +++++++++++++++++++---------------- src/menu/save_select_menu.rs | 68 ++++++++++++++++++----- src/scene/title_scene.rs | 2 +- 3 files changed, 113 insertions(+), 62 deletions(-) diff --git a/src/menu/mod.rs b/src/menu/mod.rs index 5987ca1..4cb2aee 100644 --- a/src/menu/mod.rs +++ b/src/menu/mod.rs @@ -13,6 +13,7 @@ pub mod pause_menu; pub mod save_select_menu; pub mod settings_menu; +#[derive(Clone)] pub enum MenuEntry { Hidden, Active(String), @@ -23,6 +24,7 @@ pub enum MenuEntry { DescriptiveOptions(String, usize, Vec, Vec), OptionsBar(String, f32), SaveData(MenuSaveInfo), + SaveDataSingle(MenuSaveInfo), NewSave, } @@ -38,6 +40,7 @@ impl MenuEntry { MenuEntry::DescriptiveOptions(_, _, _, _) => 16.0, MenuEntry::OptionsBar(_, _) => 16.0, MenuEntry::SaveData(_) => 32.0, + MenuEntry::SaveDataSingle(_) => 32.0, MenuEntry::NewSave => 32.0, } } @@ -53,6 +56,7 @@ impl MenuEntry { MenuEntry::DescriptiveOptions(_, _, _, _) => true, MenuEntry::OptionsBar(_, _) => true, MenuEntry::SaveData(_) => true, + MenuEntry::SaveDataSingle(_) => true, MenuEntry::NewSave => true, } } @@ -76,6 +80,7 @@ pub struct Menu { anim_num: u16, anim_wait: u16, custom_cursor: Cell, + pub draw_cursor: bool, } impl Menu { @@ -90,6 +95,7 @@ impl Menu { anim_wait: 0, entries: Vec::new(), custom_cursor: Cell::new(true), + draw_cursor: true, } } @@ -152,6 +158,7 @@ impl Menu { width = width.max(entry_width); } MenuEntry::SaveData(_) => {} + MenuEntry::SaveDataSingle(_) => {} MenuEntry::NewSave => {} } } @@ -271,59 +278,61 @@ impl Menu { entry_y = self.entries[0..(self.selected)].iter().map(|e| e.height()).sum::().max(0.0) as u16; } - if self.custom_cursor.get() { - if let Ok(batch) = state.texture_set.get_or_load_batch(ctx, &state.constants, "MenuCursor") { - rect.left = self.anim_num * 16; - rect.top = 16; - rect.right = rect.left + 16; - rect.bottom = rect.top + 16; + if self.draw_cursor { + if self.custom_cursor.get() { + if let Ok(batch) = state.texture_set.get_or_load_batch(ctx, &state.constants, "MenuCursor") { + rect.left = self.anim_num * 16; + rect.top = 16; + rect.right = rect.left + 16; + rect.bottom = rect.top + 16; - batch.add_rect(self.x as f32, self.y as f32 + 3.0 + entry_y as f32, &rect); + batch.add_rect(self.x as f32, self.y as f32 + 3.0 + entry_y as f32, &rect); + + batch.draw(ctx)?; + } else { + self.custom_cursor.set(false); + } + } + + if !self.custom_cursor.get() { + let menu_texture: &str; + let character_rect: [Rect; 4]; + + match state.menu_character { + MenuCharacter::Quote => { + menu_texture = "MyChar"; + character_rect = state.constants.title.cursor_quote; + } + MenuCharacter::Curly => { + menu_texture = "Npc/NpcRegu"; + character_rect = state.constants.title.cursor_curly; + } + MenuCharacter::Toroko => { + menu_texture = "Npc/NpcRegu"; + character_rect = state.constants.title.cursor_toroko; + } + MenuCharacter::King => { + menu_texture = "Npc/NpcRegu"; + character_rect = state.constants.title.cursor_king; + } + MenuCharacter::Sue => { + menu_texture = "Npc/NpcRegu"; + character_rect = state.constants.title.cursor_sue; + } + } + + let batch = state.texture_set.get_or_load_batch(ctx, &state.constants, menu_texture)?; + + batch.add_rect( + self.x as f32, + self.y as f32 + 4.0 + entry_y as f32, + &character_rect[self.anim_num as usize], + ); batch.draw(ctx)?; - } else { - self.custom_cursor.set(false); } } - if !self.custom_cursor.get() { - let menu_texture: &str; - let character_rect: [Rect; 4]; - - match state.menu_character { - MenuCharacter::Quote => { - menu_texture = "MyChar"; - character_rect = state.constants.title.cursor_quote; - } - MenuCharacter::Curly => { - menu_texture = "Npc/NpcRegu"; - character_rect = state.constants.title.cursor_curly; - } - MenuCharacter::Toroko => { - menu_texture = "Npc/NpcRegu"; - character_rect = state.constants.title.cursor_toroko; - } - MenuCharacter::King => { - menu_texture = "Npc/NpcRegu"; - character_rect = state.constants.title.cursor_king; - } - MenuCharacter::Sue => { - menu_texture = "Npc/NpcRegu"; - character_rect = state.constants.title.cursor_sue; - } - } - - let batch = state.texture_set.get_or_load_batch(ctx, &state.constants, menu_texture)?; - - batch.add_rect( - self.x as f32, - self.y as f32 + 4.0 + entry_y as f32, - &character_rect[self.anim_num as usize], - ); - - batch.draw(ctx)?; - } - y = self.y as f32 + 8.0; for entry in &self.entries { match entry { @@ -481,7 +490,7 @@ impl Menu { ctx, )?; } - MenuEntry::SaveData(save) => { + MenuEntry::SaveData(save) | MenuEntry::SaveDataSingle(save) => { let name = &state.stages[save.current_map as usize].name; let bar_width = (save.life as f32 / save.max_life as f32 * 39.0) as u16; let right_edge = self.x as f32 + self.width as f32 - 4.0; diff --git a/src/menu/save_select_menu.rs b/src/menu/save_select_menu.rs index ab8fe68..0a6f003 100644 --- a/src/menu/save_select_menu.rs +++ b/src/menu/save_select_menu.rs @@ -26,13 +26,16 @@ pub enum CurrentMenu { SaveMenu, DifficultyMenu, DeleteConfirm, + LoadConfirm, } pub struct SaveSelectMenu { pub saves: [MenuSaveInfo; 3], current_menu: CurrentMenu, save_menu: Menu, + save_detailed: Menu, difficulty_menu: Menu, delete_confirm: Menu, + load_confirm: Menu, skip_difficulty_menu: bool, } @@ -42,16 +45,20 @@ impl SaveSelectMenu { saves: [MenuSaveInfo::default(); 3], current_menu: CurrentMenu::SaveMenu, save_menu: Menu::new(0, 0, 230, 0), + save_detailed: Menu::new(0, 0, 230, 0), difficulty_menu: Menu::new(0, 0, 130, 0), delete_confirm: Menu::new(0, 0, 75, 0), + load_confirm: Menu::new(0, 0, 75, 0), skip_difficulty_menu: false, } } pub fn init(&mut self, state: &mut SharedGameState, ctx: &Context) -> GameResult { self.save_menu = Menu::new(0, 0, 230, 0); + self.save_detailed = Menu::new(0, 0, 230, 0); self.difficulty_menu = Menu::new(0, 0, 130, 0); self.delete_confirm = Menu::new(0, 0, 75, 0); + self.load_confirm = Menu::new(0, 0, 75, 0); self.skip_difficulty_menu = false; for (iter, save) in self.saves.iter_mut().enumerate() { @@ -72,7 +79,6 @@ impl SaveSelectMenu { } self.save_menu.push_entry(MenuEntry::Active(state.t("common.back"))); - self.save_menu.push_entry(MenuEntry::Disabled(state.t("menus.save_menu.delete_info"))); self.difficulty_menu.push_entry(MenuEntry::Disabled(state.t("menus.difficulty_menu.title"))); self.difficulty_menu.push_entry(MenuEntry::Active(state.t("menus.difficulty_menu.easy"))); @@ -88,6 +94,15 @@ impl SaveSelectMenu { self.delete_confirm.selected = 2; + self.load_confirm.push_entry(MenuEntry::Active(state.t("menus.main_menu.start"))); + self.load_confirm.push_entry(MenuEntry::Active(state.t("menus.save_menu.delete_confirm"))); + self.load_confirm.push_entry(MenuEntry::Active(state.t("common.back"))); + + self.save_detailed.draw_cursor = false; + if let MenuEntry::SaveData(save) = self.save_menu.entries[0] { + self.save_detailed.push_entry(MenuEntry::SaveDataSingle(save)); + } + self.update_sizes(state); Ok(()) @@ -101,7 +116,7 @@ impl SaveSelectMenu { self.save_menu.update_width(state); self.save_menu.update_height(); self.save_menu.x = ((state.canvas_size.0 - self.save_menu.width as f32) / 2.0).floor() as isize; - self.save_menu.y = 30 + ((state.canvas_size.1 - self.save_menu.height as f32) / 2.0).floor() as isize; + self.save_menu.y = ((state.canvas_size.1 - self.save_menu.height as f32) / 2.0).floor() as isize; self.difficulty_menu.update_width(state); self.difficulty_menu.update_height(); @@ -112,7 +127,17 @@ impl SaveSelectMenu { self.delete_confirm.update_width(state); self.delete_confirm.update_height(); self.delete_confirm.x = ((state.canvas_size.0 - self.delete_confirm.width as f32) / 2.0).floor() as isize; - self.delete_confirm.y = 30 + ((state.canvas_size.1 - self.delete_confirm.height as f32) / 2.0).floor() as isize + self.delete_confirm.y = 30 + ((state.canvas_size.1 - self.delete_confirm.height as f32) / 2.0).floor() as isize; + + self.load_confirm.update_width(state); + self.load_confirm.update_height(); + self.load_confirm.x = ((state.canvas_size.0 - self.load_confirm.width as f32) / 2.0).floor() as isize; + self.load_confirm.y = 30 + ((state.canvas_size.1 - self.load_confirm.height as f32) / 2.0).floor() as isize; + + self.save_detailed.update_width(state); + self.save_detailed.update_height(); + self.save_detailed.x = ((state.canvas_size.0 - self.save_detailed.width as f32) / 2.0).floor() as isize; + self.save_detailed.y = -40 + ((state.canvas_size.1 - self.save_detailed.height as f32) / 2.0).floor() as isize; } pub fn tick( @@ -129,12 +154,15 @@ impl SaveSelectMenu { MenuSelectionResult::Selected(slot, _) => { state.save_slot = slot + 1; - if self.skip_difficulty_menu { - state.reload_resources(ctx)?; - state.load_or_start_game(ctx)?; - } else if let Ok(_) = + if let Ok(_) = filesystem::user_open(ctx, state.get_save_filename(state.save_slot).unwrap_or("".to_string())) { + if let MenuEntry::SaveData(save) = self.save_menu.entries[slot] { + self.save_detailed.entries[0] = MenuEntry::SaveDataSingle(save); + } + self.current_menu = CurrentMenu::LoadConfirm; + self.load_confirm.selected = 0; + } else if self.skip_difficulty_menu { state.reload_resources(ctx)?; state.load_or_start_game(ctx)?; } else { @@ -142,12 +170,6 @@ impl SaveSelectMenu { self.current_menu = CurrentMenu::DifficultyMenu; } } - MenuSelectionResult::Right(slot, _, _) => { - if slot <= 2 { - self.current_menu = CurrentMenu::DeleteConfirm; - self.delete_confirm.selected = 2; - } - } _ => (), }, CurrentMenu::DifficultyMenu => match self.difficulty_menu.tick(controller, state) { @@ -181,6 +203,21 @@ impl SaveSelectMenu { self.save_menu.entries[self.save_menu.selected] = MenuEntry::NewSave; self.current_menu = CurrentMenu::SaveMenu; } + MenuSelectionResult::Selected(2, _) | MenuSelectionResult::Canceled => { + self.current_menu = CurrentMenu::LoadConfirm; + self.load_confirm.selected = 0; + } + _ => (), + }, + CurrentMenu::LoadConfirm => match self.load_confirm.tick(controller, state) { + MenuSelectionResult::Selected(0, _) => { + state.reload_resources(ctx)?; + state.load_or_start_game(ctx)?; + } + MenuSelectionResult::Selected(1, _) => { + self.current_menu = CurrentMenu::DeleteConfirm; + self.delete_confirm.selected = 2; + } MenuSelectionResult::Selected(2, _) | MenuSelectionResult::Canceled => { self.current_menu = CurrentMenu::SaveMenu; } @@ -200,8 +237,13 @@ impl SaveSelectMenu { self.difficulty_menu.draw(state, ctx)?; } CurrentMenu::DeleteConfirm => { + self.save_detailed.draw(state, ctx)?; self.delete_confirm.draw(state, ctx)?; } + CurrentMenu::LoadConfirm => { + self.save_detailed.draw(state, ctx)?; + self.load_confirm.draw(state, ctx)?; + } } Ok(()) } diff --git a/src/scene/title_scene.rs b/src/scene/title_scene.rs index d902046..9caab8d 100644 --- a/src/scene/title_scene.rs +++ b/src/scene/title_scene.rs @@ -352,7 +352,7 @@ impl Scene for TitleScene { fn draw(&self, state: &mut SharedGameState, ctx: &mut Context) -> GameResult { self.background.draw(state, ctx, &self.frame, &self.textures, &self.stage)?; - { + if self.current_menu != CurrentMenu::SaveSelectMenu { let batch = state.texture_set.get_or_load_batch(ctx, &state.constants, "Title")?; batch.add_rect( ((state.canvas_size.0 - state.constants.title.logo_rect.width() as f32) / 2.0).floor(),