1mod access;
4mod input;
5mod paint;
6mod selection;
7
8use densa_core::{Color, Point, Rect, Size};
9use densa_draw::Canvas;
10use densa_grid::{
11 CellStyle, CellText, Column, GridLayout, GridModel, GridPainter, GridSource, Palette, SortOrder,
12};
13use densa_text::Font;
14use densa_ui::{
15 AccessNode, AccessRequest, Event, EventCx, Handled, LayoutCx, Length, Overflow, PaintCx, Style,
16 Theme, Widget,
17};
18
19use crate::paint::luminance;
20
21#[derive(Debug, Clone, Copy, PartialEq)]
23enum Drag {
24 Title { column: usize, at: Point },
26 Column { column: usize, chars: u32, x: f32 },
28 Thumb { grab: f32 },
30}
31
32#[derive(Debug, Clone, Copy, PartialEq, Eq)]
35struct Selection {
36 row: usize,
37 key: Option<u64>,
38}
39
40#[derive(Debug)]
64pub struct DataGrid<M> {
65 model: M,
66 columns: Vec<Column>,
68 frozen: usize,
69 painter: Option<GridPainter>,
71 first_row: usize,
72 row_rest: f32,
74 scroll_x: f32,
76 selected: Option<Selection>,
77 anchor: Option<Selection>,
79 all: bool,
81 sort: Option<(usize, SortOrder)>,
83 drag: Option<Drag>,
84 built: Option<(Size, f32, Palette)>,
86 revisions: (u64, u64),
88 dragged: Vec<bool>,
90}
91
92impl<M: GridModel> DataGrid<M> {
93 pub fn new(model: M) -> Self {
95 let columns = model.columns().to_vec();
96 Self {
97 revisions: (model.columns_revision(), model.generation()),
98 dragged: vec![false; columns.len()],
99 model,
100 columns,
101 frozen: 0,
102 painter: None,
103 first_row: 0,
104 row_rest: 0.0,
105 scroll_x: 0.0,
106 selected: None,
107 anchor: None,
108 all: false,
109 sort: None,
110 drag: None,
111 built: None,
112 }
113 }
114
115 #[must_use]
118 pub fn with_frozen_columns(mut self, columns: usize) -> Self {
119 self.frozen = columns;
120 self
121 }
122
123 pub fn style() -> Style {
126 Style::column()
127 .grow(1.0)
128 .min_size(Length::Px(0.0), Length::Px(0.0))
129 .overflow(Overflow::Clip)
130 }
131
132 pub fn model(&self) -> &M {
134 &self.model
135 }
136
137 pub fn model_mut(&mut self, cx: &mut EventCx<'_>) -> &mut M {
140 cx.request_paint();
141 &mut self.model
142 }
143
144 pub fn reset_columns(&mut self, cx: &mut EventCx<'_>) {
147 self.read_columns(false);
148 self.built = None;
149 cx.request_layout();
150 cx.request_paint();
151 }
152
153 pub fn selected(&self) -> Option<usize> {
155 self.selected.map(|s| s.row)
156 }
157
158 pub fn select(&mut self, cx: &mut EventCx<'_>, row: Option<usize>) {
163 if row.is_some() && row == self.selected() {
164 if let Some(row) = row {
165 self.reveal_row(row);
166 }
167 cx.request_paint();
168 return;
169 }
170 self.anchor = None;
171 self.all = false;
172 self.selected = row
173 .filter(|r| *r < self.model.rows())
174 .map(|row| self.selection_of(row));
175 if let Some(row) = row {
176 self.reveal_row(row);
177 }
178 cx.request_paint();
179 }
180
181 pub fn first_row(&self) -> usize {
183 self.first_row
184 }
185
186 pub fn scroll_to_row(&mut self, cx: &mut EventCx<'_>, row: usize) {
188 self.first_row = row.min(self.last_first_row());
189 self.row_rest = 0.0;
190 cx.request_paint();
191 }
192
193 pub fn sort_order(&self) -> Option<(usize, SortOrder)> {
196 self.model.sort_state().or(self.sort)
197 }
198
199 pub fn columns(&self) -> &[Column] {
201 &self.columns
202 }
203
204 pub fn visible_rows(&self) -> usize {
206 self.painter
207 .as_ref()
208 .map_or(0, |p| p.layout().rows() as usize)
209 }
210
211 pub fn whole_rows(&self) -> usize {
213 let (Some(layout), Some((size, _, _))) = (self.layout_of(), self.built) else {
214 return 0;
215 };
216 let header = (layout.padding() + layout.row_height()) as f32;
217 let rows = ((size.height - header) / layout.row_height() as f32).floor();
218 rows.max(0.0) as usize
220 }
221
222 fn layout_of(&self) -> Option<&GridLayout> {
223 self.painter.as_ref().map(GridPainter::layout)
224 }
225
226 fn last_first_row(&self) -> usize {
228 self.model.rows().saturating_sub(self.whole_rows().max(1))
229 }
230
231 fn reveal_row(&mut self, row: usize) {
233 let whole = self.whole_rows().max(1);
234 if row < self.first_row {
235 self.first_row = row;
236 } else if row >= self.first_row + whole {
237 self.first_row = row + 1 - whole;
238 }
239 self.row_rest = 0.0;
240 }
241
242 fn selection_of(&self, row: usize) -> Selection {
244 Selection {
245 row,
246 key: self.model.row_key(row),
247 }
248 }
249
250 fn read_columns(&mut self, keep_dragged: bool) {
254 let mut columns = self.model.columns().to_vec();
255 let same = columns.len() == self.columns.len()
256 && columns
257 .iter()
258 .zip(&self.columns)
259 .all(|(a, b)| a.title == b.title);
260 if keep_dragged && same {
261 for ((new, old), dragged) in columns.iter_mut().zip(&self.columns).zip(&self.dragged) {
262 if *dragged {
263 new.chars = old.chars;
264 }
265 }
266 } else {
267 self.dragged.clear();
268 self.drag = None;
269 }
270 self.columns = columns;
271 let count = self.columns.len();
272 self.dragged.resize(count, false);
273 self.sort = self.sort.filter(|(column, _)| *column < count);
274 }
275
276 fn follow_model(&mut self) {
279 let (columns, generation) = (self.model.columns_revision(), self.model.generation());
280 if columns != self.revisions.0 {
281 self.read_columns(true);
282 if let Some((size, px, palette)) = self.built {
283 self.build(size, px, palette);
284 }
285 }
286 if generation != self.revisions.1 {
287 self.selected = None;
289 self.all = false;
290 self.sort = None;
291 self.drag = None;
292 self.first_row = 0;
293 self.row_rest = 0.0;
294 self.scroll_x = 0.0;
295 }
296 self.revisions = (columns, generation);
297 }
298
299 fn clamp(&mut self) {
302 self.selected = self.selected.and_then(|s| self.find(s));
303 self.anchor = self
304 .anchor
305 .and_then(|s| self.find(s))
306 .filter(|_| self.selected.is_some());
307 self.first_row = self.first_row.min(self.last_first_row());
308 if let Some((size, _, _)) = self.built {
309 self.scroll_x = self.scroll_x.clamp(0.0, self.max_scroll_x(size.width));
310 }
311 }
312
313 fn find(&self, selection: Selection) -> Option<Selection> {
315 let row = match selection.key {
316 Some(key) => self.model.row_of_key(key)?,
317 None => selection.row,
318 };
319 (row < self.model.rows()).then_some(Selection { row, ..selection })
320 }
321
322 fn build(&mut self, size: Size, px: f32, palette: Palette) {
324 let Ok(font) = Font::bundled_mono() else {
325 self.painter = None;
326 return;
327 };
328 let probe = GridPainter::new(self.columns.clone(), 1, font.clone(), px, palette);
330 let layout = probe.layout();
331 let header = (layout.padding() + layout.row_height()) as f32;
332 let rows = ((size.height - header) / layout.row_height() as f32).ceil();
333 let rows = rows.max(0.0) as u32;
335 self.painter = Some(GridPainter::new(
336 self.columns.clone(),
337 rows,
338 font,
339 px,
340 palette,
341 ));
342 self.built = Some((size, px, palette));
343 }
344}
345
346fn palette(theme: &Theme) -> Palette {
348 let mut palette = Palette::new();
349 palette.background = theme.surface;
350 palette.header_background = theme.background;
351 palette.header_text = theme.text_dim;
352 palette.stripe = theme.text.with_alpha(0.035);
353 palette.text = theme.text;
354 palette.dim_text = theme.text_dim;
355 if luminance(theme.surface) > 0.5 {
357 palette.positive = Color::srgb(0x1A7F37);
358 palette.negative = Color::srgb(0xCF222E);
359 }
360 palette
361}
362
363struct Shown<'a, M> {
366 model: &'a M,
367 first: usize,
368 selected: std::ops::Range<usize>,
369 highlight: Color,
370 palette: Palette,
371}
372
373impl<M: GridModel> GridSource for Shown<'_, M> {
374 fn rows(&self) -> usize {
375 self.model.rows().saturating_sub(self.first)
376 }
377
378 fn cell(&self, row: usize, column: usize, text: &mut CellText) -> CellStyle {
379 let row = self.first + row;
380 let style = self.model.cell(row, column, text, &self.palette);
381 if self.selected.contains(&row) && style.background.is_none() {
382 return style.with_background(self.highlight);
383 }
384 style
385 }
386}
387
388impl<M: GridModel + 'static> Widget for DataGrid<M> {
389 fn layout(&mut self, cx: &mut LayoutCx<'_>, size: Size) {
390 let px = cx.px(cx.theme().font_size);
391 let palette = palette(cx.theme());
392 if self.built != Some((size, px, palette)) {
393 self.build(size, px, palette);
394 }
395 self.follow_model();
396 self.clamp();
397 }
398
399 fn paint(&mut self, cx: &PaintCx<'_>, canvas: &mut Canvas<'_>) {
400 self.model.prepare();
401 self.follow_model();
402 self.clamp();
403 self.paint_grid(cx, canvas);
404 }
405
406 fn event(&mut self, cx: &mut EventCx<'_>, event: &Event<'_>) -> Handled {
407 self.handle(cx, event)
408 }
409
410 fn accepts_focus(&self) -> bool {
411 true
412 }
413
414 fn accessibility(&self, cx: &PaintCx<'_>, node: &mut AccessNode) {
415 self.describe(cx, node);
416 }
417
418 fn access_action(&mut self, cx: &mut EventCx<'_>, request: &AccessRequest<'_>) -> Handled {
419 self.act(cx, request)
420 }
421}
422
423#[derive(Debug, Clone, Copy)]
425struct Frame {
426 rect: Rect,
428 header: Rect,
430 body: Rect,
432 frozen: f32,
434}