diff --git a/src/ui/mod.rs b/src/ui/mod.rs index 432ddd8..a67ec75 100644 --- a/src/ui/mod.rs +++ b/src/ui/mod.rs @@ -6,11 +6,10 @@ use crate::book::Book; use anyhow::Result; use crossterm::event::{self, Event, KeyCode, KeyEventKind, KeyModifiers}; use ratatui::{ - self, buffer::Buffer, layout::{Constraint, Flex, Layout, Rect}, style::{Modifier, Style}, - widgets::{Block, Table, TableState, Widget, WidgetRef}, + widgets::{Block, Widget}, }; use tui_prompts::{State, Status, TextPrompt, TextState}; use tui_textarea::{CursorMove, TextArea}; @@ -36,7 +35,6 @@ pub enum Modality { pub struct AppState<'ws> { pub modality_stack: Vec, pub viewport_state: ViewportState, - pub table_state: TableState, pub command_state: TextState<'ws>, dirty: bool, popup: Vec, @@ -46,7 +44,6 @@ impl<'ws> Default for AppState<'ws> { fn default() -> Self { AppState { modality_stack: vec![Modality::default()], - table_state: Default::default(), viewport_state: Default::default(), command_state: Default::default(), dirty: Default::default(), @@ -464,10 +461,7 @@ impl<'ws> Workspace<'ws> { Box::new(|rect: Rect, buf: &mut Buffer, ws: &mut Self| ws.text_area.render(rect, buf)), Box::new(move |rect: Rect, buf: &mut Buffer, ws: &mut Self| { let sheet_name = ws.book.get_sheet_name().unwrap_or("Unknown"); - // Table widget display let table_block = Block::bordered().title_top(sheet_name); - // TODO(zaphar): We should be smarter about calculating the location properly - // Might require viewport state to do properly let viewport = Viewport::new(&ws.book).with_selected(ws.book.location.clone()).block(table_block); StatefulWidget::render(viewport, rect, buf, &mut ws.state.viewport_state); }), diff --git a/src/ui/render/mod.rs b/src/ui/render/mod.rs index b669401..facf854 100644 --- a/src/ui/render/mod.rs +++ b/src/ui/render/mod.rs @@ -1,9 +1,8 @@ use ratatui::{ self, - layout::{Constraint, Flex, Rect}, - style::{Color, Stylize}, + layout::Rect, text::{Line, Text}, - widgets::{Block, Cell, Row, Table, Widget}, + widgets::{Block, Widget}, Frame, }; use tui_popup::Popup; @@ -56,71 +55,6 @@ impl<'widget, 'ws: 'widget> Widget for &'widget mut Workspace<'ws> { } } -const COLNAMES: [&'static str; 26] = [ - "A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", - "T", "U", "V", "W", "X", "Y", "Z", -]; - -// TODO(jwall): A Viewport widget where we assume lengths for column -// sizes would probably help us to manage column scrolling. -// Could use a ViewportState and stateful rendering. - -impl<'t, 'book: 't> TryFrom<&'book Book> for Table<'t> { - fn try_from(value: &'book Book) -> std::result::Result { - // TODO(zaphar): This is apparently expensive. Maybe we can cache it somehow? - // We should do the correct thing here if this fails - let (row_count, col_count) = value.get_size()?; - let rows: Vec = (1..=row_count) - .into_iter() - .map(|ri| { - let mut cells = vec![Cell::new(Text::from(ri.to_string()))]; - cells.extend((1..=col_count).into_iter().map(|ci| { - // TODO(zaphar): Is this safe? - let content = value - .get_cell_addr_rendered(&Address { row: ri, col: ci }) - .unwrap(); - let cell = Cell::new(Text::raw(content)); - match (value.location.row == ri, value.location.col == ci) { - (true, true) => cell.fg(Color::White).underlined(), - _ => cell - .bg(if ri % 2 == 0 { - Color::Rgb(57, 61, 71) - } else { - Color::Rgb(165, 169, 160) - }) - .fg(if ri % 2 == 0 { - Color::White - } else { - Color::Rgb(31, 32, 34) - }), - } - .bold() - })); - Row::new(cells) - }) - .collect(); - let mut constraints: Vec = Vec::new(); - constraints.push(Constraint::Max(5)); - for col_idx in 0..col_count { - let size = value.get_col_size(col_idx + 1)?; - constraints.push(Constraint::Length(size as u16)); - } - let mut header = Vec::with_capacity(col_count as usize); - - header.push(Cell::new("")); - header.extend((0..(col_count as usize)).map(|i| { - let count = (i / 26) + 1; - Cell::new(COLNAMES[i % 26].repeat(count)) - })); - Ok(Table::new(rows, constraints) - .header(Row::new(header).underlined()) - .column_spacing(1) - .flex(Flex::Start)) - } - - type Error = anyhow::Error; -} - pub fn draw(frame: &mut Frame, ws: &mut Workspace) { frame.render_widget(ws, frame.area()); } diff --git a/src/ui/render/test.rs b/src/ui/render/test.rs index f13b0fc..26dc987 100644 --- a/src/ui/render/test.rs +++ b/src/ui/render/test.rs @@ -1,6 +1,6 @@ use ironcalc::base::Model; -use super::{Address, Book, Viewport, ViewportState, COLNAMES}; +use super::{Address, Book, Viewport, ViewportState}; #[test] fn test_viewport_get_visible_columns() { @@ -13,3 +13,15 @@ fn test_viewport_get_visible_columns() { assert_eq!(5, cols.len()); assert_eq!(17, cols.last().expect("Failed to get last column").idx); } + +#[test] +fn test_viewport_get_visible_rows() { + let mut state = dbg!(ViewportState::default()); + let book = Book::new(Model::new_empty("test", "en", "America/New_York").expect("Failed to make model")); + let height = 6; + let viewport = Viewport::new(&book).with_selected(Address { row: 17, col: 1 }); + let rows = dbg!(viewport.get_visible_rows(height as u16, &mut state)); + assert_eq!(height - 1, rows.len()); + assert_eq!(17 - (height - 2), *rows.first().expect("Failed to get first row")); + assert_eq!(17, *rows.last().expect("Failed to get last row")); +} diff --git a/src/ui/render/viewport.rs b/src/ui/render/viewport.rs index e06bfdf..2e7823d 100644 --- a/src/ui/render/viewport.rs +++ b/src/ui/render/viewport.rs @@ -1,5 +1,3 @@ -use std::cmp::min; - use anyhow::Result; use ratatui::{ buffer::Buffer, @@ -33,15 +31,6 @@ impl<'a> From<&'a VisibleColumn> for Constraint { #[derive(Debug, Default)] pub struct ViewportState { prev_corner: Address, - -} - -impl ViewportState { - pub fn new(location: Address) -> Self { - Self { - prev_corner: location, - } - } } /// A renderable viewport over a book. @@ -70,6 +59,30 @@ impl<'book> Viewport<'book> { self } + pub(crate) fn get_visible_rows(&self, height: u16, state: &ViewportState) -> Vec { + // TODO(jeremy): For now the row default height is 1. We'll have + // to adjust that if this changes. + let mut length = 1; + let start_row = std::cmp::min(self.selected.row, state.prev_corner.row); + let mut start = start_row; + let mut end = start_row; + for row_idx in start_row..=LAST_ROW { + let updated_length = length + 1; + if updated_length <= height { + length = updated_length; + end = row_idx; + } else if self.selected.row >= row_idx { + start = start + 1; + end = row_idx; + } else { + //dbg!(&start); + //dbg!(&end); + break; + } + } + return (start..=end).collect(); + } + pub(crate) fn get_visible_columns( &self, width: u16, @@ -125,12 +138,15 @@ impl<'book> Viewport<'book> { state: &mut ViewportState, ) -> Result> { let visible_columns = self.get_visible_columns(width, state)?; + let visible_rows = self.get_visible_rows(height, state); if let Some(vc) = visible_columns.first() { state.prev_corner.col = vc.idx } - let max_row = min(state.prev_corner.row + height as usize, LAST_COLUMN); + if let Some(vr) = visible_rows.first() { + state.prev_corner.row = *vr; + } let rows: Vec = - (state.prev_corner.row..=max_row) + visible_rows .into_iter() .map(|ri| { let mut cells = vec![Cell::new(Text::from(ri.to_string()))]; @@ -189,7 +205,7 @@ impl<'book> StatefulWidget for Viewport<'book> { fn render(self, area: Rect, buf: &mut Buffer, state: &mut Self::State) { let mut table = self - .to_table(area.width, area.height, state) + .to_table(area.width - 2, area.height - 2, state) .expect("Failed to turn viewport into a table."); if let Some(block) = self.block { table = table.block(block);