184 lines
7.6 KiB
Rust
184 lines
7.6 KiB
Rust
use crate::editor::EditorState;
|
|
use crate::menu::{MenuItem, MenuKey};
|
|
use crate::mode::PendingMode;
|
|
use kiln_core::{Archetype, Builtin};
|
|
|
|
/// One level in the editor's sidebar menu tree.
|
|
///
|
|
/// The menu is a stack of levels (the current one is the stack's last element); a
|
|
/// breadcrumb is built from their [`title`](MenuLevel::title)s. A choice opens a
|
|
/// [dialog](kiln_ui::dialog), pushes a child level (e.g. [`Main`](MenuLevel::Main)
|
|
/// descends into [`World`](MenuLevel::World) or [`Terrain`](MenuLevel::Terrain)), or
|
|
/// sets the drawing tools' current archetype (e.g. the [`Terrain`](MenuLevel::Terrain)
|
|
/// and [`Pushers`](MenuLevel::Pushers) choices, via
|
|
/// [`set_current`](EditorState::set_current)).
|
|
#[derive(Clone, Copy, PartialEq)]
|
|
pub(crate) enum MenuLevel {
|
|
/// The top-level main menu: switch boards, view scripts, more later
|
|
Main,
|
|
/// Menu of world-related concerns: set name / entry board
|
|
World,
|
|
/// Menu of floor options
|
|
Floor,
|
|
/// Normal things that go on a board: terrain
|
|
Terrain,
|
|
/// Things that do defined actions: machines
|
|
Machines,
|
|
/// Various directions of pushers
|
|
Pushers,
|
|
/// Collectible items the player picks up (e.g. gems)
|
|
Items,
|
|
}
|
|
|
|
impl MenuLevel {
|
|
/// The breadcrumb segment shown for this level.
|
|
pub(crate) fn title(self) -> &'static str {
|
|
match self {
|
|
MenuLevel::Main => "Main",
|
|
MenuLevel::World => "World",
|
|
MenuLevel::Floor => "Floor",
|
|
MenuLevel::Terrain => "Terrain",
|
|
MenuLevel::Machines => "Machines",
|
|
MenuLevel::Pushers => "Pushers",
|
|
MenuLevel::Items => "Items",
|
|
}
|
|
}
|
|
|
|
/// The list of entries for each menu
|
|
pub(crate) fn entries(self) -> Vec<MenuEntry> {
|
|
match self {
|
|
MenuLevel::Main => vec![
|
|
MenuEntry::item('p', "Play board", |ed| ed.playtest()),
|
|
MenuEntry::Separator,
|
|
MenuEntry::item('w', "World...", |ed| ed.menu.push(MenuLevel::World)),
|
|
MenuEntry::item('s', "Scripts...", |ed| ed.open_scripts_dialog()),
|
|
MenuEntry::item('b', "Boards...", |ed| ed.open_boards_dialog()),
|
|
MenuEntry::Separator,
|
|
MenuEntry::item('f', "Floor", |ed| ed.menu.push(MenuLevel::Floor)),
|
|
MenuEntry::item('t', "Terrain", |ed| ed.menu.push(MenuLevel::Terrain)),
|
|
MenuEntry::item('m', "Machines", |ed| ed.menu.push(MenuLevel::Machines)),
|
|
MenuEntry::item('i', "Items", |ed| ed.menu.push(MenuLevel::Items)),
|
|
],
|
|
MenuLevel::World => vec![
|
|
MenuEntry::item('n', "Name", |ed| ed.open_name_dialog()),
|
|
MenuEntry::value(|ed| ed.world.name.clone()),
|
|
MenuEntry::item('e', "Entry board...", |ed| ed.open_entry_dialog()),
|
|
MenuEntry::value(|ed| ed.world.start.clone()),
|
|
],
|
|
MenuLevel::Floor => vec![
|
|
MenuEntry::item('g', "Grass", |_| {}),
|
|
MenuEntry::item('d', "Dirt", |_| {}),
|
|
MenuEntry::item('s', "Stone", |_| {}),
|
|
MenuEntry::item('c', "Custom glyph [TODO]", |_| {}),
|
|
],
|
|
MenuLevel::Terrain => vec![
|
|
MenuEntry::item('w', "Wall", |ed| ed.set_current(Archetype::Wall)),
|
|
MenuEntry::item('c', "■ Crate", |ed| ed.set_current(Archetype::Crate)),
|
|
MenuEntry::item('v', "↕ Crate", |ed| ed.set_current(Archetype::VCrate)),
|
|
MenuEntry::item('h', "↔ Crate", |ed| ed.set_current(Archetype::HCrate)),
|
|
],
|
|
MenuLevel::Machines => vec![
|
|
MenuEntry::item('p', "Pushers...", |ed| ed.menu.push(MenuLevel::Pushers)),
|
|
MenuEntry::item('s', "/ CW spinner", |ed| {
|
|
ed.set_current(Archetype::Builtin(Builtin::Spinner, "spinner_cw"))
|
|
}),
|
|
MenuEntry::item('c', "\\ CCW spinner", |ed| {
|
|
ed.set_current(Archetype::Builtin(Builtin::Spinner, "spinner_ccw"))
|
|
}),
|
|
],
|
|
MenuLevel::Pushers => vec![
|
|
MenuEntry::item('n', "▲ Pusher", |ed| {
|
|
ed.set_current(Archetype::Builtin(Builtin::Pusher, "pusher_north"))
|
|
}),
|
|
MenuEntry::item('s', "▼ Pusher", |ed| {
|
|
ed.set_current(Archetype::Builtin(Builtin::Pusher, "pusher_south"))
|
|
}),
|
|
MenuEntry::item('e', "► Pusher", |ed| {
|
|
ed.set_current(Archetype::Builtin(Builtin::Pusher, "pusher_east"))
|
|
}),
|
|
MenuEntry::item('w', "◄ Pusher", |ed| {
|
|
ed.set_current(Archetype::Builtin(Builtin::Pusher, "pusher_west"))
|
|
}),
|
|
],
|
|
MenuLevel::Items => vec![MenuEntry::item('t', "♦ Gem", |ed| {
|
|
// 't' for 'treasure' — 'g' conflicts with glyph picker
|
|
ed.set_current(Archetype::Builtin(Builtin::Gem, "gem"))
|
|
})],
|
|
}
|
|
}
|
|
|
|
/// Dispatches a letter key against this menu level: finds the selectable
|
|
/// item bound to `ch` (spacers are skipped) and runs its action with full `&mut`
|
|
/// access to the editor session. Unmatched keys are ignored.
|
|
pub(crate) fn perform_key(self, ch: char, editor: &mut EditorState) {
|
|
let action = self.entries().into_iter().find_map(|entry| match entry {
|
|
MenuEntry::Item { key, action, .. } if key == ch => Some(action),
|
|
_ => None,
|
|
});
|
|
if let Some(action) = action {
|
|
action(editor);
|
|
}
|
|
}
|
|
}
|
|
|
|
/// One line in a sidebar menu level: a selectable [`Item`](MenuEntry::Item), a
|
|
/// [`CurrentValue`](MenuEntry::CurrentValue) readout, or a visual
|
|
/// [`Blank`](MenuEntry::Blank)/[`Separator`](MenuEntry::Separator) spacer.
|
|
pub(crate) enum MenuEntry {
|
|
/// A selectable, key-activated item.
|
|
Item {
|
|
/// The letter the user presses to activate this item.
|
|
key: char,
|
|
/// Display label shown after the `[key]` in the sidebar.
|
|
label: String,
|
|
/// Run when the user presses `key`; mutates the editor session.
|
|
action: Box<dyn FnOnce(&mut EditorState)>,
|
|
},
|
|
/// A blank spacer line. Part of the menu API; no current level uses one yet.
|
|
#[allow(dead_code)]
|
|
Blank,
|
|
/// A horizontal rule spanning the sidebar width.
|
|
Separator,
|
|
/// The current value of some field
|
|
CurrentValue(Box<dyn FnOnce(&EditorState) -> String>),
|
|
}
|
|
|
|
impl MenuEntry {
|
|
pub(crate) fn item<F: FnOnce(&mut EditorState) + 'static>(
|
|
key: char,
|
|
label: impl Into<String>,
|
|
action: F,
|
|
) -> Self {
|
|
MenuEntry::Item {
|
|
key,
|
|
label: label.into(),
|
|
action: Box::new(action),
|
|
}
|
|
}
|
|
|
|
pub(crate) fn value<F: FnOnce(&EditorState) -> String + 'static>(action: F) -> Self {
|
|
MenuEntry::CurrentValue(Box::new(action))
|
|
}
|
|
}
|
|
|
|
/// Returns the items for the editor's pause menu (opened with `Esc` while editing).
|
|
///
|
|
/// `[p]` reloads the world and returns to play; `[q]` quits; `Esc` resumes editing.
|
|
/// Like all menu actions these take only `&mut Ui` — the mode swap is deferred to
|
|
/// the run loop via [`Ui::pending_mode`](crate::ui::Ui::pending_mode).
|
|
pub(crate) fn editor_menu_items() -> Vec<MenuItem> {
|
|
vec![
|
|
MenuItem::new(MenuKey::Char('p'), "Play world", |ui| {
|
|
ui.pending_mode = Some(PendingMode::PlayWorld);
|
|
ui.begin_close_menu();
|
|
}),
|
|
MenuItem::new(MenuKey::Char('q'), "Quit game", |ui| {
|
|
ui.should_quit = true;
|
|
ui.begin_close_menu();
|
|
}),
|
|
MenuItem::new(MenuKey::Esc, "Resume editing", |ui| {
|
|
ui.begin_close_menu();
|
|
}),
|
|
]
|
|
}
|