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