From 115cad68a69345d9c797d05fbfc4c2631068c628 Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Wed, 27 Dec 2023 20:35:50 +0000 Subject: [PATCH] More structure. Now we hand off to subsidiary traits that can treat menus, files and editors all differently. --- src/lib.rs | 1 + src/menu.rs | 18 ++++++++ src/tui.rs | 117 +++++++++++++++++++++++++++++++++++++++++++++------- 3 files changed, 120 insertions(+), 16 deletions(-) create mode 100644 src/menu.rs diff --git a/src/lib.rs b/src/lib.rs index f29e146..0ea6840 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -10,3 +10,4 @@ pub mod text; pub mod client; pub mod activity_stack; pub mod tui; +pub mod menu; diff --git a/src/menu.rs b/src/menu.rs new file mode 100644 index 0000000..f6a6db8 --- /dev/null +++ b/src/menu.rs @@ -0,0 +1,18 @@ +use super::tui::{ActivityState, HandleEventResult, OurKey, OurKey::*}; + +struct Menu { +} + +pub fn main_menu() -> Box { + Box::new(Menu{ + }) +} + +impl ActivityState for Menu { + fn handle_keypress(&mut self, key: OurKey) -> HandleEventResult { + match key { + Pr('b') => HandleEventResult::Beep, + _ => HandleEventResult::Nothing, + } + } +} diff --git a/src/tui.rs b/src/tui.rs index 8878792..fc2c98f 100644 --- a/src/tui.rs +++ b/src/tui.rs @@ -1,5 +1,5 @@ use crossterm::{ - event::{self, Event, KeyEvent, KeyCode, KeyEventKind}, + event::{self, Event, KeyEvent, KeyCode, KeyEventKind, KeyModifiers}, terminal::{ disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen, @@ -16,6 +16,7 @@ use unicode_width::UnicodeWidthStr; use super::activity_stack::*; use super::client::Client; use super::coloured_string::{ColouredString, ColouredStringSlice}; +use super::menu::*; use super::text::{parse_html, Paragraph, TextFragment}; fn ratatui_style_from_colour(colour: char) -> Style { @@ -119,7 +120,7 @@ enum SubthreadEvent { TermEv(Event), } -enum HandleEventResult { +pub enum HandleEventResult { Nothing, Beep, Exit, @@ -132,6 +133,16 @@ pub struct Tui { state: TuiLogicalState, } +#[derive(Eq, PartialEq, Debug, Clone, Copy)] +pub enum OurKey { + Pr(char), + Ctrl(char), + FunKey(u8), + Backspace, Return, Escape, + Up, Down, Left, Right, + PgUp, PgDn, Home, End, Ins, Del, +} + impl Tui { pub fn run() -> std::io::Result<()> { stdout().execute(EnterAlternateScreen)?; @@ -166,8 +177,57 @@ impl Tui { result } + fn translate_keypress(ev: KeyEvent) -> Vec { + let main = match ev.code { + KeyCode::Up => Some(OurKey::Up), + KeyCode::Down => Some(OurKey::Down), + KeyCode::Left => Some(OurKey::Left), + KeyCode::Right => Some(OurKey::Right), + KeyCode::PageUp => Some(OurKey::PgUp), + KeyCode::PageDown => Some(OurKey::PgDn), + KeyCode::Home => Some(OurKey::Home), + KeyCode::End => Some(OurKey::End), + KeyCode::Insert => Some(OurKey::Ins), + KeyCode::Delete => Some(OurKey::Del), + KeyCode::F(n) => Some(OurKey::FunKey(n)), + KeyCode::Esc => Some(OurKey::Escape), + KeyCode::Backspace => Some(OurKey::Backspace), + KeyCode::Enter => Some(OurKey::Return), + KeyCode::Tab => Some(OurKey::Ctrl('I')), + KeyCode::Char(c) => { + let initial = if ('\0'..' ').contains(&c) { + Some(OurKey::Ctrl( + char::from_u32((c as u32) + 0x40).unwrap())) + } else if ('\u{80}'..'\u{A0}').contains(&c) { + None + } else if ev.modifiers.contains(KeyModifiers::CONTROL) { + Some(OurKey::Ctrl( + char::from_u32(((c as u32) & 0x1F) + 0x40).unwrap())) + } else { + Some(OurKey::Pr(c)) + }; + match initial { + Some(OurKey::Ctrl('H')) => Some(OurKey::Backspace), + Some(OurKey::Ctrl('M')) => Some(OurKey::Return), + Some(OurKey::Ctrl('[')) => Some(OurKey::Escape), + other => other, + } + }, + _ => None, + }; + if let Some(main) = main { + if ev.modifiers.contains(KeyModifiers::ALT) { + vec! { OurKey::Escape, main } + } else { + vec! { main } + } + } else { + vec! {} + } + } + fn main_loop(&mut self) -> std::io::Result<()> { - loop { + 'outer: loop { self.terminal.draw(|frame| { let area = frame.size(); let buf = frame.buffer_mut(); @@ -180,13 +240,19 @@ impl Tui { match ev { Event::Key(key) => { if key.kind == KeyEventKind::Press { - match self.state.handle_keypress(key) { - HandleEventResult::Beep => Self::beep()?, - - // FIXME: errors? - HandleEventResult::Exit => break Ok(()), - - HandleEventResult::Nothing => (), + for ourkey in Self::translate_keypress(key) { + match self.state.handle_keypress(ourkey) { + HandleEventResult::Beep => { + Self::beep()? + }, + + // FIXME: errors? + HandleEventResult::Exit => { + break 'outer Ok(()); + }, + + HandleEventResult::Nothing => (), + } } } }, @@ -203,14 +269,30 @@ impl Tui { } } +pub trait ActivityState { + fn handle_keypress(&mut self, key: OurKey) -> HandleEventResult; +} + struct TuiLogicalState { activity_stack: ActivityStack, + activity_state: Box, +} + +fn new_activity_state(activity: Activity) -> Box { + match activity { + Activity::NonUtil(NonUtilityActivity::MainMenu) => main_menu(), + _ => panic!("FIXME"), + } } impl TuiLogicalState { fn new() -> Self { + let activity_stack = ActivityStack::new(); + let activity_state = new_activity_state(activity_stack.top()); + TuiLogicalState { - activity_stack: ActivityStack::new(), + activity_stack: activity_stack, + activity_state: activity_state, } } @@ -222,11 +304,14 @@ impl TuiLogicalState { ).slice()); } - fn handle_keypress(&mut self, key: KeyEvent) -> HandleEventResult { - match key.code { - KeyCode::Char('q') => HandleEventResult::Exit, - KeyCode::Char('b') => HandleEventResult::Beep, - _ => HandleEventResult::Nothing, + fn handle_keypress(&mut self, key: OurKey) -> HandleEventResult { + dbg!(key); + + match key { + // FIXME: ESC should really go to the utilities menu + OurKey::Escape => HandleEventResult::Exit, + + _ => self.activity_state.handle_keypress(key), } } } -- 2.30.2