From 937fe8f4d0ab820c338e8a4ae60094874f33820d Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Fri, 29 Dec 2023 16:59:20 +0000 Subject: [PATCH] More or less principled file-position tracking. We still can't actually _scroll_ through the file, because I haven't made any actual keystrokes work yet. But we begin displaying it at the bottom, and do something sensible on resizing. --- src/file.rs | 166 +++++++++++++++++++++++++++++++++++++++++++++------- src/tui.rs | 4 ++ 2 files changed, 150 insertions(+), 20 deletions(-) diff --git a/src/file.rs b/src/file.rs index da20f71..9360ccf 100644 --- a/src/file.rs +++ b/src/file.rs @@ -1,3 +1,5 @@ +use std::collections::HashMap; + use super::client::{Client, ClientError, FeedId, FeedExtend}; use super::coloured_string::ColouredString; use super::text::*; @@ -6,55 +8,179 @@ use super::tui::{ OurKey, }; -struct FeedFile { +#[derive(Debug, PartialEq, Eq, Clone, Copy)] +enum FilePosition { + Coarse(isize), // bottom of this item is at bottom of screen + Fine(isize, usize), // line #n of this item is just off bottom of screen +} + +struct FeedFileContents { id: FeedId, + origin: isize, items: Vec>, } +impl FeedFileContents { + fn update_items(&mut self, client: &mut Client) { + // FIXME: deal with origin, and with the feed having been + // extended since we last looked + + let feed = client.borrow_feed(&self.id); + let ids: Vec<_> = feed.ids.iter().map(|x| x.clone()).collect(); + self.origin = feed.origin; + std::mem::drop(feed); + + self.items.clear(); + for id in ids { + let st = client.status_by_id(&id) + .expect("Any id stored in a Feed should also be cached") + .clone(); + self.items.push(Box::new(StatusDisplay::new(st, client))); + } + } + + fn index_limit(&self) -> isize { + self.origin.checked_add_unsigned(self.items.len()) + .expect("Out-of-range index") + } + fn last_index(&self) -> isize { self.index_limit() -1 } + + fn phys_index(&self, index: isize) -> usize { + assert!(index >= self.origin, "Index before start"); + (index - self.origin) as usize + } + + fn get(&self, index: isize) -> &Box { + let index = self.phys_index(index); + &self.items[index] + } +} + +struct FeedFile { + contents: FeedFileContents, + rendered: HashMap>, + pos: FilePosition, + last_width: Option, +} + impl FeedFile { fn new(id: FeedId, client: &mut Client) -> Result { client.fetch_feed(&id, FeedExtend::Initial)?; - let mut ff = FeedFile { + let mut contents = FeedFileContents { id: id, + origin: 0, items: Vec::new(), }; - ff.update_items(client); + contents.update_items(client); + // FIXME: once we have an LDB, that's where initial pos comes from + let initial_pos = FilePosition::Coarse(contents.last_index() as isize); + + let ff = FeedFile { + contents: contents, + rendered: HashMap::new(), + pos: initial_pos, + last_width: None, + }; Ok(ff) } - fn update_items(&mut self, client: &mut Client) { - let ids: Vec<_> = client.borrow_feed(&self.id).ids - .iter().map(|x| x.clone()).collect(); - self.items.clear(); - for id in ids { - let st = client.status_by_id(&id) - .expect("Any id stored in a Feed should also be cached") - .clone(); - self.items.push(Box::new(StatusDisplay::new(st, client))); + fn ensure_item_rendered(&mut self, index: isize, w: usize) -> + &Vec + { + if !self.rendered.contains_key(&index) { + let mut lines = Vec::new(); + + for line in self.contents.get(index).render(w) { + for frag in line.split(w) { + lines.push(frag.to_owned()); + } + } + + self.rendered.insert(index, lines); } + + self.rendered.get(&index).expect("We just made sure this was present") + } + + fn refine_pos(&mut self, w: usize) -> (isize, usize) { + let (newpos, item, line) = match self.pos { + pos @ FilePosition::Fine(item, line) => (pos, item, line), + FilePosition::Coarse(item) => { + let rendered = self.ensure_item_rendered(item, w); + let line = rendered.len(); + (FilePosition::Fine(item, line), item, line) + } + }; + + self.pos = newpos; + (item, line) + } + + fn coarsen_pos(&mut self) { + let newpos = match self.pos { + pos @ FilePosition::Coarse(_) => pos, + FilePosition::Fine(item, _line) => FilePosition::Coarse(item), + }; + + self.pos = newpos; } } impl ActivityState for FeedFile { + fn resize(&mut self, w: usize, h: usize) { + if self.last_width != Some(w) { + self.last_width = Some(w); + 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(); + } + } + fn draw(&self, w: usize, h: usize) -> (Vec, CursorPosition) { + assert_eq!(self.last_width, Some(w), "resize() should have done that"); + let (start_item, start_line) = match self.pos { + FilePosition::Fine(item, line) => (item, line), + _ => panic!("coarse position reached draw()"), + }; + + let mut item = start_item; let mut lines = Vec::new(); - 'outer: for item in &self.items { - for line in item.render(w) { - for frag in line.split(w) { - lines.push(frag.to_owned()); - if lines.len() + 1 >= h { - break 'outer; - } + '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 { + start_line + } else { + rendered.len() + }; + + for i in (0..line_limit).rev() { + lines.push(rendered[i].clone()); + if lines.len() + 1 >= h { + break 'outer; } } + item -= 1; + } + lines.reverse(); + + while lines.len() + 1 < h { + lines.extend_from_slice(&BlankLine::render_static()); } - (lines, CursorPosition::None) // FIXME + (lines, CursorPosition::None) // FIXME: status line } fn handle_keypress(&mut self, _key: OurKey, _client: &mut Client) -> diff --git a/src/tui.rs b/src/tui.rs index 6813058..1fab0f7 100644 --- a/src/tui.rs +++ b/src/tui.rs @@ -451,5 +451,9 @@ impl TuiLogicalState { fn changed_activity(&mut self, client: &mut Client) { self.activity_state = new_activity_state( self.activity_stack.top(), client); + if let Some(area) = self.last_area { + self.activity_state.resize( + area.width as usize, area.height as usize); + } } } -- 2.30.2