mirror of
https://github.com/zaphar/sheetsui.git
synced 2025-07-23 13:29:48 -04:00
feat: The beginnings of a reactive table datamodel
This commit is contained in:
parent
3d2bfc79fb
commit
507fa630d5
7
Cargo.lock
generated
7
Cargo.lock
generated
@ -2,6 +2,12 @@
|
|||||||
# It is not intended for manual editing.
|
# It is not intended for manual editing.
|
||||||
version = 3
|
version = 3
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "a1"
|
||||||
|
version = "1.0.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b8470e27ace1e4691e30b3c05a719397c10e799d3ed1814dad8d6797580d9ddd"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "allocator-api2"
|
name = "allocator-api2"
|
||||||
version = "0.2.18"
|
version = "0.2.18"
|
||||||
@ -425,6 +431,7 @@ checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
|
|||||||
name = "sheetui"
|
name = "sheetui"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"a1",
|
||||||
"clap",
|
"clap",
|
||||||
"crossterm",
|
"crossterm",
|
||||||
"ratatui",
|
"ratatui",
|
||||||
|
@ -6,6 +6,7 @@ edition = "2021"
|
|||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
a1 = "1.0.1"
|
||||||
clap = { version = "4.5.20", features = ["derive"] }
|
clap = { version = "4.5.20", features = ["derive"] }
|
||||||
crossterm = "0.28.1"
|
crossterm = "0.28.1"
|
||||||
ratatui = "0.29.0"
|
ratatui = "0.29.0"
|
||||||
|
26
src/main.rs
26
src/main.rs
@ -5,9 +5,12 @@ use crossterm::event::{self, Event, KeyCode, KeyEventKind};
|
|||||||
use ratatui::{
|
use ratatui::{
|
||||||
self,
|
self,
|
||||||
layout::{Constraint, Layout},
|
layout::{Constraint, Layout},
|
||||||
widgets::{Cell, Row, Table, Tabs},
|
widgets::{Table, Tabs},
|
||||||
Frame,
|
Frame,
|
||||||
};
|
};
|
||||||
|
use sheet::{Address, Computable, Tbl};
|
||||||
|
|
||||||
|
mod sheet;
|
||||||
|
|
||||||
#[derive(Parser, Debug)]
|
#[derive(Parser, Debug)]
|
||||||
#[command(version, about, long_about = None)]
|
#[command(version, about, long_about = None)]
|
||||||
@ -27,20 +30,17 @@ fn run(terminal: &mut ratatui::DefaultTerminal) -> std::io::Result<()> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn generate_default_rows<'a>(row_count: usize, col_count: usize) -> Vec<Row<'a>> {
|
fn generate_default_table<'a>() -> Table<'a> {
|
||||||
(0..row_count)
|
let mut tbl = Tbl::new();
|
||||||
.map(|ri| {
|
tbl.update_entry(Address::new(5, 5), Computable::Text("loc: 5, 5".to_owned()));
|
||||||
(0..col_count)
|
tbl.update_entry(Address::new(10, 10), Computable::Number(10.10));
|
||||||
.map(|ci| Cell::new(format!(" {}:{} ", ri, ci)))
|
tbl.update_entry(Address::new(0, 0), Computable::Formula("".to_owned()));
|
||||||
.collect::<Row>()
|
tbl.into()
|
||||||
.height(2)
|
|
||||||
})
|
|
||||||
.collect::<Vec<Row>>()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn draw(frame: &mut Frame) {
|
fn draw(frame: &mut Frame) {
|
||||||
use Constraint::{Min, Percentage};
|
use Constraint::{Min, Percentage};
|
||||||
let table = Table::default().rows(generate_default_rows(10, 10));
|
let table = generate_default_table();
|
||||||
let tabs = Tabs::new(vec!["sheet1"]).select(0);
|
let tabs = Tabs::new(vec!["sheet1"]).select(0);
|
||||||
let rects = Layout::vertical([Min(1), Percentage(90)]).split(frame.area());
|
let rects = Layout::vertical([Min(1), Percentage(90)]).split(frame.area());
|
||||||
|
|
||||||
@ -49,8 +49,8 @@ fn draw(frame: &mut Frame) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn main() -> std::io::Result<()> {
|
fn main() -> std::io::Result<()> {
|
||||||
let args = Args::parse();
|
let _ = Args::parse();
|
||||||
|
|
||||||
let mut terminal = ratatui::init();
|
let mut terminal = ratatui::init();
|
||||||
terminal.clear()?;
|
terminal.clear()?;
|
||||||
let app_result = run(&mut terminal);
|
let app_result = run(&mut terminal);
|
||||||
|
94
src/sheet/mod.rs
Normal file
94
src/sheet/mod.rs
Normal file
@ -0,0 +1,94 @@
|
|||||||
|
//! DataModel for a SpreadSheet
|
||||||
|
//!
|
||||||
|
//! # Overview
|
||||||
|
//!
|
||||||
|
//! Sheets can contain a [Tbl]. Tbl's contain a collection of [Address] to [Computable]
|
||||||
|
//! associations. From this we can compute the dimensions of a Tbl as well as render
|
||||||
|
//! them into a [Table] Widget.
|
||||||
|
|
||||||
|
use ratatui::widgets::{Cell, Row, Table};
|
||||||
|
|
||||||
|
use std::collections::BTreeMap;
|
||||||
|
|
||||||
|
/// The Address in a [Tbl].
|
||||||
|
#[derive(Default, Debug, PartialEq, PartialOrd, Ord, Eq)]
|
||||||
|
pub struct Address {
|
||||||
|
row: usize,
|
||||||
|
col: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Address {
|
||||||
|
pub fn new(row: usize, col: usize) -> Self {
|
||||||
|
Self { row, col }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The computable value located at an [Address].
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum Computable {
|
||||||
|
Text(String),
|
||||||
|
Number(f64),
|
||||||
|
Formula(String),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for Computable {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self::Text("".to_owned())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A single table of addressable computable values.
|
||||||
|
#[derive(Default, Debug)]
|
||||||
|
pub struct Tbl {
|
||||||
|
addresses: BTreeMap<Address, Computable>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Tbl {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Self::default()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn dimensions(&self) -> (usize, usize) {
|
||||||
|
let (mut row, mut col) = (0, 0);
|
||||||
|
for (addr, _) in &self.addresses {
|
||||||
|
row = std::cmp::max(row, addr.row);
|
||||||
|
col = std::cmp::max(col, addr.col);
|
||||||
|
}
|
||||||
|
(row, col)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_computable(&self, row: usize, col: usize) -> Option<&Computable> {
|
||||||
|
self.addresses.get(&Address::new(row, col))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn update_entry(&mut self, address: Address, computable: Computable) {
|
||||||
|
// TODO(zaphar): At some point we'll need to store the graph of computation
|
||||||
|
// dependencies
|
||||||
|
self.addresses.insert(address, computable);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'t> From<Tbl> for Table<'t> {
|
||||||
|
fn from(value: Tbl) -> Self {
|
||||||
|
let (row, col) = value.dimensions();
|
||||||
|
let rows = (0..=row)
|
||||||
|
.map(|ri| {
|
||||||
|
(0..=col)
|
||||||
|
.map(|ci| {
|
||||||
|
match value.get_computable(ri, ci) {
|
||||||
|
// TODO(zaphar): Style information
|
||||||
|
Some(Computable::Text(s)) => Cell::new(format!(" {}", s)),
|
||||||
|
Some(Computable::Number(f)) => Cell::new(format!(" {}", f)),
|
||||||
|
Some(Computable::Formula(_expr)) => Cell::new(format!(" .formula. ")),
|
||||||
|
None => Cell::new(format!(" {}:{} ", ri, ci)),
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.collect::<Row>()
|
||||||
|
})
|
||||||
|
.collect::<Vec<Row>>();
|
||||||
|
Table::default().rows(rows)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests;
|
12
src/sheet/tests.rs
Normal file
12
src/sheet/tests.rs
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_dimensions_calculation() {
|
||||||
|
let mut tbl = Tbl::new();
|
||||||
|
tbl.update_entry(Address::new(0, 0), Computable::Text(String::new()));
|
||||||
|
assert_eq!((0, 0), tbl.dimensions());
|
||||||
|
tbl.update_entry(Address::new(0, 10), Computable::Text(String::new()));
|
||||||
|
assert_eq!((0, 10), tbl.dimensions());
|
||||||
|
tbl.update_entry(Address::new(20, 5), Computable::Text(String::new()));
|
||||||
|
assert_eq!((20, 10), tbl.dimensions());
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user