use std::{ collections::{HashMap, HashSet}, rc::Rc, }; use gpui::{ AnyElement, App, Div, ElementId, Entity, FontWeight, InteractiveElement, IntoElement, ParentElement, Pixels, RenderOnce, SharedString, StatefulInteractiveElement, StyleRefinement, Styled, UniformListScrollHandle, Window, div, prelude::*, transparent_black, uniform_list, }; use super::{ Aggregate, Cell, Column, FilterRule, body::{Body, Detail, Line, OnEdit, Select, sideways, sized}, header::header, model::{figure, filtered, page, range}, rules::{groups, kept, sorted_by}, }; use crate::{ forms::{CheckState, Editing, check_mark}, layout::seeded::use_seeded, lists::{Pick, picked}, navigation::Pagination, theme::{ActiveTheme, ControlSize, Density, TextSize}, typography::{Caption, format}, }; /// One row: its key, which a selection names it by, and a cell for each column. #[derive(Clone, Debug, PartialEq)] pub struct Row { pub(crate) key: SharedString, pub(crate) cells: Vec, } impl Row { pub fn new(key: impl Into, cells: impl IntoIterator) -> Self { Self { key: key.into(), cells: cells.into_iter().collect(), } } } /// A table's own state: the page, a Shift range's start, the scroll, column widths, edges and order, opened rows, folded groups, and the cell being edited. #[derive(Default)] pub(crate) struct View { pub page: usize, pub anchor: usize, pub scroll: UniformListScrollHandle, pub widths: HashMap, pub edges: HashMap, pub narrowest: Pixels, pub order: Vec, pub open: HashSet, pub folded: HashSet, pub editing: Option<(SharedString, SharedString)>, } pub(super) type OnSelect = Rc; /// Rows under headers. A press on a header sorts, rising then falling then off; Shift adds it to the sort. A header drags to move its column and its edge drags to resize it. A query and filter rules keep rows; pages split long results, or a long table draws only the rows in view. Rows can gather into folding groups, open a detail, or edit a cell in place. With a selection, a box leads each row. Footers show figures; tinted columns show where each number sits. #[derive(IntoElement)] pub struct DataTable { pub(super) id: ElementId, pub(super) base: Div, pub(super) columns: Vec, pub(super) rows: Rc>, pub(super) query: SharedString, pub(super) filters: Vec, pub(super) any: bool, pub(super) sorts: Vec<(SharedString, bool)>, pub(super) hidden: Vec, pub(super) page_size: Option, pub(super) virtualized: bool, pub(super) selected: Option>, pub(super) on_select: Option, pub(super) detail: Option, pub(super) group_by: Option, pub(super) on_edit: Option, pub(super) density: Density, } impl Styled for DataTable { fn style(&mut self) -> &mut StyleRefinement { self.base.style() } } impl RenderOnce for DataTable { fn render(self, window: &mut Window, cx: &mut App) -> impl IntoElement { let id = self.id.clone(); let count = self.columns.len(); for row in self.rows.iter() { assert_eq!( row.cells.len(), count, "row {} needs a cell per column", row.key ); } assert!( !(self.detail.is_some() && self.virtualized), "a virtualized table draws rows of one height, so rows cannot open" ); assert!( !(self.group_by.is_some() && self.page_size.is_some()), "a grouped table is not paged" ); let pinned_any = self.columns.iter().any(|column| column.pinned); assert!( !(pinned_any && (self.virtualized || self.detail.is_some())), "pinned columns need rows of one height in one list" ); assert!( self.columns .iter() .all(|column| !column.pinned || column.width.is_some()), "a pinned column needs a width" ); let view: Entity = window.use_keyed_state((id.clone(), "view"), cx, |_, _| View::default()); let sorting = use_seeded((id.clone(), "sort"), self.sorts.clone(), window, cx); let editor = window.use_keyed_state((id.clone(), "editor"), cx, |_, _| Editing::default()); let narrowest = cx.theme().label_width() * 0.5; let rem = window.rem_size(); let keys_now: Vec = self .columns .iter() .map(|column| column.key.clone()) .collect(); view.update(cx, |view, _| { view.narrowest = narrowest.to_pixels(rem); view.order.retain(|key| keys_now.contains(key)); for key in &keys_now { if !view.order.contains(key) { view.order.push(key.clone()); } } }); if view.read(cx).editing.is_some() && editor.read(cx).field().is_none() { view.update(cx, |view, _| view.editing = None); } let rows = self.rows; let columns = Rc::new(self.columns); let index = |key: &SharedString| columns.iter().position(|column| column.key == *key); let shown_columns: Vec = view .read(cx) .order .iter() .filter(|key| !self.hidden.contains(key)) .filter_map(index) .collect(); let sort: Vec<(usize, bool)> = sorting .read(cx) .value .iter() .filter_map(|(key, rising)| index(key).map(|col| (col, *rising))) .collect(); let mut order = kept( &rows, &columns, filtered(&rows, &self.query), &self.filters, self.any, ); if !sort.is_empty() { order = sorted_by(&rows, order, &sort); } let total = order.len(); let (page_at, open, folded) = { let view = view.read(cx); (view.page, view.open.clone(), view.folded.clone()) }; let pages = self.page_size.map(|size| total.div_ceil(size).max(1)); let page_at = pages.map_or(0, |pages| page_at.min(pages - 1)); let opens = self.detail.is_some(); let mut lines = Vec::new(); let mut ordered: Vec = Vec::new(); let push_row = |ix: usize, position: usize, lines: &mut Vec| { lines.push(Line::Row { ix, position }); if opens && open.contains(&rows[ix].key) { lines.push(Line::Detail { ix }); } }; match self .group_by .as_ref() .map(|key| index(key).unwrap_or_else(|| panic!("no column {key} to group by"))) { Some(col) => { for (name, members) in groups(&rows, &order, col) { let shut = folded.contains(&name); lines.push(Line::Group { name: name.clone(), count: members.len(), folded: shut, }); if !shut { for ix in members { push_row(ix, ordered.len(), &mut lines); ordered.push(rows[ix].key.clone()); } } } } None => { ordered.extend(order.iter().map(|ix| rows[*ix].key.clone())); let offset = self.page_size.map_or(0, |size| page_at * size); let visible = match self.page_size { Some(size) => page(&order, page_at, size).to_vec(), None => order.clone(), }; for (at, ix) in visible.into_iter().enumerate() { push_row(ix, offset + at, &mut lines); } } } let ordered = Rc::new(ordered); let selected = self.selected.map(Rc::new); let select: Option