kas_view/
matrix_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//! Matrix view controller
7
8use super::*;
9use kas::event::components::ScrollComponent;
10use kas::event::{Command, FocusSource, Scroll};
11use kas::layout::solve_size_rules;
12use kas::prelude::*;
13use kas::theme::SelectionStyle;
14use kas::NavAdvance;
15#[allow(unused)] // doc links
16use kas_widgets::ScrollBars;
17use linear_map::set::LinearSet;
18use std::borrow::Borrow;
19use std::time::Instant;
20
21#[derive(Clone, Copy, Debug, Default)]
22struct Dim {
23    rows: i32,
24    cols: i32,
25}
26
27#[derive(Clone, Debug, Default)]
28struct WidgetData<K, W> {
29    key: Option<K>,
30    widget: W,
31}
32
33impl_scope! {
34    /// View controller for 2D indexable data (matrix)
35    ///
36    /// This widget generates a view over a list of data items via the
37    /// [`MatrixData`] trait. "View widgets" are constructed via a [`Driver`] to
38    /// represent visible data items. These view widgets are reassigned as
39    /// required when the matrix is scrolled, keeping the number of widgets in
40    /// use roughly proportional to the number of data items within the view.
41    ///
42    /// Each view widget has an [`Id`] corresponding to its current data
43    /// item, and may handle events and emit messages like other widegts.
44    /// See [`Driver`] documentation for more on event handling.
45    ///
46    /// This widget is [`Scrollable`], supporting keyboard, wheel and drag
47    /// scrolling. You may wish to wrap this widget with [`ScrollBars`].
48    ///
49    /// Optionally, data items may be selected; see [`Self::set_selection_mode`].
50    /// If enabled, [`SelectionMsg`] messages are reported; view widgets may
51    /// emit [`kas::messages::Select`] to have themselves be selected.
52    #[derive(Clone, Debug)]
53    #[widget]
54    pub struct MatrixView<A: MatrixData, V: Driver<A::Item, A>> {
55        core: widget_core!(),
56        frame_offset: Offset,
57        frame_size: Size,
58        driver: V,
59        widgets: Vec<WidgetData<A::Key, V::Widget>>,
60        align_hints: AlignHints,
61        ideal_len: Dim,
62        alloc_len: Dim,
63        data_len: Size,
64        /// The number of (cols, rows) in use
65        cur_len: (u32, u32),
66        first_data: (u32, u32),
67        child_size_min: Size,
68        child_size_ideal: Size,
69        child_inter_margin: Size,
70        child_size: Size,
71        scroll: ScrollComponent,
72        sel_mode: SelectionMode,
73        sel_style: SelectionStyle,
74        // TODO(opt): replace selection list with RangeOrSet type?
75        selection: LinearSet<A::Key>,
76        press_target: Option<(usize, A::Key)>,
77    }
78
79    impl Self {
80        /// Construct a new instance
81        pub fn new(driver: V) -> Self {
82            MatrixView {
83                core: Default::default(),
84                frame_offset: Default::default(),
85                frame_size: Default::default(),
86                driver,
87                widgets: Default::default(),
88                align_hints: Default::default(),
89                ideal_len: Dim { cols: 3, rows: 5 },
90                alloc_len: Dim::default(),
91                data_len: Size::ZERO,
92                cur_len: (0, 0),
93                first_data: (0, 0),
94                child_size_min: Size::ZERO,
95                child_size_ideal: Size::ZERO,
96                child_inter_margin: Size::ZERO,
97                child_size: Size::ZERO,
98                scroll: Default::default(),
99                sel_mode: SelectionMode::None,
100                sel_style: SelectionStyle::Highlight,
101                selection: Default::default(),
102                press_target: None,
103            }
104        }
105
106        /// Get the current selection mode
107        pub fn selection_mode(&self) -> SelectionMode {
108            self.sel_mode
109        }
110        /// Set the current selection mode
111        ///
112        /// By default, selection is disabled. If enabled, items may be selected
113        /// and deselected via mouse-click/touch or via a view widget emitting
114        /// [`Select`].
115        ///
116        /// On selection and deselection, a [`SelectionMsg`] message is emitted.
117        /// This is not sent to [`Driver::on_messages`].
118        ///
119        /// The driver may trigger selection by emitting [`Select`] from
120        /// [`Driver::on_messages`]. The driver is not notified of selection
121        /// except via [`Select`] from view widgets. (TODO: reconsider this.)
122        ///
123        /// [`Select`]: kas::messages::Select
124        pub fn set_selection_mode(&mut self, mode: SelectionMode) -> Action {
125            self.sel_mode = mode;
126            match mode {
127                SelectionMode::None if !self.selection.is_empty() => {
128                    self.selection.clear();
129                    Action::REDRAW
130                }
131                SelectionMode::Single if self.selection.len() > 1 => {
132                    if let Some(first) = self.selection.iter().next().cloned() {
133                        self.selection.retain(|item| *item == first);
134                    }
135                    Action::REDRAW
136                }
137                _ => Action::empty(),
138            }
139        }
140        /// Set the selection mode (inline)
141        ///
142        /// See [`Self::set_selection_mode`] documentation.
143        #[must_use]
144        pub fn with_selection_mode(mut self, mode: SelectionMode) -> Self {
145            let _ = self.set_selection_mode(mode);
146            self
147        }
148
149        /// Get the current selection style
150        pub fn selection_style(&self) -> SelectionStyle {
151            self.sel_style
152        }
153        /// Set the current selection style
154        ///
155        /// By default, [`SelectionStyle::Highlight`] is used. Other modes may
156        /// add margin between elements.
157        pub fn set_selection_style(&mut self, style: SelectionStyle) -> Action {
158            let action = if style.is_external() != self.sel_style.is_external() {
159                Action::RESIZE
160            } else {
161                Action::empty()
162            };
163            self.sel_style = style;
164            action
165        }
166        /// Set the selection style (inline)
167        ///
168        /// See [`Self::set_selection_style`] documentation.
169        #[must_use]
170        pub fn with_selection_style(mut self, style: SelectionStyle) -> Self {
171            self.sel_style = style;
172            self
173        }
174
175        /// Read the list of selected entries
176        ///
177        /// With mode [`SelectionMode::Single`] this may contain zero or one entry;
178        /// use `selected_iter().next()` to extract only the first (optional) entry.
179        pub fn selected_iter(&'_ self) -> impl Iterator<Item = &'_ A::Key> + '_ {
180            self.selection.iter()
181        }
182
183        /// Check whether an entry is selected
184        pub fn is_selected(&self, key: &A::Key) -> bool {
185            self.selection.contains(key)
186        }
187
188        /// Clear all selected items
189        pub fn clear_selected(&mut self) -> Action {
190            if self.selection.is_empty() {
191                Action::empty()
192            } else {
193                self.selection.clear();
194                Action::REDRAW
195            }
196        }
197
198        /// Directly select an item
199        ///
200        /// Does nothing if [`Self::selection_mode`] is [`SelectionMode::None`].
201        /// Does not verify the validity of `key`.
202        /// Does not send [`SelectionMsg`] messages.
203        ///
204        /// Returns `Action::REDRAW` if newly selected, `Action::empty()` if
205        /// already selected. Fails if selection mode does not permit selection
206        /// or if the key is invalid.
207        pub fn select(&mut self, key: A::Key) -> Action {
208            match self.sel_mode {
209                SelectionMode::None => return Action::empty(),
210                SelectionMode::Single => self.selection.clear(),
211                _ => (),
212            }
213            match self.selection.insert(key) {
214                true => Action::REDRAW,
215                false => Action::empty(),
216            }
217        }
218
219        /// Directly deselect an item
220        ///
221        /// Returns `Action::REDRAW` if deselected, `Action::empty()` if not
222        /// previously selected or if the key is invalid.
223        pub fn deselect(&mut self, key: &A::Key) -> Action {
224            match self.selection.remove(key) {
225                true => Action::REDRAW,
226                false => Action::empty(),
227            }
228        }
229
230        /// Set the preferred number of items visible (inline)
231        ///
232        /// This affects the (ideal) size request and whether children are sized
233        /// according to their ideal or minimum size but not the minimum size.
234        #[must_use]
235        pub fn with_num_visible(mut self, cols: i32, rows: i32) -> Self {
236            self.ideal_len = Dim { cols, rows };
237            self
238        }
239
240        fn position_solver(&self) -> PositionSolver {
241            PositionSolver {
242                pos_start: self.core.rect.pos + self.frame_offset,
243                skip: self.child_size + self.child_inter_margin,
244                size: self.child_size,
245                first_data: self.first_data,
246                cur_len: self.cur_len,
247            }
248        }
249
250        fn update_widgets(&mut self, cx: &mut ConfigCx, data: &A) -> PositionSolver {
251            let time = Instant::now();
252
253            let offset = self.scroll_offset();
254            let skip = (self.child_size + self.child_inter_margin).max(Size(1, 1));
255            let data_len = data.len();
256            let col_len = data_len.0.min(self.alloc_len.cols.cast());
257            let row_len = data_len.1.min(self.alloc_len.rows.cast());
258            let first_col = usize::conv(u64::conv(offset.0) / u64::conv(skip.0))
259                .min(data_len.0 - col_len);
260            let first_row = usize::conv(u64::conv(offset.1) / u64::conv(skip.1))
261                .min(data_len.1 - row_len);
262            self.cur_len = (col_len.cast(), row_len.cast());
263            debug_assert!(self.num_children() <= self.widgets.len());
264            self.first_data = (first_row.cast(), first_col.cast());
265
266            let solver = self.position_solver();
267
268            let cols: Vec<_> = data
269                .col_iter_from(first_col, col_len)
270                .collect();
271            if cols.len() < col_len {
272                log::warn!(
273                    "{}: data.col_iter_vec_from({}, {}) yielded insufficient items (possibly incorrect data.len())", self.identify(),
274                    first_col,
275                    col_len,
276                );
277            }
278
279            let row_iter = data.row_iter_from(first_row, row_len);
280
281            let mut row_count = 0;
282            for (rn, row) in row_iter.enumerate() {
283                row_count += 1;
284                let ri = first_row + rn;
285                for (cn, col) in cols.iter().enumerate() {
286                    let ci = first_col + cn;
287                    let i = solver.data_to_child(ci, ri);
288                    let key = data.make_key(col, &row);
289                    let id = key.make_id(self.id_ref());
290                    let w = &mut self.widgets[i];
291                    if w.key.as_ref() != Some(&key) {
292                        self.driver.set_key(&mut w.widget, &key);
293
294                        if let Some(item) = data.borrow(&key) {
295                            cx.configure(w.widget.as_node(item.borrow()), id);
296
297                            w.key = Some(key);
298                            solve_size_rules(
299                                &mut w.widget,
300                                cx.size_cx(),
301                                Some(self.child_size.0),
302                                Some(self.child_size.1),
303                            );
304                        } else {
305                            w.key = None; // disables drawing and clicking
306                        }
307                    } else if let Some(item) = data.borrow(&key) {
308                        cx.update(w.widget.as_node(item.borrow()));
309                    }
310                    w.widget.set_rect(cx, solver.rect(ci, ri), self.align_hints);
311                }
312            }
313
314            if row_count < row_len {
315                log::warn!(
316                    "{}: data.row_iter_vec_from({}, {}) yielded insufficient items (possibly incorrect data.len())", self.identify(),
317                    first_row,
318                    row_len,
319                );
320            }
321
322            let dur = (Instant::now() - time).as_micros();
323            log::trace!(target: "kas_perf::view::matrix_view", "update_widgets: {dur}μs");
324            solver
325        }
326
327        fn update_content_size(&mut self, cx: &mut ConfigCx) {
328            let view_size = self.rect().size - self.frame_size;
329            let skip = self.child_size + self.child_inter_margin;
330            let content_size = (skip.cwise_mul(self.data_len) - self.child_inter_margin)
331                .max(Size::ZERO);
332            let action = self.scroll.set_sizes(view_size, content_size);
333            cx.action(self, action);
334        }
335    }
336
337    impl Scrollable for Self {
338        fn scroll_axes(&self, size: Size) -> (bool, bool) {
339            let m = self.child_inter_margin;
340            let step = self.child_size + m;
341            let content_size = (step.cwise_mul(self.data_len) - m).max(Size::ZERO);
342            (content_size.0 > size.0, content_size.1 > size.1)
343        }
344
345        #[inline]
346        fn max_scroll_offset(&self) -> Offset {
347            self.scroll.max_offset()
348        }
349
350        #[inline]
351        fn scroll_offset(&self) -> Offset {
352            self.scroll.offset()
353        }
354
355        #[inline]
356        fn set_scroll_offset(&mut self, cx: &mut EventCx, offset: Offset) -> Offset {
357            let action = self.scroll.set_offset(offset);
358            cx.action(&self, action);
359            cx.request_update(self.id(), false);
360            self.scroll.offset()
361        }
362    }
363
364    impl Layout for Self {
365        #[inline]
366        fn num_children(&self) -> usize {
367            usize::conv(self.cur_len.0) * usize::conv(self.cur_len.1)
368        }
369        fn get_child(&self, index: usize) -> Option<&dyn Layout> {
370            self.widgets.get(index).map(|w| w.widget.as_layout())
371        }
372        fn find_child_index(&self, id: &Id) -> Option<usize> {
373            let num = self.num_children();
374            let key = A::Key::reconstruct_key(self.id_ref(), id);
375            if key.is_some() {
376                self.widgets[0..num]
377                    .iter()
378                    .enumerate()
379                    .filter_map(|(i, w)| (key == w.key).then_some(i))
380                    .next()
381            } else {
382                None
383            }
384        }
385
386        fn size_rules(&mut self, sizer: SizeCx, mut axis: AxisInfo) -> SizeRules {
387            // We use an invisible frame for highlighting selections, drawing into the margin
388            let inner_margin = if self.sel_style.is_external() {
389                sizer.inner_margins().extract(axis)
390            } else {
391                (0, 0)
392            };
393            let frame = kas::layout::FrameRules::new(0, inner_margin, (0, 0));
394
395            let other = axis.other().map(|mut size| {
396                // Use same logic as in set_rect to find per-child size:
397                let other_axis = axis.flipped();
398                size -= self.frame_size.extract(other_axis);
399                let div = Size(self.ideal_len.cols, self.ideal_len.rows).extract(other_axis);
400                (size / div)
401                    .min(self.child_size_ideal.extract(other_axis))
402                    .max(self.child_size_min.extract(other_axis))
403            });
404            axis = AxisInfo::new(axis.is_vertical(), other);
405
406            let mut child_size_min = i32::MAX;
407            let mut rules = SizeRules::EMPTY;
408            for w in self.widgets.iter_mut() {
409                if w.key.is_some() {
410                    let child_rules = w.widget.size_rules(sizer.re(), axis);
411                    child_size_min = child_size_min.min(child_rules.min_size());
412                    rules = rules.max(child_rules);
413                }
414            }
415            self.child_size_min.set_component(axis, child_size_min);
416            self.child_size_ideal.set_component(axis, rules.ideal_size());
417
418            let m = rules.margins();
419            self.child_inter_margin.set_component(
420                axis,
421                m.0.max(m.1).max(inner_margin.0).max(inner_margin.1).cast(),
422            );
423
424            let ideal_len = match axis.is_vertical() {
425                false => self.ideal_len.cols,
426                true => self.ideal_len.rows,
427            };
428            rules.multiply_with_margin(2, ideal_len);
429            rules.set_stretch(rules.stretch().max(Stretch::High));
430
431            let (rules, offset, size) = frame.surround(rules);
432            self.frame_offset.set_component(axis, offset);
433            self.frame_size.set_component(axis, size);
434            rules
435        }
436
437        fn set_rect(&mut self, cx: &mut ConfigCx, rect: Rect, hints: AlignHints) {
438            self.core.rect = rect;
439            self.align_hints = hints;
440
441            // Widgets need configuring and updating: do so by updating self.
442            self.cur_len = (0, 0); // hack: prevent drawing in the mean-time
443            cx.request_update(self.id(), false);
444
445            let avail = rect.size - self.frame_size;
446            let child_size = Size(avail.0 / self.ideal_len.cols, avail.1 / self.ideal_len.rows)
447                .min(self.child_size_ideal)
448                .max(self.child_size_min);
449            self.child_size = child_size;
450            self.update_content_size(cx);
451
452            let skip = self.child_size + self.child_inter_margin;
453            if skip.0 == 0 || skip.1 == 0 {
454                self.alloc_len = Dim { cols: 0, rows: 0 };
455                return;
456            }
457            let vis_len = (rect.size + skip - Size::splat(1)).cwise_div(skip) + Size::splat(1);
458            let req_widgets = usize::conv(vis_len.0) * usize::conv(vis_len.1);
459
460            self.alloc_len = Dim {
461                cols: vis_len.0,
462                rows: vis_len.1,
463            };
464
465            let avail_widgets = self.widgets.len();
466            if avail_widgets < req_widgets {
467                log::debug!(
468                    "set_rect: allocating widgets (old len = {}, new = {})",
469                    avail_widgets,
470                    req_widgets
471                );
472                self.widgets.resize_with(req_widgets, || {
473                    WidgetData {
474                        key: None,
475                        widget: self.driver.make(&A::Key::default()),
476                    }
477                });
478            } else if req_widgets + 64 <= avail_widgets {
479                // Free memory (rarely useful?)
480                self.widgets.truncate(req_widgets);
481            }
482            debug_assert!(self.widgets.len() >= req_widgets);
483        }
484
485        #[inline]
486        fn translation(&self) -> Offset {
487            self.scroll_offset()
488        }
489
490        fn find_id(&mut self, coord: Coord) -> Option<Id> {
491            if !self.rect().contains(coord) {
492                return None;
493            }
494
495            let num = self.num_children();
496            let coord = coord + self.scroll.offset();
497            for child in &mut self.widgets[..num] {
498                if child.key.is_some() {
499                    if let Some(id) = child.widget.find_id(coord) {
500                        return Some(id);
501                    }
502                }
503            }
504            Some(self.id())
505        }
506
507        fn draw(&mut self, mut draw: DrawCx) {
508            let offset = self.scroll_offset();
509            let rect = self.rect() + offset;
510            let num = self.num_children();
511            draw.with_clip_region(self.core.rect, offset, |mut draw| {
512                for child in &mut self.widgets[..num] {
513                    if let Some(ref key) = child.key {
514                        // Note: we don't know which widgets within 0..num are
515                        // visible, so check intersection before drawing:
516                        if rect.intersection(&child.widget.rect()).is_some() {
517                            if self.selection.contains(key) {
518                                draw.selection(child.widget.rect(), self.sel_style);
519                            }
520                            draw.recurse(&mut child.widget);
521                        }
522                    }
523                }
524            });
525        }
526    }
527
528    impl Events for Self {
529        #[inline]
530        fn make_child_id(&mut self, _: usize) -> Id {
531            // We configure children in update_widgets and do not want this method to be called
532            unimplemented!()
533        }
534
535        fn configure(&mut self, cx: &mut ConfigCx) {
536            if self.widgets.is_empty() {
537                // Initial configure: ensure some widgets are loaded to allow
538                // better sizing of self.
539                self.child_size = Size::splat(1); // hack: avoid div by 0
540
541                let len = self.ideal_len.cols * self.ideal_len.rows;
542                self.widgets.resize_with(len.cast(), || {
543                    WidgetData {
544                        key: None,
545                        widget: self.driver.make(&A::Key::default()),
546                    }
547                });
548                self.alloc_len = self.ideal_len;
549            }
550
551            cx.register_nav_fallback(self.id());
552        }
553
554        fn configure_recurse(&mut self, _: &mut ConfigCx, _: &Self::Data) {}
555
556        fn update(&mut self, cx: &mut ConfigCx, data: &A) {
557            self.selection.retain(|key| data.contains_key(key));
558
559            let (d_cols, d_rows) = data.len();
560            let data_len = Size(d_cols.cast(), d_rows.cast());
561            if data_len != self.data_len {
562                self.data_len = data_len;
563                // We must call at least SET_RECT to update scrollable region
564                // RESIZE allows recalculation of child widget size which may
565                // have been zero if no data was initially available!
566                cx.resize(&self);
567            }
568
569            self.update_widgets(cx, data);
570            self.update_content_size(cx);
571        }
572
573        fn update_recurse(&mut self, _: &mut ConfigCx, _: &Self::Data) {}
574
575        fn handle_event(&mut self, cx: &mut EventCx, data: &A, event: Event) -> IsUsed {
576            let is_used = match event {
577                Event::Command(cmd, _) => {
578                    if data.is_empty() {
579                        return Unused;
580                    }
581                    let (d_cols, d_rows) = data.len();
582                    let (last_col, last_row) = (d_cols.wrapping_sub(1), d_rows.wrapping_sub(1));
583
584                    let row_len: usize = self.cur_len.1.cast();
585                    let mut solver = self.position_solver();
586                    let (ci, ri) = match cx.nav_focus().and_then(|id| self.find_child_index(id)) {
587                        Some(index) => solver.child_to_data(index),
588                        None => return Unused,
589                    };
590
591                    use Command as C;
592                    let data_index = match cmd {
593                        C::DocHome => Some((0, 0)),
594                        C::DocEnd => Some((last_col, last_row)),
595                        C::Home => Some((0, ri)),
596                        C::End => Some((last_col, ri)),
597                        C::Left | C::WordLeft if ci > 0 => Some((ci - 1, ri)),
598                        C::Up if ri > 0 => Some((ci, ri - 1)),
599                        C::Right | C::WordRight if ci < last_col => Some((ci + 1, ri)),
600                        C::Down if ri < last_row => Some((ci, ri + 1)),
601                        C::PageUp if ri > 0 => Some((ci, ri.saturating_sub(row_len / 2))),
602                        C::PageDown if ri < last_row => {
603                            Some((ci, (ri + row_len / 2).min(last_row)))
604                        }
605                        // TODO: C::ViewUp, ...
606                        _ => None,
607                    };
608                    return if let Some((ci, ri)) = data_index {
609                        // Set nav focus and update scroll position
610                        let action = self.scroll.focus_rect(cx, solver.rect(ci, ri), self.core.rect);
611                        if !action.is_empty() {
612                            cx.action(&self, action);
613                            solver = self.update_widgets(&mut cx.config_cx(), data);
614                        }
615
616                        let index = solver.data_to_child(ci, ri);
617                        #[cfg(debug_assertions)]
618                        {
619                            let rk = data
620                                .row_iter_from(ri, 1)
621                                .next()
622                                .expect("data row len > data.row_iter_vec len");
623                            let ck = data
624                                .col_iter_from(ci, 1)
625                                .next()
626                                .expect("data col len > data.col_iter_vec len");
627                            let key = data.make_key(&ck, &rk);
628                            assert_eq!(
629                                self.widgets[index].widget.id(),
630                                key.make_id(self.id_ref()),
631                            );
632                        }
633
634                        cx.next_nav_focus(self.widgets[index].widget.id(), false, FocusSource::Key);
635                        Used
636                    } else {
637                        Unused
638                    };
639                }
640                Event::PressStart { ref press } if
641                    press.is_primary() && cx.config().event().mouse_nav_focus() =>
642                {
643                    if let Some(index) = cx.last_child() {
644                        self.press_target = self.widgets[index].key.clone().map(|k| (index, k));
645                    }
646                    if let Some((index, ref key)) = self.press_target {
647                        let w = &mut self.widgets[index];
648                        if w.key.as_ref().map(|k| k == key).unwrap_or(false) {
649                            cx.next_nav_focus(w.widget.id(), false, FocusSource::Pointer);
650                        }
651                    }
652
653                    // Press may also be grabbed by scroll component (replacing
654                    // this). Either way we can select on PressEnd.
655                    press.grab(self.id()).with_cx(cx)
656                }
657                Event::PressEnd { ref press, success } if press.is_primary() => {
658                    if let Some((index, ref key)) = self.press_target {
659                        let w = &mut self.widgets[index];
660                        if success
661                            && !matches!(self.sel_mode, SelectionMode::None)
662                            && !self.scroll.is_gliding()
663                            && w.key.as_ref().map(|k| k == key).unwrap_or(false)
664                            && w.widget.rect().contains(press.coord + self.scroll.offset())
665                        {
666                            cx.push(kas::messages::Select);
667                        }
668                    }
669                    Used
670                }
671                _ => Unused, // fall through to scroll handler
672            };
673
674            let (moved, used_by_sber) = self
675                .scroll
676                .scroll_by_event(cx, event, self.id(), self.core.rect);
677            if moved {
678                self.update_widgets(&mut cx.config_cx(), data);
679            }
680            is_used | used_by_sber
681        }
682
683        fn handle_messages(&mut self, cx: &mut EventCx, data: &A) {
684            let key;
685            if let Some(index) = cx.last_child() {
686                let w = &mut self.widgets[index];
687                key = match w.key.clone() {
688                    Some(k) => k,
689                    None => return,
690                };
691
692                self.driver.on_messages(cx, &mut w.widget, data, &key);
693            } else {
694                // Message is from self
695                key = match self.press_target.clone() {
696                    Some((_, k)) => k,
697                    None => return,
698                };
699            }
700
701            if let Some(kas::messages::Select) = cx.try_pop() {
702                match self.sel_mode {
703                    SelectionMode::None => (),
704                    SelectionMode::Single => {
705                        cx.redraw(&self);
706                        self.selection.clear();
707                        self.selection.insert(key.clone());
708                        cx.push(SelectionMsg::Select(key));
709                    }
710                    SelectionMode::Multiple => {
711                        cx.redraw(&self);
712                        if self.selection.remove(&key) {
713                            cx.push(SelectionMsg::Deselect(key));
714                        } else {
715                            self.selection.insert(key.clone());
716                            cx.push(SelectionMsg::Select(key));
717                        }
718                    }
719                }
720            }
721        }
722
723        fn handle_scroll(&mut self, cx: &mut EventCx, data: &A, scroll: Scroll) {
724            let act = self.scroll.scroll(cx, self.rect(), scroll);
725            self.update_widgets(&mut cx.config_cx(), data);
726            cx.action(self, act);
727        }
728    }
729
730    // Direct implementation of this trait outside of Kas code is not supported!
731    impl Widget for Self {
732        type Data = A;
733
734        fn for_child_node(
735            &mut self,
736            data: &A,
737            index: usize,
738            closure: Box<dyn FnOnce(Node<'_>) + '_>,
739        ) {
740            if let Some(w) = self.widgets.get_mut(index) {
741                if let Some(ref key) = w.key {
742                    if let Some(item) = data.borrow(key) {
743                        closure(w.widget.as_node(item.borrow()));
744                    }
745                }
746            }
747        }
748
749        fn _configure(&mut self, cx: &mut ConfigCx, data: &A, id: Id) {
750            self.core.id = id;
751            #[cfg(debug_assertions)]
752            self.core.status.configure(&self.core.id);
753
754            self.configure(cx);
755            self.update(cx, data);
756        }
757
758        fn _update(&mut self, cx: &mut ConfigCx, data: &A) {
759            #[cfg(debug_assertions)]
760            self.core.status.update(&self.core.id);
761
762            self.update(cx, data);
763        }
764
765        fn _send(
766            &mut self,
767            cx: &mut EventCx,
768            data: &A,
769            id: Id,
770            event: Event,
771        ) -> IsUsed {
772            kas::impls::_send(self, cx, data, id, event)
773        }
774
775        fn _replay(&mut self, cx: &mut EventCx, data: &A, id: Id) {
776            kas::impls::_replay(self, cx, data, id);
777        }
778
779        // Non-standard implementation to allow mapping new children
780        fn _nav_next(
781            &mut self,
782            cx: &mut ConfigCx,
783            data: &A,
784            focus: Option<&Id>,
785            advance: NavAdvance,
786        ) -> Option<Id> {
787            if cx.is_disabled(self.id_ref()) || self.cur_len == (0, 0) {
788                return None;
789            }
790
791            let mut child = focus.and_then(|id| self.find_child_index(id));
792
793            if let Some(index) = child {
794                if let Some(Some(id)) = self.as_node(data)
795                    .for_child(index, |mut w| w._nav_next(cx, focus, advance))
796                {
797                    return Some(id);
798                }
799            }
800
801            let reverse = match advance {
802                NavAdvance::None => return None,
803                NavAdvance::Forward(_) => false,
804                NavAdvance::Reverse(_) => true,
805            };
806
807            let mut starting_child = child;
808            loop {
809                let mut solver = self.position_solver();
810                let (d_cols, d_rows) = data.len();
811                let (ci, ri) = if let Some(index) = child {
812                    let (ci, ri) = solver.child_to_data(index);
813                    if !reverse {
814                        if ci + 1 < d_cols {
815                            (ci + 1, ri)
816                        } else if ri + 1 < d_rows {
817                            (0, ri + 1)
818                        } else {
819                            return None;
820                        }
821                    } else {
822                        if ci > 0 {
823                            (ci - 1, ri)
824                        } else if ri > 0 {
825                            (d_cols - 1, ri - 1)
826                        } else {
827                            return None;
828                        }
829                    }
830                } else if !reverse {
831                    (0, 0)
832                } else {
833                    (d_cols - 1, d_rows - 1)
834                };
835
836                let action = self.scroll.self_focus_rect(solver.rect(ci, ri), self.core.rect);
837                if !action.is_empty() {
838                    cx.action(&self, action);
839                    solver = self.update_widgets(cx, data);
840                }
841
842                let index = solver.data_to_child(ci, ri);
843                if let Some(Some(id)) = self.as_node(data)
844                    .for_child(index, |mut w| w._nav_next(cx, focus, advance))
845                {
846                    return Some(id);
847                }
848
849                child = Some(index);
850                if starting_child == child {
851                    return None;
852                } else if starting_child.is_none() {
853                    starting_child = child;
854                }
855            }
856        }
857    }
858}
859
860#[derive(Debug)]
861struct PositionSolver {
862    pos_start: Coord,
863    skip: Size,
864    size: Size,
865    first_data: (u32, u32),
866    cur_len: (u32, u32),
867}
868
869impl PositionSolver {
870    /// Map a data index to child index
871    fn data_to_child(&self, ci: usize, ri: usize) -> usize {
872        let col_len: usize = self.cur_len.0.cast();
873        let row_len: usize = self.cur_len.1.cast();
874        (ci % col_len) + (ri % row_len) * col_len
875    }
876
877    /// Map a child index to `(col_index, row_index)`
878    fn child_to_data(&self, index: usize) -> (usize, usize) {
879        let col_len: usize = self.cur_len.0.cast();
880        let row_len: usize = self.cur_len.1.cast();
881        let first_col: usize = self.first_data.0.cast();
882        let first_row: usize = self.first_data.1.cast();
883        let col_start = (first_col / col_len) * col_len;
884        let row_start = (first_row / row_len) * row_len;
885        let mut col_index = col_start + index % col_len;
886        let mut row_index = row_start + index / col_len;
887        if col_index < first_col {
888            col_index += col_len;
889        }
890        if row_index < first_row {
891            row_index += row_len;
892        }
893        (col_index, row_index)
894    }
895
896    /// Rect of data item (ci, ri)
897    fn rect(&self, ci: usize, ri: usize) -> Rect {
898        let pos = self.pos_start + self.skip.cwise_mul(Size(ci.cast(), ri.cast()));
899        Rect::new(pos, self.size)
900    }
901}