+use std::collections::HashMap;
+
use super::client::{Client, ClientError, FeedId, FeedExtend};
use super::coloured_string::ColouredString;
use super::text::*;
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<Box<dyn TextFragment>>,
}
+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<dyn TextFragment> {
+ let index = self.phys_index(index);
+ &self.items[index]
+ }
+}
+
+struct FeedFile {
+ contents: FeedFileContents,
+ rendered: HashMap<isize, Vec<ColouredString>>,
+ pos: FilePosition,
+ last_width: Option<usize>,
+}
+
impl FeedFile {
fn new(id: FeedId, client: &mut Client) -> Result<Self, ClientError> {
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<ColouredString>
+ {
+ 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<ColouredString>, 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) ->