mirror of
https://github.com/zaphar/sheetsui.git
synced 2025-07-22 21:09:48 -04:00
wip: Row scrolling
This commit is contained in:
parent
a22b51cdfd
commit
c564ce452d
@ -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<Modality>,
|
||||
pub viewport_state: ViewportState,
|
||||
pub table_state: TableState,
|
||||
pub command_state: TextState<'ws>,
|
||||
dirty: bool,
|
||||
popup: Vec<String>,
|
||||
@ -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);
|
||||
}),
|
||||
|
@ -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<Self, Self::Error> {
|
||||
// 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<Row> = (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<Constraint> = 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());
|
||||
}
|
||||
|
@ -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"));
|
||||
}
|
||||
|
@ -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<usize> {
|
||||
// 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<Table<'widget>> {
|
||||
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<Row> =
|
||||
(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);
|
||||
|
Loading…
x
Reference in New Issue
Block a user