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