Skip to main content

ferroui_controls/primitives/
selecting_items_control.rs

1use super::{ItemSelectionEventTriggers, TemplateAppliedEventArgs, TemplatedControlImpl, TemplatedControlImplExt, TextSearch};
2use crate::assigned_binding::AssignedBinding;
3use crate::items_source::{items_equal, ItemsChangedEventArgs};
4use crate::navigable_containers::as_navigable_container;
5use crate::selection::{
6    ISelectionModel, InternalSelectionModel, SelectionModelExtensions, SelectionModelSelectionChangedEventArgs,
7};
8use crate::utils::BindingEvaluator;
9use crate::{
10    Control, ControlImpl, ItemsControl, ItemsControlImpl, ItemsControlImplExt, SelectionChangedEventArgs,
11    SelectionMode,
12};
13use ferroui_base::collections::{FerroList, NotifyCollectionChangedAction};
14use ferroui_base::data::BindingMode;
15use ferroui_base::input::{
16    FocusChangedEventArgs, FocusManager, InputElement, InputElementImpl, InputElementImplExt, KeyEventArgs,
17    KeyboardNavigation, NavigationDirection, PointerEventArgs, TextInputEventArgs,
18};
19use ferroui_base::interactivity::{
20    IRoutedEventArgs, Interactive, InteractiveImpl, RoutedEvent, RoutedEventArgs, RoutedEventHandlerToken,
21    RoutingStrategies,
22};
23use ferroui_base::layout::LayoutableImpl;
24use ferroui_base::reactive::IDisposable;
25use ferroui_base::threading::DispatcherTimer;
26use ferroui_base::{
27    ferro_class, ferro_impl_classes, ferro_property, ferro_routed_event, instantiate, AnyValue, AttachedProperty,
28    BoxedValue, DirectProperty, DirectPropertyMetadata, FerroObjectImpl, FerroObjectImplExt, FerroProperty,
29    FerroPropertyChangedEventArgs, Ref, StyledElementImpl, StyledElementImplExt, StyledProperty,
30    StyledPropertyOptions, Visual, VisualImpl, VisualImplExt, VisualTreeAttachmentEventArgs,
31};
32use std::cell::{Cell, RefCell};
33use std::ops::Deref;
34use std::rc::Rc;
35use std::time::Duration;
36
37/// The list held by the `SelectedItems` property: a notifying list of
38/// untyped items. Equality is the identity of the list.
39#[derive(Clone)]
40pub struct SelectedItemsList(pub Rc<FerroList<Option<BoxedValue>>>);
41
42impl SelectedItemsList {
43    /// Creates an empty list.
44    pub fn new() -> Self {
45        Self(Rc::new(FerroList::new()))
46    }
47
48    /// Creates a list with the given items.
49    pub fn from_items(items: impl IntoIterator<Item = Option<BoxedValue>>) -> Self {
50        Self(Rc::new(FerroList::from_items(items)))
51    }
52}
53
54impl Default for SelectedItemsList {
55    fn default() -> Self {
56        Self::new()
57    }
58}
59
60impl PartialEq for SelectedItemsList {
61    fn eq(&self, other: &Self) -> bool {
62        Rc::ptr_eq(&self.0, &other.0)
63    }
64}
65
66impl Deref for SelectedItemsList {
67    type Target = FerroList<Option<BoxedValue>>;
68
69    fn deref(&self) -> &Self::Target {
70        &self.0
71    }
72}
73
74// When in a begin/end init block, or when the data context is updating, we
75// need to defer changes to the selection model because we have no idea in
76// which order properties will be set. Consider:
77//
78// - Both the items and the selected item are bound
79// - The data context changes
80// - The binding for the selected item updates first, producing an item
81// - The items are searched to find the index of the new selected item
82// - However the items aren't yet updated; the item is not found
83// - The selected index is incorrectly set to -1
84//
85// This logic cannot be encapsulated in the selection model because the
86// selection model can also be bound, consider:
87//
88// - Both the items and the selection are bound
89// - The data context changes
90// - The binding for the items updates first
91// - The new items are assigned to the source of the selection
92// - The binding for the selection updates, producing a new selection model
93// - Both the old and new selection models have the incorrect source
94#[derive(Default)]
95struct UpdateState {
96    update_count: i32,
97    selection: Option<Rc<dyn ISelectionModel>>,
98    selected_items: Option<Option<SelectedItemsList>>,
99    selected_index: Option<i32>,
100    selected_item: Option<Option<BoxedValue>>,
101    selected_value: Option<Option<BoxedValue>>,
102}
103
104/// Resets a flag when dropped.
105struct Reset<'a>(&'a Cell<bool>);
106
107impl Drop for Reset<'_> {
108    fn drop(&mut self) {
109        self.0.set(false);
110    }
111}
112
113/// An [`ItemsControl`] that maintains a selection.
114///
115/// [`SelectingItemsControl`] provides a base class for [`ItemsControl`]s
116/// that maintain a selection (single or multiple). By default only its
117/// `selected_index` and `selected_item` properties are visible; the current
118/// multiple `selection` and `selected_items` together with the
119/// `selection_mode` properties are protected, however a derived class can
120/// expose these if it wishes to support multiple selection.
121///
122/// [`SelectingItemsControl`] maintains a selection respecting the current
123/// `selection_mode` but it does not react to user input; this must be
124/// handled in a derived class. It does, however, respond to
125/// `is_selected_changed_event` events from items and updates the selection
126/// accordingly.
127#[repr(C)]
128pub struct SelectingItemsControl {
129    base: ItemsControl,
130    text_search_term: RefCell<String>,
131    text_search_timer: RefCell<Option<(Rc<DispatcherTimer>, Rc<dyn IDisposable>)>>,
132    selection: RefCell<Option<Rc<dyn ISelectionModel>>>,
133    selection_property_changed_token: Cell<u64>,
134    selection_changed_subscription: RefCell<Option<Rc<dyn IDisposable>>>,
135    old_selected_index: Cell<i32>,
136    old_selected_item: RefCell<Option<BoxedValue>>,
137    old_selected_items: RefCell<Option<SelectedItemsList>>,
138    selected_items_snapshot: RefCell<Vec<Option<BoxedValue>>>,
139    selected_items_before_reset: RefCell<Option<Vec<Option<BoxedValue>>>>,
140    ignore_container_selection_changed: Cell<bool>,
141    update_state: RefCell<Option<UpdateState>>,
142    has_scrolled_to_selected_item: Cell<bool>,
143    selected_value_binding_evaluator: RefCell<Option<Ref<BindingEvaluator>>>,
144    is_selection_change_active: Cell<bool>,
145    unverified_selected_index: Cell<i32>,
146    auto_scroll_requested_index: Cell<i32>,
147}
148
149ferro_class! {
150    SelectingItemsControl: ItemsControl, virtuals SelectingItemsControlImpl: ItemsControlImpl {
151        /// Analyses a pointer event received by a selectable element, and
152        /// determines whether the action should trigger selection.
153        fn should_trigger_selection(this, selectable: &Visual, event_args: &PointerEventArgs) -> bool;
154        /// Analyses a key event received by a selectable element, and
155        /// determines whether the action should trigger selection.
156        fn should_trigger_selection_key(this, selectable: &Visual, event_args: &KeyEventArgs) -> bool;
157        /// Updates the selection based on an event that may have originated
158        /// in a container that belongs to the control.
159        ///
160        /// Returns true if the event originated from a container that
161        /// belongs to the control and triggered selection; otherwise false.
162        fn update_selection_from_event(this, container: &Ref<Control>, event_args: &dyn IRoutedEventArgs) -> bool;
163    }
164}
165ferroui_base::ferro_class_info!(SelectingItemsControl { new: SelectingItemsControl::new });
166
167ferro_impl_classes!(SelectingItemsControl: LayoutableImpl, InteractiveImpl, ControlImpl);
168
169impl FerroObjectImpl for SelectingItemsControl {
170    fn constructed(this: &Self) {
171        Self::parent_constructed(this);
172
173        let weak = this.to_ref().downgrade();
174        this.items().add_source_changed(Rc::new(move || {
175            if let Some(this) = weak.upgrade() {
176                this.on_items_view_source_changed();
177            }
178        }));
179
180        let weak = this.to_ref().downgrade();
181        this.items().add_pre_collection_changed(Rc::new(move |e: &ItemsChangedEventArgs<'_>| {
182            if let Some(this) = weak.upgrade() {
183                this.on_items_view_pre_collection_changed(e);
184            }
185        }));
186    }
187
188    fn on_property_changed(this: &Self, change: &FerroPropertyChangedEventArgs<'_>) {
189        Self::parent_on_property_changed(this, change);
190
191        if change.property() == Self::auto_scroll_to_selected_item_property().as_property() {
192            this.auto_scroll_to_selected_item_if_necessary(this.get_anchor_index());
193        } else if change.property() == Visual::is_visible_property().as_property() {
194            if change.get_new_value::<bool>() {
195                this.auto_scroll_to_selected_item_if_necessary(this.get_anchor_index());
196            }
197        } else if change.property() == Self::selection_mode_property().as_property() {
198            let selection = this.selection.borrow().clone();
199            if let Some(selection) = selection {
200                let new_value = change.get_new_value::<SelectionMode>();
201                selection.set_single_select(!new_value.contains(SelectionMode::MULTIPLE));
202            }
203        } else if change.property() == Self::wrap_selection_property().as_property() {
204            this.set_wrap_focus(this.wrap_selection());
205        } else if change.property() == Self::selected_value_property().as_property() {
206            if this.is_selection_change_active.get() {
207                return;
208            }
209
210            let new_value = change.get_new_value::<Option<BoxedValue>>();
211
212            if let Some(state) = this.update_state.borrow_mut().as_mut() {
213                state.selected_value = Some(new_value);
214                return;
215            }
216
217            this.select_item_with_value(&new_value);
218        } else if change.property() == Self::selected_value_binding_property().as_property() {
219            let idx = this.selected_index();
220
221            // If no selection is active, don't do anything as the selected
222            // value is already null.
223            if idx == -1 {
224                return;
225            }
226
227            let Some(value) = change.get_new_value::<Option<AssignedBinding>>() else {
228                // Clearing the selected value binding makes the selected
229                // value the item itself.
230                this.set_current_value(Self::selected_value_property(), this.selected_item());
231                return;
232            };
233
234            let selected_item = this.selected_item();
235
236            this.is_selection_change_active.set(true);
237            let _reset = Reset(&this.is_selection_change_active);
238
239            let binding_evaluator = this.get_selected_value_binding_evaluator(&value);
240
241            // Re-evaluate the selected value with the new binding.
242            this.set_current_value(Self::selected_value_property(), binding_evaluator.evaluate(&selected_item));
243        }
244    }
245}
246
247impl StyledElementImpl for SelectingItemsControl {
248    fn begin_init(this: &Self) {
249        Self::parent_begin_init(this);
250        this.begin_updating();
251    }
252
253    fn try_end_init(this: &Self) -> Result<(), ferroui_base::InitializationError> {
254        Self::parent_try_end_init(this)?;
255        this.end_updating();
256        Ok(())
257    }
258
259    fn on_data_context_begin_update(this: &Self) {
260        Self::parent_on_data_context_begin_update(this);
261        this.begin_updating();
262    }
263
264    fn on_data_context_end_update(this: &Self) {
265        Self::parent_on_data_context_end_update(this);
266        this.end_updating();
267    }
268
269    fn on_initialized(this: &Self) {
270        Self::parent_on_initialized(this);
271
272        let selection = this.selection.borrow().clone();
273        this.try_initialize_selection_source(selection.as_ref(), this.update_state.borrow().is_none());
274        this.select_inline_items_marked_as_selected();
275    }
276}
277
278impl VisualImpl for SelectingItemsControl {
279    fn on_attached_to_visual_tree(this: &Self, e: &VisualTreeAttachmentEventArgs) {
280        Self::parent_on_attached_to_visual_tree(this, e);
281
282        this.auto_scroll_to_selected_item_if_necessary(this.get_anchor_index());
283    }
284}
285
286impl InputElementImpl for SelectingItemsControl {
287    fn on_text_input(this: &Self, e: &TextInputEventArgs) {
288        if !e.handled() {
289            if !this.is_text_search_enabled() {
290                return;
291            }
292
293            this.stop_text_search_timer();
294
295            if let Some(text) = &e.text {
296                this.text_search_term.borrow_mut().push_str(text);
297            }
298
299            let term = this.text_search_term.borrow().clone();
300            let new_index = this.get_index_from_text_search(&term);
301            if new_index >= 0 {
302                this.set_selected_index(new_index);
303            }
304
305            this.start_text_search_timer();
306
307            e.set_handled(true);
308        }
309
310        Self::parent_on_text_input(this, e);
311    }
312}
313
314impl TemplatedControlImpl for SelectingItemsControl {
315    fn on_apply_template(this: &Self, e: &TemplateAppliedEventArgs) {
316        Self::parent_on_apply_template(this, e);
317
318        this.auto_scroll_to_selected_item_if_necessary(this.get_anchor_index());
319    }
320}
321
322impl ItemsControlImpl for SelectingItemsControl {
323    fn on_items_view_collection_changed(this: &Self, e: &ItemsChangedEventArgs<'_>) {
324        Self::parent_on_items_view_collection_changed(this, e);
325
326        // Do not change the selected index during initialization.
327        if this.update_state.borrow().is_some() {
328            return;
329        }
330
331        if this.always_selected() && this.selected_index() == -1 && this.item_count() > 0 {
332            this.set_selected_index(this.choose_first_visible_and_enabled_index());
333        }
334    }
335
336    fn prepare_container_for_item_override(
337        this: &Self,
338        container: &Ref<Control>,
339        item: &Option<BoxedValue>,
340        index: i32,
341    ) {
342        // Ensure that the selection model is created at this point so that
343        // accessing it in `container_for_item_prepared_override` doesn't
344        // cause it to be initialized (which can make containers become
345        // deselected when they're synced with the empty selection mode).
346        this.get_or_create_selection_model();
347
348        Self::parent_prepare_container_for_item_override(this, container, item, index);
349    }
350
351    fn container_for_item_prepared_override(
352        this: &Self,
353        container: &Ref<Control>,
354        item: &Option<BoxedValue>,
355        index: i32,
356    ) {
357        Self::parent_container_for_item_prepared_override(this, container, item, index);
358
359        // Once the container has been full prepared and added to the tree,
360        // any bindings from styles or item container themes are guaranteed
361        // to be applied.
362        if !container.is_set(Self::is_selected_property().as_property()) {
363            // The is selected property is not set on the container: update
364            // the container selection based on the current selection as
365            // understood by this control.
366            this.mark_container_selected(container, this.selection().is_selected(index));
367        } else {
368            // The is selected property is set on the container: there is a
369            // style or item container theme which has bound the property.
370            // Update our selection based on the selection state of the
371            // container.
372            let container_is_selected = Self::get_is_selected(container);
373
374            if container_is_selected != this.selection().is_selected(index) {
375                this.update_selection(index, container_is_selected, false, true, false, false);
376            }
377        }
378
379        if this.selection().anchor_index() == index {
380            KeyboardNavigation::set_tab_once_active_element(this, Some(container.clone().upcast()));
381        }
382
383        if this.always_selected() {
384            if this.selected_index() == -1 && container.is_visible() && container.is_enabled() {
385                this.set_selected_index(index);
386            } else if index == this.selected_index() && (!container.is_visible() || !container.is_enabled()) {
387                this.move_selection_to_first_visible_and_enabled_item();
388            }
389        }
390    }
391
392    fn container_index_changed_override(this: &Self, container: &Ref<Control>, old_index: i32, new_index: i32) {
393        Self::parent_container_index_changed_override(this, container, old_index, new_index);
394        this.mark_container_selected(container, this.selection().is_selected(new_index));
395    }
396
397    fn clear_container_for_item_override(this: &Self, element: &Ref<Control>) {
398        Self::parent_clear_container_for_item_override(this, element);
399
400        this.ignore_container_selection_changed.set(true);
401        let _reset = Reset(&this.ignore_container_selection_changed);
402        element.clear_value(Self::is_selected_property());
403    }
404}
405
406impl SelectingItemsControlImpl for SelectingItemsControl {
407    fn should_trigger_selection(_this: &Self, selectable: &Visual, event_args: &PointerEventArgs) -> bool {
408        ItemSelectionEventTriggers::should_trigger_selection(selectable, event_args)
409    }
410
411    fn should_trigger_selection_key(_this: &Self, selectable: &Visual, event_args: &KeyEventArgs) -> bool {
412        ItemSelectionEventTriggers::should_trigger_selection_key(selectable, event_args)
413    }
414
415    fn update_selection_from_event(this: &Self, container: &Ref<Control>, event_args: &dyn IRoutedEventArgs) -> bool {
416        let args = event_args.as_routed_event_args();
417
418        if args.handled() {
419            return false;
420        }
421
422        let container_index = this.index_from_container(container);
423        if container_index == -1 {
424            return false;
425        }
426
427        let pointer_event = event_args.downcast_ref::<PointerEventArgs>();
428        let key_event = event_args.downcast_ref::<KeyEventArgs>();
429        let is_focus_event = event_args.is::<FocusChangedEventArgs>();
430
431        let triggers = match (pointer_event, key_event) {
432            (Some(pointer_event), _) => this.should_trigger_selection(container, pointer_event),
433            (None, Some(key_event)) => this.should_trigger_selection_key(container, key_event),
434            (None, None) => is_focus_event,
435        };
436
437        if !triggers {
438            return false;
439        }
440
441        this.update_selection(
442            container_index,
443            true,
444            ItemSelectionEventTriggers::has_range_selection_modifier(container, event_args),
445            ItemSelectionEventTriggers::has_toggle_selection_modifier(container, event_args),
446            pointer_event.is_some_and(|e| e.properties().is_right_button_pressed),
447            is_focus_event,
448        );
449
450        if pointer_event.is_some() {
451            crate::platform::PlatformFeedbackExtensions::perform_feedback(
452                &**container as &InputElement,
453                crate::platform::FeedbackAction::click(),
454            );
455        }
456
457        args.set_handled(true);
458        true
459    }
460}
461
462ferroui_base::ferro_properties! { impl SelectingItemsControl, also [
463    SelectingItemsControl::wrap_selection_property,
464] {
465    ferro_property!(
466        /// Defines the `AutoScrollToSelectedItem` property.
467        pub fn auto_scroll_to_selected_item_property() -> StyledProperty<bool> {
468            FerroProperty::register::<SelectingItemsControl, _>("AutoScrollToSelectedItem", true)
469        }
470    );
471
472    ferro_property!(
473        /// Defines the `SelectedIndex` property.
474        pub fn selected_index_property() -> DirectProperty<SelectingItemsControl, i32> {
475            FerroProperty::register_direct_with::<SelectingItemsControl, _>(
476                "SelectedIndex",
477                |o| o.selected_index(),
478                Some(|o, v| o.set_selected_index(v)),
479                DirectPropertyMetadata::new(Some(-1)).with_default_binding_mode(BindingMode::TwoWay),
480            )
481        }
482    );
483
484    ferro_property!(
485        /// Defines the `SelectedItem` property.
486        pub fn selected_item_property() -> DirectProperty<SelectingItemsControl, Option<BoxedValue>> {
487            FerroProperty::register_direct_with::<SelectingItemsControl, _>(
488                "SelectedItem",
489                |o| o.selected_item(),
490                Some(|o, v| o.set_selected_item(v)),
491                DirectPropertyMetadata::new(Some(None))
492                    .with_default_binding_mode(BindingMode::TwoWay)
493                    .with_enable_data_validation(true),
494            )
495        }
496    );
497
498    ferro_property!(
499        /// Defines the `SelectedValue` property.
500        pub fn selected_value_property() -> StyledProperty<Option<BoxedValue>> {
501            FerroProperty::register_with::<SelectingItemsControl, _>(
502                "SelectedValue",
503                StyledPropertyOptions::new(None).default_binding_mode(BindingMode::TwoWay),
504            )
505        }
506    );
507
508    ferro_property!(
509        /// Defines the `SelectedValueBinding` property.
510        pub fn selected_value_binding_property() -> StyledProperty<Option<AssignedBinding>> {
511            FerroProperty::register::<SelectingItemsControl, _>("SelectedValueBinding", None)
512        }
513    );
514
515    ferro_property!(
516        /// Defines the `SelectedItems` property.
517        pub fn selected_items_property() -> DirectProperty<SelectingItemsControl, Option<SelectedItemsList>> {
518            FerroProperty::register_direct::<SelectingItemsControl, _>(
519                "SelectedItems",
520                |o| o.selected_items(),
521                Some(|o, v| o.set_selected_items(v)),
522                None,
523            )
524        }
525    );
526
527    ferro_property!(
528        /// Defines the `Selection` property.
529        pub fn selection_property() -> DirectProperty<SelectingItemsControl, Option<Rc<dyn ISelectionModel>>> {
530            FerroProperty::register_direct::<SelectingItemsControl, _>(
531                "Selection",
532                |o| Some(o.selection()),
533                Some(|o, v| o.set_selection(v)),
534                None,
535            )
536        }
537    );
538
539    ferro_property!(
540        /// Defines the `SelectionMode` property.
541        pub fn selection_mode_property() -> StyledProperty<SelectionMode> {
542            FerroProperty::register::<SelectingItemsControl, _>("SelectionMode", SelectionMode::SINGLE)
543        }
544    );
545
546    ferro_property!(
547        /// Defines the `IsSelected` attached property on a container
548        /// [`Control`].
549        pub fn is_selected_property() -> AttachedProperty<bool> {
550            FerroProperty::register_attached_with::<SelectingItemsControl, Control, _>(
551                "IsSelected",
552                StyledPropertyOptions::new(false).default_binding_mode(BindingMode::TwoWay),
553            )
554        }
555    );
556
557    ferro_property!(
558        /// Defines the `IsTextSearchEnabled` property.
559        pub fn is_text_search_enabled_property() -> StyledProperty<bool> {
560            FerroProperty::register::<SelectingItemsControl, _>("IsTextSearchEnabled", false)
561        }
562    );
563} }
564
565impl SelectingItemsControl {
566    ferro_routed_event!(
567        /// Event that should be raised by containers when their selection
568        /// state changes to notify the parent [`SelectingItemsControl`]
569        /// that their selection state has changed.
570        pub fn is_selected_changed_event() -> RoutedEvent<RoutedEventArgs> {
571            RoutedEvent::register::<SelectingItemsControl, _>("IsSelectedChanged", RoutingStrategies::BUBBLE)
572        }
573    );
574
575    ferro_routed_event!(
576        /// Defines the `SelectionChanged` event.
577        pub fn selection_changed_event() -> RoutedEvent<SelectionChangedEventArgs> {
578            RoutedEvent::register::<SelectingItemsControl, _>("SelectionChanged", RoutingStrategies::BUBBLE)
579        }
580    );
581
582    ferro_property!(for SelectingItemsControl;
583        /// Defines the `WrapSelection` property.
584        pub fn wrap_selection_property() -> StyledProperty<bool> {
585            FerroProperty::register::<SelectingItemsControl, _>("WrapSelection", false)
586        }
587    );
588
589    pub(crate) fn static_constructor() {
590        Self::is_selected_changed_event()
591            .add_class_handler::<SelectingItemsControl>(|x, e| x.container_selection_changed(e));
592    }
593
594    /// Creates the class data; see [`ferroui_base::FerroObject::construct`].
595    pub fn construct() -> Self {
596        Self {
597            base: ItemsControl::construct(),
598            text_search_term: RefCell::new(String::new()),
599            text_search_timer: RefCell::new(None),
600            selection: RefCell::new(None),
601            selection_property_changed_token: Cell::new(0),
602            selection_changed_subscription: RefCell::new(None),
603            old_selected_index: Cell::new(0),
604            old_selected_item: RefCell::new(None),
605            old_selected_items: RefCell::new(None),
606            selected_items_snapshot: RefCell::new(Vec::new()),
607            selected_items_before_reset: RefCell::new(None),
608            ignore_container_selection_changed: Cell::new(false),
609            update_state: RefCell::new(None),
610            has_scrolled_to_selected_item: Cell::new(false),
611            selected_value_binding_evaluator: RefCell::new(None),
612            is_selection_change_active: Cell::new(false),
613            unverified_selected_index: Cell::new(-1),
614            auto_scroll_requested_index: Cell::new(-1),
615        }
616    }
617
618    pub fn new() -> Ref<Self> {
619        instantiate(Self::construct())
620    }
621
622    /// Occurs when the control's selection changes.
623    pub fn selection_changed(
624        &self,
625        handler: impl Fn(&Interactive, &SelectionChangedEventArgs) + 'static,
626    ) -> RoutedEventHandlerToken {
627        self.add_handler(Self::selection_changed_event(), handler)
628    }
629
630    /// Gets or sets a value indicating whether to automatically scroll to
631    /// newly selected items.
632    pub fn auto_scroll_to_selected_item(&self) -> bool {
633        self.get_value(Self::auto_scroll_to_selected_item_property())
634    }
635
636    pub fn set_auto_scroll_to_selected_item(&self, value: bool) {
637        self.set_value(Self::auto_scroll_to_selected_item_property(), value)
638    }
639
640    /// Gets or sets the index of the selected item.
641    pub fn selected_index(&self) -> i32 {
642        // When a begin/end init or data context update is in place we
643        // return the value to be updated here, even though it's not yet
644        // active and the property changed notification has not yet been
645        // raised. If we don't do this then the old value will be written
646        // back to the source when two-way bound, and the update value will
647        // be lost.
648        let pending = self.update_state.borrow().as_ref().map(|state| state.selected_index);
649        if let Some(pending) = pending {
650            return match pending {
651                Some(selected_index) => selected_index,
652                None => self.try_get_existing_selection().map_or(-1, |selection| selection.selected_index()),
653            };
654        }
655
656        self.selection().selected_index()
657    }
658
659    pub fn set_selected_index(&self, value: i32) {
660        if let Some(state) = self.update_state.borrow_mut().as_mut() {
661            state.selected_index = Some(value);
662            return;
663        }
664
665        self.selection().set_selected_index(value);
666    }
667
668    /// Gets or sets the selected item.
669    pub fn selected_item(&self) -> Option<BoxedValue> {
670        // See the selected index getter for more information.
671        let pending = self.update_state.borrow().as_ref().map(|state| state.selected_item.clone());
672        if let Some(pending) = pending {
673            return match pending {
674                Some(selected_item) => selected_item,
675                None => self.try_get_existing_selection().and_then(|selection| selection.selected_item()),
676            };
677        }
678
679        self.selection().selected_item()
680    }
681
682    pub fn set_selected_item(&self, value: Option<BoxedValue>) {
683        if let Some(state) = self.update_state.borrow_mut().as_mut() {
684            state.selected_item = Some(value);
685            return;
686        }
687
688        self.selection().set_selected_item(value);
689    }
690
691    /// Gets the binding instance which will be used to get the value for
692    /// the `selected_value` property of the selected item.
693    pub fn selected_value_binding(&self) -> Option<AssignedBinding> {
694        self.get_value(Self::selected_value_binding_property())
695    }
696
697    pub fn set_selected_value_binding(&self, value: Option<AssignedBinding>) {
698        self.set_value(Self::selected_value_binding_property(), value)
699    }
700
701    /// Gets or sets the value of the selected item, obtained using
702    /// `selected_value_binding`.
703    pub fn selected_value(&self) -> Option<BoxedValue> {
704        self.get_value(Self::selected_value_property())
705    }
706
707    pub fn set_selected_value(&self, value: Option<BoxedValue>) {
708        self.set_value(Self::selected_value_property(), value)
709    }
710
711    /// Gets or sets the selected items.
712    ///
713    /// By default returns a new list that can be manipulated to change the
714    /// selection. This property can be set to an existing list to keep the
715    /// selection of the control in sync with it.
716    pub fn selected_items(&self) -> Option<SelectedItemsList> {
717        // See the selected index setter for more information.
718        let pending = self.update_state.borrow().as_ref().and_then(|state| state.selected_items.clone());
719        if let Some(pending) = pending {
720            return pending;
721        }
722
723        let selection = self.selection();
724        if let Some(ism) = selection.as_internal_selection_model() {
725            let result = SelectedItemsList(ism.writable_selected_items());
726            *self.old_selected_items.borrow_mut() = Some(result.clone());
727            return Some(result);
728        }
729
730        None
731    }
732
733    pub fn set_selected_items(&self, value: Option<SelectedItemsList>) {
734        if let Some(state) = self.update_state.borrow_mut().as_mut() {
735            state.selected_items = Some(value);
736            return;
737        }
738
739        let selection = self.selection();
740        match selection.as_internal_selection_model() {
741            Some(i) => i.set_writable_selected_items(value.map(|v| v.0)),
742            None => panic!("Cannot set both Selection and SelectedItems."),
743        }
744    }
745
746    /// Gets or sets the model that holds the current selection.
747    pub fn selection(&self) -> Rc<dyn ISelectionModel> {
748        let pending = self.update_state.borrow().as_ref().and_then(|state| state.selection.clone());
749        match pending {
750            Some(selection) => selection,
751            None => self.get_or_create_selection_model(),
752        }
753    }
754
755    pub fn set_selection(&self, value: Option<Rc<dyn ISelectionModel>>) {
756        let value = value.unwrap_or_else(|| self.create_default_selection_model());
757
758        if let Some(state) = self.update_state.borrow_mut().as_mut() {
759            state.selection = Some(value);
760            return;
761        }
762
763        let current = self.selection.borrow().clone();
764        if current.as_ref().is_some_and(|current| crate::selection::selection_model_ptr_eq(current, &value)) {
765            return;
766        }
767
768        if let Some(source) = value.source() {
769            if !source.ptr_eq(&self.items_view().source()) {
770                panic!(
771                    "The supplied ISelectionModel already has an assigned Source but this \
772                     collection is different to the Items on the control."
773                );
774            }
775        }
776
777        let old_selection = current.as_ref().map(|selection| selection.selected_items().to_vec());
778        self.deinitialize_selection_model(current.as_ref());
779        *self.selection.borrow_mut() = Some(value.clone());
780
781        if let Some(old_selection) = old_selection.filter(|items| !items.is_empty()) {
782            self.raise_event(&SelectionChangedEventArgs::new(
783                Some(Self::selection_changed_event()),
784                old_selection,
785                Vec::new(),
786            ));
787        }
788
789        self.initialize_selection_model(&value);
790        let selected_items = self.selected_items();
791        let old_selected_items = self.old_selected_items.borrow().clone();
792        if old_selected_items != selected_items {
793            self.raise_direct_property_changed(Self::selected_items_property(), &old_selected_items, &selected_items);
794            *self.old_selected_items.borrow_mut() = selected_items;
795        }
796    }
797
798    /// Gets or sets a value that specifies whether a user can jump to a
799    /// value by typing.
800    pub fn is_text_search_enabled(&self) -> bool {
801        self.get_value(Self::is_text_search_enabled_property())
802    }
803
804    pub fn set_is_text_search_enabled(&self, value: bool) {
805        self.set_value(Self::is_text_search_enabled_property(), value)
806    }
807
808    /// Gets or sets a value which indicates whether to wrap around when the
809    /// first or last item is reached.
810    pub fn wrap_selection(&self) -> bool {
811        self.get_value(Self::wrap_selection_property())
812    }
813
814    pub fn set_wrap_selection(&self, value: bool) {
815        self.set_value(Self::wrap_selection_property(), value)
816    }
817
818    /// Gets or sets the selection mode.
819    ///
820    /// Note that the selection mode only applies to selections made via
821    /// user interaction. Multiple selections can be made programmatically
822    /// regardless of the value of this property.
823    pub fn selection_mode(&self) -> SelectionMode {
824        self.get_value(Self::selection_mode_property())
825    }
826
827    pub fn set_selection_mode(&self, value: SelectionMode) {
828        self.set_value(Self::selection_mode_property(), value)
829    }
830
831    /// Gets a value indicating whether the `ALWAYS_SELECTED` flag is set.
832    pub fn always_selected(&self) -> bool {
833        self.selection_mode().contains(SelectionMode::ALWAYS_SELECTED)
834    }
835
836    /// Gets the value of the `IsSelected` attached property on a control.
837    // Inline items are their own containers, but a container is only read when it's realized, and a
838    // ComboBox doesn't realize its items until the drop-down is first opened.
839    fn select_inline_items_marked_as_selected(&self) {
840        if self.items_source().is_some() || self.selection().count() > 0 {
841            return;
842        }
843
844        let multiple = self.selection_mode().contains(SelectionMode::MULTIPLE);
845        let items_view = self.items_view().clone();
846
847        for i in 0..items_view.count() {
848            let container = items_view.get_at(i).as_ref().and_then(Control::from_boxed);
849            if let Some(container) = container {
850                if container.is_set(Self::is_selected_property().as_property()) && Self::get_is_selected(&container) {
851                    self.selection().select(i as i32);
852
853                    if !multiple {
854                        break;
855                    }
856                }
857            }
858        }
859    }
860
861    pub fn get_is_selected(control: &Control) -> bool {
862        control.get_value(Self::is_selected_property())
863    }
864
865    /// Sets the value of the `IsSelected` attached property on a control.
866    pub fn set_is_selected(control: &Control, value: bool) {
867        control.set_value(Self::is_selected_property(), value)
868    }
869
870    /// Tries to get the container that was the source of an event.
871    ///
872    /// Returns the container or `None` if the event did not originate in a
873    /// container.
874    pub fn get_container_from_event_source(&self, event_source: Option<&Ref<Visual>>) -> Option<Ref<Control>> {
875        let this: Ref<ferroui_base::StyledElement> = self.to_ref().upcast();
876        let mut current = event_source.cloned();
877
878        while let Some(visual) = current {
879            if let Some(control) = visual.cast::<Control>() {
880                if control.parent().as_ref() == Some(&this) && self.index_from_container(&control) != -1 {
881                    return Some(control);
882                }
883            }
884
885            current = visual.visual_parent();
886        }
887
888        None
889    }
890
891    /// Returns the [`SelectingItemsControl`] that owns the specified
892    /// container control.
893    pub fn selecting_items_control_from_item_container(container: &Ref<Control>) -> Option<Ref<SelectingItemsControl>> {
894        ItemsControl::items_control_from_item_container(container).and_then(|c| c.cast::<SelectingItemsControl>())
895    }
896
897    fn on_items_view_pre_collection_changed(&self, e: &ItemsChangedEventArgs<'_>) {
898        if e.action == NotifyCollectionChangedAction::Reset && !self.selected_items_snapshot.borrow().is_empty() {
899            let snapshot = self.selected_items_snapshot.borrow().clone();
900            *self.selected_items_before_reset.borrow_mut() = Some(snapshot);
901        }
902    }
903
904    pub(crate) fn get_anchor_index(&self) -> i32 {
905        let selection = if self.update_state.borrow().is_some() {
906            self.try_get_existing_selection()
907        } else {
908            Some(self.selection())
909        };
910        selection.map_or(-1, |selection| selection.anchor_index())
911    }
912
913    fn try_get_existing_selection(&self) -> Option<Rc<dyn ISelectionModel>> {
914        let pending = self.update_state.borrow().as_ref().and_then(|state| state.selection.clone());
915        match pending {
916            Some(selection) => Some(selection),
917            None => self.selection.borrow().clone(),
918        }
919    }
920
921    /// Moves the selection in the specified direction relative to the
922    /// current selection.
923    ///
924    /// Returns true if the selection was moved; otherwise false.
925    pub fn move_selection(&self, direction: NavigationDirection, wrap: bool, range_modifier: bool) -> bool {
926        let focused = FocusManager::get_focus_manager(self).and_then(|focus| focus.get_focused_element());
927        let focused: Option<Ref<Visual>> = focused.map(Ref::upcast);
928        let from = self
929            .get_container_from_event_source(focused.as_ref())
930            .or_else(|| self.container_from_index(self.selection().anchor_index()));
931        self.move_selection_from(from.as_ref(), direction, wrap, range_modifier)
932    }
933
934    /// Moves the selection in the specified direction relative to the
935    /// specified container.
936    ///
937    /// Returns true if the selection was moved; otherwise false.
938    pub fn move_selection_from(
939        &self,
940        from: Option<&Ref<Control>>,
941        mut direction: NavigationDirection,
942        wrap: bool,
943        range_modifier: bool,
944    ) -> bool {
945        let Some(panel) = self.presenter().and_then(|presenter| presenter.panel()) else { return false };
946        let Some(container) = as_navigable_container(&panel) else { return false };
947
948        if from.is_none() {
949            direction = match direction {
950                NavigationDirection::Down => NavigationDirection::First,
951                NavigationDirection::Up => NavigationDirection::Last,
952                NavigationDirection::Right => NavigationDirection::First,
953                NavigationDirection::Left => NavigationDirection::Last,
954                other => other,
955            };
956        }
957
958        let from: Option<Ref<InputElement>> = from.map(|from| from.clone().upcast());
959        let next = ItemsControl::get_next_control(container, direction, from.as_ref(), wrap);
960
961        if let Some(next) = next.and_then(|next| next.cast::<Control>()) {
962            let index = self.index_from_container(&next);
963
964            if index != -1 {
965                self.update_selection(index, true, range_modifier, false, false, false);
966                next.focus();
967                return true;
968            }
969        }
970
971        false
972    }
973
974    /// Updates the selection for an item based on user interaction.
975    ///
976    /// `index` is the index of the item, `select` whether the item should
977    /// be selected or unselected, `range_modifier` whether the range
978    /// modifier is enabled (i.e. shift key), `toggle_modifier` whether the
979    /// toggle modifier is enabled (i.e. ctrl key), `right_button` whether
980    /// the event is a right-click and `from_focus` whether the event is a
981    /// focus event.
982    pub fn update_selection(
983        &self,
984        index: i32,
985        select: bool,
986        range_modifier: bool,
987        toggle_modifier: bool,
988        right_button: bool,
989        from_focus: bool,
990    ) {
991        if index < 0 || index >= self.item_count() {
992            return;
993        }
994
995        let mode = self.selection_mode();
996        let multi = mode.contains(SelectionMode::MULTIPLE);
997        let toggle = toggle_modifier || mode.contains(SelectionMode::TOGGLE);
998        let range = multi && range_modifier;
999        let selection = self.selection();
1000
1001        if !select {
1002            selection.deselect(index);
1003        } else if right_button {
1004            if !selection.is_selected(index) {
1005                self.set_selected_index(index);
1006            }
1007        } else if range {
1008            let operation = SelectionModelExtensions::batch_update(&selection);
1009            if !toggle_modifier {
1010                selection.clear();
1011            }
1012            selection.select_range(selection.anchor_index(), index);
1013            operation.dispose();
1014        } else if !from_focus && toggle {
1015            if multi {
1016                if selection.is_selected(index) {
1017                    selection.deselect(index);
1018                } else {
1019                    selection.select(index);
1020                }
1021            } else {
1022                self.set_selected_index(if self.selected_index() == index { -1 } else { index });
1023            }
1024        } else if !toggle {
1025            let operation = SelectionModelExtensions::batch_update(&selection);
1026            selection.clear();
1027            selection.select(index);
1028            operation.dispose();
1029        }
1030    }
1031
1032    /// Updates the selection for a container based on user interaction.
1033    #[deprecated(note = "Call update_selection_from_event instead.")]
1034    pub fn update_selection_for_container(
1035        &self,
1036        container: &Ref<Control>,
1037        select: bool,
1038        range_modifier: bool,
1039        toggle_modifier: bool,
1040        right_button: bool,
1041        from_focus: bool,
1042    ) {
1043        let index = self.index_from_container(container);
1044
1045        if index != -1 {
1046            self.update_selection(index, select, range_modifier, toggle_modifier, right_button, from_focus);
1047        }
1048    }
1049
1050    /// Updates the selection based on an event source that may have
1051    /// originated in a container that belongs to the control.
1052    ///
1053    /// Returns true if the event originated from a container that belongs
1054    /// to the control; otherwise false.
1055    #[deprecated(note = "Call update_selection_from_event instead.")]
1056    pub fn update_selection_from_event_source(
1057        &self,
1058        event_source: Option<&Ref<Visual>>,
1059        select: bool,
1060        range_modifier: bool,
1061        toggle_modifier: bool,
1062        right_button: bool,
1063        from_focus: bool,
1064    ) -> bool {
1065        let Some(container) = self.get_container_from_event_source(event_source) else { return false };
1066
1067        #[allow(deprecated)]
1068        self.update_selection_for_container(
1069            &container,
1070            select,
1071            range_modifier,
1072            toggle_modifier,
1073            right_button,
1074            from_focus,
1075        );
1076        true
1077    }
1078
1079    fn get_or_create_selection_model(&self) -> Rc<dyn ISelectionModel> {
1080        let existing = self.selection.borrow().clone();
1081        if let Some(existing) = existing {
1082            return existing;
1083        }
1084
1085        let selection = self.create_default_selection_model();
1086        *self.selection.borrow_mut() = Some(selection.clone());
1087        self.initialize_selection_model(&selection);
1088
1089        // Initializing the model raises events: the handlers may have
1090        // replaced it.
1091        let current = self.selection.borrow().clone();
1092        current.unwrap_or(selection)
1093    }
1094
1095    fn on_items_view_source_changed(&self) {
1096        if self.update_state.borrow().is_none() {
1097            let selection = self.selection.borrow().clone();
1098            self.try_initialize_selection_source(selection.as_ref(), true);
1099        }
1100    }
1101
1102    /// Called when a property changes on the selection model.
1103    fn on_selection_model_property_changed(&self, property_name: &str) {
1104        match property_name {
1105            "AnchorIndex" => {
1106                self.has_scrolled_to_selected_item.set(false);
1107
1108                let anchor_index = self.get_anchor_index();
1109                KeyboardNavigation::set_tab_once_active_element(
1110                    self,
1111                    self.container_from_index(anchor_index).map(Ref::upcast),
1112                );
1113                self.auto_scroll_to_selected_item_if_necessary(anchor_index);
1114            }
1115            "SelectedIndex" => {
1116                let selected_index = self.selected_index();
1117                let old_selected_index = self.old_selected_index.get();
1118                if old_selected_index != selected_index {
1119                    self.raise_direct_property_changed(
1120                        Self::selected_index_property(),
1121                        &old_selected_index,
1122                        &selected_index,
1123                    );
1124                    self.old_selected_index.set(selected_index);
1125                }
1126            }
1127            "SelectedItem" => {
1128                let selected_item = self.selected_item();
1129                let old_selected_item = self.old_selected_item.borrow().clone();
1130                if !items_equal(&selected_item, &old_selected_item) {
1131                    self.raise_direct_property_changed(
1132                        Self::selected_item_property(),
1133                        &old_selected_item,
1134                        &selected_item,
1135                    );
1136                    *self.old_selected_item.borrow_mut() = selected_item;
1137                }
1138            }
1139            "WritableSelectedItems" => {
1140                let old_selected_items = self.old_selected_items.borrow().clone();
1141                // The reference compares the old list with the read-only
1142                // selected items of the internal model, which is never the
1143                // same object: the change is raised whenever the selection
1144                // is an internal model or there was a list before.
1145                let is_internal = self.selection().as_internal_selection_model().is_some();
1146                if is_internal || old_selected_items.is_some() {
1147                    let selected_items = self.selected_items();
1148                    self.raise_direct_property_changed(
1149                        Self::selected_items_property(),
1150                        &old_selected_items,
1151                        &selected_items,
1152                    );
1153                    *self.old_selected_items.borrow_mut() = selected_items;
1154                }
1155            }
1156            "Source" => self.clear_value(Self::selected_value_property()),
1157            _ => {}
1158        }
1159    }
1160
1161    /// Called when the selection changed event is raised on the selection
1162    /// model.
1163    fn on_selection_model_selection_changed(&self, e: &dyn SelectionModelSelectionChangedEventArgs) {
1164        let mark = |index: i32, selected: bool| {
1165            if let Some(container) = self.container_from_index(index) {
1166                self.mark_container_selected(&container, selected);
1167            }
1168        };
1169
1170        let selected_indexes = e.selected_indexes();
1171        for i in 0..selected_indexes.count() {
1172            mark(selected_indexes.get(i), true);
1173        }
1174
1175        let deselected_indexes = e.deselected_indexes();
1176        for i in 0..deselected_indexes.count() {
1177            mark(deselected_indexes.get(i), false);
1178        }
1179
1180        if !self.is_selection_change_active.get() {
1181            self.update_selected_value_from_item();
1182        }
1183
1184        {
1185            let selected_items = self.selection().selected_items();
1186            let mut snapshot = self.selected_items_snapshot.borrow_mut();
1187            snapshot.clear();
1188            snapshot.extend(selected_items.iter());
1189        }
1190        *self.selected_items_before_reset.borrow_mut() = None;
1191
1192        self.raise_selection_changed(
1193            || e.deselected_items().to_vec(),
1194            || e.selected_items().to_vec(),
1195        );
1196
1197        self.verify_selected_index();
1198    }
1199
1200    /// Called when the lost selection event is raised on the selection
1201    /// model.
1202    fn on_selection_model_lost_selection(&self) {
1203        let before_reset = self.selected_items_before_reset.take();
1204        if let Some(before_reset) = before_reset.filter(|items| !items.is_empty()) {
1205            // Deviation (DEVIATIONS.md, Selection and pages): the items
1206            // selected before the reset are reported as removed here and
1207            // the selection model raises no change for them, so they are
1208            // dropped from the snapshot and from the selected value. The
1209            // reference keeps both until the next change of the selection,
1210            // which here keeps the removed items alive.
1211            let reported = std::mem::take(&mut *self.selected_items_snapshot.borrow_mut());
1212            drop(reported);
1213            self.update_selected_value_from_item();
1214
1215            self.raise_selection_changed(|| before_reset, Vec::new);
1216        }
1217
1218        if self.always_selected() && self.items_view().count() > 0 {
1219            self.set_selected_index(self.choose_first_visible_and_enabled_index());
1220        }
1221    }
1222
1223    fn raise_selection_changed(
1224        &self,
1225        removed_items: impl FnOnce() -> Vec<Option<BoxedValue>>,
1226        added_items: impl FnOnce() -> Vec<Option<BoxedValue>>,
1227    ) {
1228        let route = self.build_event_route(Self::selection_changed_event());
1229
1230        if route.has_handlers() {
1231            self.raise_event(&SelectionChangedEventArgs::new(
1232                Some(Self::selection_changed_event()),
1233                removed_items(),
1234                added_items(),
1235            ));
1236        }
1237    }
1238
1239    fn select_item_with_value(&self, value: &Option<BoxedValue>) {
1240        if self.item_count() == 0 || self.is_selection_change_active.get() {
1241            return;
1242        }
1243
1244        self.is_selection_change_active.set(true);
1245        let _reset = Reset(&self.is_selection_change_active);
1246
1247        match self.find_item_with_value(value) {
1248            Some(si) => self.set_selected_item(si),
1249            None => self.set_selected_item(None),
1250        }
1251    }
1252
1253    /// The item whose value is `value`; `None` if there is no such item
1254    /// (the unset result of the reference).
1255    fn find_item_with_value(&self, value: &Option<BoxedValue>) -> Option<Option<BoxedValue>> {
1256        if self.item_count() == 0 || value.is_none() {
1257            return None;
1258        }
1259
1260        let items = self.items_view().clone();
1261
1262        let Some(binding) = self.selected_value_binding() else {
1263            // No selected value binding set, the selected value is the item
1264            // itself. Still verify the value passed in is in the items
1265            // list.
1266            let index = items.index_of(value);
1267
1268            return if index >= 0 { Some(value.clone()) } else { None };
1269        };
1270
1271        let binding_evaluator = self.get_selected_value_binding_evaluator(&binding);
1272
1273        // Matching UWP behavior, if duplicates are present, return the
1274        // first item matching the selected value provided.
1275        for item in items.iter() {
1276            let item_value = binding_evaluator.evaluate(&item);
1277
1278            if items_equal(&item_value, value) {
1279                binding_evaluator.clear_data_context();
1280                return Some(item);
1281            }
1282        }
1283
1284        binding_evaluator.clear_data_context();
1285
1286        None
1287    }
1288
1289    fn update_selected_value_from_item(&self) {
1290        if self.is_selection_change_active.get() {
1291            return;
1292        }
1293
1294        let binding = self.selected_value_binding();
1295        let item = self.selected_item();
1296
1297        self.is_selection_change_active.set(true);
1298        let _reset = Reset(&self.is_selection_change_active);
1299
1300        match binding {
1301            Some(binding) if item.is_some() => {
1302                let binding_evaluator = self.get_selected_value_binding_evaluator(&binding);
1303                self.set_current_value(Self::selected_value_property(), binding_evaluator.evaluate(&item));
1304            }
1305            _ => {
1306                // No selected value binding, the selected value is the item
1307                // itself.
1308                self.set_current_value(Self::selected_value_property(), item);
1309            }
1310        }
1311    }
1312
1313    fn auto_scroll_to_selected_item_if_necessary(&self, anchor_index: i32) {
1314        if self.auto_scroll_to_selected_item()
1315            && self.presenter().is_some()
1316            && anchor_index >= 0
1317            && self.is_attached_to_visual_tree()
1318        {
1319            if !self.has_scrolled_to_selected_item.get() {
1320                self.scroll_into_view(anchor_index);
1321                self.auto_scroll_requested_index.set(anchor_index);
1322                self.has_scrolled_to_selected_item.set(true);
1323            }
1324        } else if self.auto_scroll_requested_index.get() >= 0 {
1325            // The conditions for auto-scrolling no longer hold, cancel the
1326            // scroll we requested earlier.
1327            if let Some(presenter) = self.presenter() {
1328                presenter.cancel_scroll_into_view(self.auto_scroll_requested_index.get());
1329            }
1330            self.auto_scroll_requested_index.set(-1);
1331        }
1332    }
1333
1334    /// Called when a container raises the `is_selected_changed_event`.
1335    fn container_selection_changed(&self, e: &RoutedEventArgs) {
1336        if !self.ignore_container_selection_changed.get() {
1337            let this: Ref<ferroui_base::StyledElement> = self.to_ref().upcast();
1338            let control = e.source().and_then(|source| source.cast::<Control>());
1339            if let Some(control) = control.filter(|control| control.parent().as_ref() == Some(&this)) {
1340                let index = self.index_from_container(&control);
1341                if index >= 0 {
1342                    if Self::get_is_selected(&control) {
1343                        self.selection().select(index);
1344                    } else {
1345                        self.selection().deselect(index);
1346                    }
1347                }
1348            }
1349        }
1350
1351        if !e.is_source(self) {
1352            e.set_handled(true);
1353        }
1354    }
1355
1356    /// Sets the `IsSelected` property on a container.
1357    fn mark_container_selected(&self, container: &Control, selected: bool) {
1358        self.ignore_container_selection_changed.set(true);
1359        let _reset = Reset(&self.ignore_container_selection_changed);
1360
1361        container.set_current_value(Self::is_selected_property(), selected);
1362    }
1363
1364    /// Finds the index of the first item that is visible and enabled.
1365    ///
1366    /// The second value is true if the returned index is known to be
1367    /// visible and enabled; false if the returned index is a non-realized,
1368    /// non-visual item and its container state is unknown.
1369    fn get_first_visible_and_enabled_index(&self) -> (i32, bool) {
1370        let count = self.item_count();
1371        if count == 0 {
1372            return (-1, true);
1373        }
1374
1375        for i in 0..count {
1376            if let Some(container) = self.container_from_index(i) {
1377                if container.is_visible() && container.is_enabled() {
1378                    return (i, true);
1379                }
1380
1381                continue;
1382            }
1383
1384            let item = self.items_view().get_at(i as usize);
1385            let Some(item) = item else { continue };
1386
1387            let value: &dyn AnyValue = &*item;
1388            // `item is Visual`, held by any handle.
1389            let visual = match value.downcast_ref::<Ref<Control>>() {
1390                Some(c) => Some(c.clone().upcast::<Visual>()),
1391                None => match value.downcast_ref::<Ref<Visual>>() {
1392                    Some(v) => Some(v.clone()),
1393                    None => ferroui_base::data::core::ValueTypes::as_object(value).and_then(|o| o.cast::<Visual>()),
1394                },
1395            };
1396            if let Some(v) = visual {
1397                if v.is_visible() && v.downcast_ref::<Control>().is_none_or(|c| c.is_enabled()) {
1398                    return (i, true);
1399                }
1400            } else {
1401                // The container isn't realized so its visibility/enabled
1402                // state is unknown.
1403                return (i, false);
1404            }
1405        }
1406
1407        (-1, true)
1408    }
1409
1410    fn choose_first_visible_and_enabled_index(&self) -> i32 {
1411        let (index, verified) = self.get_first_visible_and_enabled_index();
1412        self.unverified_selected_index.set(if verified { -1 } else { index });
1413        index
1414    }
1415
1416    fn verify_selected_index(&self) {
1417        if self.unverified_selected_index.get() == -1 {
1418            return;
1419        }
1420
1421        if !self.always_selected() {
1422            self.unverified_selected_index.set(-1);
1423            return;
1424        }
1425
1426        // The selection has moved elsewhere in the meantime.
1427        if self.unverified_selected_index.get() != self.selected_index() {
1428            self.unverified_selected_index.set(-1);
1429            return;
1430        }
1431
1432        // Still not realized: `container_for_item_prepared_override` will
1433        // check it.
1434        let Some(container) = self.container_from_index(self.unverified_selected_index.get()) else { return };
1435
1436        self.unverified_selected_index.set(-1);
1437
1438        if !container.is_visible() || !container.is_enabled() {
1439            self.move_selection_to_first_visible_and_enabled_item();
1440        }
1441    }
1442
1443    fn move_selection_to_first_visible_and_enabled_item(&self) {
1444        let index = self.get_first_realized_visible_and_enabled_index();
1445        if index != self.selected_index() {
1446            self.set_selected_index(index);
1447        }
1448    }
1449
1450    fn get_first_realized_visible_and_enabled_index(&self) -> i32 {
1451        let count = self.item_count();
1452        for i in 0..count {
1453            if let Some(container) = self.container_from_index(i) {
1454                if container.is_visible() && container.is_enabled() {
1455                    return i;
1456                }
1457            }
1458        }
1459        -1
1460    }
1461
1462    /// Update the containers using the current selection.
1463    fn update_container_selection(&self) {
1464        if let Some(panel) = self.presenter().and_then(|presenter| presenter.panel()) {
1465            let selection = self.selection();
1466            for container in panel.children().snapshot().iter() {
1467                self.mark_container_selected(container, selection.is_selected(self.index_from_container(&container.clone())));
1468            }
1469        }
1470    }
1471
1472    fn create_default_selection_model(&self) -> Rc<dyn ISelectionModel> {
1473        let model = InternalSelectionModel::new();
1474        model.set_single_select(!self.selection_mode().contains(SelectionMode::MULTIPLE));
1475        model
1476    }
1477
1478    fn initialize_selection_model(&self, model: &Rc<dyn ISelectionModel>) {
1479        if self.update_state.borrow().is_none() {
1480            self.try_initialize_selection_source(Some(model), false);
1481        }
1482
1483        let weak = self.to_ref().downgrade();
1484        let token = model.property_changed().add(Rc::new(move |name: &str| {
1485            if let Some(this) = weak.upgrade() {
1486                this.on_selection_model_property_changed(name);
1487            }
1488        }));
1489        self.selection_property_changed_token.set(token);
1490
1491        let weak = self.to_ref().downgrade();
1492        let subscription = model.selection_changed(Rc::new(move |e: &dyn SelectionModelSelectionChangedEventArgs| {
1493            if let Some(this) = weak.upgrade() {
1494                this.on_selection_model_selection_changed(e);
1495            }
1496        }));
1497        *self.selection_changed_subscription.borrow_mut() = Some(subscription);
1498
1499        let weak = self.to_ref().downgrade();
1500        model.lost_selection(Rc::new(move || {
1501            if let Some(this) = weak.upgrade() {
1502                this.on_selection_model_lost_selection();
1503            }
1504        }));
1505
1506        if model.single_select() {
1507            self.set_selection_mode(self.selection_mode() & !SelectionMode::MULTIPLE);
1508        } else {
1509            self.set_selection_mode(self.selection_mode() | SelectionMode::MULTIPLE);
1510        }
1511
1512        self.old_selected_index.set(model.selected_index());
1513        *self.old_selected_item.borrow_mut() = model.selected_item();
1514        {
1515            let selected_items = model.selected_items();
1516            let mut snapshot = self.selected_items_snapshot.borrow_mut();
1517            snapshot.clear();
1518            snapshot.extend(selected_items.iter());
1519        }
1520
1521        if self.update_state.borrow().is_none() && self.always_selected() && model.count() == 0 {
1522            model.set_selected_index(self.choose_first_visible_and_enabled_index());
1523        }
1524
1525        self.update_container_selection();
1526
1527        if self.selected_index() != -1 {
1528            self.raise_event(&SelectionChangedEventArgs::new(
1529                Some(Self::selection_changed_event()),
1530                Vec::new(),
1531                self.selection().selected_items().to_vec(),
1532            ));
1533        }
1534    }
1535
1536    fn try_initialize_selection_source(
1537        &self,
1538        selection: Option<&Rc<dyn ISelectionModel>>,
1539        should_select_item_from_selected_value: bool,
1540    ) {
1541        let Some(selection) = selection else { return };
1542        let Some(source) = self.items_view().try_get_initialized_source() else { return };
1543
1544        // The internal selection model keeps the selected index and
1545        // selected item values before the items source is set. However, the
1546        // selected value isn't part of that model, so we have to set the
1547        // selected item from the selected value manually now that we have a
1548        // source.
1549        //
1550        // While this works, this is messy: we effectively have "lazy
1551        // selection initialization" in 3 places:
1552        //  - the update state (all selection properties, for begin/end
1553        //    init)
1554        //  - the internal selection model (selected index/selected item)
1555        //  - this control (selected value)
1556        //
1557        // There's the opportunity to have a single place responsible for
1558        // this logic.
1559        if should_select_item_from_selected_value
1560            && selection.selected_index() == -1
1561            && selection.selected_item().is_none()
1562        {
1563            if let Some(item) = self.find_item_with_value(&self.selected_value()) {
1564                selection.set_selected_item(item);
1565            }
1566        }
1567
1568        selection.set_source(Some(source));
1569    }
1570
1571    fn deinitialize_selection_model(&self, model: Option<&Rc<dyn ISelectionModel>>) {
1572        if let Some(model) = model {
1573            model.property_changed().remove(self.selection_property_changed_token.get());
1574            if let Some(subscription) = self.selection_changed_subscription.take() {
1575                subscription.dispose();
1576            }
1577        }
1578    }
1579
1580    fn begin_updating(&self) {
1581        let mut state = self.update_state.borrow_mut();
1582        let state = state.get_or_insert_with(UpdateState::default);
1583        state.update_count += 1;
1584    }
1585
1586    fn end_updating(&self) {
1587        let finished = {
1588            let mut state = self.update_state.borrow_mut();
1589            match state.as_mut() {
1590                Some(update_state) => {
1591                    update_state.update_count -= 1;
1592                    update_state.update_count == 0
1593                }
1594                None => false,
1595            }
1596        };
1597
1598        if !finished {
1599            return;
1600        }
1601
1602        let Some(mut state) = self.update_state.take() else { return };
1603
1604        if let Some(selection) = state.selection.take() {
1605            self.set_selection(Some(selection));
1606        }
1607
1608        let current = self.selection.borrow().clone();
1609        if let Some(s) = current.as_ref().and_then(|selection| selection.as_internal_selection_model()) {
1610            s.update(
1611                self.items_view().try_get_initialized_source(),
1612                state.selected_items.clone().map(|items| items.map(|items| items.0)),
1613            );
1614        } else {
1615            if let Some(selected_items) = state.selected_items.clone() {
1616                self.set_selected_items(selected_items);
1617            }
1618
1619            self.try_initialize_selection_source(Some(&self.selection()), false);
1620        }
1621
1622        if let Some(selected_value) = &state.selected_value {
1623            if let Some(item) = self.find_item_with_value(selected_value) {
1624                state.selected_item = Some(item);
1625            }
1626        }
1627
1628        // Selected index vs selected item:
1629        // - If only one has a value, use it
1630        // - If both have a value, prefer the one having a "non-empty"
1631        //   value, e.g. not -1 nor null
1632        // - If both have a "non-empty" value, prefer the index
1633        if let Some(selected_index) = state.selected_index {
1634            match state.selected_item.clone() {
1635                Some(selected_item) if selected_index < 0 => self.set_selected_item(selected_item),
1636                _ => self.set_selected_index(selected_index),
1637            }
1638        } else if let Some(selected_item) = state.selected_item.clone() {
1639            self.set_selected_item(selected_item);
1640        }
1641
1642        if self.always_selected() && self.selected_index() == -1 && self.item_count() > 0 {
1643            self.set_selected_index(self.choose_first_visible_and_enabled_index());
1644        }
1645    }
1646
1647    fn start_text_search_timer(&self) {
1648        let timer = DispatcherTimer::new();
1649        timer.set_interval(Duration::from_secs(1));
1650        let weak = self.to_ref().downgrade();
1651        let tick = timer.tick(move |_| {
1652            if let Some(this) = weak.upgrade() {
1653                this.text_search_timer_tick();
1654            }
1655        });
1656        timer.start();
1657        *self.text_search_timer.borrow_mut() = Some((timer, tick));
1658    }
1659
1660    fn stop_text_search_timer(&self) {
1661        let Some((timer, tick)) = self.text_search_timer.take() else { return };
1662
1663        tick.dispose();
1664        timer.stop();
1665    }
1666
1667    fn text_search_timer_tick(&self) {
1668        self.text_search_term.borrow_mut().clear();
1669        self.stop_text_search_timer();
1670    }
1671
1672    fn get_index_from_text_search(&self, text_search_term: &str) -> i32 {
1673        if text_search_term.is_empty() {
1674            return -1;
1675        }
1676
1677        let count = self.items().count();
1678        if count == 0 {
1679            return -1;
1680        }
1681
1682        let text_binding = TextSearch::get_text_binding(self).or_else(|| self.display_member_binding());
1683        let text_binding_evaluator = BindingEvaluator::try_create(text_binding.as_ref());
1684
1685        let mut result = -1;
1686
1687        for i in 0..count {
1688            let text = TextSearch::get_effective_text(&self.items().get_at(i), text_binding_evaluator.as_ref());
1689            if starts_with_ordinal_ignore_case(&text, text_search_term) {
1690                result = i as i32;
1691                break;
1692            }
1693        }
1694
1695        if let Some(evaluator) = text_binding_evaluator {
1696            evaluator.dispose();
1697        }
1698
1699        result
1700    }
1701
1702    fn get_selected_value_binding_evaluator(&self, binding: &AssignedBinding) -> Ref<BindingEvaluator> {
1703        let existing = self.selected_value_binding_evaluator.borrow().clone();
1704        let evaluator = match existing {
1705            Some(evaluator) => evaluator,
1706            None => {
1707                let evaluator = BindingEvaluator::new();
1708                *self.selected_value_binding_evaluator.borrow_mut() = Some(evaluator.clone());
1709                evaluator
1710            }
1711        };
1712        evaluator.update_binding(binding);
1713        evaluator
1714    }
1715}
1716
1717/// Whether `text` starts with `prefix`, comparing without regard to case
1718/// and culture.
1719fn starts_with_ordinal_ignore_case(text: &str, prefix: &str) -> bool {
1720    let mut text = text.chars().flat_map(char::to_uppercase);
1721    for expected in prefix.chars().flat_map(char::to_uppercase) {
1722        if text.next() != Some(expected) {
1723            return false;
1724        }
1725    }
1726    true
1727}