Skip to main content

kas_view/
grid_view.rs

1// Licensed under the Apache License, Version 2.0 (the "License");
2// you may not use this file except in compliance with the License.
3// You may obtain a copy of the License in the LICENSE-APACHE file or at:
4//     https://www.apache.org/licenses/LICENSE-2.0
5
6//! Grid view controller
7
8use crate::clerk::{Changes, Key, TokenClerk};
9use crate::{Driver, SelectionMode, SelectionMsg, Update};
10use kas::event::components::{ClickInput, ClickInputAction};
11use kas::event::{FocusSource, Scroll, TimerHandle};
12use kas::layout::{GridCellInfo, solve_size_rules};
13use kas::prelude::*;
14use kas::theme::SelectionStyle;
15#[allow(unused)] // doc links
16use kas_widgets::ScrollRegion;
17use linear_map::set::LinearSet;
18use std::borrow::Borrow;
19use std::ops::Range;
20use std::time::Instant;
21
22const TIMER_UPDATE_WIDGETS: TimerHandle = TimerHandle::new(1, true);
23
24#[impl_self]
25mod GridCell {
26    /// A wrapper for selectable items
27    ///
28    /// This widget adds a thin frame around contents, supporting navigation
29    /// focus and activation.
30    ///
31    /// # Messages
32    ///
33    /// When activated, this widget pushes [`Select`] to the message stack.
34    ///
35    /// [`Select`]: kas::messages::Select
36    #[widget]
37    #[layout(frame!(self.inner).with_style(kas::theme::FrameStyle::NavFocus))]
38    struct GridCell<K, I, V: Driver<K, I>> {
39        core: widget_core!(),
40        index: GridIndex,
41        selected: Option<bool>,
42        /// The inner widget
43        #[widget]
44        inner: V::Widget,
45    }
46
47    impl Self {
48        /// Construct a frame
49        #[inline]
50        fn new(inner: V::Widget) -> Self {
51            GridCell {
52                core: Default::default(),
53                index: GridIndex::default(),
54                selected: None,
55                inner,
56            }
57        }
58    }
59
60    impl Tile for Self {
61        fn role(&self, cx: &mut dyn RoleCx) -> Role<'_> {
62            if let Some(label) = V::label(&self.inner) {
63                cx.set_label(label);
64            }
65            Role::GridCell {
66                info: Some(GridCellInfo::new(self.index.col, self.index.row)),
67                selected: self.selected,
68            }
69        }
70
71        fn navigable(&self) -> bool {
72            V::navigable(&self.inner)
73        }
74    }
75
76    impl Events for Self {
77        type Data = I;
78
79        fn handle_event(&mut self, cx: &mut EventCx, _: &Self::Data, event: Event) -> IsUsed {
80            match event {
81                Event::Command(cmd, code) if cmd.is_activate() => {
82                    cx.depress_with_key(&self, code);
83                    cx.push(kas::messages::Select);
84                    Used
85                }
86                _ => Unused,
87            }
88        }
89    }
90}
91
92#[autoimpl(Debug ignore self.item where C::Token: trait)]
93struct WidgetData<C: TokenClerk<GridIndex>, V: Driver<C::Key, C::Item>> {
94    token: Option<C::Token>,
95    is_mock: bool,
96    item: GridCell<C::Key, C::Item, V>,
97}
98
99impl<C: TokenClerk<GridIndex>, V: Driver<C::Key, C::Item>> WidgetData<C, V> {
100    fn key(&self) -> Option<&C::Key> {
101        self.token.as_ref().map(Borrow::borrow)
102    }
103}
104
105/// Index of a grid cell
106#[derive(Copy, Clone, Debug, Default, PartialEq, Eq, Hash)]
107pub struct GridIndex {
108    pub col: u32,
109    pub row: u32,
110}
111
112impl GridIndex {
113    /// Zero
114    pub const ZERO: GridIndex = GridIndex { col: 0, row: 0 };
115
116    /// Construct, copying `x` to both fields
117    pub const fn splat(x: u32) -> Self {
118        GridIndex { col: x, row: x }
119    }
120}
121
122impl GridIndex {
123    /// Yield the component-wise minimum
124    pub fn min(self, rhs: Self) -> Self {
125        GridIndex {
126            col: self.col.min(rhs.col),
127            row: self.row.min(rhs.row),
128        }
129    }
130}
131
132impl std::ops::Add for GridIndex {
133    type Output = Self;
134    fn add(self, rhs: Self) -> Self {
135        GridIndex {
136            col: self.col + rhs.col,
137            row: self.row + rhs.row,
138        }
139    }
140}
141
142impl Key for GridIndex {
143    fn make_id(&self, parent: &Id) -> Id {
144        parent
145            .make_child(self.col.cast())
146            .make_child(self.row.cast())
147    }
148
149    fn reconstruct_key(parent: &Id, child: &Id) -> Option<Self> {
150        let mut iter = child.iter_keys_after(parent);
151        let col = iter.next().map(|i| i.cast())?;
152        let row = iter.next().map(|i| i.cast())?;
153        Some(GridIndex { col, row })
154    }
155}
156
157#[derive(Debug)]
158struct FocusGridCell(GridIndex);
159
160#[impl_self]
161mod GridView {
162    /// View controller for 2D indexable data (grid)
163    ///
164    /// This widget generates a view over a list of data items via a
165    /// [`TokenClerk`]. "View widgets" are constructed via a [`Driver`]
166    /// to represent visible data items. These view widgets are reassigned as
167    /// required when the grid is scrolled, keeping the number of widgets in
168    /// use roughly proportional to the number of data items within the view.
169    ///
170    /// Each view widget has an [`Id`] corresponding to its current data
171    /// item, and may handle events and emit messages like other widegts.
172    /// See [`Driver`] documentation for more on event handling.
173    ///
174    /// ### Special behaviour
175    ///
176    /// This is a [`Viewport`] widget. It should be wrapped by a scroll handler
177    /// like [`ScrollRegion`].
178    ///
179    /// This widget supports navigation of children using arrow keys and other
180    /// navigation keys when those keys are not handled by the child itself.
181    ///
182    /// Optionally, data items may be selected; see [`Self::set_selection_mode`].
183    /// If enabled, [`SelectionMsg`] messages are reported; view widgets may
184    /// emit [`kas::messages::Select`] to have themselves be selected.
185    #[widget]
186    pub struct GridView<C: TokenClerk<GridIndex>, V: Driver<C::Key, C::Item>> {
187        core: widget_core!(),
188        frame_offset: Offset,
189        frame_size: Size,
190        clerk: C,
191        driver: V,
192        widgets: Vec<WidgetData<C, V>>,
193        align_hints: AlignHints,
194        ideal_len: GridIndex,
195        alloc_len: GridIndex,
196        data_len: GridIndex,
197        token_update: Update,
198        rect_update: bool,
199        immediate_scroll_update: bool,
200        len_is_known: bool,
201        cur_len: GridIndex,
202        first_data: GridIndex,
203        /// Last data item to have navigation focus
204        last_focus: GridIndex,
205        child_inter_margin: Size,
206        child_size: Size,
207        /// The current view offset
208        offset: Offset,
209        virtual_offset: Offset,
210        sel_mode: SelectionMode,
211        sel_style: SelectionStyle,
212        // TODO(opt): replace selection list with RangeOrSet type?
213        selection: LinearSet<C::Key>,
214        click: ClickInput,
215        press_target: Option<(usize, C::Key)>,
216    }
217
218    impl Default for Self
219    where
220        C: Default,
221        V: Default,
222    {
223        fn default() -> Self {
224            Self::new(C::default(), V::default())
225        }
226    }
227    impl Self {
228        /// Construct a new instance
229        pub fn new(clerk: C, driver: V) -> Self {
230            GridView {
231                core: Default::default(),
232                frame_offset: Default::default(),
233                frame_size: Default::default(),
234                clerk,
235                driver,
236                widgets: Default::default(),
237                align_hints: Default::default(),
238                ideal_len: GridIndex { col: 3, row: 5 },
239                alloc_len: GridIndex::ZERO,
240                data_len: GridIndex::ZERO,
241                token_update: Update::None,
242                rect_update: false,
243                immediate_scroll_update: false,
244                len_is_known: false,
245                cur_len: GridIndex::ZERO,
246                first_data: GridIndex::ZERO,
247                last_focus: GridIndex::ZERO,
248                child_inter_margin: Size::ZERO,
249                child_size: Size::ZERO,
250                offset: Offset::ZERO,
251                virtual_offset: Offset::ZERO,
252                sel_mode: SelectionMode::None,
253                sel_style: SelectionStyle::Highlight,
254                selection: Default::default(),
255                click: Default::default(),
256                press_target: None,
257            }
258        }
259
260        /// Access the data clerk
261        pub fn clerk(&self) -> &C {
262            &self.clerk
263        }
264
265        /// Access the data clerk (mutably)
266        ///
267        /// Changes to the clerk must be notified with an update to the
268        /// `GridView`, for example using [`ConfigCx::update`].
269        pub fn clerk_mut(&mut self) -> &mut C {
270            &mut self.clerk
271        }
272
273        /// Get the range of visible data items
274        ///
275        /// Data items within this range may be visible (or should at least be
276        /// allocated some pixel within the controller's view).
277        pub fn view_range(&self) -> Range<GridIndex> {
278            self.first_data..self.first_data + self.cur_len
279        }
280
281        /// Get the current selection mode
282        pub fn selection_mode(&self) -> SelectionMode {
283            self.sel_mode
284        }
285        /// Set the current selection mode
286        ///
287        /// By default, selection is disabled. If enabled, items may be selected
288        /// and deselected via mouse-click/touch or via a view widget emitting
289        /// [`Select`].
290        ///
291        /// On selection and deselection, a [`SelectionMsg`] message is emitted.
292        ///
293        /// [`Select`]: kas::messages::Select
294        pub fn set_selection_mode(&mut self, cx: &mut EventState, mode: SelectionMode) {
295            self.sel_mode = mode;
296            match mode {
297                SelectionMode::None if !self.selection.is_empty() => {
298                    self.selection.clear();
299                    cx.redraw(self);
300                }
301                SelectionMode::Single if self.selection.len() > 1 => {
302                    if let Some(first) = self.selection.iter().next().cloned() {
303                        self.selection.retain(|item| *item == first);
304                    }
305                    cx.redraw(self);
306                }
307                _ => (),
308            }
309        }
310        /// Set the initial selection mode (inline)
311        ///
312        /// See [`Self::set_selection_mode`] documentation.
313        #[must_use]
314        pub fn with_selection_mode(mut self, mode: SelectionMode) -> Self {
315            debug_assert!(self.selection.is_empty());
316            self.sel_mode = mode;
317            self
318        }
319
320        /// Get the current selection style
321        pub fn selection_style(&self) -> SelectionStyle {
322            self.sel_style
323        }
324        /// Set the current selection style
325        ///
326        /// By default, [`SelectionStyle::Highlight`] is used. Other modes may
327        /// add margin between elements.
328        pub fn set_selection_style(&mut self, cx: &mut ConfigCx, style: SelectionStyle) {
329            if style.is_external() != self.sel_style.is_external() {
330                cx.resize();
331            };
332            self.sel_style = style;
333        }
334        /// Set the selection style (inline)
335        ///
336        /// See [`Self::set_selection_style`] documentation.
337        #[must_use]
338        pub fn with_selection_style(mut self, style: SelectionStyle) -> Self {
339            self.sel_style = style;
340            self
341        }
342
343        /// Read the list of selected entries
344        ///
345        /// With mode [`SelectionMode::Single`] this may contain zero or one entry;
346        /// use `selected_iter().next()` to extract only the first (optional) entry.
347        pub fn selected_iter(&'_ self) -> impl Iterator<Item = &'_ C::Key> + '_ {
348            self.selection.iter()
349        }
350
351        /// Check whether an entry is selected
352        pub fn is_selected(&self, key: &C::Key) -> bool {
353            self.selection.contains(key)
354        }
355
356        /// Clear all selected items
357        pub fn clear_selected(&mut self, cx: &mut EventState) {
358            if !self.selection.is_empty() {
359                self.selection.clear();
360                cx.redraw(self);
361            }
362        }
363
364        /// Directly select an item
365        ///
366        /// Does nothing if [`Self::selection_mode`] is [`SelectionMode::None`].
367        /// Does not verify the validity of `key`.
368        /// Does not send [`SelectionMsg`] messages.
369        ///
370        /// Returns `true` if newly selected, `false` if
371        /// already selected. Fails if selection mode does not permit selection
372        /// or if the key is invalid.
373        pub fn select(&mut self, cx: &mut EventState, key: C::Key) -> bool {
374            match self.sel_mode {
375                SelectionMode::None => return false,
376                SelectionMode::Single => self.selection.clear(),
377                _ => (),
378            }
379            let r = self.selection.insert(key);
380            if r {
381                cx.redraw(self);
382            }
383            r
384        }
385
386        /// Directly deselect an item
387        ///
388        /// Returns `true` if deselected, `false` if not
389        /// previously selected or if the key is invalid.
390        pub fn deselect(&mut self, cx: &mut EventState, key: &C::Key) -> bool {
391            let r = self.selection.remove(key);
392            if r {
393                cx.redraw(self);
394            }
395            r
396        }
397
398        /// Set the preferred number of items visible (inline)
399        ///
400        /// This affects the (ideal) size request and whether children are sized
401        /// according to their ideal or minimum size but not the minimum size.
402        #[must_use]
403        pub fn with_num_visible(mut self, cols: u32, rows: u32) -> Self {
404            self.ideal_len = GridIndex {
405                col: cols,
406                row: rows,
407            };
408            self
409        }
410
411        /// Widgets in the range `0..self.cur_end()` are currently in use
412        #[inline]
413        fn cur_end(&self) -> usize {
414            usize::conv(self.cur_len.col) * usize::conv(self.cur_len.row)
415        }
416
417        fn position_solver(&self) -> PositionSolver {
418            PositionSolver {
419                pos_start: self.rect().pos + self.frame_offset + self.virtual_offset,
420                skip: self.child_size + self.child_inter_margin,
421                size: self.child_size,
422                first_data: self.first_data,
423                cur_len: self.cur_len,
424            }
425        }
426
427        // Call after scrolling to re-map widgets (if required)
428        //
429        // This auto-detects whether remapping is required, unless `self.token_update` is set.
430        #[inline]
431        fn post_scroll(&mut self, cx: &mut ConfigCx, data: &C::Data) {
432            self.handle_update(cx, data, Changes::None, false);
433        }
434
435        // Handle a data clerk update or change in view position
436        fn handle_update(
437            &mut self,
438            cx: &mut ConfigCx,
439            data: &C::Data,
440            changes: Changes<GridIndex>,
441            force_update: bool,
442        ) {
443            // TODO(opt): let Changes::Range only update a sub-set of items
444            if matches!(changes, Changes::Range(_) | Changes::Any) {
445                self.token_update = self.token_update.max(Update::Token);
446            }
447
448            let offset = self.offset;
449            let skip = (self.child_size + self.child_inter_margin).max(Size(1, 1));
450            let first_col = u32::conv(u64::conv(offset.0) / u64::conv(skip.0));
451            let first_row = u32::conv(u64::conv(offset.1) / u64::conv(skip.1));
452
453            let lbound = GridIndex {
454                col: first_col + 2 * self.alloc_len.col,
455                row: first_row + 2 * self.alloc_len.row,
456            };
457
458            let data_len;
459            if !self.len_is_known || changes != Changes::None {
460                let result = self.clerk.len(data, lbound);
461                self.len_is_known = result.is_known();
462                data_len = result.len();
463                if data_len != self.data_len {
464                    self.data_len = data_len;
465                    cx.resize();
466                    self.token_update = Update::Token;
467                }
468            } else {
469                data_len = self.data_len;
470            }
471            let cur_len = data_len.min(self.alloc_len);
472
473            let first_data = GridIndex {
474                col: first_col.min(data_len.col - cur_len.col),
475                row: first_row.min(data_len.row - cur_len.row),
476            };
477            let (mut start, mut end) = (first_data, first_data + cur_len);
478            let (old_start, old_end) = (self.first_data, self.first_data + self.cur_len);
479
480            let virtual_offset = -Offset(offset.0 & 0x7FF0_0000, offset.1 & 0x7FF0_0000);
481            if virtual_offset != self.virtual_offset {
482                self.virtual_offset = virtual_offset;
483                self.rect_update = true;
484            } else if force_update || self.rect_update || self.token_update != Update::None {
485                // This forces an update to all widgets
486            } else if start == old_start && cur_len == self.cur_len {
487                return;
488            } else if start.col == old_start.col && end.col == old_end.col {
489                if start.row >= old_start.row {
490                    start.row = start.row.max(old_end.row);
491                } else if end.row <= old_end.row {
492                    end.row = end.row.min(old_start.row);
493                }
494                if start.row >= end.row {
495                    return;
496                }
497            } else if start.row == old_start.row && end.row == old_end.row {
498                if start.col >= old_start.col {
499                    start.col = start.col.max(old_end.col);
500                } else if end.col <= old_end.col {
501                    end.col = end.col.min(old_start.col);
502                }
503                if start.col >= end.col {
504                    return;
505                }
506            }
507
508            self.cur_len = cur_len;
509            debug_assert!(self.cur_end() <= self.widgets.len());
510            self.first_data = first_data;
511
512            self.map_view_widgets(cx, data, start..end, force_update);
513        }
514
515        // Assign view widgets to data as required and set their rects
516        //
517        // View widgets are configured and sized if assigned a new data item.
518        fn map_view_widgets(
519            &mut self,
520            cx: &mut ConfigCx,
521            data: &C::Data,
522            Range { start, end }: Range<GridIndex>,
523            force_update: bool,
524        ) {
525            let time = Instant::now();
526
527            self.clerk
528                .prepare_range(cx, self.id(), self.view_range(), data, start..end);
529
530            let id = self.id();
531
532            let solver = self.position_solver();
533            for row in start.row..end.row {
534                for col in start.col..end.col {
535                    let cell = GridIndex { col, row };
536                    let i = solver.data_to_child(cell);
537                    let w = &mut self.widgets[i];
538
539                    let force = self.token_update != Update::None;
540                    let changes = self.clerk.update_token(data, cell, force, &mut w.token);
541                    w.is_mock = false;
542                    let Some(token) = w.token.as_ref() else {
543                        continue;
544                    };
545
546                    let mut rect_update = self.rect_update;
547                    if changes.key() || self.token_update == Update::Configure {
548                        w.item.index = cell;
549                        // TODO(opt): some impls of Driver::set_key do nothing
550                        // and do not need re-configure (beyond the first).
551                        self.driver.set_key(&mut w.item.inner, token.borrow());
552
553                        let item = self.clerk.item(data, token);
554                        let id = token.borrow().make_id(&id);
555                        cx.configure(w.item.as_node(item), id);
556
557                        solve_size_rules(
558                            &mut w.item,
559                            &mut cx.size_cx(),
560                            Some(self.child_size.0),
561                            Some(self.child_size.1),
562                        );
563                        rect_update = true;
564                    } else if force_update || changes.item() {
565                        let item = self.clerk.item(data, token);
566                        cx.update(w.item.as_node(item));
567                    }
568
569                    if rect_update {
570                        w.item
571                            .set_rect(&mut cx.size_cx(), solver.rect(cell), self.align_hints);
572                    }
573                }
574            }
575
576            self.token_update = Update::None;
577            self.rect_update = false;
578
579            let dur = (Instant::now() - time).as_micros();
580            log::debug!(
581                target: "kas_perf::view::grid_view",
582                "map_view_widgets {}×{} widgets in {dur}μs",
583                end.col - start.col,
584                end.row - start.row,
585            );
586        }
587    }
588
589    impl Layout for Self {
590        fn size_rules(&mut self, cx: &mut SizeCx, mut axis: AxisInfo) -> SizeRules {
591            // We use an invisible frame for highlighting selections, drawing into the margin
592            let inner_margin = if self.sel_style.is_external() {
593                cx.inner_margins().extract(axis)
594            } else {
595                (0, 0)
596            };
597            let frame = kas::layout::FrameRules::new(0, inner_margin, (0, 0));
598
599            let other = axis
600                .other()
601                .map(|_| self.child_size.extract(axis.flipped()));
602            axis = AxisInfo::new(axis.is_vertical(), other);
603
604            let mut rules = SizeRules::EMPTY;
605            for w in self.widgets.iter_mut() {
606                if w.token.is_some() || w.is_mock {
607                    let child_rules = w.item.size_rules(cx, axis);
608                    rules = rules.max(child_rules);
609                }
610            }
611
612            // Always use min child size
613            let size = rules.min_size().max(1);
614            self.child_size.set_component(axis, size);
615
616            let m = rules.margins();
617            let inter_margin = m.0.max(m.1).max(inner_margin.0).max(inner_margin.1);
618            let stretch = rules.stretch();
619            let m = rules.margins();
620            self.child_inter_margin
621                .set_component(axis, inter_margin.cast());
622            let inter_margin: i32 = inter_margin.cast();
623
624            let min_len = 2;
625            let ideal_len = i32::conv(match axis.is_vertical() {
626                false => self.ideal_len.col,
627                true => self.ideal_len.row,
628            });
629            let min = min_len * size + (min_len - 1) * inter_margin;
630            let ideal = ideal_len * size + (ideal_len - 1) * inter_margin;
631            let rules = SizeRules::new(min, ideal, stretch.max(Stretch::High)).with_margins(m);
632
633            let (rules, offset, size) = frame.surround(rules);
634            self.frame_offset.set_component(axis, offset);
635            self.frame_size.set_component(axis, size);
636            rules
637        }
638
639        fn set_rect(&mut self, cx: &mut SizeCx, rect: Rect, hints: AlignHints) {
640            self.core.set_rect(rect);
641            self.align_hints = hints;
642
643            let skip = self.child_size + self.child_inter_margin;
644            if skip.0 == 0 || skip.1 == 0 {
645                self.alloc_len = GridIndex::ZERO;
646                return;
647            }
648            let vis_len = (rect.size + skip - Size::splat(1)).cwise_div(skip) + Size::splat(1);
649            let req_widgets = usize::conv(vis_len.0) * usize::conv(vis_len.1);
650
651            self.alloc_len = GridIndex {
652                col: vis_len.0.cast(),
653                row: vis_len.1.cast(),
654            };
655
656            let avail_widgets = self.widgets.len();
657            if avail_widgets < req_widgets {
658                log::debug!(
659                    "set_rect: allocating widgets (old len = {avail_widgets}, new = {req_widgets})",
660                );
661                self.widgets.resize_with(req_widgets, || WidgetData {
662                    token: None,
663                    is_mock: false,
664                    item: GridCell::new(self.driver.make(&C::Key::default())),
665                });
666            }
667
668            // Call set_rect on children. (This might sometimes be unnecessary,
669            // except that the Layout::set_rect specification requires this
670            // action and we cannot guarantee that the requested
671            // TIMER_UPDATE_WIDGETS event will be immediately.)
672            let solver = self.position_solver();
673            for row in solver.first_data.row..solver.first_data.row + solver.cur_len.row {
674                for col in solver.first_data.col..solver.first_data.col + solver.cur_len.col {
675                    let cell = GridIndex { col, row };
676                    let i = solver.data_to_child(cell);
677                    let w = &mut self.widgets[i];
678                    if w.token.is_some() {
679                        w.item.set_rect(cx, solver.rect(cell), self.align_hints);
680                    }
681                }
682            }
683
684            // Also queue a call to map_view_widgets since ranges may have changed
685            self.rect_update = true;
686            cx.request_frame_timer(self.id(), TIMER_UPDATE_WIDGETS);
687        }
688    }
689
690    impl Viewport for Self {
691        fn content_size(&self) -> Size {
692            let m = self.child_inter_margin;
693            let step = self.child_size + m;
694            let data_len = self.data_len;
695            Size(
696                step.0 * i32::conv(data_len.col) - m.0,
697                step.1 * i32::conv(data_len.row) - m.1,
698            )
699            .max(Size::ZERO)
700        }
701
702        fn set_offset(&mut self, _: &mut SizeCx, _: Rect, offset: Offset) {
703            // NOTE: we assume that the viewport is close enough to self.rect()
704            // that prepared widgets will suffice
705            self.offset = offset;
706        }
707
708        fn update_offset(&mut self, cx: &mut ConfigCx, data: &Self::Data, _: Rect, offset: Offset) {
709            self.offset = offset;
710            if self.immediate_scroll_update {
711                self.immediate_scroll_update = false;
712                self.post_scroll(cx, data);
713            } else {
714                // NOTE: using a frame timer instead of immediate update is an
715                // optimization (for high-poll-rate mice) but not essential.
716                cx.request_frame_timer(self.id(), TIMER_UPDATE_WIDGETS);
717            }
718        }
719
720        fn draw_with_offset(&self, mut draw: DrawCx, viewport: Rect, offset: Offset) {
721            // We use a new pass to clip and offset scrolled content:
722            let offset = offset + self.virtual_offset;
723            let rect = viewport + offset;
724            let num = self.cur_end();
725            draw.with_clip_region(viewport, offset, |mut draw| {
726                for child in &self.widgets[..num] {
727                    if let Some(key) = child.key() {
728                        // Note: we don't know which widgets within 0..num are
729                        // visible, so check intersection before drawing:
730                        if rect.intersection(&child.item.rect()).is_some() {
731                            if self.selection.contains(key) {
732                                draw.selection(child.item.rect(), self.sel_style);
733                            }
734                            child.item.draw(draw.re());
735                        }
736                    }
737                }
738            });
739        }
740    }
741
742    impl Tile for Self {
743        fn role(&self, _: &mut dyn RoleCx) -> Role<'_> {
744            Role::Grid {
745                columns: self.len_is_known.then(|| self.data_len.col.cast()),
746                rows: self.len_is_known.then(|| self.data_len.row.cast()),
747            }
748        }
749
750        #[inline]
751        fn child_indices(&self) -> ChildIndices {
752            ChildIndices::range(0..self.cur_end())
753        }
754        fn get_child(&self, index: usize) -> Option<&dyn Tile> {
755            self.widgets
756                .get(index)
757                .filter(|w| w.token.is_some())
758                .map(|w| w.item.as_tile())
759        }
760        fn find_child_index(&self, id: &Id) -> Option<usize> {
761            let key = C::Key::reconstruct_key(self.id_ref(), id);
762            if key.is_some() {
763                let num = self.cur_end();
764                for (i, w) in self.widgets[..num].iter().enumerate() {
765                    if key.as_ref() == w.key() {
766                        return Some(i);
767                    }
768                }
769            }
770            None
771        }
772
773        fn nav_next(&self, reverse: bool, from: Option<usize>) -> Option<usize> {
774            if self.data_len.col == 0 || self.data_len.row == 0 {
775                return None;
776            }
777
778            let solver = self.position_solver();
779            let cell = if V::TAB_NAVIGABLE {
780                let first_data = self.first_data;
781                let skip = self.child_size + self.child_inter_margin;
782                let stride = self.rect().size.cwise_div(skip);
783                let last_visible = GridIndex {
784                    col: (first_data.col + u32::conv(stride.0)).min(self.data_len.col - 1),
785                    row: (first_data.row + u32::conv(stride.1)).min(self.data_len.row - 1),
786                };
787                if let Some(index) = from {
788                    let cell = solver.child_to_data(index);
789                    if !reverse {
790                        if cell.col + 1 < last_visible.col {
791                            GridIndex {
792                                col: cell.col + 1,
793                                row: cell.row,
794                            }
795                        } else if cell.row + 1 < last_visible.row {
796                            GridIndex {
797                                col: 0,
798                                row: cell.row + 1,
799                            }
800                        } else {
801                            return None;
802                        }
803                    } else {
804                        if cell.col > first_data.col {
805                            GridIndex {
806                                col: cell.col - 1,
807                                row: cell.row,
808                            }
809                        } else if cell.row > first_data.row {
810                            GridIndex {
811                                col: self.data_len.col - 1,
812                                row: cell.row - 1,
813                            }
814                        } else {
815                            return None;
816                        }
817                    }
818                } else if !reverse {
819                    first_data
820                } else {
821                    last_visible
822                }
823            } else {
824                if from.is_some() {
825                    return None;
826                } else {
827                    self.last_focus
828                }
829            };
830
831            let index = solver.data_to_child(cell);
832            self.get_child(index).is_some().then_some(index)
833        }
834
835        #[inline]
836        fn translation(&self, _: usize) -> Offset {
837            self.virtual_offset
838        }
839    }
840
841    impl Events for Self {
842        #[inline]
843        fn make_child_id(&mut self, _: usize) -> Id {
844            // We configure children in map_view_widgets and do not want this method to be called
845            unimplemented!()
846        }
847
848        fn probe(&self, coord: Coord) -> Id {
849            let num = self.cur_end();
850            let coord = coord + self.translation(0);
851            for child in &self.widgets[..num] {
852                if child.token.is_some()
853                    && let Some(id) = child.item.try_probe(coord)
854                {
855                    return id;
856                }
857            }
858            self.id()
859        }
860
861        fn configure(&mut self, cx: &mut ConfigCx) {
862            cx.register_nav_fallback(self.id());
863
864            if self.widgets.is_empty() {
865                // Ensure alloc_len > 0 for initial sizing
866                self.child_size = Size::splat(1); // hack: avoid div by 0
867
868                let len = self.ideal_len.col * self.ideal_len.row;
869                self.widgets.resize_with(len.cast(), || WidgetData {
870                    token: None,
871                    is_mock: false,
872                    item: GridCell::new(self.driver.make(&C::Key::default())),
873                });
874                self.alloc_len = self.ideal_len;
875            } else {
876                // Force reconfiguration:
877                for w in &mut self.widgets {
878                    w.token = None;
879                }
880            }
881
882            self.token_update = Update::Configure;
883            // Self::update() will be called next
884        }
885
886        fn update(&mut self, cx: &mut ConfigCx, data: &C::Data) {
887            let changes = self.clerk.update(cx, self.id(), self.view_range(), data);
888            if self.token_update != Update::None || changes != Changes::None {
889                self.handle_update(cx, data, changes, true);
890            } else {
891                let end = self.cur_end();
892                for w in &mut self.widgets[..end] {
893                    if let Some(ref token) = w.token {
894                        let item = self.clerk.item(data, token);
895                        cx.update(w.item.as_node(item));
896                    }
897                }
898            }
899
900            let id = self.id();
901            if self.cur_len == GridIndex::ZERO
902                && let Some(w) = self.widgets.get_mut(0)
903                && w.token.is_none()
904                && !w.is_mock
905                && let Some(item) = self.clerk.mock_item(data)
906            {
907                // Construct a mock widget for initial sizing
908                cx.configure(w.item.as_node(&item), id);
909                w.is_mock = true;
910            }
911        }
912
913        #[inline]
914        fn recurse_indices(&self) -> ChildIndices {
915            ChildIndices::none()
916        }
917
918        fn child_nav_focus(&mut self, cx: &mut EventCx, _: Id) {
919            if let Some(index) = cx.last_child()
920                && self.get_child(index).is_some()
921            {
922                let solver = self.position_solver();
923                self.last_focus = solver.child_to_data(index);
924            }
925        }
926
927        fn handle_event(&mut self, cx: &mut EventCx, data: &C::Data, event: Event) -> IsUsed {
928            match event {
929                Event::Command(cmd, _) => {
930                    let len = self.data_len;
931                    if len == GridIndex::ZERO {
932                        return Unused;
933                    }
934                    let (last_col, last_row) = (len.col.wrapping_sub(1), len.row.wrapping_sub(1));
935
936                    let row_len = self.cur_len.row;
937                    let solver = self.position_solver();
938                    let cell = match cx.nav_focus().and_then(|id| self.find_child_index(id)) {
939                        Some(index) => solver.child_to_data(index),
940                        None => return Unused,
941                    };
942                    let (ci, ri) = (cell.col, cell.row);
943
944                    use Command as C;
945                    let data_index = match cmd {
946                        C::DocHome => Some((0, 0)),
947                        C::DocEnd => Some((last_col, last_row)),
948                        C::Home => Some((0, ri)),
949                        C::End => Some((last_col, ri)),
950                        C::Left | C::WordLeft if ci > 0 => Some((ci - 1, ri)),
951                        C::Up if ri > 0 => Some((ci, ri - 1)),
952                        C::Right | C::WordRight if ci < last_col => Some((ci + 1, ri)),
953                        C::Down if ri < last_row => Some((ci, ri + 1)),
954                        C::PageUp if ri > 0 => Some((ci, ri.saturating_sub(row_len / 2))),
955                        C::PageDown if ri < last_row => {
956                            Some((ci, (ri + row_len / 2).min(last_row)))
957                        }
958                        // TODO: C::ViewUp, ...
959                        _ => None,
960                    };
961                    if let Some((col, row)) = data_index {
962                        let cell = GridIndex { col, row };
963                        // Set nav focus and update scroll position
964                        let rect = solver.rect(cell) - self.virtual_offset;
965                        cx.set_scroll(Scroll::Rect(rect));
966
967                        let index = solver.data_to_child(cell);
968                        let w = &self.widgets[index];
969                        if w.item.index == cell && w.token.is_some() {
970                            cx.next_nav_focus(w.item.id(), false, FocusSource::Key);
971                        } else {
972                            self.immediate_scroll_update = true;
973                            cx.send(self.id(), FocusGridCell(cell));
974                        }
975                        Used
976                    } else {
977                        Unused
978                    }
979                }
980                Event::Timer(TIMER_UPDATE_WIDGETS) => {
981                    self.post_scroll(cx, data);
982                    Used
983                }
984                event => match self.click.handle(cx, self.id(), event) {
985                    ClickInputAction::Used => Used,
986                    ClickInputAction::Unused => Unused,
987                    ClickInputAction::ClickStart { .. } => {
988                        if let Some(index) = cx.last_child() {
989                            self.press_target =
990                                self.widgets[index].key().map(|k| (index, k.clone()));
991                        }
992                        Used
993                    }
994                    ClickInputAction::ClickEnd { coord, success } => {
995                        if let Some((index, ref key)) = self.press_target {
996                            let w = &mut self.widgets[index];
997                            if success
998                                && !matches!(self.sel_mode, SelectionMode::None)
999                                && w.key() == Some(key)
1000                                && w.item.rect().contains(coord + self.translation(0))
1001                            {
1002                                cx.push(kas::messages::Select);
1003                            }
1004                        }
1005                        Used
1006                    }
1007                },
1008            }
1009        }
1010
1011        fn handle_messages(&mut self, cx: &mut EventCx, data: &C::Data) {
1012            if let Some(FocusGridCell(cell)) = cx.try_pop() {
1013                let solver = self.position_solver();
1014                let index = solver.data_to_child(cell);
1015                let w = &self.widgets[index];
1016                if w.item.index == cell && w.token.is_some() {
1017                    cx.next_nav_focus(w.item.id(), false, FocusSource::Key);
1018                } else {
1019                    log::error!("GridView failed to set focus: data item {cell:?} not in view");
1020                }
1021            }
1022
1023            let mut opt_key = None;
1024            if let Some(index) = cx.last_child() {
1025                // Message is from a child
1026                if let Some(token) = self.widgets.get_mut(index).and_then(|w| w.token.as_mut()) {
1027                    opt_key = Some(Borrow::<C::Key>::borrow(token).clone());
1028                } else {
1029                    return; // should be unreachable
1030                };
1031            }
1032
1033            if let Some(kas::messages::Select) = cx.try_pop() {
1034                let key = match opt_key {
1035                    Some(key) => key,
1036                    None => match self.press_target.as_ref() {
1037                        Some((_, k)) => k.clone(),
1038                        None => return,
1039                    },
1040                };
1041                opt_key = None;
1042
1043                match self.sel_mode {
1044                    SelectionMode::None => (),
1045                    SelectionMode::Single => {
1046                        cx.redraw();
1047                        self.selection.clear();
1048                        self.selection.insert(key.clone());
1049                        cx.push(SelectionMsg::Select(key));
1050                    }
1051                    SelectionMode::Multiple => {
1052                        cx.redraw();
1053                        if self.selection.remove(&key) {
1054                            cx.push(SelectionMsg::Deselect(key));
1055                        } else {
1056                            self.selection.insert(key.clone());
1057                            cx.push(SelectionMsg::Select(key));
1058                        }
1059                    }
1060                }
1061            }
1062
1063            let changes =
1064                self.clerk
1065                    .handle_messages(cx, self.id(), self.view_range(), data, opt_key);
1066            if changes != Changes::None {
1067                self.handle_update(cx, data, changes, false);
1068            }
1069        }
1070
1071        fn handle_scroll(&mut self, cx: &mut EventCx, _: &C::Data, scroll: Scroll) {
1072            cx.set_scroll(scroll - self.virtual_offset);
1073        }
1074    }
1075
1076    // Direct implementation of this trait outside of Kas code is not supported!
1077    impl Widget for Self {
1078        type Data = C::Data;
1079
1080        fn child_node<'n>(&'n mut self, data: &'n C::Data, index: usize) -> Option<Node<'n>> {
1081            if let Some(w) = self.widgets.get_mut(index)
1082                && let Some(ref token) = w.token
1083            {
1084                let item = self.clerk.item(data, token);
1085                return Some(w.item.as_node(item));
1086            }
1087
1088            None
1089        }
1090    }
1091}
1092
1093#[derive(Debug)]
1094struct PositionSolver {
1095    pos_start: Coord,
1096    skip: Size,
1097    size: Size,
1098    first_data: GridIndex,
1099    cur_len: GridIndex,
1100}
1101
1102impl PositionSolver {
1103    /// Map a data index to child index
1104    fn data_to_child(&self, cell: GridIndex) -> usize {
1105        let col_len: usize = self.cur_len.col.cast();
1106        let row_len: usize = self.cur_len.row.cast();
1107        (cell.col as usize % col_len) + (cell.row as usize % row_len) * col_len
1108    }
1109
1110    /// Map a child index to a data index
1111    fn child_to_data(&self, index: usize) -> GridIndex {
1112        let col_len = self.cur_len.col;
1113        let row_len = self.cur_len.row;
1114        let ci: u32 = (index % usize::conv(col_len)).cast();
1115        let ri: u32 = (index / usize::conv(col_len)).cast();
1116        let mut col = (self.first_data.col / col_len) * col_len + ci;
1117        let mut row = (self.first_data.row / row_len) * row_len + ri;
1118        if col < self.first_data.col {
1119            col += col_len;
1120        }
1121        if row < self.first_data.row {
1122            row += row_len;
1123        }
1124        GridIndex { col, row }
1125    }
1126
1127    /// Rect of data item (ci, ri)
1128    fn rect(&self, GridIndex { col, row }: GridIndex) -> Rect {
1129        let pos = self.pos_start + self.skip.cwise_mul(Size(col.cast(), row.cast()));
1130        Rect::new(pos, self.size)
1131    }
1132}