diff --git a/crates/apps/spreadsheet/spreadsheet-engine/src/data_source.rs b/crates/apps/spreadsheet/spreadsheet-engine/src/data_source.rs deleted file mode 100644 index 8ce90f0..0000000 --- a/crates/apps/spreadsheet/spreadsheet-engine/src/data_source.rs +++ /dev/null @@ -1,414 +0,0 @@ -//! A grid data-source abstraction: what a grid pulls visible cells from. -//! -//! The Makepad datagrid reference can render **one billion virtual cells** -//! (1,000,000 rows × 1,000 columns) by deriving each value procedurally -//! from `(row, col)` — nothing is stored, and a header click sorts all a -//! million rows by permuting an index rather than moving data. -//! -//! This module provides the same seam for our engine: [`GridDataSource`] -//! is the trait a grid renders from, [`SpreadsheetData`] implements it for -//! the real store, and [`VirtualSheet`] implements it procedurally. The -//! sort scales because it permutes a `Vec` of row indices. - -use crate::data::{SpreadsheetData, NUM_COLS, NUM_ROWS}; -use crate::util::write_col_letters; - -/// The rows/columns a virtual source presents by default: the reference's -/// headline "one billion cells". -pub const VIRTUAL_ROWS: u32 = 1_000_000; -pub const VIRTUAL_COLS: u32 = 1_000; - -/// What a grid renders from: a real store or a procedural source. -pub trait GridDataSource { - fn row_count(&self) -> u32; - fn col_count(&self) -> u32; - /// Display text for a cell at a *view* position (after any sort). - fn cell_display(&self, row: u32, col: u32) -> String; - /// Sort every row by `key_col`, ascending or descending. - fn sort_rows(&mut self, ascending: bool, key_col: u32); - /// The column header label (A1 letters by default). - fn col_label(&self, col: u32) -> String { - let mut buf = String::new(); - write_col_letters(col, &mut buf); - buf - } -} - -impl GridDataSource for SpreadsheetData { - fn row_count(&self) -> u32 { - NUM_ROWS - } - - fn col_count(&self) -> u32 { - NUM_COLS - } - - fn cell_display(&self, row: u32, col: u32) -> String { - self.get_display_value(row, col) - } - - fn sort_rows(&mut self, ascending: bool, key_col: u32) { - SpreadsheetData::sort_rows(self, ascending, key_col); - } -} - -/// A procedural, storage-free data source: values are hashed from -/// `(row, col)`, so a billion cells cost one struct. Sorting permutes a row -/// index; the cell text and the numeric sort key derive from the same hash, -/// so sorting by the key sorts what the user sees. -pub struct VirtualSheet { - rows: u32, - cols: u32, - /// View row → data row. `None` until the first sort (identity order). - perm: Option>, - /// The active sort: `(key column, ascending)`. - sort: Option<(u32, bool)>, -} - -/// The reference's 64-bit mix for `(row, col)`. -fn hash2(row: u64, col: u64) -> u64 { - let mut x = row - .wrapping_mul(0x9e37_79b9_7f4a_7c15) - .wrapping_add(col.wrapping_mul(0xbf58_476d_1ce4_e5b9)) - .wrapping_add(0x94d0_49bb_1331_11eb); - x ^= x >> 30; - x = x.wrapping_mul(0xbf58_476d_1ce4_e5b9); - x ^= x >> 27; - x = x.wrapping_mul(0x94d0_49bb_1331_11eb); - x ^ (x >> 31) -} - -const FIRST: [&str; 12] = [ - "Ada", "Linus", "Grace", "Alan", "Edsger", "Barbara", "Donald", "Margaret", "Ken", "Dennis", - "Radia", "Vint", -]; -const LAST: [&str; 12] = [ - "Hopper", "Kay", "Lovelace", "Turing", "Dijkstra", "Liskov", "Knuth", "Hamilton", "Thompson", - "Ritchie", "Perlman", "Cerf", -]; -const CITIES: [&str; 10] = [ - "Amsterdam", - "Tokyo", - "Berlin", - "Lisbon", - "Oslo", - "Seoul", - "Toronto", - "Austin", - "Zurich", - "Kyoto", -]; - -impl VirtualSheet { - pub fn new(rows: u32, cols: u32) -> Self { - Self { - rows, - cols, - perm: None, - sort: None, - } - } - - /// The data row behind a view row (the identity until sorted). - pub fn data_row(&self, view_row: u32) -> u32 { - match &self.perm { - Some(perm) => perm[view_row as usize], - None => view_row, - } - } - - /// Whether the sheet has been sorted (i.e. `perm` is non-trivial). - pub fn is_sorted(&self) -> bool { - self.perm.is_some() - } - - /// The active sort, if any: `(key column, ascending)`. - pub fn sort_state(&self) -> Option<(u32, bool)> { - self.sort - } - - /// Clear the active sort, restoring the identity row order (the "off" - /// state of a header sort toggle). - pub fn reset_sort(&mut self) { - self.perm = None; - self.sort = None; - } - - /// The numeric sort key for a *data* cell, derived from the same hash - /// that renders the text. - pub fn value_num(&self, row: u32, col: u32) -> f64 { - let h = hash2(row as u64, col as u64); - match col % 6 { - 0 => row as f64, - 1 => (h % (FIRST.len() * LAST.len()) as u64) as f64, - 2 => (h % CITIES.len() as u64) as f64, - 3 => ((h % 2_000_000) as f64 / 100.0) - 10000.0, - 4 => (h % 1000) as f64 / 10.0, - _ => (h % 2) as f64, - } - } - - /// The rendered text for a *data* cell. - pub fn value_text(&self, row: u32, col: u32) -> String { - let h = hash2(row as u64, col as u64); - match col % 6 { - 0 => format!("{}", row), - 1 => { - let i = (h % (FIRST.len() * LAST.len()) as u64) as usize; - format!("{} {}", FIRST[i % FIRST.len()], LAST[i / FIRST.len()]) - } - 2 => CITIES[(h % CITIES.len() as u64) as usize].to_string(), - 3 => { - let v = ((h % 2_000_000) as f64 / 100.0) - 10000.0; - format!("{:.2}", v) - } - 4 => format!("{:.1}%", (h % 1000) as f64 / 10.0), - _ => { - if h.is_multiple_of(2) { - "yes".to_string() - } else { - "no".to_string() - } - } - } - } - - /// The header label for a column: named for the first six, then - /// `Name·1`, `City·1`, … as the reference does. - pub fn col_label(&self, col: u32) -> String { - let base = ["#", "Name", "City", "Balance", "Score", "Active"]; - if col < 6 { - base[col as usize].to_string() - } else { - format!("{}·{}", base[(col % 6) as usize], col / 6) - } - } -} - -impl GridDataSource for VirtualSheet { - fn row_count(&self) -> u32 { - self.rows - } - - fn col_count(&self) -> u32 { - self.cols - } - - fn cell_display(&self, row: u32, col: u32) -> String { - self.value_text(self.data_row(row), col) - } - - /// Sort every row by `key_col`, permuting the row index. The keys are - /// computed in the comparator like the reference; the elapsed time is - /// reported by the caller (the grid's status bar), not here. - fn sort_rows(&mut self, ascending: bool, key_col: u32) { - let mut perm: Vec = (0..self.rows).collect(); - perm.sort_unstable_by(|&a, &b| { - let ka = self.value_num(a, key_col); - let kb = self.value_num(b, key_col); - let ord = ka.partial_cmp(&kb).unwrap_or(std::cmp::Ordering::Equal); - let ord = if ascending { ord } else { ord.reverse() }; - // Deterministic tie-break by data row, independent of direction. - ord.then(a.cmp(&b)) - }); - self.perm = Some(perm); - self.sort = Some((key_col, ascending)); - } - - fn col_label(&self, col: u32) -> String { - VirtualSheet::col_label(self, col) - } -} - -#[cfg(test)] -mod tests { - use super::*; - - /// The hash is deterministic: the same `(row, col)` always yields the - /// same value, so cells and sort keys never change between frames. - #[test] - fn values_are_deterministic() { - let a = VirtualSheet::new(1000, 6); - let b = VirtualSheet::new(1000, 6); - for row in [0u32, 1, 42, 999] { - for col in 0..6u32 { - assert_eq!(a.value_text(row, col), b.value_text(row, col)); - assert_eq!(a.value_num(row, col), b.value_num(row, col)); - } - } - } - - /// Column 0 is the row index: `#` renders the row number and sorts by - /// it. - #[test] - fn column_zero_is_the_row_index() { - let s = VirtualSheet::new(10, 6); - assert_eq!(s.value_text(7, 0), "7"); - assert_eq!(s.value_num(7, 0), 7.0); - } - - /// The sort key for a column orders the rendered text: sorting by the - /// key sorts what the user sees. - #[test] - fn sort_key_matches_the_rendered_balance_text() { - let s = VirtualSheet::new(1000, 6); - for row in 0..100u32 { - let text = s.value_text(row, 3); - let num = s.value_num(row, 3); - // Balance text is the numeric value with two decimals. - assert_eq!(text, format!("{:.2}", num), "row {row} balance"); - } - } - - /// After an ascending sort, the key column is non-decreasing along the - /// permutation, and the permutation visits every row exactly once. - #[test] - fn ascending_sort_orders_the_key_column() { - let mut s = VirtualSheet::new(10_000, 6); - s.sort_rows(true, 3); - let perm = s.perm.as_ref().unwrap(); - assert_eq!(perm.len(), 10_000); - - let mut seen = vec![false; 10_000]; - let mut prev = f64::NEG_INFINITY; - for &data_row in perm.iter() { - seen[data_row as usize] = true; - let v = s.value_num(data_row, 3); - assert!(v >= prev, "balance {v} below previous {prev}"); - prev = v; - } - assert!(seen.iter().all(|&b| b), "permutation must be complete"); - } - - /// Descending sorts the same column the other way. - #[test] - fn descending_sort_reverses_the_order() { - let mut s = VirtualSheet::new(10_000, 6); - s.sort_rows(false, 3); - let perm = s.perm.as_ref().unwrap(); - let mut prev = f64::INFINITY; - for &data_row in perm.iter() { - let v = s.value_num(data_row, 3); - assert!(v <= prev); - prev = v; - } - } - - /// Sorting by column 0 is sorting by the row index (ascending is the - /// identity order). - #[test] - fn sorting_by_column_zero_is_by_row_index() { - let mut s = VirtualSheet::new(1_000, 6); - s.sort_rows(true, 0); - let perm = s.perm.as_ref().unwrap(); - assert_eq!(perm[0], 0); - assert_eq!(perm[1], 1); - assert_eq!(perm[999], 999); - - s.sort_rows(false, 0); - let perm = s.perm.as_ref().unwrap(); - assert_eq!(perm[0], 999); - assert_eq!(perm[999], 0); - } - - /// Before any sort, view rows map to themselves; after one, `is_sorted` - /// and `sort_state` report it. - #[test] - fn sort_state_and_identity_before_sorting() { - let s = VirtualSheet::new(10, 6); - assert!(!s.is_sorted()); - assert_eq!(s.sort_state(), None); - assert_eq!(s.data_row(7), 7); - - let mut s = VirtualSheet::new(10, 6); - s.sort_rows(true, 4); - assert!(s.is_sorted()); - assert_eq!(s.sort_state(), Some((4, true))); - // `cell_display` now routes through the permutation. - let perm = s.perm.clone().unwrap(); - assert_eq!(s.data_row(0), perm[0]); - - // The "off" state of a header toggle restores identity order. - s.reset_sort(); - assert!(!s.is_sorted()); - assert_eq!(s.sort_state(), None); - assert_eq!(s.data_row(7), 7); - } - - /// Column labels: the six base names, then suffixed repeats. - #[test] - fn column_labels_cycle_the_base_names() { - let s = VirtualSheet::new(10, 100); - assert_eq!(s.col_label(0), "#"); - assert_eq!(s.col_label(1), "Name"); - assert_eq!(s.col_label(5), "Active"); - assert_eq!(s.col_label(6), "#·1"); - assert_eq!(s.col_label(7), "Name·1"); - assert_eq!(s.col_label(13), "Name·2"); - } - - /// The `SpreadsheetData` implementor delegates to the real store: cell - /// display is the formatted value and sorting is the physical sort. - #[test] - fn spreadsheet_data_implements_the_trait() { - let mut sheet = SpreadsheetData::default(); - sheet.set_cell(0, 0, "3"); - sheet.set_cell(1, 0, "1"); - sheet.set_cell(2, 0, "2"); - - assert_eq!(sheet.row_count(), NUM_ROWS); - assert_eq!(sheet.col_count(), NUM_COLS); - assert_eq!(sheet.cell_display(0, 0), "3"); - - GridDataSource::sort_rows(&mut sheet, true, 0); - assert_eq!(sheet.get_raw(0, 0), "1"); - assert_eq!(sheet.get_raw(2, 0), "3"); - - // The default column label is A1 letters. - assert_eq!(sheet.col_label(0), "A"); - assert_eq!(sheet.col_label(25), "Z"); - assert_eq!(sheet.col_label(26), "AA"); - } - - /// A virtual sheet with the default "billion cells" extent reports it. - #[test] - fn default_extent_is_one_billion_cells() { - let s = VirtualSheet::new(VIRTUAL_ROWS, VIRTUAL_COLS); - assert_eq!(s.row_count(), 1_000_000); - assert_eq!(s.col_count(), 1_000); - // A billion cells render from zero stored data. - assert!(!s.value_text(999_999, 999).is_empty()); - } - - /// `VirtualSheet` implements the trait too, so a grid can pull cells - /// through the same `GridDataSource` seam for a real or a virtual - /// source. - #[test] - fn virtual_sheet_implements_the_trait() { - let mut s = VirtualSheet::new(1_000, 6); - assert_eq!(s.row_count(), 1_000); - assert_eq!(s.col_count(), 6); - assert_eq!(GridDataSource::cell_display(&s, 0, 0), "0"); - assert_eq!(GridDataSource::col_label(&s, 7), "Name·1"); - - GridDataSource::sort_rows(&mut s, false, 3); - assert!(s.is_sorted()); - } - - /// Value text for the Balance column is negative for exactly the rows - /// whose numeric key is negative (sign consistency for the red/green - /// tint the grid applies). - #[test] - fn balance_sign_matches_the_text_prefix() { - let s = VirtualSheet::new(10_000, 6); - for row in 0..1_000u32 { - let num = s.value_num(row, 3); - let text = s.value_text(row, 3); - assert_eq!( - text.starts_with('-'), - num < 0.0, - "row {row}: text {text} vs key {num}" - ); - } - } -} diff --git a/crates/apps/spreadsheet/spreadsheet-engine/src/lib.rs b/crates/apps/spreadsheet/spreadsheet-engine/src/lib.rs index 8b9304c..7b9e764 100644 --- a/crates/apps/spreadsheet/spreadsheet-engine/src/lib.rs +++ b/crates/apps/spreadsheet/spreadsheet-engine/src/lib.rs @@ -16,7 +16,6 @@ pub mod autofill; pub mod data; -pub mod data_source; pub mod dates; pub mod formula2; pub mod persistence; diff --git a/crates/apps/spreadsheet/spreadsheet-ui/src/lib.rs b/crates/apps/spreadsheet/spreadsheet-ui/src/lib.rs index 0ae5178..27e9922 100644 --- a/crates/apps/spreadsheet/spreadsheet-ui/src/lib.rs +++ b/crates/apps/spreadsheet/spreadsheet-ui/src/lib.rs @@ -26,7 +26,6 @@ pub mod sort_state; pub mod sparkline; pub mod text_measure; pub mod trend_chart; -pub mod virtual_grid; pub mod workspace; pub mod zoom; @@ -44,6 +43,5 @@ use makepad_widgets::ScriptVm; pub fn script_mod(vm: &mut ScriptVm) { grid::script_mod(vm); trend_chart::script_mod(vm); - virtual_grid::script_mod(vm); workspace::script_mod(vm); } diff --git a/crates/apps/spreadsheet/spreadsheet-ui/src/virtual_grid.rs b/crates/apps/spreadsheet/spreadsheet-ui/src/virtual_grid.rs deleted file mode 100644 index 86b7b62..0000000 --- a/crates/apps/spreadsheet/spreadsheet-ui/src/virtual_grid.rs +++ /dev/null @@ -1,411 +0,0 @@ -//! `VirtualGrid` widget: a virtualized grid over a procedural source. -//! -//! Renders one billion virtual cells (1,000,000 rows × 1,000 columns) with -//! nothing stored: only the visible cells are drawn, values derive from a -//! hashed `(row, col)`, and a column-header click sorts the full million -//! rows by permuting an index. This is the reference datagrid's "Big Data" -//! tab, built on `VirtualSheet` (engine) + `GridMetrics`/`TextMeasureCache` -//! (measured UI modules). -//! -//! The file carries the `script_mod!` DSL and is excluded from coverage -//! (like `grid.rs` and `trend_chart.rs`); the logic it delegates to lives -//! in the engine's `data_source.rs` and the measured geometry modules. - -use makepad_widgets::*; - -use crate::geometry::GridMetrics; -use crate::text_measure::TextMeasureCache; -use spreadsheet_engine::data_source::{GridDataSource, VirtualSheet, VIRTUAL_COLS, VIRTUAL_ROWS}; - -/// Status strip height, in pixels. -const STATUS_H: f64 = 22.0; - -/// In-progress drag state. -enum VDrag { - None, - Panning { last: DVec2 }, - HeaderPending { col: u32, down: DVec2 }, -} - -#[derive(Script, ScriptHook, Widget)] -pub struct VirtualGrid { - #[uid] - uid: WidgetUid, - #[source] - source: ScriptObjectRef, - #[walk] - walk: Walk, - #[layout] - layout: Layout, - #[redraw] - #[live] - draw_bg: DrawColor, - #[live] - draw_header_bg: DrawColor, - #[live] - draw_cell_bg: DrawColor, - #[live] - draw_grid_line: DrawColor, - #[live] - draw_text: DrawText, - #[live] - draw_header_text: DrawText, - #[live] - pub header_bg_color: Vec4f, - #[live] - pub header_text_color: Vec4f, - #[live] - pub cell_bg_color: Vec4f, - #[live] - pub cell_alt_bg_color: Vec4f, - #[live] - pub grid_line_color: Vec4f, - #[live] - pub text_color: Vec4f, - #[live] - pub pos_text_color: Vec4f, - #[live] - pub neg_text_color: Vec4f, - #[live(110.0)] - pub col_width: f32, - #[live(24.0)] - pub row_height: f32, - #[live(76.0)] - pub row_header_width: f32, - #[live(28.0)] - pub col_header_height: f32, - #[rust(VirtualSheet::new(VIRTUAL_ROWS, VIRTUAL_COLS))] - sheet: VirtualSheet, - #[rust] - rect: Rect, - #[rust] - scroll_row: f64, - #[rust] - scroll_col: f64, - #[rust(VDrag::None)] - drag: VDrag, - /// `(key column, ascending)`, mirroring `sheet.sort_state()`. - #[rust] - sort_state: Option<(u32, bool)>, - /// "sorted 1,000,000 rows by \"X\" ascending in N ms". - #[rust] - sort_report: String, - #[rust] - measure: TextMeasureCache, - #[rust] - last_touch: Option, -} - -impl VirtualGrid { - fn metrics(&self) -> GridMetrics { - GridMetrics { - origin: (self.rect.pos.x, self.rect.pos.y), - row_header_width: self.row_header_width as f64, - col_header_height: self.col_header_height as f64, - default_col_width: self.col_width as f64, - default_row_height: self.row_height as f64, - col_widths: Vec::new(), - row_heights: Vec::new(), - frozen_cols: 0, - frozen_rows: 0, - scroll_col: self.scroll_col, - scroll_row: self.scroll_row, - num_cols: self.sheet.col_count(), - num_rows: self.sheet.row_count(), - } - } - - fn toggle_sort(&mut self, col: u32) { - let t0 = std::time::Instant::now(); - self.sort_state = match self.sort_state { - Some((c, true)) if c == col => { - self.sheet.sort_rows(false, col); - Some((col, false)) - } - Some((c, false)) if c == col => { - self.sheet.reset_sort(); - None - } - _ => { - self.sheet.sort_rows(true, col); - Some((col, true)) - } - }; - let ms = t0.elapsed().as_millis(); - self.sort_report = match self.sort_state { - Some((c, asc)) => format!( - "sorted {} rows by \"{}\" {} in {} ms", - self.sheet.row_count(), - self.sheet.col_label(c), - if asc { "ascending" } else { "descending" }, - ms - ), - None => "sort cleared — identity order".to_string(), - }; - } - - fn scroll_by(&mut self, cx: &mut Cx, delta: DVec2) { - let cell_w = self.col_width as f64; - let cell_h = self.row_height as f64; - let body_w = (self.rect.size.x - self.row_header_width as f64).max(1.0); - let body_h = (self.rect.size.y - self.col_header_height as f64 - STATUS_H).max(1.0); - let vis_cols = (body_w / cell_w).max(1.0); - let vis_rows = (body_h / cell_h).max(1.0); - let max_scroll_col = (self.sheet.col_count() as f64 - vis_cols).max(0.0); - let max_scroll_row = (self.sheet.row_count() as f64 - vis_rows).max(0.0); - self.scroll_row = (self.scroll_row + delta.y / cell_h).clamp(0.0, max_scroll_row); - self.scroll_col = (self.scroll_col + delta.x / cell_w).clamp(0.0, max_scroll_col); - self.redraw(cx); - } -} - -impl Widget for VirtualGrid { - fn handle_event(&mut self, cx: &mut Cx, event: &Event, _scope: &mut Scope) { - match event.hits_with_capture_overload(cx, self.draw_bg.area(), true) { - Hit::FingerDown(fe) if fe.is_primary_hit() => { - self.last_touch = Some(fe.abs); - cx.set_key_focus(self.draw_bg.area()); - if fe.abs.y < self.rect.pos.y + self.col_header_height as f64 { - if let Some((col, _)) = self.metrics().col_at_x(fe.abs.x) { - self.drag = VDrag::HeaderPending { col, down: fe.abs }; - } - } else { - self.drag = VDrag::Panning { last: fe.abs }; - } - } - Hit::FingerHoverIn(fe) | Hit::FingerHoverOver(fe) => { - if fe.abs.y < self.rect.pos.y + self.col_header_height as f64 - && fe.abs.x >= self.rect.pos.x + self.row_header_width as f64 - { - cx.set_cursor(MouseCursor::Grab); - } else { - cx.set_cursor(MouseCursor::Default); - } - } - Hit::FingerMove(fe) => match self.drag { - VDrag::Panning { last } => { - let delta = last - fe.abs; - self.scroll_by(cx, delta); - self.drag = VDrag::Panning { last: fe.abs }; - } - VDrag::HeaderPending { col, down } => { - if (fe.abs - down).length() > 8.0 { - // Became a pan rather than a click. - self.drag = VDrag::Panning { last: fe.abs }; - } else { - self.drag = VDrag::HeaderPending { col, down }; - } - } - VDrag::None => { - if let Some(start) = self.last_touch { - if (fe.abs - start).length() > 8.0 { - self.drag = VDrag::Panning { last: fe.abs }; - } - } - } - }, - Hit::FingerUp(_) => { - if let VDrag::HeaderPending { col, .. } = self.drag { - self.toggle_sort(col); - self.redraw(cx); - } - self.drag = VDrag::None; - self.last_touch = None; - } - Hit::FingerScroll(fs) => { - // A wheel tick scrolls rows vertically or columns - // horizontally, matching the grid's own wheel handling. - if fs.scroll.y.abs() > f64::EPSILON { - self.scroll_by(cx, dvec2(0.0, fs.scroll.y)); - } else { - self.scroll_by(cx, dvec2(fs.scroll.x, 0.0)); - } - } - _ => {} - } - } - - fn draw_walk(&mut self, cx: &mut Cx2d, _scope: &mut Scope, walk: Walk) -> DrawStep { - self.rect = cx.walk_turtle(walk); - self.draw_bg.draw_abs(cx, self.rect); - - let m = self.metrics(); - let cell_w = self.col_width as f64; - let cell_h = self.row_height as f64; - let header_h = self.col_header_height as f64; - let row_hdr_w = self.row_header_width as f64; - - let body_top = self.rect.pos.y + header_h; - let body_left = self.rect.pos.x + row_hdr_w; - let body_w = (self.rect.size.x - row_hdr_w).max(1.0); - let body_h = (self.rect.size.y - header_h - STATUS_H).max(1.0); - - let first_col = m.first_visible_col(); - let first_row = m.first_visible_row(); - let vis_cols = (body_w / cell_w).ceil() as u32 + 1; - let vis_rows = (body_h / cell_h).ceil() as u32 + 1; - let last_col = (first_col + vis_cols).min(m.num_cols); - let last_row = (first_row + vis_rows).min(m.num_rows); - let sub_col_px = m.scroll_col.fract() * cell_w; - let sub_row_px = m.scroll_row.fract() * cell_h; - - // Cells, zebra-striped, only the visible range. - for row in first_row..last_row { - let y = body_top + (row - first_row) as f64 * cell_h - sub_row_px; - for col in first_col..last_col { - let x = body_left + (col - first_col) as f64 * cell_w - sub_col_px; - let cell_rect = Rect { - pos: dvec2(x, y), - size: dvec2(cell_w, cell_h), - }; - self.draw_cell_bg.color = if row % 2 == 1 { - self.cell_alt_bg_color - } else { - self.cell_bg_color - }; - self.draw_cell_bg.draw_abs(cx, cell_rect); - - let text = self.sheet.cell_display(row, col); - if !text.is_empty() { - let kind = col % 6; - self.draw_text.color = if kind == 3 { - if text.starts_with('-') { - self.neg_text_color - } else { - self.pos_text_color - } - } else if kind == 0 { - self.header_text_color - } else { - self.text_color - }; - let w = self.measure.measure(&text, false); - let tx = match kind { - 0 | 3 | 4 => x + cell_w - 8.0 - w, - _ => x + 6.0, - }; - let ty = y + (cell_h - 14.0) * 0.5; - self.draw_text.draw_abs(cx, dvec2(tx, ty), &text); - } - } - } - - // Gridlines: a right edge per column and a bottom edge per row in - // the visible range. - self.draw_grid_line.color = self.grid_line_color; - for col in first_col..=last_col { - let x = body_left + (col - first_col) as f64 * cell_w - sub_col_px; - self.draw_grid_line.draw_abs( - cx, - Rect { - pos: dvec2(x + cell_w - 1.0, body_top), - size: dvec2(1.0, body_h), - }, - ); - } - for row in first_row..=last_row { - let y = body_top + (row - first_row) as f64 * cell_h - sub_row_px; - self.draw_grid_line.draw_abs( - cx, - Rect { - pos: dvec2(body_left, y + cell_h - 1.0), - size: dvec2(body_w, 1.0), - }, - ); - } - - // Column headers, with the sort glyph on the active column. - self.draw_header_bg.color = self.header_bg_color; - self.draw_header_bg.draw_abs( - cx, - Rect { - pos: self.rect.pos, - size: dvec2(self.rect.size.x, header_h), - }, - ); - self.draw_header_text.color = self.header_text_color; - for col in first_col..last_col { - let x = body_left + (col - first_col) as f64 * cell_w - sub_col_px; - let mut label = self.sheet.col_label(col); - if let Some((sc, asc)) = self.sort_state { - if sc == col { - label.push_str(if asc { " ▲" } else { " ▼" }); - } - } - self.draw_header_text.draw_abs( - cx, - dvec2(x + 6.0, self.rect.pos.y + (header_h - 14.0) * 0.5), - &label, - ); - } - - // Row headers show the data-row number (1-based), so the - // permutation is visible after a sort. - self.draw_header_bg.draw_abs( - cx, - Rect { - pos: dvec2(self.rect.pos.x, body_top), - size: dvec2(row_hdr_w, body_h), - }, - ); - for row in first_row..last_row { - let y = body_top + (row - first_row) as f64 * cell_h - sub_row_px; - let data = self.sheet.data_row(row); - let label = format!("{}", data + 1); - let tx = self.rect.pos.x + row_hdr_w - 8.0 - label.len() as f64 * 6.0; - self.draw_header_text - .draw_abs(cx, dvec2(tx, y + (cell_h - 14.0) * 0.5), &label); - } - - // Status strip: extents, visible-cell count, and the sort report. - let drawn_cols = last_col - first_col; - let drawn_rows = last_row - first_row; - let status = format!( - "{} rows × {} columns = {} virtual cells · drawing {}×{} = {} cells{}", - self.sheet.row_count(), - self.sheet.col_count(), - self.sheet.row_count() as u64 * self.sheet.col_count() as u64, - drawn_rows, - drawn_cols, - drawn_rows * drawn_cols, - if self.sort_report.is_empty() { - String::new() - } else { - format!(" · {}", self.sort_report) - }, - ); - self.draw_header_text.color = self.header_text_color; - self.draw_header_text.draw_abs( - cx, - dvec2( - self.rect.pos.x + 10.0, - self.rect.pos.y + self.rect.size.y - STATUS_H + 4.0, - ), - &status, - ); - - DrawStep::done() - } -} - -// The script_mod! macro generates a `script_mod` function. -// We wrap it in `pub fn` so `lib.rs` can call it. -script_mod! { - use mod.prelude.widgets.* - - mod.widgets.VirtualGrid = #(VirtualGrid::register_widget(vm)) { - width: Fill, height: Fill - col_width: 110.0, row_height: 24.0, row_header_width: 76.0, col_header_height: 28.0 - header_bg_color: #x242438, header_text_color: #x8a8aa5 - cell_bg_color: #x1a1a2e, cell_alt_bg_color: #x1e1e30 - grid_line_color: #x2a2a3e - text_color: #xd8d8e8, pos_text_color: #x81c995, neg_text_color: #xef6a6a - - draw_bg +: { draw_depth: 0.0 color: #x1a1a2e } - draw_header_bg +: { draw_depth: 0.1 } - draw_cell_bg +: { draw_depth: 0.15 } - draw_grid_line +: { draw_depth: 0.2 } - draw_text +: { draw_depth: 0.3 color: #xd8d8e8 text_style: theme.font_regular } - draw_header_text +: { draw_depth: 0.3 color: #x8a8aa5 text_style: theme.font_bold } - } -} diff --git a/crates/apps/spreadsheet/spreadsheet-ui/src/workspace.rs b/crates/apps/spreadsheet/spreadsheet-ui/src/workspace.rs index 1058bfa..966ca5c 100644 --- a/crates/apps/spreadsheet/spreadsheet-ui/src/workspace.rs +++ b/crates/apps/spreadsheet/spreadsheet-ui/src/workspace.rs @@ -81,10 +81,6 @@ pub struct SpreadsheetWorkspace { /// 0.25s ticker driving the market. #[rust] timer: Option, - /// Whether the "Big Data" virtual grid is shown instead of the - /// spreadsheet grid. - #[rust] - big_mode: bool, } impl Widget for SpreadsheetWorkspace { @@ -755,19 +751,6 @@ impl WidgetMatchEvent for SpreadsheetWorkspace { } } - // Big Data view: swap the spreadsheet grid for the virtual grid. - if self.button(cx, ids!(bigdata_btn)).clicked(actions) { - self.commit_formula_bar_if_dirty(cx); - self.big_mode = !self.big_mode; - if let Some(mut v) = self.widget(cx, ids!(grid_view)).borrow_mut::() { - v.set_visible(cx, !self.big_mode); - } - if let Some(mut v) = self.widget(cx, ids!(big_view)).borrow_mut::() { - v.set_visible(cx, self.big_mode); - } - self.view.redraw(cx); - } - if self.button(cx, ids!(undo_btn)).clicked(actions) { self.commit_formula_bar_if_dirty(cx); if self.model.borrow_mut().workbook_mut().undo() { @@ -906,8 +889,6 @@ script_mod! { zoom_out_btn := Button { text: "-", width: 28.0, draw_bg +: { color: #x313244 }, draw_text +: { color: #xd8d8e8, text_style +: { font_size: 12.0 } } } zoom_reset_btn := Button { text: "100%", width: 44.0, draw_bg +: { color: #x313244 }, draw_text +: { color: #xd8d8e8, text_style +: { font_size: 11.0 } } } zoom_in_btn := Button { text: "+", width: 28.0, draw_bg +: { color: #x313244 }, draw_text +: { color: #xd8d8e8, text_style +: { font_size: 12.0 } } } - separator6 := View { width: 1.0, height: 26.0, draw_bg +: { color: #x3a3a4e } } - bigdata_btn := Button { text: "BigData", width: 60.0, draw_bg +: { color: #x313244 }, draw_text +: { color: #x4fc3f7, text_style +: { font_size: 11.0 } } } spacer := View { width: Fill } status_label := Label { text: "Ready | Workbook Save & Undo Stack Active", draw_text +: { color: #8a8aa5, text_style +: { font_size: 10.5 } } } @@ -932,16 +913,8 @@ script_mod! { } grid_container := View { - width: Fill, height: Fill, flow: Overlay - - grid_view := View { - width: Fill, height: Fill - grid := mod.widgets.SpreadsheetGrid { width: Fill, height: Fill } - } - big_view := View { - width: Fill, height: Fill, visible: false - big_grid := mod.widgets.VirtualGrid { width: Fill, height: Fill } - } + width: Fill, height: Fill + grid := mod.widgets.SpreadsheetGrid { width: Fill, height: Fill } } // Live chart panel: line + candlesticks for the selected row. diff --git a/tools/test-spreadsheet-coverage.sh b/tools/test-spreadsheet-coverage.sh index 8c3d05a..53561dc 100755 --- a/tools/test-spreadsheet-coverage.sh +++ b/tools/test-spreadsheet-coverage.sh @@ -126,7 +126,7 @@ IGNORE_COMMON='(/cargo/registry|/cargo/git|/rustc/)' # grid.rs, ui.rs and workspace.rs are the three files carrying `script_mod!`. # Their *testable* logic belongs in a measured module — see geometry.rs — # rather than behind this exclusion. -IGNORE_UI="$IGNORE_COMMON"'|spreadsheet-ui/src/(grid|ui|workspace|trend_chart|virtual_grid)\.rs|spreadsheet-ui/src/bin/' +IGNORE_UI="$IGNORE_COMMON"'|spreadsheet-ui/src/(grid|ui|workspace|trend_chart)\.rs|spreadsheet-ui/src/bin/' # Collect every instrumented test binary as `-object` arguments. #