1mod access;
4mod input;
5mod paint;
6
7use densa_core::{Color, Point, Rect, Size};
8use densa_draw::Canvas;
9use densa_grid::{
10 CellStyle, CellText, Column, GridLayout, GridModel, GridPainter, GridSource, Palette, SortOrder,
11};
12use densa_text::Font;
13use densa_ui::{
14 AccessNode, AccessRequest, Event, EventCx, Handled, LayoutCx, Length, Overflow, PaintCx, Style,
15 Theme, Widget,
16};
17
18use crate::paint::luminance;
19
20#[derive(Debug, Clone, Copy, PartialEq)]
22enum Drag {
23 Title { column: usize, at: Point },
25 Column { column: usize, chars: u32, x: f32 },
27 Thumb { grab: f32 },
29}
30
31#[derive(Debug, Clone, Copy, PartialEq, Eq)]
34struct Selection {
35 row: usize,
36 key: Option<u64>,
37}
38
39#[derive(Debug)]
59pub struct DataGrid<M> {
60 model: M,
61 columns: Vec<Column>,
63 frozen: usize,
64 painter: Option<GridPainter>,
66 first_row: usize,
67 row_rest: f32,
69 scroll_x: f32,
71 selected: Option<Selection>,
72 sort: Option<(usize, SortOrder)>,
74 drag: Option<Drag>,
75 built: Option<(Size, f32, Palette)>,
77 revisions: (u64, u64),
79}
80
81impl<M: GridModel> DataGrid<M> {
82 pub fn new(model: M) -> Self {
84 let columns = model.columns().to_vec();
85 Self {
86 revisions: (model.columns_revision(), model.generation()),
87 model,
88 columns,
89 frozen: 0,
90 painter: None,
91 first_row: 0,
92 row_rest: 0.0,
93 scroll_x: 0.0,
94 selected: None,
95 sort: None,
96 drag: None,
97 built: None,
98 }
99 }
100
101 #[must_use]
104 pub fn with_frozen_columns(mut self, columns: usize) -> Self {
105 self.frozen = columns;
106 self
107 }
108
109 pub fn style() -> Style {
112 Style::column()
113 .grow(1.0)
114 .min_size(Length::Px(0.0), Length::Px(0.0))
115 .overflow(Overflow::Clip)
116 }
117
118 pub fn model(&self) -> &M {
120 &self.model
121 }
122
123 pub fn model_mut(&mut self, cx: &mut EventCx<'_>) -> &mut M {
126 cx.request_paint();
127 &mut self.model
128 }
129
130 pub fn reset_columns(&mut self, cx: &mut EventCx<'_>) {
133 self.read_columns();
134 self.built = None;
135 cx.request_layout();
136 cx.request_paint();
137 }
138
139 pub fn selected(&self) -> Option<usize> {
141 self.selected.map(|s| s.row)
142 }
143
144 pub fn select(&mut self, cx: &mut EventCx<'_>, row: Option<usize>) {
146 self.selected = row
147 .filter(|r| *r < self.model.rows())
148 .map(|row| self.selection_of(row));
149 if let Some(row) = row {
150 self.reveal_row(row);
151 }
152 cx.request_paint();
153 }
154
155 pub fn first_row(&self) -> usize {
157 self.first_row
158 }
159
160 pub fn scroll_to_row(&mut self, cx: &mut EventCx<'_>, row: usize) {
162 self.first_row = row.min(self.last_first_row());
163 self.row_rest = 0.0;
164 cx.request_paint();
165 }
166
167 pub fn sort_order(&self) -> Option<(usize, SortOrder)> {
170 self.model.sort_state().or(self.sort)
171 }
172
173 pub fn columns(&self) -> &[Column] {
175 &self.columns
176 }
177
178 pub fn visible_rows(&self) -> usize {
180 self.painter
181 .as_ref()
182 .map_or(0, |p| p.layout().rows() as usize)
183 }
184
185 pub fn whole_rows(&self) -> usize {
187 let (Some(layout), Some((size, _, _))) = (self.layout_of(), self.built) else {
188 return 0;
189 };
190 let header = (layout.padding() + layout.row_height()) as f32;
191 let rows = ((size.height - header) / layout.row_height() as f32).floor();
192 rows.max(0.0) as usize
194 }
195
196 fn layout_of(&self) -> Option<&GridLayout> {
197 self.painter.as_ref().map(GridPainter::layout)
198 }
199
200 fn last_first_row(&self) -> usize {
202 self.model.rows().saturating_sub(self.whole_rows().max(1))
203 }
204
205 fn reveal_row(&mut self, row: usize) {
207 let whole = self.whole_rows().max(1);
208 if row < self.first_row {
209 self.first_row = row;
210 } else if row >= self.first_row + whole {
211 self.first_row = row + 1 - whole;
212 }
213 self.row_rest = 0.0;
214 }
215
216 fn selection_of(&self, row: usize) -> Selection {
218 Selection {
219 row,
220 key: self.model.row_key(row),
221 }
222 }
223
224 fn read_columns(&mut self) {
226 self.columns = self.model.columns().to_vec();
227 let count = self.columns.len();
228 self.drag = None;
229 self.sort = self.sort.filter(|(column, _)| *column < count);
230 }
231
232 fn follow_model(&mut self) {
235 let (columns, generation) = (self.model.columns_revision(), self.model.generation());
236 if columns != self.revisions.0 {
237 self.read_columns();
238 if let Some((size, px, palette)) = self.built {
239 self.build(size, px, palette);
240 }
241 }
242 if generation != self.revisions.1 {
243 self.selected = None;
244 self.sort = None;
245 self.drag = None;
246 self.first_row = 0;
247 self.row_rest = 0.0;
248 self.scroll_x = 0.0;
249 }
250 self.revisions = (columns, generation);
251 }
252
253 fn clamp(&mut self) {
256 let rows = self.model.rows();
257 self.selected = self.selected.and_then(|s| match s.key {
258 Some(key) => self.model.row_of_key(key).map(|row| Selection { row, ..s }),
259 None => Some(s),
260 });
261 self.selected = self.selected.filter(|s| s.row < rows);
262 self.first_row = self.first_row.min(self.last_first_row());
263 if let Some((size, _, _)) = self.built {
264 self.scroll_x = self.scroll_x.clamp(0.0, self.max_scroll_x(size.width));
265 }
266 }
267
268 fn build(&mut self, size: Size, px: f32, palette: Palette) {
270 let Ok(font) = Font::bundled_mono() else {
271 self.painter = None;
272 return;
273 };
274 let probe = GridPainter::new(self.columns.clone(), 1, font.clone(), px, palette);
276 let layout = probe.layout();
277 let header = (layout.padding() + layout.row_height()) as f32;
278 let rows = ((size.height - header) / layout.row_height() as f32).ceil();
279 let rows = rows.max(0.0) as u32;
281 self.painter = Some(GridPainter::new(
282 self.columns.clone(),
283 rows,
284 font,
285 px,
286 palette,
287 ));
288 self.built = Some((size, px, palette));
289 }
290}
291
292fn palette(theme: &Theme) -> Palette {
294 let mut palette = Palette::new();
295 palette.background = theme.surface;
296 palette.header_background = theme.background;
297 palette.header_text = theme.text_dim;
298 palette.stripe = theme.text.with_alpha(0.035);
299 palette.text = theme.text;
300 palette.dim_text = theme.text_dim;
301 if luminance(theme.surface) > 0.5 {
303 palette.positive = Color::srgb(0x1A7F37);
304 palette.negative = Color::srgb(0xCF222E);
305 }
306 palette
307}
308
309struct Shown<'a, M> {
312 model: &'a M,
313 first: usize,
314 selected: Option<usize>,
315 highlight: Color,
316 palette: Palette,
317}
318
319impl<M: GridModel> GridSource for Shown<'_, M> {
320 fn rows(&self) -> usize {
321 self.model.rows().saturating_sub(self.first)
322 }
323
324 fn cell(&self, row: usize, column: usize, text: &mut CellText) -> CellStyle {
325 let row = self.first + row;
326 let style = self.model.cell(row, column, text, &self.palette);
327 if self.selected == Some(row) && style.background.is_none() {
328 return style.with_background(self.highlight);
329 }
330 style
331 }
332}
333
334impl<M: GridModel + 'static> Widget for DataGrid<M> {
335 fn layout(&mut self, cx: &mut LayoutCx<'_>, size: Size) {
336 let px = cx.px(cx.theme().font_size);
337 let palette = palette(cx.theme());
338 if self.built != Some((size, px, palette)) {
339 self.build(size, px, palette);
340 }
341 self.follow_model();
342 self.clamp();
343 }
344
345 fn paint(&mut self, cx: &PaintCx<'_>, canvas: &mut Canvas<'_>) {
346 self.model.prepare();
347 self.follow_model();
348 self.clamp();
349 self.paint_grid(cx, canvas);
350 }
351
352 fn event(&mut self, cx: &mut EventCx<'_>, event: &Event<'_>) -> Handled {
353 self.handle(cx, event)
354 }
355
356 fn accepts_focus(&self) -> bool {
357 true
358 }
359
360 fn accessibility(&self, cx: &PaintCx<'_>, node: &mut AccessNode) {
361 self.describe(cx, node);
362 }
363
364 fn access_action(&mut self, cx: &mut EventCx<'_>, request: &AccessRequest<'_>) -> Handled {
365 self.act(cx, request)
366 }
367}
368
369#[derive(Debug, Clone, Copy)]
371struct Frame {
372 rect: Rect,
374 header: Rect,
376 body: Rect,
378 frozen: f32,
380}