keys 2
This commit is contained in:
+68
-33
@@ -1,15 +1,16 @@
|
|||||||
use crate::glyph::Glyph;
|
use crate::glyph::Glyph;
|
||||||
use crate::utils::{Behavior, Pushable};
|
use crate::utils::{Behavior, Pushable};
|
||||||
use color::Rgba8;
|
use color::Rgba8;
|
||||||
|
use crate::game::KeyType;
|
||||||
|
|
||||||
/// Declares the set of script-backed archetype families.
|
/// Declares the set of script-backed archetype families.
|
||||||
///
|
///
|
||||||
/// Each entry specifies:
|
/// Each entry specifies:
|
||||||
/// - A `Variant` name (becomes a [`Builtin`] enum variant).
|
/// - A `Variant` name (becomes a [`Builtin`] enum variant).
|
||||||
/// - A `["name" => tile, …]` list: one map-file keyword per alias with the tile
|
/// - A `["name" => Glyph { … }, …]` list: one map-file keyword per alias with the
|
||||||
/// index for the editor glyph. All aliases in a family share `behavior`, `fg`,
|
/// default [`Glyph`] for the editor. Per-alias glyphs allow aliases in the same
|
||||||
/// `bg`, and embedded Rhai `script`.
|
/// family to differ in color (e.g. the eight `Key` variants).
|
||||||
/// - `behavior`, `fg`, `bg`: per-family defaults.
|
/// - `behavior`: shared across all aliases in the family.
|
||||||
/// - `script`: the embedded Rhai source; `include_str!` paths are relative to this
|
/// - `script`: the embedded Rhai source; `include_str!` paths are relative to this
|
||||||
/// file, so `include_str!("scripts/pusher.rhai")` resolves to
|
/// file, so `include_str!("scripts/pusher.rhai")` resolves to
|
||||||
/// `kiln-core/src/scripts/pusher.rhai`.
|
/// `kiln-core/src/scripts/pusher.rhai`.
|
||||||
@@ -21,10 +22,8 @@ use color::Rgba8;
|
|||||||
macro_rules! builtins {
|
macro_rules! builtins {
|
||||||
(
|
(
|
||||||
$(
|
$(
|
||||||
$variant:ident => [ $( $name:literal => $tile:literal ),+ $(,)? ] {
|
$variant:ident => [ $( $name:literal => $glyph:expr ),+ $(,)? ] {
|
||||||
behavior: $behavior:expr,
|
behavior: $behavior:expr,
|
||||||
fg: $fg:expr,
|
|
||||||
bg: $bg:expr,
|
|
||||||
script: $script:expr $(,)?
|
script: $script:expr $(,)?
|
||||||
}
|
}
|
||||||
),+ $(,)?
|
),+ $(,)?
|
||||||
@@ -66,16 +65,14 @@ macro_rules! builtins {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the glyph for `alias`: the family's fg/bg with the alias's tile.
|
/// Returns the default glyph for `alias`. Each alias owns its own glyph,
|
||||||
|
/// so aliases within a family can differ in color (e.g. colored keys).
|
||||||
/// Falls back to a transparent glyph for unrecognized aliases (shouldn't
|
/// Falls back to a transparent glyph for unrecognized aliases (shouldn't
|
||||||
/// happen in practice since aliases are all from the macro).
|
/// happen in practice since aliases are all from the macro).
|
||||||
pub(crate) fn default_glyph_for(self, alias: &str) -> Glyph {
|
pub(crate) fn default_glyph_for(self, alias: &str) -> Glyph {
|
||||||
// The outer repetition brings $fg/$bg into scope; the inner one selects
|
|
||||||
// the tile for the specific alias. Both are statically resolved at
|
|
||||||
// compile time.
|
|
||||||
match alias {
|
match alias {
|
||||||
$(
|
$(
|
||||||
$( $name => Glyph { tile: $tile, fg: $fg, bg: $bg }, )+
|
$( $name => $glyph, )+
|
||||||
)+
|
)+
|
||||||
_ => Glyph::transparent(),
|
_ => Glyph::transparent(),
|
||||||
}
|
}
|
||||||
@@ -91,38 +88,48 @@ macro_rules! builtins {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
builtins! {
|
// Shorthand helpers used only within the builtins! invocation below.
|
||||||
Gem => ["gem" => 4] {
|
// `g(tile, r, g, b)` builds a Glyph with the given tile and fg on black bg.
|
||||||
behavior: Behavior { solid: true, opaque: false, pushable: Pushable::Any, grab: true },
|
const fn g(tile: u32, r: u8, gr: u8, b: u8) -> Glyph {
|
||||||
fg: Rgba8 { r: 0x50, g: 0x50, b: 0xFF, a: 255 },
|
Glyph {
|
||||||
|
tile,
|
||||||
|
fg: Rgba8 { r, g: gr, b, a: 255 },
|
||||||
bg: Rgba8 { r: 0, g: 0, b: 0, a: 255 },
|
bg: Rgba8 { r: 0, g: 0, b: 0, a: 255 },
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
builtins! {
|
||||||
|
Gem => ["gem" => g(4, 0x50, 0x50, 0xFF)] {
|
||||||
|
behavior: Behavior { solid: true, opaque: false, pushable: Pushable::Any, grab: true },
|
||||||
script: include_str!("scripts/gem.rhai"),
|
script: include_str!("scripts/gem.rhai"),
|
||||||
},
|
},
|
||||||
Pusher => ["pusher_north" => 30, "pusher_south" => 31, "pusher_east" => 16, "pusher_west" => 17] {
|
Pusher => [
|
||||||
|
"pusher_north" => g(30, 0xAA, 0xAA, 0xAA),
|
||||||
|
"pusher_south" => g(31, 0xAA, 0xAA, 0xAA),
|
||||||
|
"pusher_east" => g(16, 0xAA, 0xAA, 0xAA),
|
||||||
|
"pusher_west" => g(17, 0xAA, 0xAA, 0xAA),
|
||||||
|
] {
|
||||||
behavior: Behavior { solid: true, opaque: true, pushable: Pushable::No, grab: false },
|
behavior: Behavior { solid: true, opaque: true, pushable: Pushable::No, grab: false },
|
||||||
fg: Rgba8 { r: 0xAA, g: 0xAA, b: 0xAA, a: 255 },
|
|
||||||
bg: Rgba8 { r: 0, g: 0, b: 0, a: 255 },
|
|
||||||
script: include_str!("scripts/pusher.rhai"),
|
script: include_str!("scripts/pusher.rhai"),
|
||||||
},
|
},
|
||||||
Spinner => ["spinner_cw" => 47, "spinner_ccw" => 92] {
|
Spinner => [
|
||||||
|
"spinner_cw" => g(47, 0xAA, 0xAA, 0xAA),
|
||||||
|
"spinner_ccw" => g(92, 0xAA, 0xAA, 0xAA),
|
||||||
|
] {
|
||||||
behavior: Behavior { solid: true, opaque: true, pushable: Pushable::No, grab: false },
|
behavior: Behavior { solid: true, opaque: true, pushable: Pushable::No, grab: false },
|
||||||
fg: Rgba8 { r: 0xAA, g: 0xAA, b: 0xAA, a: 255 },
|
|
||||||
bg: Rgba8 { r: 0, g: 0, b: 0, a: 255 },
|
|
||||||
script: include_str!("scripts/spinner.rhai"),
|
script: include_str!("scripts/spinner.rhai"),
|
||||||
},
|
},
|
||||||
Key => [
|
Key => [ // TODO these should refer to the key colors in Keyring
|
||||||
"key_red" => 12,
|
"key_blue" => KeyType::Blue.glyph(),
|
||||||
"key_orange" => 12,
|
"key_green" => KeyType::Green.glyph(),
|
||||||
"key_yellow" => 12,
|
"key_cyan" => KeyType::Cyan.glyph(),
|
||||||
"key_green" => 12,
|
"key_red" => KeyType::Red.glyph(),
|
||||||
"key_blue" => 12,
|
"key_purple" => KeyType::Purple.glyph(),
|
||||||
"key_cyan" => 12,
|
"key_orange" => KeyType::Orange.glyph(),
|
||||||
"key_purple" => 12,
|
"key_yellow" => KeyType::Yellow.glyph(),
|
||||||
"key_white" => 12,
|
"key_white" => KeyType::White.glyph(),
|
||||||
] {
|
] {
|
||||||
behavior: Behavior { solid: true, opaque: false, pushable: Pushable::Any, grab: true },
|
behavior: Behavior { solid: true, opaque: false, pushable: Pushable::Any, grab: true },
|
||||||
fg: Rgba8 { r: 0xAA, g: 0xAA, b: 0xAA, a: 255 },
|
|
||||||
bg: Rgba8 { r: 0, g: 0, b: 0, a: 255 },
|
|
||||||
script: include_str!("scripts/key.rhai"),
|
script: include_str!("scripts/key.rhai"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -325,6 +332,9 @@ mod tests {
|
|||||||
("pusher_west", 17),
|
("pusher_west", 17),
|
||||||
("spinner_cw", 47),
|
("spinner_cw", 47),
|
||||||
("spinner_ccw", 92),
|
("spinner_ccw", 92),
|
||||||
|
("key_red", 12),
|
||||||
|
("key_blue", 12),
|
||||||
|
("key_white", 12),
|
||||||
] {
|
] {
|
||||||
let arch = Archetype::try_from(name)
|
let arch = Archetype::try_from(name)
|
||||||
.unwrap_or_else(|_| panic!("'{name}' should parse as a builtin"));
|
.unwrap_or_else(|_| panic!("'{name}' should parse as a builtin"));
|
||||||
@@ -336,4 +346,29 @@ mod tests {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn key_aliases_have_distinct_fg_colors() {
|
||||||
|
use crate::game::KeyType;
|
||||||
|
// Each alias must match the corresponding KeyType glyph — single source of truth.
|
||||||
|
let cases = [
|
||||||
|
("key_blue", KeyType::Blue),
|
||||||
|
("key_green", KeyType::Green),
|
||||||
|
("key_cyan", KeyType::Cyan),
|
||||||
|
("key_red", KeyType::Red),
|
||||||
|
("key_purple", KeyType::Purple),
|
||||||
|
("key_orange", KeyType::Orange),
|
||||||
|
("key_yellow", KeyType::Yellow),
|
||||||
|
("key_white", KeyType::White),
|
||||||
|
];
|
||||||
|
for (name, key_type) in cases {
|
||||||
|
let arch = Archetype::try_from(name)
|
||||||
|
.unwrap_or_else(|_| panic!("'{name}' should parse"));
|
||||||
|
assert_eq!(
|
||||||
|
arch.default_glyph().fg,
|
||||||
|
key_type.glyph().fg,
|
||||||
|
"'{name}' fg doesn't match KeyType"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+32
-10
@@ -11,6 +11,28 @@ use std::time::Duration;
|
|||||||
/// How long a `say()` speech bubble stays on screen, in seconds.
|
/// How long a `say()` speech bubble stays on screen, in seconds.
|
||||||
pub const SAY_DURATION: f64 = 3.0;
|
pub const SAY_DURATION: f64 = 3.0;
|
||||||
|
|
||||||
|
#[derive(Copy, Clone, Debug)]
|
||||||
|
pub enum KeyType {
|
||||||
|
Red, Orange, Yellow, Green, Blue, Cyan, Purple, White
|
||||||
|
}
|
||||||
|
|
||||||
|
impl KeyType {
|
||||||
|
pub fn glyph(self) -> Glyph {
|
||||||
|
let fg = match self {
|
||||||
|
KeyType::Red => Rgba8 { r: 0xFF, g: 0x50, b: 0x50, a: 255 },
|
||||||
|
KeyType::Orange => Rgba8 { r: 0xFF, g: 0x88, b: 0x00, a: 255 },
|
||||||
|
KeyType::Yellow => Rgba8 { r: 0xFF, g: 0xFF, b: 0x50, a: 255 },
|
||||||
|
KeyType::Green => Rgba8 { r: 0x50, g: 0xFF, b: 0x50, a: 255 },
|
||||||
|
KeyType::Blue => Rgba8 { r: 0x50, g: 0x50, b: 0xFF, a: 255 },
|
||||||
|
KeyType::Cyan => Rgba8 { r: 0x00, g: 0xAA, b: 0xAA, a: 255 },
|
||||||
|
KeyType::Purple => Rgba8 { r: 0xAA, g: 0x00, b: 0xAA, a: 255 },
|
||||||
|
KeyType::White => Rgba8 { r: 0xFF, g: 0xFF, b: 0xFF, a: 255 }
|
||||||
|
};
|
||||||
|
|
||||||
|
Glyph { tile: 12, fg, bg: Rgba8 { r: 0, g: 0, b: 0, a: 255 } }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// The player's key inventory: one boolean per key color.
|
/// The player's key inventory: one boolean per key color.
|
||||||
///
|
///
|
||||||
/// Each field is `true` when the player holds a key of that color. Use
|
/// Each field is `true` when the player holds a key of that color. Use
|
||||||
@@ -60,14 +82,14 @@ impl Keyring {
|
|||||||
/// Order: blue, green, cyan, red, purple, orange, yellow, white.
|
/// Order: blue, green, cyan, red, purple, orange, yellow, white.
|
||||||
pub fn colors(&self) -> [(bool, Rgba8); 8] {
|
pub fn colors(&self) -> [(bool, Rgba8); 8] {
|
||||||
[
|
[
|
||||||
(self.blue, Rgba8 { r: 0x00, g: 0x00, b: 0xAA, a: 255 }),
|
(self.blue, KeyType::Blue.glyph().fg),
|
||||||
(self.green, Rgba8 { r: 0x00, g: 0xAA, b: 0x00, a: 255 }),
|
(self.green, KeyType::Green.glyph().fg),
|
||||||
(self.cyan, Rgba8 { r: 0x00, g: 0xAA, b: 0xAA, a: 255 }),
|
(self.cyan, KeyType::Cyan.glyph().fg),
|
||||||
(self.red, Rgba8 { r: 0xAA, g: 0x00, b: 0x00, a: 255 }),
|
(self.red, KeyType::Red.glyph().fg),
|
||||||
(self.purple, Rgba8 { r: 0xAA, g: 0x00, b: 0xAA, a: 255 }),
|
(self.purple, KeyType::Purple.glyph().fg),
|
||||||
(self.orange, Rgba8 { r: 0xFF, g: 0x88, b: 0x00, a: 255 }),
|
(self.orange, KeyType::Orange.glyph().fg),
|
||||||
(self.yellow, Rgba8 { r: 0xFF, g: 0xFF, b: 0x55, a: 255 }),
|
(self.yellow, KeyType::Yellow.glyph().fg),
|
||||||
(self.white, Rgba8 { r: 0xFF, g: 0xFF, b: 0xFF, a: 255 }),
|
(self.white, KeyType::White.glyph().fg),
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -75,6 +97,7 @@ impl Keyring {
|
|||||||
// Re-export ScrollLine so kiln-tui can pattern-match scroll content without
|
// Re-export ScrollLine so kiln-tui can pattern-match scroll content without
|
||||||
// accessing the private `action` module directly.
|
// accessing the private `action` module directly.
|
||||||
pub use crate::action::ScrollLine;
|
pub use crate::action::ScrollLine;
|
||||||
|
use crate::glyph::Glyph;
|
||||||
|
|
||||||
/// An active scroll overlay opened by a scripted object via `scroll()`.
|
/// An active scroll overlay opened by a scripted object via `scroll()`.
|
||||||
///
|
///
|
||||||
@@ -166,8 +189,7 @@ impl GameState {
|
|||||||
board_transition: None,
|
board_transition: None,
|
||||||
player_health: 5,
|
player_health: 5,
|
||||||
player_gems: 0,
|
player_gems: 0,
|
||||||
// Test starting keys: red, green, orange, white.
|
player_keys: Keyring::default(),
|
||||||
player_keys: Keyring { red: true, green: true, orange: true, white: true, ..Default::default() },
|
|
||||||
};
|
};
|
||||||
state.drain_errors();
|
state.drain_errors();
|
||||||
state
|
state
|
||||||
|
|||||||
+10
-2
@@ -13,7 +13,7 @@ use crate::editor_menu::{MenuEntry, MenuLevel};
|
|||||||
use crate::log::{LogWidget, log_preview_line};
|
use crate::log::{LogWidget, log_preview_line};
|
||||||
use crate::render::{BoardWidget, board_to_screen, screen_to_board};
|
use crate::render::{BoardWidget, board_to_screen, screen_to_board};
|
||||||
use crate::ui::Ui;
|
use crate::ui::Ui;
|
||||||
use crate::utils::rgba8_to_color;
|
use crate::utils::{glyph_to_span, rgba8_to_color};
|
||||||
use kiln_core::cp437::tile_to_char;
|
use kiln_core::cp437::tile_to_char;
|
||||||
use kiln_core::game::GameState;
|
use kiln_core::game::GameState;
|
||||||
use kiln_core::glyph::Glyph;
|
use kiln_core::glyph::Glyph;
|
||||||
@@ -458,6 +458,14 @@ pub(crate) fn draw_editor(frame: &mut Frame, ed: &mut EditorState, ui: &mut Ui)
|
|||||||
Span::styled(label, label_style),
|
Span::styled(label, label_style),
|
||||||
]));
|
]));
|
||||||
}
|
}
|
||||||
|
MenuEntry::Glyph { key, glyph, label, .. } => {
|
||||||
|
lines.push(Line::from(vec![
|
||||||
|
Span::styled(format!("[{}] ", key), key_style),
|
||||||
|
glyph_to_span(glyph),
|
||||||
|
Span::raw(" "),
|
||||||
|
Span::styled(label, label_style),
|
||||||
|
]));
|
||||||
|
}
|
||||||
MenuEntry::Blank => lines.push(Line::from("")),
|
MenuEntry::Blank => lines.push(Line::from("")),
|
||||||
// A horizontal rule spanning the sidebar's inner width.
|
// A horizontal rule spanning the sidebar's inner width.
|
||||||
MenuEntry::Separator => lines.push(Line::from(Span::styled(
|
MenuEntry::Separator => lines.push(Line::from(Span::styled(
|
||||||
@@ -514,7 +522,7 @@ fn draw_footer_lines<'a>(
|
|||||||
// The archetype currently being drawn (no UI to change it yet).
|
// The archetype currently being drawn (no UI to change it yet).
|
||||||
Line::from(Span::styled(ed.current_archetype.name(), label_style)),
|
Line::from(Span::styled(ed.current_archetype.name(), label_style)),
|
||||||
Line::from(vec![
|
Line::from(vec![
|
||||||
Span::styled("[g] ", key_style),
|
Span::styled("[G] ", key_style),
|
||||||
Span::styled("Glyph ", label_style),
|
Span::styled("Glyph ", label_style),
|
||||||
preview,
|
preview,
|
||||||
]),
|
]),
|
||||||
|
|||||||
@@ -2,6 +2,8 @@ use crate::editor::EditorState;
|
|||||||
use crate::menu::{MenuItem, MenuKey};
|
use crate::menu::{MenuItem, MenuKey};
|
||||||
use crate::mode::PendingMode;
|
use crate::mode::PendingMode;
|
||||||
use kiln_core::{Archetype, Builtin};
|
use kiln_core::{Archetype, Builtin};
|
||||||
|
use kiln_core::game::KeyType;
|
||||||
|
use kiln_core::glyph::Glyph;
|
||||||
|
|
||||||
/// One level in the editor's sidebar menu tree.
|
/// One level in the editor's sidebar menu tree.
|
||||||
///
|
///
|
||||||
@@ -100,10 +102,21 @@ impl MenuLevel {
|
|||||||
ed.set_current(Archetype::Builtin(Builtin::Pusher, "pusher_west"))
|
ed.set_current(Archetype::Builtin(Builtin::Pusher, "pusher_west"))
|
||||||
}),
|
}),
|
||||||
],
|
],
|
||||||
MenuLevel::Items => vec![MenuEntry::item('t', "♦ Gem", |ed| {
|
MenuLevel::Items => vec![
|
||||||
// 't' for 'treasure' — 'g' conflicts with glyph picker
|
MenuEntry::item('t', "♦ Gem", |ed| {
|
||||||
ed.set_current(Archetype::Builtin(Builtin::Gem, "gem"))
|
// 't' for 'treasure' — 'g' conflicts with glyph picker
|
||||||
})],
|
ed.set_current(Archetype::Builtin(Builtin::Gem, "gem"))
|
||||||
|
}),
|
||||||
|
MenuEntry::Separator,
|
||||||
|
MenuEntry::glyph('b', KeyType::Blue.glyph(), "Blue key", |ed| ed.set_current(Archetype::Builtin(Builtin::Key, "key_blue"))),
|
||||||
|
MenuEntry::glyph('g', KeyType::Green.glyph(),"Green key", |ed| ed.set_current(Archetype::Builtin(Builtin::Key, "key_green"))),
|
||||||
|
MenuEntry::glyph('c', KeyType::Cyan.glyph(),"Cyan key", |ed| ed.set_current(Archetype::Builtin(Builtin::Key, "key_cyan"))),
|
||||||
|
MenuEntry::glyph('r', KeyType::Red.glyph(),"Red key", |ed| ed.set_current(Archetype::Builtin(Builtin::Key, "key_red"))),
|
||||||
|
MenuEntry::glyph('p', KeyType::Purple.glyph(),"Purple key", |ed| ed.set_current(Archetype::Builtin(Builtin::Key, "key_purple"))),
|
||||||
|
MenuEntry::glyph('o', KeyType::Orange.glyph(), "Orange key", |ed| ed.set_current(Archetype::Builtin(Builtin::Key, "key_orange"))),
|
||||||
|
MenuEntry::glyph('y', KeyType::Yellow.glyph(), "Yellow key", |ed| ed.set_current(Archetype::Builtin(Builtin::Key, "key_yellow"))),
|
||||||
|
MenuEntry::glyph('w', KeyType::White.glyph(), "White key", |ed| ed.set_current(Archetype::Builtin(Builtin::Key, "key_white"))),
|
||||||
|
],
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -113,6 +126,7 @@ impl MenuLevel {
|
|||||||
pub(crate) fn perform_key(self, ch: char, editor: &mut EditorState) {
|
pub(crate) fn perform_key(self, ch: char, editor: &mut EditorState) {
|
||||||
let action = self.entries().into_iter().find_map(|entry| match entry {
|
let action = self.entries().into_iter().find_map(|entry| match entry {
|
||||||
MenuEntry::Item { key, action, .. } if key == ch => Some(action),
|
MenuEntry::Item { key, action, .. } if key == ch => Some(action),
|
||||||
|
MenuEntry::Glyph { key, action, .. } if key == ch => Some(action),
|
||||||
_ => None,
|
_ => None,
|
||||||
});
|
});
|
||||||
if let Some(action) = action {
|
if let Some(action) = action {
|
||||||
@@ -134,6 +148,17 @@ pub(crate) enum MenuEntry {
|
|||||||
/// Run when the user presses `key`; mutates the editor session.
|
/// Run when the user presses `key`; mutates the editor session.
|
||||||
action: Box<dyn FnOnce(&mut EditorState)>,
|
action: Box<dyn FnOnce(&mut EditorState)>,
|
||||||
},
|
},
|
||||||
|
/// An item labeled by a styled Glyph
|
||||||
|
Glyph {
|
||||||
|
/// The letter the user presses to activate this item.
|
||||||
|
key: char,
|
||||||
|
/// The glyph to show next to the label
|
||||||
|
glyph: Glyph,
|
||||||
|
/// 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.
|
/// A blank spacer line. Part of the menu API; no current level uses one yet.
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
Blank,
|
Blank,
|
||||||
@@ -156,6 +181,20 @@ impl MenuEntry {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(crate) fn glyph<F: FnOnce(&mut EditorState) + 'static>(
|
||||||
|
key: char,
|
||||||
|
glyph: Glyph,
|
||||||
|
label: impl Into<String>,
|
||||||
|
action: F,
|
||||||
|
) -> Self {
|
||||||
|
MenuEntry::Glyph {
|
||||||
|
key,
|
||||||
|
glyph,
|
||||||
|
label: label.into(),
|
||||||
|
action: Box::new(action),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub(crate) fn value<F: FnOnce(&EditorState) -> String + 'static>(action: F) -> Self {
|
pub(crate) fn value<F: FnOnce(&EditorState) -> String + 'static>(action: F) -> Self {
|
||||||
MenuEntry::CurrentValue(Box::new(action))
|
MenuEntry::CurrentValue(Box::new(action))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -131,7 +131,7 @@ pub(crate) fn handle_editor_input(event: Event, term_w: u16, ed: &mut EditorStat
|
|||||||
KeyCode::Right => ed.move_cursor(1, 0),
|
KeyCode::Right => ed.move_cursor(1, 0),
|
||||||
KeyCode::Char('l') => ui.log.toggle(),
|
KeyCode::Char('l') => ui.log.toggle(),
|
||||||
// `g` opens the glyph picker to edit the current drawing glyph.
|
// `g` opens the glyph picker to edit the current drawing glyph.
|
||||||
KeyCode::Char('g') => ed.open_glyph_picker(),
|
KeyCode::Char('G') => ed.open_glyph_picker(),
|
||||||
// Space stamps the current thing; Tab toggles draw mode.
|
// Space stamps the current thing; Tab toggles draw mode.
|
||||||
KeyCode::Char(' ') => ed.place_current(),
|
KeyCode::Char(' ') => ed.place_current(),
|
||||||
KeyCode::Tab => ed.toggle_draw_mode(),
|
KeyCode::Tab => ed.toggle_draw_mode(),
|
||||||
@@ -143,7 +143,7 @@ pub(crate) fn handle_editor_input(event: Event, term_w: u16, ed: &mut EditorStat
|
|||||||
},
|
},
|
||||||
Event::Mouse(m) => match m.kind {
|
Event::Mouse(m) => match m.kind {
|
||||||
// Right-click moves the cursor to the clicked cell (if on the board).
|
// Right-click moves the cursor to the clicked cell (if on the board).
|
||||||
MouseEventKind::Down(MouseButton::Right) => ed.set_cursor_at_screen(m.column, m.row),
|
MouseEventKind::Down(MouseButton::Left) => ed.set_cursor_at_screen(m.column, m.row),
|
||||||
// Dragging the divider resizes the sidebar.
|
// Dragging the divider resizes the sidebar.
|
||||||
MouseEventKind::Drag(_) => ed.resize_sidebar(term_w.saturating_sub(m.column), term_w),
|
MouseEventKind::Drag(_) => ed.resize_sidebar(term_w.saturating_sub(m.column), term_w),
|
||||||
_ => {}
|
_ => {}
|
||||||
|
|||||||
@@ -86,7 +86,7 @@ impl Widget for StatusSidebarWidget {
|
|||||||
Line::from(vec![
|
Line::from(vec![
|
||||||
Span::raw("Gems: "),
|
Span::raw("Gems: "),
|
||||||
Span::styled(gem_char, gem_style),
|
Span::styled(gem_char, gem_style),
|
||||||
Span::raw(format!("{}", self.gems)),
|
Span::raw(format!("{} ", self.gems)),
|
||||||
]),
|
]),
|
||||||
Line::from(""),
|
Line::from(""),
|
||||||
Line::from_iter(key_spans.into_iter()),
|
Line::from_iter(key_spans.into_iter()),
|
||||||
|
|||||||
@@ -1,6 +1,10 @@
|
|||||||
use color::Rgba8;
|
use color::Rgba8;
|
||||||
|
use kiln_core::cp437::tile_to_char;
|
||||||
|
use kiln_core::glyph::Glyph;
|
||||||
use ratatui::layout::Rect;
|
use ratatui::layout::Rect;
|
||||||
use ratatui::prelude::Color;
|
use ratatui::prelude::Color;
|
||||||
|
use ratatui::style::Style;
|
||||||
|
use ratatui::text::Span;
|
||||||
|
|
||||||
/// Converts a core [`Rgba8`] color into a ratatui truecolor [`Color`].
|
/// Converts a core [`Rgba8`] color into a ratatui truecolor [`Color`].
|
||||||
///
|
///
|
||||||
@@ -10,6 +14,17 @@ pub fn rgba8_to_color(c: Rgba8) -> Color {
|
|||||||
Color::Rgb(c.r, c.g, c.b)
|
Color::Rgb(c.r, c.g, c.b)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Converts a [`Glyph`] to a single-character styled [`Span`].
|
||||||
|
///
|
||||||
|
/// The tile index is mapped to a CP437 character; fg and bg are both applied.
|
||||||
|
pub fn glyph_to_span(glyph: Glyph) -> Span<'static> {
|
||||||
|
let ch = tile_to_char(glyph.tile).to_string();
|
||||||
|
let style = Style::default()
|
||||||
|
.fg(rgba8_to_color(glyph.fg))
|
||||||
|
.bg(rgba8_to_color(glyph.bg));
|
||||||
|
Span::styled(ch, style)
|
||||||
|
}
|
||||||
|
|
||||||
/// Returns true if two `Rect`s share at least one cell.
|
/// Returns true if two `Rect`s share at least one cell.
|
||||||
pub fn rects_overlap(a: Rect, b: Rect) -> bool {
|
pub fn rects_overlap(a: Rect, b: Rect) -> bool {
|
||||||
a.x < b.x + b.width && b.x < a.x + a.width && a.y < b.y + b.height && b.y < a.y + a.height
|
a.x < b.x + b.width && b.x < a.x + a.width && a.y < b.y + b.height && b.y < a.y + a.height
|
||||||
|
|||||||
Reference in New Issue
Block a user