chiark / gitweb /
Enough menu handling to [ESC][X][X]!
authorSimon Tatham <anakin@pobox.com>
Thu, 28 Dec 2023 12:33:30 +0000 (12:33 +0000)
committerSimon Tatham <anakin@pobox.com>
Thu, 28 Dec 2023 12:33:30 +0000 (12:33 +0000)
Cargo.toml
src/menu.rs
src/text.rs
src/tui.rs

index 9877f9ac8418db24150aa7dfbd6442db6f22347d..440b7fc664bda92506fa46adb10ef7fd886e1967 100644 (file)
@@ -9,6 +9,7 @@ chrono = { version = "0.4.31", features = ["serde"] }
 crossterm = "0.27.0"
 html2text = { version = "0.9.0", features = ["css"] }
 html5ever = "0.26.0"
+itertools = "0.12.0"
 ratatui = "0.25.0"
 regex = "1.10.2"
 reqwest = { version = "0.11.23", features = ["blocking"] }
index d5ce6dc6c73484ba30c0f227336b77551ba0a98f..37a9f22b7e4ca7325e36bc2cc66d2cb5349be74a 100644 (file)
@@ -1,10 +1,11 @@
 use std::collections::HashMap;
+use itertools::Itertools;
 
-use super::activity_stack::{Activity, NonUtilityActivity};
+use super::activity_stack::{NonUtilityActivity, UtilityActivity};
 use super::coloured_string::ColouredString;
 use super::text::*;
 use super::tui::{
-    ActivityState, CursorPosition, HandleEventResult,
+    ActivityState, CursorPosition, LogicalAction,
     OurKey, OurKey::*
 };
 
@@ -13,10 +14,6 @@ enum MenuLine {
     Key(MenuKeypressLine),
 }
 
-enum Action {
-    Goto(Activity),
-}
-
 fn find_substring(haystack: &str, needle: &str)
                   -> Option<(usize, usize, usize)> {
     if let Some(pos) = haystack.find(needle) {
@@ -44,7 +41,7 @@ struct Menu {
     title: FileHeader,
     status: FileStatusLineFinal,
     lines: Vec<MenuLine>,
-    actions: HashMap<OurKey, Action>,
+    actions: HashMap<OurKey, LogicalAction>,
 }
 
 impl Menu {
@@ -56,21 +53,39 @@ impl Menu {
             status.add(OurKey::Return, "Back", 10)
         };
 
-        Menu{
+        let mut menu = Menu {
             title: FileHeader::new(title),
             status: status.finalise(),
             lines: Vec::new(),
             actions: HashMap::new(),
+        };
+
+        if !is_main {
+            menu.actions.insert(OurKey::Return, LogicalAction::Pop);
         }
+
+        menu
     }
 
     fn add_action_coloured(&mut self, key: OurKey, desc: ColouredString,
-                           action: Action) {
+                           action: LogicalAction) {
         self.lines.push(MenuLine::Key(MenuKeypressLine::new(key, desc)));
+
+        if let Pr(c) = key {
+            if let Ok(c) = c.to_lowercase().
+                to_string().chars().exactly_one() {
+                    self.actions.insert(Pr(c), action.clone());
+                }
+            if let Ok(c) = c.to_uppercase().
+                to_string().chars().exactly_one() {
+                    self.actions.insert(Pr(c), action.clone());
+                }
+        }
+
         self.actions.insert(key, action);
     }
 
-    fn add_action(&mut self, key: OurKey, desc: &str, action: Action) {
+    fn add_action(&mut self, key: OurKey, desc: &str, action: LogicalAction) {
         let highlight_char = match key {
             Pr(c) => Some(c),
             Ctrl(c) => Some(c),
@@ -101,17 +116,6 @@ impl Menu {
     }
 }
 
-pub fn main_menu() -> Box<dyn ActivityState> {
-    let mut menu = Menu::new(
-        ColouredString::uniform("Mastodonochrome Main Menu", 'H'), true);
-
-    menu.add_action(Pr('H'), "Home timeline", Action::Goto(
-        NonUtilityActivity::HomeTimelineFile.into()));
-    menu.add_blank_line();
-
-    Box::new(menu)
-}
-
 impl ActivityState for Menu {
     fn draw(&self, w: usize, h: usize)
             -> (Vec<ColouredString>, CursorPosition) {
@@ -138,10 +142,50 @@ impl ActivityState for Menu {
         (lines, CursorPosition::End)
     }
 
-    fn handle_keypress(&mut self, key: OurKey) -> HandleEventResult {
-        match key {
-            Pr('b') => HandleEventResult::Beep,
-            _ => HandleEventResult::Nothing,
+    fn handle_keypress(&mut self, key: OurKey) -> LogicalAction {
+        dbg!(&self.actions, &key);
+        match self.actions.get(&key) {
+            Some(action) => action.clone(),
+            None => LogicalAction::Nothing,
         }
     }
 }
+
+pub fn main_menu() -> Box<dyn ActivityState> {
+    let mut menu = Menu::new(
+        ColouredString::uniform("Mastodonochrome Main Menu", 'H'), true);
+
+    menu.add_action(Pr('H'), "Home timeline", LogicalAction::Goto(
+        NonUtilityActivity::HomeTimelineFile.into()));
+
+    Box::new(menu)
+}
+
+pub fn utils_menu() -> Box<dyn ActivityState> {
+    let mut menu = Menu::new(
+        ColouredString::general(
+            "Utilities [ESC]",
+            "HHHHHHHHHHHKKKH"), false);
+
+    menu.add_action(Pr('E'), "Examine User", LogicalAction::NYI);
+    menu.add_action(Pr('Y'), "Examine Yourself", LogicalAction::NYI);
+    menu.add_blank_line();
+    menu.add_action(Pr('L'), "Logs menu", LogicalAction::Goto(
+        UtilityActivity::LogsMenu1.into()));
+    menu.add_blank_line();
+    menu.add_action(Pr('X'), "Exit Mastodonochrome", LogicalAction::Goto(
+        UtilityActivity::ExitMenu.into()));
+
+    Box::new(menu)
+}
+
+pub fn exit_menu() -> Box<dyn ActivityState> {
+    let mut menu = Menu::new(
+        ColouredString::general(
+            "Exit Mastodonochrome [ESC][X]",
+            "HHHHHHHHHHHHHHHHHHHHHHKKKHHKH"), false);
+
+    menu.add_action(Pr('X'), "Confirm exit", LogicalAction::Exit);
+
+    Box::new(menu)
+}
index 8ce0cf6a6206b8d5dd987d42724f3d9bb700c0bc..638c03d2312821d56ea38fd426944073dc8396ae 100644 (file)
@@ -1271,7 +1271,6 @@ impl TextFragment for FileStatusLineFinal {
         let avail = width - min(
             width, line.width() + cpropwidth + extraspace + 1);
         let minindex = self.priwidth.partition_point(|(_, w)| *w <= avail);
-        dbg!(avail, &self.priwidth, minindex);
         if minindex > 0 {
             let minpri = self.priwidth[minindex - 1].0;
 
index 03d769d08db5c744e53ec552c0a7d6daa7b23e7c..7467738e009a5860010107bd3a459fd93839b639 100644 (file)
@@ -119,7 +119,7 @@ enum SubthreadEvent {
     TermEv(Event),
 }
 
-pub enum HandleEventResult {
+pub enum PhysicalAction {
     Nothing,
     Beep,
     Exit,
@@ -246,16 +246,16 @@ impl Tui {
                             if key.kind == KeyEventKind::Press {
                                 for ourkey in Self::translate_keypress(key) {
                                     match self.state.handle_keypress(ourkey) {
-                                        HandleEventResult::Beep => {
+                                        PhysicalAction::Beep => {
                                             Self::beep()?
                                         },
 
                                         // FIXME: errors?
-                                        HandleEventResult::Exit => {
+                                        PhysicalAction::Exit => {
                                             break 'outer Ok(());
                                         },
 
-                                        HandleEventResult::Nothing => (),
+                                        PhysicalAction::Nothing => (),
                                     }
                                 }
                             }
@@ -273,15 +273,26 @@ impl Tui {
     }
 }
 
+#[derive(Debug)]
 pub enum CursorPosition {
     None, // cursor is hidden
     End, // cursor at the end of the last drawn line (quite common in this UI)
     At(usize, usize), // (x,y)
 }
 
+#[derive(Debug, Clone)]
+pub enum LogicalAction {
+    Beep,
+    Pop,
+    Goto(Activity),
+    Exit,
+    Nothing,
+    NYI, // FIXME: get rid of this once everything is implemented
+}
+
 pub trait ActivityState {
     fn draw(&self, w: usize, h: usize) -> (Vec<ColouredString>, CursorPosition);
-    fn handle_keypress(&mut self, key: OurKey) -> HandleEventResult;
+    fn handle_keypress(&mut self, key: OurKey) -> LogicalAction;
 }
 
 struct TuiLogicalState {
@@ -292,6 +303,8 @@ struct TuiLogicalState {
 fn new_activity_state(activity: Activity) -> Box<dyn ActivityState> {
     match activity {
         Activity::NonUtil(NonUtilityActivity::MainMenu) => main_menu(),
+        Activity::Util(UtilityActivity::UtilsMenu) => utils_menu(),
+        Activity::Util(UtilityActivity::ExitMenu) => exit_menu(),
         _ => panic!("FIXME"),
     }
 }
@@ -329,12 +342,35 @@ impl TuiLogicalState {
         }
     }
 
-    fn handle_keypress(&mut self, key: OurKey) -> HandleEventResult {
-        match key {
-            // FIXME: ESC should really go to the utilities menu
-            OurKey::Escape => HandleEventResult::Exit,
+    fn handle_keypress(&mut self, key: OurKey) -> PhysicalAction {
+        let logact = match key {
+            // Central handling of [ESC]: it _always_ goes to the
+            // utilities menu, from any UI context at all.
+            OurKey::Escape => LogicalAction::Goto(
+                UtilityActivity::UtilsMenu.into()),
 
             _ => self.activity_state.handle_keypress(key),
+        };
+
+        match logact {
+            LogicalAction::Beep => PhysicalAction::Beep,
+            LogicalAction::Exit => PhysicalAction::Exit,
+            LogicalAction::Nothing => PhysicalAction::Nothing,
+            LogicalAction::NYI => PhysicalAction::Beep,
+            LogicalAction::Goto(activity) => {
+                self.activity_stack.goto(activity);
+                self.changed_activity();
+                PhysicalAction::Nothing
+            },
+            LogicalAction::Pop => {
+                self.activity_stack.pop();
+                self.changed_activity();
+                PhysicalAction::Nothing
+            },
         }
     }
+
+    fn changed_activity(&mut self) {
+        self.activity_state = new_activity_state(self.activity_stack.top());
+    }
 }