chiark / gitweb /
Begin actually displaying menu options!
authorSimon Tatham <anakin@pobox.com>
Thu, 28 Dec 2023 11:49:02 +0000 (11:49 +0000)
committerSimon Tatham <anakin@pobox.com>
Thu, 28 Dec 2023 12:03:03 +0000 (12:03 +0000)
src/menu.rs
src/text.rs
src/tui.rs

index 069ef9eb8676343ee58e91e6532b3dbe7c65c962..d5ce6dc6c73484ba30c0f227336b77551ba0a98f 100644 (file)
@@ -1,3 +1,6 @@
+use std::collections::HashMap;
+
+use super::activity_stack::{Activity, NonUtilityActivity};
 use super::coloured_string::ColouredString;
 use super::text::*;
 use super::tui::{
@@ -5,15 +8,108 @@ use super::tui::{
     OurKey, OurKey::*
 };
 
+enum MenuLine {
+    Blank,
+    Key(MenuKeypressLine),
+}
+
+enum Action {
+    Goto(Activity),
+}
+
+fn find_substring(haystack: &str, needle: &str)
+                  -> Option<(usize, usize, usize)> {
+    if let Some(pos) = haystack.find(needle) {
+        let (pre, post) = haystack.split_at(pos);
+        let pre_nchars = pre.chars().count();
+        let needle_nchars = needle.chars().count();
+        let post_nchars = post.chars().count();
+        Some((pre_nchars, needle_nchars, post_nchars - needle_nchars))
+    } else {
+        None
+    }
+}
+
+fn find_highlight_char(desc: &str, c: char)
+                       -> Option<(usize, usize, usize)> {
+    let found = find_substring(desc, &c.to_uppercase().to_string());
+    if found.is_some() {
+        found
+    } else {
+        find_substring(desc, &c.to_lowercase().to_string())
+    }
+}
+
 struct Menu {
     title: FileHeader,
+    status: FileStatusLineFinal,
+    lines: Vec<MenuLine>,
+    actions: HashMap<OurKey, Action>,
+}
+
+impl Menu {
+    fn new(title: ColouredString, is_main: bool) -> Self {
+        let status = FileStatusLine::new();
+        let status = if is_main {
+            status.message("Select an option")
+        } else {
+            status.add(OurKey::Return, "Back", 10)
+        };
+
+        Menu{
+            title: FileHeader::new(title),
+            status: status.finalise(),
+            lines: Vec::new(),
+            actions: HashMap::new(),
+        }
+    }
+
+    fn add_action_coloured(&mut self, key: OurKey, desc: ColouredString,
+                           action: Action) {
+        self.lines.push(MenuLine::Key(MenuKeypressLine::new(key, desc)));
+        self.actions.insert(key, action);
+    }
+
+    fn add_action(&mut self, key: OurKey, desc: &str, action: Action) {
+        let highlight_char = match key {
+            Pr(c) => Some(c),
+            Ctrl(c) => Some(c),
+            _ => None,
+        };
+
+        let highlight = if let Some(c) = highlight_char {
+            find_highlight_char(desc, c)
+        } else {
+            None
+        };
+
+        let desc_coloured = if let Some((before, during, after)) = highlight {
+            ColouredString::general(
+                desc,
+                &("H".repeat(before) +
+                  &"K".repeat(during) +
+                  &"H".repeat(after)))
+        } else {
+            ColouredString::uniform(desc, 'H')
+        };
+
+        self.add_action_coloured(key, desc_coloured, action)
+    }
+
+    fn add_blank_line(&mut self) {
+        self.lines.push(MenuLine::Blank);
+    }
 }
 
 pub fn main_menu() -> Box<dyn ActivityState> {
-    Box::new(Menu{
-        title: FileHeader::new(
-            ColouredString::uniform("Mastodonochrome Main Menu", 'H')),
-    })
+    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 {
@@ -21,6 +117,24 @@ impl ActivityState for Menu {
             -> (Vec<ColouredString>, CursorPosition) {
         let mut lines = Vec::new();
         lines.extend_from_slice(&self.title.render(w));
+        lines.extend_from_slice(&BlankLine::render_static());
+
+        // FIXME: once menus get too big, we'll need to keep a current
+        // starting position, and be able to scroll up and down the
+        // menu with [<] and [>]
+        for line in &self.lines {
+            lines.extend_from_slice(&match line {
+                MenuLine::Blank => BlankLine::render_static(),
+                MenuLine::Key(mk) => mk.render(w),
+            });
+        }
+
+        while lines.len() + 1 < h {
+            lines.extend_from_slice(&BlankLine::render_static());
+        }
+
+        lines.extend_from_slice(&self.status.render(w));
+
         (lines, CursorPosition::End)
     }
 
index 8b3cff0dbf78b1ab98313063091037c3af8bfbf4..26cd15304d5e69715ebff643dd020afc65733b9f 100644 (file)
@@ -23,16 +23,20 @@ impl BlankLine {
     pub fn new() -> Self {
         BlankLine{}
     }
-}
 
-impl TextFragment for BlankLine {
-    fn render(&self, _width: usize) -> Vec<ColouredString> {
+    pub fn render_static() -> Vec<ColouredString> {
         vec! {
             ColouredString::plain(""),
         }
     }
 }
 
+impl TextFragment for BlankLine {
+    fn render(&self, _width: usize) -> Vec<ColouredString> {
+        Self::render_static()
+    }
+}
+
 #[test]
 fn test_blank() {
     assert_eq!(BlankLine::new().render(40), vec! {
index e191aeefa9934bea1bd1a877a3c1ffc8e70c88bc..03d769d08db5c744e53ec552c0a7d6daa7b23e7c 100644 (file)
@@ -133,7 +133,7 @@ pub struct Tui {
     // FIXME: we'll need a Client here
 }
 
-#[derive(Eq, PartialEq, Debug, Clone, Copy)]
+#[derive(Eq, PartialEq, Debug, Clone, Copy, Hash)]
 pub enum OurKey {
     Pr(char),
     Ctrl(char),
@@ -315,6 +315,9 @@ impl TuiLogicalState {
         let mut last_x = 0;
         let mut last_y = 0;
         for (y, line) in lines.iter().enumerate() {
+            if y >= area.height as usize {
+                break;
+            }
             ratatui_set_string(buf, 0, y, &line.slice());
             last_y = y;
             last_x = line.width();