chiark / gitweb /
Scrolling upward mostly works.
authorSimon Tatham <anakin@pobox.com>
Fri, 29 Dec 2023 18:08:43 +0000 (18:08 +0000)
committerSimon Tatham <anakin@pobox.com>
Fri, 29 Dec 2023 18:17:34 +0000 (18:17 +0000)
Doesn't clip at the top of the file yet, but this is a start!

src/file.rs
src/menu.rs
src/text.rs
src/tui.rs

index 9360ccf184f1a9ca88813da0fd2f409734412bea..b61dc27483492e9ee10703cad613f049c3d96915 100644 (file)
@@ -1,3 +1,4 @@
+use std::cmp::{min, max};
 use std::collections::HashMap;
 
 use super::client::{Client, ClientError, FeedId, FeedExtend};
@@ -5,7 +6,7 @@ use super::coloured_string::ColouredString;
 use super::text::*;
 use super::tui::{
     ActivityState, CursorPosition, LogicalAction,
-    OurKey,
+    OurKey, OurKey::*,
 };
 
 #[derive(Debug, PartialEq, Eq, Clone, Copy)]
@@ -60,7 +61,7 @@ struct FeedFile {
     contents: FeedFileContents,
     rendered: HashMap<isize, Vec<ColouredString>>,
     pos: FilePosition,
-    last_width: Option<usize>,
+    last_size: Option<(usize, usize)>,
 }
 
 impl FeedFile {
@@ -82,7 +83,7 @@ impl FeedFile {
             contents: contents,
             rendered: HashMap::new(),
             pos: initial_pos,
-            last_width: None,
+            last_size: None,
         };
         Ok(ff)
     }
@@ -105,6 +106,20 @@ impl FeedFile {
         self.rendered.get(&index).expect("We just made sure this was present")
     }
 
+    fn ensure_enough_rendered(&mut self) {
+        let (w, h) = self.last_size.expect(
+            "ensure_enough_rendered before resize");
+
+        let (item, line) = self.refine_pos(w);
+
+        let mut item = item;
+        let mut lines_rendered = line;
+        while item > self.contents.origin && lines_rendered + 1 < h {
+            item -= 1;
+            lines_rendered += self.ensure_item_rendered(item, w).len();
+        }
+    }
+
     fn refine_pos(&mut self, w: usize) -> (isize, usize) {
         let (newpos, item, line) = match self.pos {
             pos @ FilePosition::Fine(item, line) => (pos, item, line),
@@ -127,28 +142,49 @@ impl FeedFile {
 
         self.pos = newpos;
     }
+
+    fn move_up(&mut self, distance: usize) {
+        let (w, _h) = self.last_size.expect("move_up before resize");
+        let (item, line) = match self.pos {
+            FilePosition::Fine(item, line) => (item, line),
+            _ => panic!("coarse position reached move_up()"),
+        };
+
+        let mut item = item;
+        let mut line = line;
+        let mut remaining = distance;
+        while remaining > 0 {
+            let this = min(line, remaining);
+            remaining -= this;
+            line -= this;
+            if line == 0 {
+                if item <= self.contents.origin {
+                    break;
+                }
+                item -= 1;
+                line = self.ensure_item_rendered(item, w).len();
+            }
+        }
+        // FIXME: clip position at top of file
+        self.pos = FilePosition::Fine(item, line);
+        self.ensure_enough_rendered();
+    }
 }
 
 impl ActivityState for FeedFile {
     fn resize(&mut self, w: usize, h: usize) {
-        if self.last_width != Some(w) {
-            self.last_width = Some(w);
+        if self.last_size != Some((w, h)) {
+            self.last_size = Some((w, h));
             self.coarsen_pos();
             self.rendered.clear();
         }
-        let (item, line) = self.refine_pos(w);
-
-        let mut item = item;
-        let mut lines_rendered = line;
-        while item > self.contents.origin && lines_rendered + 1 < h {
-            item -= 1;
-            lines_rendered += self.ensure_item_rendered(item, w).len();
-        }
+        self.ensure_enough_rendered();
     }
 
     fn draw(&self, w: usize, h: usize)
             -> (Vec<ColouredString>, CursorPosition) {
-        assert_eq!(self.last_width, Some(w), "resize() should have done that");
+        assert_eq!(self.last_size, Some((w, h)),
+                   "last resize() inconsistent with draw()");
         let (start_item, start_line) = match self.pos {
             FilePosition::Fine(item, line) => (item, line),
             _ => panic!("coarse position reached draw()"),
@@ -156,11 +192,16 @@ impl ActivityState for FeedFile {
 
         let mut item = start_item;
         let mut lines = Vec::new();
+        let mut at_bottom = item == self.contents.last_index();
 
+        // Retrieve rendered lines from the bottom of the window upwards
         'outer: while item >= self.contents.origin && lines.len() + 1 < h {
             let rendered = self.rendered.get(&item)
                 .expect("unrendered item reached draw()");
             let line_limit = if item == start_item {
+                if start_line != rendered.len() {
+                    at_bottom = false;
+                }
                 start_line
             } else {
                 rendered.len()
@@ -180,13 +221,41 @@ impl ActivityState for FeedFile {
             lines.extend_from_slice(&BlankLine::render_static());
         }
 
-        (lines, CursorPosition::None) // FIXME: status line
+        let fs = FileStatusLine::new();
+        let fs = if at_bottom {
+            fs.add(Pr('-'), "Back", 99)
+        } else {
+            fs.add(Space, "Down", 99)
+        };
+        let fs = fs.add(Pr('q'), "Exit", 100)
+            .finalise();
+        // FIXME: document more keys
+        // FIXME: best-effort percentage calculation
+        lines.extend_from_slice(&fs.render(w));
+
+        (lines, CursorPosition::End)
     }
 
-    fn handle_keypress(&mut self, _key: OurKey, _client: &mut Client) ->
+    fn handle_keypress(&mut self, key: OurKey, _client: &mut Client) ->
         LogicalAction
     {
-        LogicalAction::Nothing // FIXME
+        let (_w, h) = match self.last_size {
+            Some(size) => size,
+            None => panic!("handle_keypress before resize"),
+        };
+
+        match key {
+            Pr('q') | Pr('Q') => LogicalAction::Pop,
+            Up => {
+                self.move_up(1);
+                LogicalAction::Nothing
+            },
+            Pr('-') | Pr('b') | Pr('B') | PgUp | Left => {
+                self.move_up(max(1, h - min(h, 3)));
+                LogicalAction::Nothing
+            },
+            _ => LogicalAction::Nothing,
+        }
     }
 }
 
index 18c4fe494bb6111ef1d0d702b82343698f277fec..b26d494b2b9158897c4dd0825de47a258ecc4542 100644 (file)
@@ -52,7 +52,7 @@ impl Menu {
         let status = if is_main {
             status.message("Select an option")
         } else {
-            status.add(OurKey::Return, "Back", 10)
+            status.add(Return, "Back", 10)
         };
 
         let mut menu = Menu {
@@ -63,7 +63,7 @@ impl Menu {
         };
 
         if !is_main {
-            menu.actions.insert(OurKey::Return, LogicalAction::Pop);
+            menu.actions.insert(Return, LogicalAction::Pop);
         }
 
         menu
index 1d46e5de195b48538e1b991153e024e6ab9d104d..04f8d6b28ee171b9290d61902ec800b7b8b87d14 100644 (file)
@@ -1096,6 +1096,7 @@ fn key_to_string(key: OurKey) -> String {
         OurKey::Backspace => "BS".to_string(), // likely won't come up
         OurKey::Return => "RET".to_string(),
         OurKey::Escape => "ESC".to_string(),
+        OurKey::Space => "SPACE".to_string(),
         OurKey::Up => "Up".to_string(),
         OurKey::Down => "Down".to_string(),
         OurKey::Left => "Left".to_string(),
index 1fab0f74eb26573b6c5210d0bcd1ccac894919c9..a595f7c1aa6bd758169ec0f61370451a93d1f62b 100644 (file)
@@ -141,7 +141,7 @@ pub enum OurKey {
     Pr(char),
     Ctrl(char),
     FunKey(u8),
-    Backspace, Return, Escape,
+    Backspace, Return, Escape, Space,
     Up, Down, Left, Right,
     PgUp, PgDn, Home, End, Ins, Del,
 }
@@ -253,6 +253,7 @@ impl Tui {
                     Some(OurKey::Ctrl('H')) => Some(OurKey::Backspace),
                     Some(OurKey::Ctrl('M')) => Some(OurKey::Return),
                     Some(OurKey::Ctrl('[')) => Some(OurKey::Escape),
+                    Some(OurKey::Pr(' ')) => Some(OurKey::Space),
                     other => other,
                 }
             },