Skip to main content

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