From d60c03674628a7310d0dccfee6198f9eafabebdd Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Thu, 28 Dec 2023 12:33:30 +0000 Subject: [PATCH] Enough menu handling to [ESC][X][X]! --- Cargo.toml | 1 + src/menu.rs | 94 +++++++++++++++++++++++++++++++++++++++-------------- src/text.rs | 1 - src/tui.rs | 54 +++++++++++++++++++++++++----- 4 files changed, 115 insertions(+), 35 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 9877f9a..440b7fc 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -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"] } diff --git a/src/menu.rs b/src/menu.rs index d5ce6dc..37a9f22 100644 --- a/src/menu.rs +++ b/src/menu.rs @@ -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, - actions: HashMap, + actions: HashMap, } 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 { - 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, 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 { + 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 { + 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 { + 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) +} diff --git a/src/text.rs b/src/text.rs index 8ce0cf6..638c03d 100644 --- a/src/text.rs +++ b/src/text.rs @@ -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; diff --git a/src/tui.rs b/src/tui.rs index 03d769d..7467738 100644 --- a/src/tui.rs +++ b/src/tui.rs @@ -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, 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 { 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()); + } } -- 2.30.2