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