2021-08-16 07:47:11 +00:00
|
|
|
use std::cell::Cell;
|
|
|
|
|
2021-03-23 01:49:55 +00:00
|
|
|
use crate::common::Rect;
|
2021-01-27 18:20:47 +00:00
|
|
|
use crate::framework::context::Context;
|
|
|
|
use crate::framework::error::GameResult;
|
2020-11-28 19:25:51 +00:00
|
|
|
use crate::input::combined_menu_controller::CombinedMenuController;
|
2020-12-20 20:57:17 +00:00
|
|
|
use crate::shared_game_state::SharedGameState;
|
2021-08-16 07:47:11 +00:00
|
|
|
|
|
|
|
pub mod settings_menu;
|
2020-09-20 01:05:41 +00:00
|
|
|
|
2021-01-27 18:20:47 +00:00
|
|
|
pub struct MenuSaveInfo {}
|
2021-01-01 01:46:01 +00:00
|
|
|
|
2020-09-20 01:05:41 +00:00
|
|
|
pub enum MenuEntry {
|
2021-01-01 01:46:01 +00:00
|
|
|
Hidden,
|
2020-09-20 01:05:41 +00:00
|
|
|
Active(String),
|
2021-08-16 07:47:11 +00:00
|
|
|
DisabledWhite(String),
|
2020-09-20 01:05:41 +00:00
|
|
|
Disabled(String),
|
2020-09-21 22:33:16 +00:00
|
|
|
Toggle(String, bool),
|
2021-01-01 01:46:01 +00:00
|
|
|
Options(String, usize, Vec<String>),
|
|
|
|
SaveData(MenuSaveInfo),
|
|
|
|
NewSave,
|
2020-09-20 01:05:41 +00:00
|
|
|
}
|
|
|
|
|
2020-12-20 20:57:17 +00:00
|
|
|
impl MenuEntry {
|
|
|
|
pub fn height(&self) -> f64 {
|
2021-01-01 01:46:01 +00:00
|
|
|
match self {
|
|
|
|
MenuEntry::Hidden => 0.0,
|
|
|
|
MenuEntry::Active(_) => 14.0,
|
2021-08-16 07:47:11 +00:00
|
|
|
MenuEntry::DisabledWhite(_) => 14.0,
|
2021-01-01 01:46:01 +00:00
|
|
|
MenuEntry::Disabled(_) => 14.0,
|
|
|
|
MenuEntry::Toggle(_, _) => 14.0,
|
|
|
|
MenuEntry::Options(_, _, _) => 14.0,
|
|
|
|
MenuEntry::SaveData(_) => 30.0,
|
|
|
|
MenuEntry::NewSave => 30.0,
|
|
|
|
}
|
2020-12-20 20:57:17 +00:00
|
|
|
}
|
2021-08-16 07:47:11 +00:00
|
|
|
|
|
|
|
pub fn selectable(&self) -> bool {
|
|
|
|
match self {
|
|
|
|
MenuEntry::Hidden => false,
|
|
|
|
MenuEntry::Active(_) => true,
|
|
|
|
MenuEntry::DisabledWhite(_) => false,
|
|
|
|
MenuEntry::Disabled(_) => false,
|
|
|
|
MenuEntry::Toggle(_, _) => true,
|
|
|
|
MenuEntry::Options(_, _, _) => true,
|
|
|
|
MenuEntry::SaveData(_) => true,
|
|
|
|
MenuEntry::NewSave => true,
|
|
|
|
}
|
|
|
|
}
|
2020-12-20 20:57:17 +00:00
|
|
|
}
|
|
|
|
|
2020-09-20 01:05:41 +00:00
|
|
|
pub enum MenuSelectionResult<'a> {
|
|
|
|
None,
|
|
|
|
Canceled,
|
2020-09-21 22:33:16 +00:00
|
|
|
Selected(usize, &'a mut MenuEntry),
|
2021-03-23 01:49:55 +00:00
|
|
|
Left(usize, &'a mut MenuEntry),
|
|
|
|
Right(usize, &'a mut MenuEntry),
|
2020-09-20 01:05:41 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
pub struct Menu {
|
|
|
|
pub x: isize,
|
|
|
|
pub y: isize,
|
2020-11-17 02:42:45 +00:00
|
|
|
pub width: u16,
|
|
|
|
pub height: u16,
|
2020-09-25 19:25:40 +00:00
|
|
|
pub selected: usize,
|
|
|
|
pub entries: Vec<MenuEntry>,
|
2021-01-01 01:46:01 +00:00
|
|
|
entry_y: u16,
|
2020-09-20 01:05:41 +00:00
|
|
|
anim_num: u16,
|
|
|
|
anim_wait: u16,
|
2021-06-21 11:12:58 +00:00
|
|
|
custom_cursor: Cell<bool>,
|
2020-09-20 01:05:41 +00:00
|
|
|
}
|
|
|
|
|
2020-11-17 02:42:45 +00:00
|
|
|
static QUOTE_FRAMES: [u16; 4] = [0, 1, 0, 2];
|
2020-09-20 01:05:41 +00:00
|
|
|
|
|
|
|
impl Menu {
|
2020-11-17 02:42:45 +00:00
|
|
|
pub fn new(x: isize, y: isize, width: u16, height: u16) -> Menu {
|
2021-06-21 11:12:58 +00:00
|
|
|
Menu {
|
|
|
|
x,
|
|
|
|
y,
|
|
|
|
width,
|
|
|
|
height,
|
|
|
|
selected: 0,
|
|
|
|
entry_y: 0,
|
|
|
|
anim_num: 0,
|
|
|
|
anim_wait: 0,
|
|
|
|
entries: Vec::new(),
|
|
|
|
custom_cursor: Cell::new(true),
|
|
|
|
}
|
2020-09-20 01:05:41 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
pub fn push_entry(&mut self, entry: MenuEntry) {
|
|
|
|
self.entries.push(entry);
|
|
|
|
}
|
|
|
|
|
2021-01-01 01:46:01 +00:00
|
|
|
pub fn update_height(&mut self) {
|
|
|
|
let mut height = 6.0;
|
|
|
|
|
|
|
|
for entry in self.entries.iter() {
|
|
|
|
height += entry.height();
|
|
|
|
}
|
|
|
|
|
|
|
|
self.height = height.max(6.0) as u16;
|
|
|
|
}
|
|
|
|
|
2020-09-20 01:05:41 +00:00
|
|
|
pub fn draw(&self, state: &mut SharedGameState, ctx: &mut Context) -> GameResult {
|
|
|
|
let batch = state.texture_set.get_or_load_batch(ctx, &state.constants, "TextBox")?;
|
|
|
|
|
2020-12-25 22:39:41 +00:00
|
|
|
let mut rect;
|
|
|
|
let mut rect2;
|
2020-09-20 01:05:41 +00:00
|
|
|
|
|
|
|
rect = state.constants.title.menu_left_top;
|
|
|
|
batch.add_rect(self.x as f32 - rect.width() as f32, self.y as f32 - rect.height() as f32, &rect);
|
|
|
|
rect = state.constants.title.menu_right_top;
|
|
|
|
batch.add_rect(self.x as f32 + self.width as f32, self.y as f32 - rect.height() as f32, &rect);
|
|
|
|
rect = state.constants.title.menu_left_bottom;
|
|
|
|
batch.add_rect(self.x as f32 - rect.width() as f32, self.y as f32 + self.height as f32, &rect);
|
|
|
|
rect = state.constants.title.menu_right_bottom;
|
|
|
|
batch.add_rect(self.x as f32 + self.width as f32, self.y as f32 + self.height as f32, &rect);
|
|
|
|
|
|
|
|
rect = state.constants.title.menu_top;
|
|
|
|
rect2 = state.constants.title.menu_bottom;
|
|
|
|
let mut x = self.x as f32;
|
|
|
|
let mut y = self.y as f32;
|
|
|
|
let mut width = self.width;
|
|
|
|
let mut height = self.height;
|
|
|
|
|
|
|
|
while width > 0 {
|
|
|
|
rect.right = if width >= rect.width() {
|
|
|
|
width = width.saturating_sub(rect.width());
|
|
|
|
rect.right
|
|
|
|
} else {
|
|
|
|
let old_width = width;
|
|
|
|
width = 0;
|
|
|
|
rect.left + old_width
|
|
|
|
};
|
|
|
|
rect2.right = rect.right;
|
|
|
|
|
|
|
|
batch.add_rect(x, y - rect.height() as f32, &rect);
|
|
|
|
batch.add_rect(x, y + self.height as f32, &rect2);
|
|
|
|
x += rect.width() as f32;
|
|
|
|
}
|
|
|
|
|
|
|
|
x = self.x as f32;
|
|
|
|
rect = state.constants.title.menu_left;
|
|
|
|
rect2 = state.constants.title.menu_right;
|
|
|
|
while height > 0 {
|
|
|
|
rect.bottom = if height >= rect.height() {
|
|
|
|
height = height.saturating_sub(rect.height());
|
|
|
|
rect.bottom
|
|
|
|
} else {
|
|
|
|
let old_height = height;
|
|
|
|
height = 0;
|
|
|
|
rect.top + old_height
|
|
|
|
};
|
|
|
|
rect2.bottom = rect.bottom;
|
|
|
|
|
|
|
|
batch.add_rect(x - rect.width() as f32, y, &rect);
|
|
|
|
batch.add_rect(x + self.width as f32, y, &rect2);
|
|
|
|
y += rect.height() as f32;
|
|
|
|
}
|
|
|
|
|
|
|
|
height = self.height;
|
|
|
|
y = self.y as f32;
|
|
|
|
|
|
|
|
while height > 0 {
|
|
|
|
rect = state.constants.title.menu_middle;
|
|
|
|
width = self.width;
|
|
|
|
x = self.x as f32;
|
|
|
|
|
|
|
|
rect.bottom = if height >= rect.height() {
|
|
|
|
height = height.saturating_sub(rect.height());
|
|
|
|
rect.bottom
|
|
|
|
} else {
|
|
|
|
let old_height = height;
|
|
|
|
height = 0;
|
|
|
|
rect.top + old_height
|
|
|
|
};
|
|
|
|
|
|
|
|
while width > 0 {
|
|
|
|
rect.right = if width >= rect.width() {
|
|
|
|
width = width.saturating_sub(rect.width());
|
|
|
|
rect.right
|
|
|
|
} else {
|
|
|
|
let old_width = width;
|
|
|
|
width = 0;
|
|
|
|
rect.left + old_width
|
|
|
|
};
|
|
|
|
|
|
|
|
batch.add_rect(x, y, &rect);
|
|
|
|
|
|
|
|
x += rect.width() as f32;
|
|
|
|
}
|
|
|
|
|
|
|
|
y += rect.height() as f32;
|
|
|
|
}
|
|
|
|
|
|
|
|
batch.draw(ctx)?;
|
|
|
|
|
2021-06-21 11:12:58 +00:00
|
|
|
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;
|
2020-09-20 01:05:41 +00:00
|
|
|
|
2021-06-21 11:12:58 +00:00
|
|
|
batch.add_rect(self.x as f32, self.y as f32 + 3.0 + self.entry_y as f32, &rect);
|
2020-09-20 01:05:41 +00:00
|
|
|
|
2021-06-21 11:12:58 +00:00
|
|
|
batch.draw(ctx)?;
|
|
|
|
} else {
|
|
|
|
self.custom_cursor.set(false);
|
|
|
|
}
|
|
|
|
}
|
2020-09-20 01:05:41 +00:00
|
|
|
|
2021-06-21 11:12:58 +00:00
|
|
|
if !self.custom_cursor.get() {
|
|
|
|
let batch = state.texture_set.get_or_load_batch(ctx, &state.constants, "MyChar")?;
|
|
|
|
|
|
|
|
rect.left = QUOTE_FRAMES[self.anim_num as usize] * 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 + 2.0 + self.entry_y as f32, &rect);
|
|
|
|
|
|
|
|
batch.draw(ctx)?;
|
|
|
|
}
|
2020-09-20 01:05:41 +00:00
|
|
|
|
|
|
|
y = self.y as f32 + 6.0;
|
|
|
|
for entry in self.entries.iter() {
|
|
|
|
match entry {
|
2021-08-16 07:47:11 +00:00
|
|
|
MenuEntry::Active(name) | MenuEntry::DisabledWhite(name) => {
|
2021-03-23 01:49:55 +00:00
|
|
|
state.font.draw_text(
|
|
|
|
name.chars(),
|
|
|
|
self.x as f32 + 20.0,
|
|
|
|
y,
|
|
|
|
&state.constants,
|
|
|
|
&mut state.texture_set,
|
|
|
|
ctx,
|
|
|
|
)?;
|
2020-09-20 01:05:41 +00:00
|
|
|
}
|
|
|
|
MenuEntry::Disabled(name) => {
|
2021-03-23 01:49:55 +00:00
|
|
|
state.font.draw_colored_text(
|
|
|
|
name.chars(),
|
|
|
|
self.x as f32 + 20.0,
|
|
|
|
y,
|
|
|
|
(0xa0, 0xa0, 0xff, 0xff),
|
|
|
|
&state.constants,
|
|
|
|
&mut state.texture_set,
|
|
|
|
ctx,
|
|
|
|
)?;
|
2020-09-20 01:05:41 +00:00
|
|
|
}
|
2020-09-21 22:33:16 +00:00
|
|
|
MenuEntry::Toggle(name, value) => {
|
|
|
|
let value_text = if *value { "ON" } else { "OFF" };
|
|
|
|
let val_text_len = state.font.text_width(value_text.chars(), &state.constants);
|
|
|
|
|
2021-03-23 01:49:55 +00:00
|
|
|
state.font.draw_text(
|
|
|
|
name.chars(),
|
|
|
|
self.x as f32 + 20.0,
|
|
|
|
y,
|
|
|
|
&state.constants,
|
|
|
|
&mut state.texture_set,
|
|
|
|
ctx,
|
|
|
|
)?;
|
|
|
|
|
|
|
|
state.font.draw_text(
|
|
|
|
value_text.chars(),
|
|
|
|
self.x as f32 + self.width as f32 - val_text_len,
|
|
|
|
y,
|
|
|
|
&state.constants,
|
|
|
|
&mut state.texture_set,
|
|
|
|
ctx,
|
|
|
|
)?;
|
2020-09-21 22:33:16 +00:00
|
|
|
}
|
2021-08-16 07:47:11 +00:00
|
|
|
MenuEntry::Options(name, index, value) => {
|
|
|
|
let value_text = if let Some(text) = value.get(*index) { text.as_str() } else { "???" };
|
|
|
|
let val_text_len = state.font.text_width(value_text.chars(), &state.constants);
|
|
|
|
|
|
|
|
state.font.draw_text(
|
|
|
|
name.chars(),
|
|
|
|
self.x as f32 + 20.0,
|
|
|
|
y,
|
|
|
|
&state.constants,
|
|
|
|
&mut state.texture_set,
|
|
|
|
ctx,
|
|
|
|
)?;
|
|
|
|
|
|
|
|
state.font.draw_text(
|
|
|
|
value_text.chars(),
|
|
|
|
self.x as f32 + self.width as f32 - val_text_len,
|
|
|
|
y,
|
|
|
|
&state.constants,
|
|
|
|
&mut state.texture_set,
|
|
|
|
ctx,
|
|
|
|
)?;
|
|
|
|
}
|
2021-01-01 01:46:01 +00:00
|
|
|
_ => {}
|
2020-09-20 01:05:41 +00:00
|
|
|
}
|
|
|
|
|
2020-12-20 20:57:17 +00:00
|
|
|
y += entry.height() as f32;
|
2020-09-20 01:05:41 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
2021-03-23 01:49:55 +00:00
|
|
|
pub fn tick(
|
|
|
|
&mut self,
|
|
|
|
controller: &mut CombinedMenuController,
|
|
|
|
state: &mut SharedGameState,
|
|
|
|
) -> MenuSelectionResult {
|
2020-11-28 19:25:51 +00:00
|
|
|
if controller.trigger_back() {
|
2020-09-20 01:05:41 +00:00
|
|
|
state.sound_manager.play_sfx(5);
|
|
|
|
return MenuSelectionResult::Canceled;
|
|
|
|
}
|
|
|
|
|
2020-11-28 19:25:51 +00:00
|
|
|
if (controller.trigger_up() || controller.trigger_down()) && !self.entries.is_empty() {
|
2020-09-20 01:05:41 +00:00
|
|
|
state.sound_manager.play_sfx(1);
|
|
|
|
loop {
|
2020-11-28 19:25:51 +00:00
|
|
|
if controller.trigger_down() {
|
2020-09-20 01:05:41 +00:00
|
|
|
self.selected += 1;
|
|
|
|
if self.selected == self.entries.len() {
|
|
|
|
self.selected = 0;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
if self.selected == 0 {
|
|
|
|
self.selected = self.entries.len();
|
|
|
|
}
|
|
|
|
self.selected -= 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
if let Some(entry) = self.entries.get(self.selected) {
|
2021-08-16 07:47:11 +00:00
|
|
|
if entry.selectable() {
|
|
|
|
break;
|
2020-09-20 01:05:41 +00:00
|
|
|
}
|
|
|
|
} else {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-01-01 01:46:01 +00:00
|
|
|
if !self.entries.is_empty() {
|
2021-03-23 01:49:55 +00:00
|
|
|
self.entry_y = self.entries[0..(self.selected)].iter().map(|e| e.height()).sum::<f64>().max(0.0) as u16;
|
2021-01-01 01:46:01 +00:00
|
|
|
}
|
|
|
|
|
2020-12-20 20:57:17 +00:00
|
|
|
let mut y = self.y as f32 + 6.0;
|
|
|
|
for (idx, entry) in self.entries.iter_mut().enumerate() {
|
|
|
|
let entry_bounds = Rect::new_size(self.x, y as isize, self.width as isize, entry.height() as isize);
|
|
|
|
y += entry.height() as f32;
|
|
|
|
|
|
|
|
match entry {
|
2021-08-16 07:47:11 +00:00
|
|
|
MenuEntry::Active(_) | MenuEntry::Toggle(_, _) | MenuEntry::Options(_, _, _)
|
2021-03-23 01:49:55 +00:00
|
|
|
if (self.selected == idx && controller.trigger_ok())
|
|
|
|
|| state.touch_controls.consume_click_in(entry_bounds) =>
|
|
|
|
{
|
2020-12-20 20:57:17 +00:00
|
|
|
state.sound_manager.play_sfx(18);
|
|
|
|
return MenuSelectionResult::Selected(idx, entry);
|
2020-09-20 01:05:41 +00:00
|
|
|
}
|
2021-03-23 01:49:55 +00:00
|
|
|
MenuEntry::Options(_, _, _) if controller.trigger_left() => {
|
|
|
|
state.sound_manager.play_sfx(1);
|
|
|
|
return MenuSelectionResult::Left(self.selected, entry);
|
|
|
|
}
|
|
|
|
MenuEntry::Options(_, _, _) if controller.trigger_right() => {
|
|
|
|
state.sound_manager.play_sfx(1);
|
|
|
|
return MenuSelectionResult::Right(self.selected, entry);
|
|
|
|
}
|
2020-12-20 20:57:17 +00:00
|
|
|
_ => {}
|
2020-09-20 01:05:41 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// todo nikumaru counter support
|
|
|
|
self.anim_wait += 1;
|
|
|
|
if self.anim_wait > 8 {
|
|
|
|
self.anim_wait = 0;
|
|
|
|
|
|
|
|
self.anim_num += 1;
|
|
|
|
if self.anim_num >= QUOTE_FRAMES.len() as u16 {
|
|
|
|
self.anim_num = 0;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
MenuSelectionResult::None
|
|
|
|
}
|
|
|
|
}
|