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}
89
90impl<M: GridModel> DataGrid<M> {
91 pub fn new(model: M) -> Self {
93 let columns = model.columns().to_vec();
94 Self {
95 revisions: (model.columns_revision(), model.generation()),
96 model,
97 columns,
98 frozen: 0,
99 painter: None,
100 first_row: 0,
101 row_rest: 0.0,
102 scroll_x: 0.0,
103 selected: None,
104 anchor: None,
105 all: false,
106 sort: None,
107 drag: None,
108 built: None,
109 }
110 }
111
112 #[must_use]
115 pub fn with_frozen_columns(mut self, columns: usize) -> Self {
116 self.frozen = columns;
117 self
118 }
119
120 pub fn style() -> Style {
123 Style::column()
124 .grow(1.0)
125 .min_size(Length::Px(0.0), Length::Px(0.0))
126 .overflow(Overflow::Clip)
127 }
128
129 pub fn model(&self) -> &M {
131 &self.model
132 }
133
134 pub fn model_mut(&mut self, cx: &mut EventCx<'_>) -> &mut M {
137 cx.request_paint();
138 &mut self.model
139 }
140
141 pub fn reset_columns(&mut self, cx: &mut EventCx<'_>) {
144 self.read_columns();
145 self.built = None;
146 cx.request_layout();
147 cx.request_paint();
148 }
149
150 pub fn selected(&self) -> Option<usize> {
152 self.selected.map(|s| s.row)
153 }
154
155 pub fn select(&mut self, cx: &mut EventCx<'_>, row: Option<usize>) {
160 if row.is_some() && row == self.selected() {
161 if let Some(row) = row {
162 self.reveal_row(row);
163 }
164 cx.request_paint();
165 return;
166 }
167 self.anchor = None;
168 self.all = false;
169 self.selected = row
170 .filter(|r| *r < self.model.rows())
171 .map(|row| self.selection_of(row));
172 if let Some(row) = row {
173 self.reveal_row(row);
174 }
175 cx.request_paint();
176 }
177
178 pub fn first_row(&self) -> usize {
180 self.first_row
181 }
182
183 pub fn scroll_to_row(&mut self, cx: &mut EventCx<'_>, row: usize) {
185 self.first_row = row.min(self.last_first_row());
186 self.row_rest = 0.0;
187 cx.request_paint();
188 }
189
190 pub fn sort_order(&self) -> Option<(usize, SortOrder)> {
193 self.model.sort_state().or(self.sort)
194 }
195
196 pub fn columns(&self) -> &[Column] {
198 &self.columns
199 }
200
201 pub fn visible_rows(&self) -> usize {
203 self.painter
204 .as_ref()
205 .map_or(0, |p| p.layout().rows() as usize)
206 }
207
208 pub fn whole_rows(&self) -> usize {
210 let (Some(layout), Some((size, _, _))) = (self.layout_of(), self.built) else {
211 return 0;
212 };
213 let header = (layout.padding() + layout.row_height()) as f32;
214 let rows = ((size.height - header) / layout.row_height() as f32).floor();
215 rows.max(0.0) as usize
217 }
218
219 fn layout_of(&self) -> Option<&GridLayout> {
220 self.painter.as_ref().map(GridPainter::layout)
221 }
222
223 fn last_first_row(&self) -> usize {
225 self.model.rows().saturating_sub(self.whole_rows().max(1))
226 }
227
228 fn reveal_row(&mut self, row: usize) {
230 let whole = self.whole_rows().max(1);
231 if row < self.first_row {
232 self.first_row = row;
233 } else if row >= self.first_row + whole {
234 self.first_row = row + 1 - whole;
235 }
236 self.row_rest = 0.0;
237 }
238
239 fn selection_of(&self, row: usize) -> Selection {
241 Selection {
242 row,
243 key: self.model.row_key(row),
244 }
245 }
246
247 fn read_columns(&mut self) {
249 self.columns = self.model.columns().to_vec();
250 let count = self.columns.len();
251 self.drag = None;
252 self.sort = self.sort.filter(|(column, _)| *column < count);
253 }
254
255 fn follow_model(&mut self) {
258 let (columns, generation) = (self.model.columns_revision(), self.model.generation());
259 if columns != self.revisions.0 {
260 self.read_columns();
261 if let Some((size, px, palette)) = self.built {
262 self.build(size, px, palette);
263 }
264 }
265 if generation != self.revisions.1 {
266 self.selected = None;
268 self.all = false;
269 self.sort = None;
270 self.drag = None;
271 self.first_row = 0;
272 self.row_rest = 0.0;
273 self.scroll_x = 0.0;
274 }
275 self.revisions = (columns, generation);
276 }
277
278 fn clamp(&mut self) {
281 self.selected = self.selected.and_then(|s| self.find(s));
282 self.anchor = self
283 .anchor
284 .and_then(|s| self.find(s))
285 .filter(|_| self.selected.is_some());
286 self.first_row = self.first_row.min(self.last_first_row());
287 if let Some((size, _, _)) = self.built {
288 self.scroll_x = self.scroll_x.clamp(0.0, self.max_scroll_x(size.width));
289 }
290 }
291
292 fn find(&self, selection: Selection) -> Option<Selection> {
294 let row = match selection.key {
295 Some(key) => self.model.row_of_key(key)?,
296 None => selection.row,
297 };
298 (row < self.model.rows()).then_some(Selection { row, ..selection })
299 }
300
301 fn build(&mut self, size: Size, px: f32, palette: Palette) {
303 let Ok(font) = Font::bundled_mono() else {
304 self.painter = None;
305 return;
306 };
307 let probe = GridPainter::new(self.columns.clone(), 1, font.clone(), px, palette);
309 let layout = probe.layout();
310 let header = (layout.padding() + layout.row_height()) as f32;
311 let rows = ((size.height - header) / layout.row_height() as f32).ceil();
312 let rows = rows.max(0.0) as u32;
314 self.painter = Some(GridPainter::new(
315 self.columns.clone(),
316 rows,
317 font,
318 px,
319 palette,
320 ));
321 self.built = Some((size, px, palette));
322 }
323}
324
325fn palette(theme: &Theme) -> Palette {
327 let mut palette = Palette::new();
328 palette.background = theme.surface;
329 palette.header_background = theme.background;
330 palette.header_text = theme.text_dim;
331 palette.stripe = theme.text.with_alpha(0.035);
332 palette.text = theme.text;
333 palette.dim_text = theme.text_dim;
334 if luminance(theme.surface) > 0.5 {
336 palette.positive = Color::srgb(0x1A7F37);
337 palette.negative = Color::srgb(0xCF222E);
338 }
339 palette
340}
341
342struct Shown<'a, M> {
345 model: &'a M,
346 first: usize,
347 selected: std::ops::Range<usize>,
348 highlight: Color,
349 palette: Palette,
350}
351
352impl<M: GridModel> GridSource for Shown<'_, M> {
353 fn rows(&self) -> usize {
354 self.model.rows().saturating_sub(self.first)
355 }
356
357 fn cell(&self, row: usize, column: usize, text: &mut CellText) -> CellStyle {
358 let row = self.first + row;
359 let style = self.model.cell(row, column, text, &self.palette);
360 if self.selected.contains(&row) && style.background.is_none() {
361 return style.with_background(self.highlight);
362 }
363 style
364 }
365}
366
367impl<M: GridModel + 'static> Widget for DataGrid<M> {
368 fn layout(&mut self, cx: &mut LayoutCx<'_>, size: Size) {
369 let px = cx.px(cx.theme().font_size);
370 let palette = palette(cx.theme());
371 if self.built != Some((size, px, palette)) {
372 self.build(size, px, palette);
373 }
374 self.follow_model();
375 self.clamp();
376 }
377
378 fn paint(&mut self, cx: &PaintCx<'_>, canvas: &mut Canvas<'_>) {
379 self.model.prepare();
380 self.follow_model();
381 self.clamp();
382 self.paint_grid(cx, canvas);
383 }
384
385 fn event(&mut self, cx: &mut EventCx<'_>, event: &Event<'_>) -> Handled {
386 self.handle(cx, event)
387 }
388
389 fn accepts_focus(&self) -> bool {
390 true
391 }
392
393 fn accessibility(&self, cx: &PaintCx<'_>, node: &mut AccessNode) {
394 self.describe(cx, node);
395 }
396
397 fn access_action(&mut self, cx: &mut EventCx<'_>, request: &AccessRequest<'_>) -> Handled {
398 self.act(cx, request)
399 }
400}
401
402#[derive(Debug, Clone, Copy)]
404struct Frame {
405 rect: Rect,
407 header: Rect,
409 body: Rect,
411 frozen: f32,
413}