mirror of
https://codeberg.org/unspeaker/tengri.git
synced 2025-12-06 03:36:42 +01:00
tui: add pgup/pgdn and extract tui_keys
This commit is contained in:
parent
7ba37f3f02
commit
95149b79c4
4 changed files with 136 additions and 130 deletions
|
|
@ -63,3 +63,18 @@ pub(crate) use std::ffi::OsString;
|
||||||
//engine.run(&state)?;
|
//engine.run(&state)?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(test)] #[test] fn test_parse_key () {
|
||||||
|
use KeyModifiers as Mods;
|
||||||
|
let test = |x: &str, y|assert_eq!(KeyMatcher::new(x).build(), Some(Event::Key(y)));
|
||||||
|
//test(":x",
|
||||||
|
//KeyEvent::new(KeyCode::Char('x'), Mods::NONE));
|
||||||
|
//test(":ctrl-x",
|
||||||
|
//KeyEvent::new(KeyCode::Char('x'), Mods::CONTROL));
|
||||||
|
//test(":alt-x",
|
||||||
|
//KeyEvent::new(KeyCode::Char('x'), Mods::ALT));
|
||||||
|
//test(":shift-x",
|
||||||
|
//KeyEvent::new(KeyCode::Char('x'), Mods::SHIFT));
|
||||||
|
//test(":ctrl-alt-shift-x",
|
||||||
|
//KeyEvent::new(KeyCode::Char('x'), Mods::CONTROL | Mods::ALT | Mods::SHIFT ));
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@ use std::time::Duration;
|
||||||
|
|
||||||
mod tui_buffer; pub use self::tui_buffer::*;
|
mod tui_buffer; pub use self::tui_buffer::*;
|
||||||
mod tui_input; pub use self::tui_input::*;
|
mod tui_input; pub use self::tui_input::*;
|
||||||
|
mod tui_keys; pub use self::tui_keys::*;
|
||||||
mod tui_output; pub use self::tui_output::*;
|
mod tui_output; pub use self::tui_output::*;
|
||||||
mod tui_perf; pub use self::tui_perf::*;
|
mod tui_perf; pub use self::tui_perf::*;
|
||||||
|
|
||||||
|
|
@ -14,30 +15,6 @@ pub struct Tui {
|
||||||
pub perf: PerfModel,
|
pub perf: PerfModel,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait TuiRun<R: Render<TuiOut> + Handle<TuiIn> + 'static> {
|
|
||||||
/// Run an app in the main loop.
|
|
||||||
fn run (&self, state: &Arc<RwLock<R>>) -> Usually<()>;
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T: Render<TuiOut> + Handle<TuiIn> + 'static> TuiRun<T> for Arc<RwLock<Tui>> {
|
|
||||||
fn run (&self, state: &Arc<RwLock<T>>) -> Usually<()> {
|
|
||||||
let _input_thread = TuiIn::run_input(self, state, Duration::from_millis(100));
|
|
||||||
self.write().unwrap().setup()?;
|
|
||||||
let render_thread = TuiOut::run_output(self, state, Duration::from_millis(10));
|
|
||||||
match render_thread.join() {
|
|
||||||
Ok(result) => {
|
|
||||||
self.write().unwrap().teardown()?;
|
|
||||||
println!("\n\rRan successfully: {result:?}\n\r");
|
|
||||||
},
|
|
||||||
Err(error) => {
|
|
||||||
self.write().unwrap().teardown()?;
|
|
||||||
panic!("\n\rRender thread failed: {error:?}.\n\r")
|
|
||||||
},
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Tui {
|
impl Tui {
|
||||||
/// Construct a new TUI engine and wrap it for shared ownership.
|
/// Construct a new TUI engine and wrap it for shared ownership.
|
||||||
pub fn new () -> Usually<Arc<RwLock<Self>>> {
|
pub fn new () -> Usually<Arc<RwLock<Self>>> {
|
||||||
|
|
@ -89,3 +66,27 @@ impl Tui {
|
||||||
disable_raw_mode().map_err(Into::into)
|
disable_raw_mode().map_err(Into::into)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub trait TuiRun<R: Render<TuiOut> + Handle<TuiIn> + 'static> {
|
||||||
|
/// Run an app in the main loop.
|
||||||
|
fn run (&self, state: &Arc<RwLock<R>>) -> Usually<()>;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: Render<TuiOut> + Handle<TuiIn> + 'static> TuiRun<T> for Arc<RwLock<Tui>> {
|
||||||
|
fn run (&self, state: &Arc<RwLock<T>>) -> Usually<()> {
|
||||||
|
let _input_thread = TuiIn::run_input(self, state, Duration::from_millis(100));
|
||||||
|
self.write().unwrap().setup()?;
|
||||||
|
let render_thread = TuiOut::run_output(self, state, Duration::from_millis(10));
|
||||||
|
match render_thread.join() {
|
||||||
|
Ok(result) => {
|
||||||
|
self.write().unwrap().teardown()?;
|
||||||
|
println!("\n\rRan successfully: {result:?}\n\r");
|
||||||
|
},
|
||||||
|
Err(error) => {
|
||||||
|
self.write().unwrap().teardown()?;
|
||||||
|
panic!("\n\rRender thread failed: {error:?}.\n\r")
|
||||||
|
},
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,7 @@ impl Input for TuiIn {
|
||||||
fn is_done (&self) -> bool { self.0.fetch_and(true, Relaxed) }
|
fn is_done (&self) -> bool { self.0.fetch_and(true, Relaxed) }
|
||||||
fn done (&self) { self.0.store(true, Relaxed); }
|
fn done (&self) { self.0.store(true, Relaxed); }
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TuiIn {
|
impl TuiIn {
|
||||||
/// Spawn the input thread.
|
/// Spawn the input thread.
|
||||||
pub fn run_input <T: Handle<TuiIn> + 'static> (
|
pub fn run_input <T: Handle<TuiIn> + 'static> (
|
||||||
|
|
@ -47,6 +48,7 @@ impl TuiIn {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl AtomInput for TuiIn {
|
impl AtomInput for TuiIn {
|
||||||
fn matches_atom (&self, token: &str) -> bool {
|
fn matches_atom (&self, token: &str) -> bool {
|
||||||
if let Some(event) = KeyMatcher::new(token).build() {
|
if let Some(event) = KeyMatcher::new(token).build() {
|
||||||
|
|
@ -55,106 +57,4 @@ impl AtomInput for TuiIn {
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
//fn get_event (item: &AtomItem<impl AsRef<str>>) -> Option<Event> {
|
|
||||||
//match item { AtomItem::Sym(s) => KeyMatcher::new(s).build(), _ => None }
|
|
||||||
//}
|
|
||||||
}
|
|
||||||
struct KeyMatcher {
|
|
||||||
valid: bool,
|
|
||||||
key: Option<KeyCode>,
|
|
||||||
mods: KeyModifiers,
|
|
||||||
}
|
|
||||||
impl KeyMatcher {
|
|
||||||
fn new (token: impl AsRef<str>) -> Self {
|
|
||||||
let token = token.as_ref();
|
|
||||||
if token.len() < 2 {
|
|
||||||
Self { valid: false, key: None, mods: KeyModifiers::NONE }
|
|
||||||
} else if token.chars().next() != Some('@') {
|
|
||||||
Self { valid: false, key: None, mods: KeyModifiers::NONE }
|
|
||||||
} else {
|
|
||||||
Self { valid: true, key: None, mods: KeyModifiers::NONE }.next(&token[1..])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
fn next (mut self, token: &str) -> Self {
|
|
||||||
let mut tokens = token.split('-').peekable();
|
|
||||||
while let Some(token) = tokens.next() {
|
|
||||||
if tokens.peek().is_some() {
|
|
||||||
match token {
|
|
||||||
"ctrl" | "Ctrl" | "c" | "C" => self.mods |= KeyModifiers::CONTROL,
|
|
||||||
"alt" | "Alt" | "m" | "M" => self.mods |= KeyModifiers::ALT,
|
|
||||||
"shift" | "Shift" | "s" | "S" => {
|
|
||||||
self.mods |= KeyModifiers::SHIFT;
|
|
||||||
// + TODO normalize character case, BackTab, etc.
|
|
||||||
},
|
|
||||||
_ => panic!("unknown modifier {token}"),
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
self.key = if token.len() == 1 {
|
|
||||||
Some(KeyCode::Char(token.chars().next().unwrap()))
|
|
||||||
} else {
|
|
||||||
Some(Self::named_key(token).unwrap_or_else(||panic!("unknown character {token}")))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
self
|
|
||||||
}
|
|
||||||
fn named_key (token: &str) -> Option<KeyCode> {
|
|
||||||
use KeyCode::*;
|
|
||||||
Some(match token {
|
|
||||||
"up" => Up,
|
|
||||||
"down" => Down,
|
|
||||||
"left" => Left,
|
|
||||||
"right" => Right,
|
|
||||||
"esc" | "escape" => Esc,
|
|
||||||
"enter" | "return" => Enter,
|
|
||||||
"delete" | "del" => Delete,
|
|
||||||
"tab" => Tab,
|
|
||||||
"space" => Char(' '),
|
|
||||||
"comma" => Char(','),
|
|
||||||
"period" => Char('.'),
|
|
||||||
"plus" => Char('+'),
|
|
||||||
"minus" | "dash" => Char('-'),
|
|
||||||
"equal" | "equals" => Char('='),
|
|
||||||
"underscore" => Char('_'),
|
|
||||||
"backtick" => Char('`'),
|
|
||||||
"lt" => Char('<'),
|
|
||||||
"gt" => Char('>'),
|
|
||||||
"openbracket" => Char('['),
|
|
||||||
"closebracket" => Char(']'),
|
|
||||||
"f1" => F(1),
|
|
||||||
"f2" => F(2),
|
|
||||||
"f3" => F(3),
|
|
||||||
"f4" => F(4),
|
|
||||||
"f5" => F(5),
|
|
||||||
"f6" => F(6),
|
|
||||||
"f7" => F(7),
|
|
||||||
"f8" => F(8),
|
|
||||||
"f9" => F(9),
|
|
||||||
"f10" => F(10),
|
|
||||||
"f11" => F(11),
|
|
||||||
"f12" => F(12),
|
|
||||||
_ => return None,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
fn build (self) -> Option<Event> {
|
|
||||||
if self.valid && self.key.is_some() {
|
|
||||||
Some(Event::Key(KeyEvent::new(self.key.unwrap(), self.mods)))
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#[cfg(test)] #[test] fn test_parse_key () {
|
|
||||||
use KeyModifiers as Mods;
|
|
||||||
let test = |x: &str, y|assert_eq!(KeyMatcher::new(x).build(), Some(Event::Key(y)));
|
|
||||||
//test(":x",
|
|
||||||
//KeyEvent::new(KeyCode::Char('x'), Mods::NONE));
|
|
||||||
//test(":ctrl-x",
|
|
||||||
//KeyEvent::new(KeyCode::Char('x'), Mods::CONTROL));
|
|
||||||
//test(":alt-x",
|
|
||||||
//KeyEvent::new(KeyCode::Char('x'), Mods::ALT));
|
|
||||||
//test(":shift-x",
|
|
||||||
//KeyEvent::new(KeyCode::Char('x'), Mods::SHIFT));
|
|
||||||
//test(":ctrl-alt-shift-x",
|
|
||||||
//KeyEvent::new(KeyCode::Char('x'), Mods::CONTROL | Mods::ALT | Mods::SHIFT ));
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
90
tui/src/tui_engine/tui_keys.rs
Normal file
90
tui/src/tui_engine/tui_keys.rs
Normal file
|
|
@ -0,0 +1,90 @@
|
||||||
|
use crate::*;
|
||||||
|
|
||||||
|
pub struct KeyMatcher {
|
||||||
|
valid: bool,
|
||||||
|
key: Option<KeyCode>,
|
||||||
|
mods: KeyModifiers,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl KeyMatcher {
|
||||||
|
pub fn new (token: impl AsRef<str>) -> Self {
|
||||||
|
let token = token.as_ref();
|
||||||
|
if token.len() < 2 {
|
||||||
|
Self { valid: false, key: None, mods: KeyModifiers::NONE }
|
||||||
|
} else if token.chars().next() != Some('@') {
|
||||||
|
Self { valid: false, key: None, mods: KeyModifiers::NONE }
|
||||||
|
} else {
|
||||||
|
Self { valid: true, key: None, mods: KeyModifiers::NONE }.next(&token[1..])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn build (self) -> Option<Event> {
|
||||||
|
if self.valid && self.key.is_some() {
|
||||||
|
Some(Event::Key(KeyEvent::new(self.key.unwrap(), self.mods)))
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fn next (mut self, token: &str) -> Self {
|
||||||
|
let mut tokens = token.split('-').peekable();
|
||||||
|
while let Some(token) = tokens.next() {
|
||||||
|
if tokens.peek().is_some() {
|
||||||
|
match token {
|
||||||
|
"ctrl" | "Ctrl" | "c" | "C" => self.mods |= KeyModifiers::CONTROL,
|
||||||
|
"alt" | "Alt" | "m" | "M" => self.mods |= KeyModifiers::ALT,
|
||||||
|
"shift" | "Shift" | "s" | "S" => {
|
||||||
|
self.mods |= KeyModifiers::SHIFT;
|
||||||
|
// + TODO normalize character case, BackTab, etc.
|
||||||
|
},
|
||||||
|
_ => panic!("unknown modifier {token}"),
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
self.key = if token.len() == 1 {
|
||||||
|
Some(KeyCode::Char(token.chars().next().unwrap()))
|
||||||
|
} else {
|
||||||
|
Some(Self::named_key(token).unwrap_or_else(||panic!("unknown character {token}")))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
self
|
||||||
|
}
|
||||||
|
fn named_key (token: &str) -> Option<KeyCode> {
|
||||||
|
use KeyCode::*;
|
||||||
|
Some(match token {
|
||||||
|
"up" => Up,
|
||||||
|
"down" => Down,
|
||||||
|
"left" => Left,
|
||||||
|
"right" => Right,
|
||||||
|
"esc" | "escape" => Esc,
|
||||||
|
"enter" | "return" => Enter,
|
||||||
|
"delete" | "del" => Delete,
|
||||||
|
"tab" => Tab,
|
||||||
|
"space" => Char(' '),
|
||||||
|
"comma" => Char(','),
|
||||||
|
"period" => Char('.'),
|
||||||
|
"plus" => Char('+'),
|
||||||
|
"minus" | "dash" => Char('-'),
|
||||||
|
"equal" | "equals" => Char('='),
|
||||||
|
"underscore" => Char('_'),
|
||||||
|
"backtick" => Char('`'),
|
||||||
|
"lt" => Char('<'),
|
||||||
|
"gt" => Char('>'),
|
||||||
|
"openbracket" => Char('['),
|
||||||
|
"closebracket" => Char(']'),
|
||||||
|
"pgup" | "pageup" => PageUp,
|
||||||
|
"pgdn" | "pagedown" => PageDown,
|
||||||
|
"f1" => F(1),
|
||||||
|
"f2" => F(2),
|
||||||
|
"f3" => F(3),
|
||||||
|
"f4" => F(4),
|
||||||
|
"f5" => F(5),
|
||||||
|
"f6" => F(6),
|
||||||
|
"f7" => F(7),
|
||||||
|
"f8" => F(8),
|
||||||
|
"f9" => F(9),
|
||||||
|
"f10" => F(10),
|
||||||
|
"f11" => F(11),
|
||||||
|
"f12" => F(12),
|
||||||
|
_ => return None,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue