Skip to main content

teksilo_core/styles/
table_style.rs

1// SPDX-License-Identifier: MPL-2.0
2// SPDX-FileCopyrightText: 2026 FernTech
3
4//! Tier-3 style protocol for `TableView` and `TreeTableView`. See
5//! `docs/styling-system.md`.
6//!
7//! Multi-method trait shared by both data-grid widgets. Chrome is split
8//! between *composed* widgets (one widget per header cell, sort indicator,
9//! and row band) and a *batched paint pass* for grid lines + frozen-column
10//! shadow. Grid lines genuinely need the batched path — composing one
11//! `RectWidget` per line on a 1000-row virtualized viewport would defeat
12//! the virtualization budget. The "recipe describes, widget paints the
13//! batched case" split applies here for specialty widgets.
14//!
15//! ## Wiring status
16//!
17//! The trait surface, the `TableGridRecipe`, and the
18//! `style_slots.table` slot are in place. Wiring `TableView` /
19//! `TreeTableView` through `make_*` is intentionally deferred. The
20//! widgets currently still own their cell / row / header / grid-line
21//! chrome directly; every dimension lives on
22//! `teksilo_widgets::styles::recipe_table_style` as `pub const`s.
23
24use std::rc::Rc;
25
26use teksilo_tokens::BorderRole;
27
28use crate::build_context::BuildContext;
29use crate::signal::Signal;
30use crate::widget_id::WidgetId;
31
32/// Sort direction for header cells.
33#[derive(Debug, Clone, Copy, PartialEq, Eq)]
34pub enum SortDirection {
35    Ascending,
36    Descending,
37}
38
39pub struct TableHeaderCellConfig {
40    pub label: WidgetId,
41    pub sort: Option<SortDirection>,
42    pub is_hovered: Signal<bool>,
43    pub is_resizing: Signal<bool>,
44}
45
46pub struct TableRowConfig {
47    pub index: usize,
48    pub is_selected: Signal<bool>,
49    pub is_hovered: Signal<bool>,
50    pub is_alt: bool,
51    /// Whether the view holds keyboard focus. `None` = "treat as always
52    /// focused" (the stock `TableView` paints its selection band directly and
53    /// passes `None` here). A custom style that paints row backgrounds reactively
54    /// can supply this — combined with [`is_window_active`](Self::is_window_active)
55    /// — to desaturate the selection (`SelectedInactive`) when focus is elsewhere.
56    pub is_focused: Option<Signal<bool>>,
57    /// Whether the host window is active (`focused AND not occluded`). `None` =
58    /// "treat as always active". Custom styles combine this with
59    /// [`is_focused`](Self::is_focused) so a selected row desaturates in a
60    /// background window, matching the stock views.
61    pub is_window_active: Option<Signal<bool>>,
62}
63
64/// Recipe — non-widget data describing the batched paint pass for
65/// grid lines and the frozen-column shadow. Consumed by
66/// `TableView::paint` / `TreeTableView::paint` directly. Custom styles
67/// override the entire recipe via `TableStyle::grid()`.
68#[derive(Debug, Clone, Copy, PartialEq)]
69pub struct TableGridRecipe {
70    /// Vertical and horizontal grid-line stroke width.
71    pub line_thickness: f32,
72    /// Border role for grid lines. Defaults to `Divider`.
73    pub line_role: BorderRole,
74    /// Width of the shadow drawn at the frozen-column boundary. `0.0`
75    /// disables the shadow.
76    pub frozen_shadow_width: f32,
77}
78
79impl Default for TableGridRecipe {
80    fn default() -> Self {
81        Self {
82            line_thickness: 1.0,
83            line_role: BorderRole::Divider,
84            frozen_shadow_width: 4.0,
85        }
86    }
87}
88
89pub trait TableStyle: 'static {
90    fn make_header_cell(&self, cfg: &TableHeaderCellConfig, ctx: &mut BuildContext) -> WidgetId;
91    fn make_sort_indicator(&self, direction: SortDirection, ctx: &mut BuildContext) -> WidgetId;
92    /// Row-band chrome (selection / hover / alt) — composed *behind*
93    /// the cells.
94    fn make_row_background(&self, cfg: &TableRowConfig, ctx: &mut BuildContext) -> WidgetId;
95    /// Grid-line + frozen-column-shadow recipe — the table's own paint
96    /// pass batches over the virtualized viewport using this data.
97    fn grid(&self) -> TableGridRecipe;
98}
99
100pub type SharedTableStyle = Rc<dyn TableStyle>;