chiark / gitweb /
More structure.
authorSimon Tatham <anakin@pobox.com>
Wed, 27 Dec 2023 20:35:50 +0000 (20:35 +0000)
committerSimon Tatham <anakin@pobox.com>
Wed, 27 Dec 2023 20:35:50 +0000 (20:35 +0000)
Now we hand off to subsidiary traits that can treat menus, files and
editors all differently.

src/lib.rs
src/menu.rs [new file with mode: 0644]
src/tui.rs

index f29e146084bf8b5d3f4cb2a31def1fdf34f820d0..0ea68400d73e9d4b809312b1654f22b5ee2543fa 100644 (file)
@@ -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 (file)
index 0000000..f6a6db8
--- /dev/null
@@ -0,0 +1,18 @@
+use super::tui::{ActivityState, HandleEventResult, OurKey, OurKey::*};
+
+struct Menu {
+}
+
+pub fn main_menu() -> Box<dyn ActivityState> {
+    Box::new(Menu{
+    })
+}
+
+impl ActivityState for Menu {
+    fn handle_keypress(&mut self, key: OurKey) -> HandleEventResult {
+        match key {
+            Pr('b') => HandleEventResult::Beep,
+            _ => HandleEventResult::Nothing,
+        }
+    }
+}
index 88787924a58cf6419fa36916b96c13c32b15ec11..fc2c98fb359b3fa14c3a132db302305acdb01673 100644 (file)
@@ -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<OurKey> {
+        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<dyn ActivityState>,
+}
+
+fn new_activity_state(activity: Activity) -> Box<dyn ActivityState> {
+    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),
         }
     }
 }