Skip to main content

agg_gui/widgets/table/
config.rs

1//! Configuration types for the `Table` widget.
2//!
3//! Defines column sizing, row descriptions, cell/header info structs, and
4//! the painter callback type aliases shared by the builder and widget impl.
5
6use std::sync::Arc;
7
8use crate::draw_ctx::DrawCtx;
9use crate::geometry::Rect;
10use crate::text::Font;
11use crate::theme::Visuals;
12
13// ── Configuration types ─────────────────────────────────────────────────────
14
15/// How a column derives its width.
16#[derive(Clone, Copy, Debug)]
17pub enum ColumnSize {
18    Auto(f64),
19    Exact(f64),
20    Remainder { at_least: f64, clip: bool },
21}
22
23#[derive(Clone, Copy, Debug)]
24pub struct TableColumn {
25    pub size: ColumnSize,
26    pub resizable: bool,
27}
28
29impl TableColumn {
30    pub fn auto(initial: f64) -> Self {
31        Self {
32            size: ColumnSize::Auto(initial),
33            resizable: false,
34        }
35    }
36    pub fn exact(w: f64) -> Self {
37        Self {
38            size: ColumnSize::Exact(w),
39            resizable: false,
40        }
41    }
42    pub fn remainder() -> Self {
43        Self {
44            size: ColumnSize::Remainder {
45                at_least: 16.0,
46                clip: false,
47            },
48            resizable: false,
49        }
50    }
51    pub fn at_least(mut self, v: f64) -> Self {
52        if let ColumnSize::Remainder {
53            ref mut at_least, ..
54        } = self.size
55        {
56            *at_least = v;
57        }
58        self
59    }
60    pub fn clip(mut self, on: bool) -> Self {
61        if let ColumnSize::Remainder { ref mut clip, .. } = self.size {
62            *clip = on;
63        }
64        self
65    }
66    pub fn resizable(mut self, on: bool) -> Self {
67        self.resizable = on;
68        self
69    }
70}
71
72/// Minimum width any column can be resized to.
73pub const MIN_COL_W: f64 = 16.0;
74/// Pixel half-width of the resize hit zone around a column's right edge.
75pub const RESIZE_HIT_HALF: f64 = 4.0;
76
77/// Distribute `total_w` across `columns` according to their sizing modes.
78///
79/// `overrides[i] = Some(w)` pins column `i` to the user-resized width and
80/// removes it from the auto/remainder distribution.  `Remainder` columns
81/// share whatever space is left after fixed + overridden columns, never
82/// going below their `at_least`.
83pub fn distribute_widths(
84    columns: &[TableColumn],
85    total_w: f64,
86    overrides: &[Option<f64>],
87) -> Vec<f64> {
88    let n = columns.len();
89    let mut out = vec![0.0_f64; n];
90    let mut remainder_indices: Vec<usize> = Vec::new();
91    let mut remainder_min = 0.0_f64;
92    let mut fixed_total = 0.0_f64;
93    for (i, c) in columns.iter().enumerate() {
94        if let Some(w) = overrides.get(i).copied().flatten() {
95            out[i] = w.max(MIN_COL_W);
96            fixed_total += out[i];
97            continue;
98        }
99        match c.size {
100            ColumnSize::Auto(w) | ColumnSize::Exact(w) => {
101                out[i] = w;
102                fixed_total += w;
103            }
104            ColumnSize::Remainder { at_least, .. } => {
105                remainder_indices.push(i);
106                remainder_min += at_least;
107            }
108        }
109    }
110    let leftover = (total_w - fixed_total).max(remainder_min);
111    if !remainder_indices.is_empty() {
112        let each = leftover / remainder_indices.len() as f64;
113        for &i in &remainder_indices {
114            let at_least = match columns[i].size {
115                ColumnSize::Remainder { at_least, .. } => at_least,
116                _ => 0.0,
117            };
118            out[i] = each.max(at_least);
119        }
120    }
121    out
122}
123
124/// Row-set description.
125#[derive(Clone, Debug)]
126pub enum TableRows {
127    Homogeneous { count: usize, height: f64 },
128    Heterogeneous { heights: Vec<f64> },
129}
130
131impl TableRows {
132    pub fn count(&self) -> usize {
133        match self {
134            TableRows::Homogeneous { count, .. } => *count,
135            TableRows::Heterogeneous { heights } => heights.len(),
136        }
137    }
138    pub fn height_at(&self, i: usize) -> f64 {
139        match self {
140            TableRows::Homogeneous { height, .. } => *height,
141            TableRows::Heterogeneous { heights } => heights.get(i).copied().unwrap_or(0.0),
142        }
143    }
144    pub fn total_height(&self) -> f64 {
145        match self {
146            TableRows::Homogeneous { count, height } => *count as f64 * *height,
147            TableRows::Heterogeneous { heights } => heights.iter().copied().sum(),
148        }
149    }
150    pub fn top_down_y_at(&self, i: usize) -> f64 {
151        match self {
152            TableRows::Homogeneous { height, .. } => i as f64 * *height,
153            TableRows::Heterogeneous { heights } => {
154                let take = i.min(heights.len());
155                heights[..take].iter().copied().sum()
156            }
157        }
158    }
159}
160
161// ── Painter callback shapes ─────────────────────────────────────────────────
162
163pub struct CellInfo<'a> {
164    pub row: usize,
165    pub col: usize,
166    /// Cell rect in widget-local Y-up coordinates of the table body.
167    /// The table has clipped to this rect before invoking the painter.
168    pub rect: Rect,
169    pub selected: bool,
170    pub visuals: &'a Visuals,
171    pub font: &'a Arc<Font>,
172}
173
174pub struct HeaderInfo<'a> {
175    pub col: usize,
176    pub rect: Rect,
177    pub visuals: &'a Visuals,
178    pub font: &'a Arc<Font>,
179}
180
181pub type CellPainter = Box<dyn FnMut(&CellInfo, &mut dyn DrawCtx)>;
182pub type HeaderPainter = Box<dyn FnMut(&HeaderInfo, &mut dyn DrawCtx)>;
183/// Optional callback for header clicks. Receives column index and click
184/// position relative to the header *cell* rect (Y-up).
185pub type HeaderClick = Box<dyn FnMut(usize, f64, f64) -> crate::event::EventResult>;
186/// Per-row predicate (e.g. for overlines).
187pub type RowPredicate = Box<dyn Fn(usize) -> bool>;
188/// Callback returning the live row spec.  Called on every layout pass when
189/// set, so external state changes (e.g. switching demo modes or
190/// resizing the dataset) flow into the table without an observer widget.
191pub type RowsProvider = Box<dyn Fn() -> TableRows>;