Skip to main content

ferroui_controls/
tree_view.rs

1use crate::generators::RecycleKey;
2use crate::items_source::{items_equal, ItemsChangedEventArgs, ItemsView};
3use crate::primitives::{
4    ItemSelectionEventTriggers, SelectedItemsList, SelectingItemsControl, TemplatedControlImpl,
5};
6use crate::{
7    Control, ControlImpl, ItemsControl, ItemsControlImpl, ItemsControlImplExt, SelectionChangedEventArgs, SelectionMode, TreeViewItem,
8};
9use ferroui_base::collections::{NotifyCollectionChangedAction, NotifyCollectionChangedEventArgs};
10use ferroui_base::input::{
11    FocusChangedEventArgs, ICustomKeyboardNavigation, InputElement, InputElementImpl, KeyEventArgs, KeyModifiers,
12    NavigationDirection, NavigationMethod, PointerEventArgs,
13};
14use ferroui_base::interactivity::{
15    IRoutedEventArgs, Interactive, InteractiveImpl, RoutedEventArgs, RoutedEventHandlerToken,
16};
17use ferroui_base::layout::LayoutableImpl;
18use ferroui_base::{
19    ferro_class, ferro_impl_classes, ferro_property, instantiate, BoxedValue, DirectProperty, FerroObject,
20    FerroObjectImpl, FerroProperty, Ref, StyledElementImpl, StyledProperty, Visual, VisualImpl,
21};
22use std::cell::{Cell, RefCell};
23use std::rc::Rc;
24
25/// Displays a hierarchical tree of data.
26#[repr(C)]
27pub struct TreeView {
28    base: ItemsControl,
29    selected_item: RefCell<Option<BoxedValue>>,
30    selected_items: RefCell<Option<SelectedItemsList>>,
31    selected_items_subscription: Cell<Option<u64>>,
32    syncing_selected_items: Cell<bool>,
33    suppress_selection_changed: Cell<bool>,
34    reported_selection: RefCell<Vec<Option<BoxedValue>>>,
35}
36
37ferro_class! {
38    TreeView: ItemsControl, virtuals TreeViewImpl: ItemsControlImpl {
39        /// Analyses a pointer event received by a selectable element, and
40        /// determines whether the action should trigger selection on press,
41        /// on release, or not at all.
42        fn should_trigger_selection(this, selectable: &Visual, event_args: &PointerEventArgs) -> bool;
43        /// Analyses a key event received by a selectable element, and
44        /// determines whether the action should trigger selection.
45        fn should_trigger_selection_key(this, selectable: &Visual, event_args: &KeyEventArgs) -> bool;
46        /// Updates the selection based on an event that may have originated
47        /// in a container that belongs to the control.
48        ///
49        /// Returns true if the event originated from a container that
50        /// belongs to the control and triggered selection; otherwise false.
51        fn update_selection_from_event(this, container: &Ref<Control>, event_args: &dyn IRoutedEventArgs) -> bool;
52    }
53}
54ferroui_base::ferro_class_info!(TreeView { new: TreeView::new });
55
56ferro_impl_classes!(
57    TreeView: StyledElementImpl,
58    VisualImpl,
59    LayoutableImpl,
60    InteractiveImpl,
61    TemplatedControlImpl
62);
63
64impl ControlImpl for TreeView {
65    fn on_create_automation_peer(this: &Self) -> Ref<crate::automation::peers::AutomationPeer> {
66        crate::automation::peers::TreeViewAutomationPeer::new(this).upcast()
67    }
68}
69
70ferroui_base::ferro_impl_classes!(TreeView: FerroObjectImpl);
71
72impl InputElementImpl for TreeView {
73    fn as_custom_keyboard_navigation(this: &Self) -> Option<&dyn ICustomKeyboardNavigation> {
74        Some(this)
75    }
76
77    fn on_got_focus(this: &Self, e: &FocusChangedEventArgs) {
78        if e.navigation_method == NavigationMethod::Directional {
79            e.set_handled(this.update_selection_from_event_source(
80                e.source(),
81                true,
82                e.key_modifiers.contains(KeyModifiers::SHIFT),
83                false,
84                false,
85            ));
86        }
87    }
88
89    fn on_key_down(this: &Self, e: &KeyEventArgs) {
90        let direction = e.key.to_navigation_direction(KeyModifiers::NONE);
91
92        if let (Some(direction), false) = (direction.filter(|direction| direction.is_directional()), e.handled()) {
93            if this.selected_item().is_some() {
94                let from = this.get_container_from_event_source(e.source());
95                let next = this.get_container_in_direction(from, direction, true);
96
97                if let Some(next) = next {
98                    e.set_handled(next.focus_with(NavigationMethod::Directional, KeyModifiers::NONE));
99                }
100            } else {
101                this.set_selected_item(this.items_view().get_at(0));
102            }
103        }
104
105        if !e.handled() {
106            let keymap = this.get_platform_settings().map(|settings| settings.hotkey_configuration());
107            let select_all = keymap.is_some_and(|keymap| keymap.select_all.iter().any(|g| g.matches(Some(e))));
108
109            if this.selection_mode() == SelectionMode::MULTIPLE && select_all {
110                this.select_all();
111                e.set_handled(true);
112            }
113        }
114    }
115}
116
117impl ItemsControlImpl for TreeView {
118    fn create_container_for_item_override(
119        _this: &Self,
120        _item: &Option<BoxedValue>,
121        _index: i32,
122        _recycle_key: Option<RecycleKey>,
123    ) -> Ref<Control> {
124        TreeViewItem::new().upcast()
125    }
126
127    fn needs_container_override(this: &Self, item: &Option<BoxedValue>, _index: i32) -> (bool, Option<RecycleKey>) {
128        this.needs_container::<TreeViewItem>(item)
129    }
130
131    fn container_for_item_prepared_override(
132        this: &Self,
133        container: &Ref<Control>,
134        item: &Option<BoxedValue>,
135        index: i32,
136    ) {
137        Self::parent_container_for_item_prepared_override(this, container, item, index);
138
139        // Once the container has been full prepared and added to the tree,
140        // any bindings from styles or item container themes are guaranteed
141        // to be applied.
142        if container.is_set(SelectingItemsControl::is_selected_property().as_property()) {
143            // The is selected property is set on the container: there is a
144            // style or item container theme which has bound the property.
145            // Update our selection based on the selection state of the
146            // container.
147            let container_is_selected = SelectingItemsControl::get_is_selected(container);
148            this.update_selection_from_container(container, container_is_selected, false, true, false);
149        }
150
151        // The is selected property is not set on the container: update the
152        // container selection based on the current selection as understood
153        // by this control.
154        this.mark_container_selected(container, this.selected_items().contains(item));
155
156        // If the newly realized container is the selected container, scroll
157        // to it after layout.
158        if this.auto_scroll_to_selected_item() && items_equal(&this.selected_item(), item) {
159            container.bring_into_view();
160        }
161    }
162
163    fn on_items_view_collection_changed(this: &Self, e: &ItemsChangedEventArgs<'_>) {
164        Self::parent_on_items_view_collection_changed(this, e);
165
166        match e.action {
167            NotifyCollectionChangedAction::Remove | NotifyCollectionChangedAction::Replace => {
168                for i in e.old_items {
169                    this.selected_items().remove(&i);
170                }
171            }
172            NotifyCollectionChangedAction::Reset => this.selected_items().clear(),
173            _ => {}
174        }
175    }
176}
177
178impl TreeViewImpl for TreeView {
179    fn should_trigger_selection(_this: &Self, selectable: &Visual, event_args: &PointerEventArgs) -> bool {
180        ItemSelectionEventTriggers::should_trigger_selection(selectable, event_args)
181    }
182
183    fn should_trigger_selection_key(_this: &Self, selectable: &Visual, event_args: &KeyEventArgs) -> bool {
184        ItemSelectionEventTriggers::should_trigger_selection_key(selectable, event_args)
185    }
186
187    fn update_selection_from_event(this: &Self, container: &Ref<Control>, event_args: &dyn IRoutedEventArgs) -> bool {
188        let args = event_args.as_routed_event_args();
189
190        if args.handled() {
191            return false;
192        }
193
194        let pointer_event = event_args.downcast_ref::<PointerEventArgs>();
195        let key_event = event_args.downcast_ref::<KeyEventArgs>();
196
197        let triggers = match (pointer_event, key_event) {
198            (Some(pointer_event), _) => this.should_trigger_selection(container, pointer_event),
199            (None, Some(key_event)) => this.should_trigger_selection_key(container, key_event),
200            (None, None) => false,
201        };
202
203        if !triggers {
204            return false;
205        }
206
207        this.update_selection_from_container(
208            container,
209            true,
210            ItemSelectionEventTriggers::has_range_selection_modifier(container, event_args),
211            ItemSelectionEventTriggers::has_toggle_selection_modifier(container, event_args),
212            pointer_event.is_some_and(|e| e.properties().is_right_button_pressed),
213        );
214
215        if pointer_event.is_some() {
216            crate::platform::PlatformFeedbackExtensions::perform_feedback(
217                &**container as &InputElement,
218                crate::platform::FeedbackAction::click(),
219            );
220        }
221
222        args.set_handled(true);
223        true
224    }
225}
226
227impl ICustomKeyboardNavigation for TreeView {
228    fn get_next(&self, element: &Ref<InputElement>, direction: NavigationDirection) -> (bool, Option<Ref<InputElement>>) {
229        if direction == NavigationDirection::Next || direction == NavigationDirection::Previous {
230            if !self.is_visual_ancestor_of(element) {
231                let selected_item = self.selected_item.borrow().clone();
232                let result = match selected_item {
233                    Some(_) => self.tree_container_from_item(&selected_item),
234                    None => self.container_from_index(0),
235                };
236
237                // The selected item may not be in the tree view.
238                return (result.is_some(), result.map(Ref::upcast));
239            }
240
241            return (true, None);
242        }
243
244        (false, None)
245    }
246}
247
248ferroui_base::ferro_properties! { impl TreeView {
249    ferro_property!(
250        /// Defines the `AutoScrollToSelectedItem` property.
251        pub fn auto_scroll_to_selected_item_property() -> StyledProperty<bool> {
252            SelectingItemsControl::auto_scroll_to_selected_item_property().add_owner::<TreeView>()
253        }
254    );
255
256    ferro_property!(
257        /// Defines the `SelectedItem` property.
258        pub fn selected_item_property() -> DirectProperty<TreeView, Option<BoxedValue>> {
259            SelectingItemsControl::selected_item_property().add_owner::<TreeView>(
260                |o| o.selected_item(),
261                Some(|o, v| o.set_selected_item(v)),
262                None,
263            )
264        }
265    );
266
267    ferro_property!(
268        /// Defines the `SelectedItems` property.
269        pub fn selected_items_property() -> DirectProperty<TreeView, Option<SelectedItemsList>> {
270            FerroProperty::register_direct::<TreeView, _>(
271                "SelectedItems",
272                |o| Some(o.selected_items()),
273                Some(|o, v| o.set_selected_items(v)),
274                None,
275            )
276        }
277    );
278
279    ferro_property!(
280        /// Defines the `SelectionMode` property.
281        pub fn selection_mode_property() -> StyledProperty<SelectionMode> {
282            SelectingItemsControl::selection_mode_property().add_owner::<TreeView>()
283        }
284    );
285} }
286
287impl TreeView {
288    /// Initializes static members of the [`TreeView`] class.
289    fn static_constructor() {
290        SelectingItemsControl::is_selected_changed_event()
291            .add_class_handler::<TreeView>(|x, e| x.container_selection_changed(e));
292    }
293
294    /// Creates the class data; see [`ferroui_base::FerroObject::construct`].
295    pub fn construct() -> Self {
296        Self {
297            base: ItemsControl::construct(),
298            selected_item: RefCell::new(None),
299            selected_items: RefCell::new(None),
300            selected_items_subscription: Cell::new(None),
301            syncing_selected_items: Cell::new(false),
302            suppress_selection_changed: Cell::new(false),
303            reported_selection: RefCell::new(Vec::new()),
304        }
305    }
306
307    pub fn new() -> Ref<Self> {
308        instantiate(Self::construct())
309    }
310
311    /// Occurs when the control's selection changes.
312    pub fn selection_changed(
313        &self,
314        handler: impl Fn(&Interactive, &SelectionChangedEventArgs) + 'static,
315    ) -> RoutedEventHandlerToken {
316        self.add_handler(SelectingItemsControl::selection_changed_event(), handler)
317    }
318
319    /// Gets or sets a value indicating whether to automatically scroll to
320    /// newly selected items.
321    ///
322    /// This property is of limited use with [`TreeView`] as it will only
323    /// scroll to realized items. To scroll to a non-expanded item, you need
324    /// to ensure that its ancestors are expanded.
325    pub fn auto_scroll_to_selected_item(&self) -> bool {
326        self.get_value(Self::auto_scroll_to_selected_item_property())
327    }
328
329    pub fn set_auto_scroll_to_selected_item(&self, value: bool) {
330        self.set_value(Self::auto_scroll_to_selected_item_property(), value)
331    }
332
333    /// Gets or sets the selection mode.
334    pub fn selection_mode(&self) -> SelectionMode {
335        self.get_value(Self::selection_mode_property())
336    }
337
338    pub fn set_selection_mode(&self, value: SelectionMode) {
339        self.set_value(Self::selection_mode_property(), value)
340    }
341
342    /// Gets or sets the selected item.
343    ///
344    /// Note that setting this property only currently works if the item is
345    /// expanded to be visible.
346    pub fn selected_item(&self) -> Option<BoxedValue> {
347        self.selected_item.borrow().clone()
348    }
349
350    pub fn set_selected_item(&self, value: Option<BoxedValue>) {
351        let selected_items = self.selected_items();
352
353        self.set_and_raise(Self::selected_item_property(), &self.selected_item, value.clone());
354
355        if value.is_some() {
356            if selected_items.count() != 1 || !items_equal(&selected_items.get(0), &value) {
357                self.select_single_item(value);
358            }
359        } else if self.selected_items().count() > 0 {
360            self.selected_items().clear();
361        }
362    }
363
364    /// Gets or sets the selected items.
365    pub fn selected_items(&self) -> SelectedItemsList {
366        let existing = self.selected_items.borrow().clone();
367        match existing {
368            Some(selected_items) => selected_items,
369            None => {
370                let selected_items = SelectedItemsList::new();
371                *self.selected_items.borrow_mut() = Some(selected_items.clone());
372                self.subscribe_to_selected_items();
373                selected_items
374            }
375        }
376    }
377
378    pub fn set_selected_items(&self, value: Option<SelectedItemsList>) {
379        // The reference rejects fixed size and read-only collections here;
380        // the list type of the property is neither.
381        self.unsubscribe_from_selected_items();
382        *self.selected_items.borrow_mut() = Some(value.unwrap_or_default());
383        self.subscribe_to_selected_items();
384    }
385
386    /// Expands the specified [`TreeViewItem`] and all its descendent
387    /// [`TreeViewItem`]s.
388    pub fn expand_sub_tree(&self, item: &TreeViewItem) {
389        item.set_is_expanded(true);
390
391        if item.items_panel_root().is_none() {
392            if let Some(layout_manager) = self.get_layout_manager() {
393                layout_manager.execute_layout_pass();
394            }
395        }
396
397        if let Some(panel) = item.items_panel_root() {
398            for child in panel.children().snapshot().iter() {
399                if let Some(tree_view_item) = child.downcast_ref::<TreeViewItem>() {
400                    self.expand_sub_tree(tree_view_item);
401                }
402            }
403        }
404    }
405
406    /// Collapses the specified [`TreeViewItem`] and all its descendent
407    /// [`TreeViewItem`]s.
408    pub fn collapse_sub_tree(&self, item: &TreeViewItem) {
409        item.set_is_expanded(false);
410
411        if let Some(panel) = item.items_panel_root() {
412            for child in panel.children().snapshot().iter() {
413                if let Some(tree_view_item) = child.downcast_ref::<TreeViewItem>() {
414                    self.collapse_sub_tree(tree_view_item);
415                }
416            }
417        }
418    }
419
420    /// Selects all items in the [`TreeView`].
421    ///
422    /// Note that this method only selects nodes currently visible due to
423    /// their parent nodes being expanded: it does not expand nodes.
424    pub fn select_all(&self) {
425        fn add_items(items_control: &ItemsControl, all_items: &mut Vec<Option<BoxedValue>>) {
426            all_items.extend(items_control.items_view().iter());
427
428            for child in items_control.get_realized_containers() {
429                if let Some(child_items_control) = child.downcast_ref::<ItemsControl>() {
430                    add_items(child_items_control, all_items);
431                }
432            }
433        }
434
435        let mut all_items = Vec::new();
436        add_items(self, &mut all_items);
437
438        self.suppress_selection_changed.set(true);
439        Self::synchronize_items(&self.selected_items(), &all_items);
440        self.suppress_selection_changed.set(false);
441
442        self.raise_pending_selection_changed();
443    }
444
445    /// Deselects all items in the [`TreeView`].
446    pub fn unselect_all(&self) {
447        self.selected_items().clear();
448    }
449
450    /// Gets the currently realized containers of the whole tree.
451    pub fn get_realized_tree_containers(&self) -> Vec<Ref<Control>> {
452        fn get_realized_containers(items_control: &ItemsControl, result: &mut Vec<Ref<Control>>) {
453            for container in items_control.get_realized_containers() {
454                result.push(container.clone());
455                if let Some(items_control_container) = container.downcast_ref::<ItemsControl>() {
456                    get_realized_containers(items_control_container, result);
457                }
458            }
459        }
460
461        let mut result = Vec::new();
462        get_realized_containers(self, &mut result);
463        result
464    }
465
466    /// Returns the container of the specified item, searching the whole
467    /// tree.
468    pub fn tree_container_from_item(&self, item: &Option<BoxedValue>) -> Option<Ref<Control>> {
469        fn tree_container_from_item(items_control: &ItemsControl, item: &Option<BoxedValue>) -> Option<Ref<Control>> {
470            if let Some(container) = items_control.container_from_item(item) {
471                return Some(container);
472            }
473
474            for child in items_control.get_realized_containers() {
475                if let Some(child_items_control) = child.downcast_ref::<ItemsControl>() {
476                    if let Some(child_container) = tree_container_from_item(child_items_control, item) {
477                        return Some(child_container);
478                    }
479                }
480            }
481
482            None
483        }
484
485        tree_container_from_item(self, item)
486    }
487
488    /// Returns the item of the specified container, searching the whole
489    /// tree.
490    pub fn tree_item_from_container(&self, container: &Ref<Control>) -> Option<BoxedValue> {
491        fn tree_item_from_container(items_control: &ItemsControl, container: &Ref<Control>) -> Option<BoxedValue> {
492            if let Some(item) = items_control.item_from_container(container) {
493                return Some(item);
494            }
495
496            for child in items_control.get_realized_containers() {
497                if let Some(child_items_control) = child.downcast_ref::<ItemsControl>() {
498                    if let Some(child_item) = tree_item_from_container(child_items_control, container) {
499                        return Some(child_item);
500                    }
501                }
502            }
503
504            None
505        }
506
507        tree_item_from_container(self, container)
508    }
509
510    /// Subscribes to the collection changed event of the selected items.
511    fn subscribe_to_selected_items(&self) {
512        let selected_items = self.selected_items.borrow().clone();
513
514        if let Some(selected_items) = selected_items {
515            let weak = self.to_ref().downgrade();
516            let token = selected_items.add_collection_changed(Rc::new(
517                move |e: &NotifyCollectionChangedEventArgs<'_, Option<BoxedValue>>| {
518                    if let Some(this) = weak.upgrade() {
519                        this.selected_items_collection_changed(&e.into());
520                    }
521                },
522            ));
523            self.selected_items_subscription.set(Some(token));
524        }
525
526        self.selected_items_collection_changed(&ItemsChangedEventArgs::RESET);
527    }
528
529    fn select_single_item(&self, item: Option<BoxedValue>) {
530        let old_value = self.selected_item.borrow().clone();
531        self.syncing_selected_items.set(true);
532        self.suppress_selection_changed.set(true);
533
534        self.selected_items().clear();
535        *self.selected_item.borrow_mut() = item.clone();
536        self.selected_items().add(item);
537
538        self.syncing_selected_items.set(false);
539        self.suppress_selection_changed.set(false);
540
541        self.raise_pending_selection_changed();
542
543        let new_value = self.selected_item.borrow().clone();
544        self.raise_direct_property_changed(Self::selected_item_property(), &old_value, &new_value);
545    }
546
547    /// Called when the collection changed event of the selected items is
548    /// raised.
549    fn selected_items_collection_changed(&self, e: &ItemsChangedEventArgs<'_>) {
550        let mut added: Option<Vec<Option<BoxedValue>>> = None;
551        let mut removed: Option<Vec<Option<BoxedValue>>> = None;
552
553        match e.action {
554            NotifyCollectionChangedAction::Add => {
555                self.selected_items_added(e.new_items);
556
557                let selected_item = self.selected_item();
558
559                if self.auto_scroll_to_selected_item()
560                    && selected_item.is_some()
561                    && items_equal(&e.new_items.get(0), &selected_item)
562                {
563                    if let Some(container) = self.tree_container_from_item(&selected_item) {
564                        container.bring_into_view();
565                    }
566                }
567
568                added = Some(e.new_items.to_vec());
569            }
570            NotifyCollectionChangedAction::Remove => {
571                if !self.syncing_selected_items.get() {
572                    if self.selected_items().count() == 0 {
573                        self.set_selected_item(None);
574                    } else {
575                        let selected_item = self.selected_item.borrow().clone();
576                        let selected_index = self.selected_items().index_of(&selected_item);
577
578                        if selected_index.is_none() {
579                            let old = selected_item;
580                            let new = self.selected_items().get(0);
581                            *self.selected_item.borrow_mut() = new.clone();
582
583                            self.raise_direct_property_changed(Self::selected_item_property(), &old, &new);
584                        }
585                    }
586                }
587
588                for item in e.old_items {
589                    self.mark_item_selected(&item, false);
590                }
591
592                removed = Some(e.old_items.to_vec());
593            }
594            NotifyCollectionChangedAction::Reset => {
595                for container in self.get_realized_tree_containers() {
596                    self.mark_container_selected(&container, false);
597                }
598
599                // A reset carries no item lists, so the delta comes from the last reported selection.
600                let (reset_removed, reset_added) = self.get_selection_delta();
601                removed = Some(reset_removed);
602                added = Some(reset_added);
603
604                if self.selected_items().count() > 0 {
605                    let selected_items = self.selected_items().to_vec();
606                    self.selected_items_added(ItemsView::from_slice(&selected_items));
607                } else if !self.syncing_selected_items.get() {
608                    self.set_selected_item(None);
609                }
610            }
611            NotifyCollectionChangedAction::Replace => {
612                for item in e.old_items {
613                    self.mark_item_selected(&item, false);
614                }
615
616                for item in e.new_items {
617                    self.mark_item_selected(&item, true);
618                }
619
620                let first = self.selected_items().get(0);
621                if !items_equal(&self.selected_item(), &first) && !self.syncing_selected_items.get() {
622                    let old_item = self.selected_item();
623                    *self.selected_item.borrow_mut() = first.clone();
624                    self.raise_direct_property_changed(Self::selected_item_property(), &old_item, &first);
625                }
626
627                added = Some(e.new_items.to_vec());
628                removed = Some(e.old_items.to_vec());
629            }
630            _ => {}
631        }
632
633        // Suppressed changes keep accumulating until the caller raises the event itself.
634        if !self.suppress_selection_changed.get() {
635            self.raise_selection_changed(removed, added);
636        }
637    }
638
639    /// Compares the current selection against the last reported selection:
640    /// the items deselected since and the items selected since.
641    fn get_selection_delta(&self) -> (Vec<Option<BoxedValue>>, Vec<Option<BoxedValue>>) {
642        let selected: Vec<Option<BoxedValue>> =
643            self.selected_items().to_vec().into_iter().filter(Option::is_some).collect();
644        let reported = self.reported_selection.borrow();
645        let contains = |list: &[Option<BoxedValue>], item: &Option<BoxedValue>| list.iter().any(|x| items_equal(x, item));
646
647        let removed = reported.iter().filter(|x| !contains(&selected[..], *x)).cloned().collect();
648        let added = selected.iter().filter(|x| !contains(&reported[..], *x)).cloned().collect();
649        (removed, added)
650    }
651
652    /// Raises the selection changed event for everything that has changed
653    /// since the event was last raised.
654    fn raise_pending_selection_changed(&self) {
655        let (removed, added) = self.get_selection_delta();
656        self.raise_selection_changed(Some(removed), Some(added));
657    }
658
659    fn raise_selection_changed(&self, removed: Option<Vec<Option<BoxedValue>>>, added: Option<Vec<Option<BoxedValue>>>) {
660        let reported: Vec<Option<BoxedValue>> =
661            self.selected_items().to_vec().into_iter().filter(Option::is_some).collect();
662        *self.reported_selection.borrow_mut() = reported;
663
664        if added.as_ref().is_some_and(|added| !added.is_empty())
665            || removed.as_ref().is_some_and(|removed| !removed.is_empty())
666        {
667            self.raise_event(&SelectionChangedEventArgs::new(
668                Some(SelectingItemsControl::selection_changed_event()),
669                removed.unwrap_or_default(),
670                added.unwrap_or_default(),
671            ));
672        }
673    }
674
675    fn mark_item_selected(&self, item: &Option<BoxedValue>, selected: bool) {
676        if let Some(container) = self.tree_container_from_item(item) {
677            self.mark_container_selected(&container, selected);
678        }
679    }
680
681    fn selected_items_added(&self, items: ItemsView<'_>) {
682        if items.is_empty() {
683            return;
684        }
685
686        for item in items {
687            self.mark_item_selected(&item, true);
688        }
689
690        if self.selected_item().is_none() && !self.syncing_selected_items.get() {
691            self.set_and_raise(Self::selected_item_property(), &self.selected_item, items.get(0));
692        }
693    }
694
695    /// Unsubscribes from the collection changed event of the selected
696    /// items.
697    fn unsubscribe_from_selected_items(&self) {
698        let selected_items = self.selected_items.borrow().clone();
699
700        if let (Some(selected_items), Some(token)) = (selected_items, self.selected_items_subscription.take()) {
701            selected_items.remove_collection_changed(token);
702        }
703    }
704
705    fn get_container_in_direction(
706        &self,
707        from: Option<Ref<TreeViewItem>>,
708        direction: NavigationDirection,
709        into_children: bool,
710    ) -> Option<Ref<TreeViewItem>> {
711        // The parent of the item is the tree view or another item; anything
712        // else (or no item to navigate from) has nothing to navigate to.
713        let from = from?;
714        let parent_item = from.parent().and_then(|parent| parent.cast::<TreeViewItem>());
715        let parent: Ref<ItemsControl> = match &parent_item {
716            Some(parent_item) => parent_item.clone().upcast(),
717            None => from.parent().and_then(|parent| parent.cast::<TreeView>())?.upcast(),
718        };
719
720        let index = parent.index_from_container(&from.clone().upcast());
721        let as_item = |container: Option<Ref<Control>>| container.and_then(|container| container.cast::<TreeViewItem>());
722
723        match direction {
724            NavigationDirection::Up => {
725                if index > 0 {
726                    let previous = as_item(parent.container_from_index(index - 1))?;
727                    if previous.is_expanded() && previous.item_count() > 0 {
728                        as_item(previous.container_from_index(previous.item_count() - 1))
729                    } else {
730                        Some(previous)
731                    }
732                } else {
733                    parent_item
734                }
735            }
736            NavigationDirection::Down | NavigationDirection::Right => {
737                if from.is_expanded() && into_children && from.item_count() > 0 {
738                    as_item(from.container_from_index(0))
739                } else if index < parent.item_count() - 1 {
740                    as_item(parent.container_from_index(index + 1))
741                } else if let Some(parent_item) = parent_item {
742                    self.get_container_in_direction(Some(parent_item), direction, false)
743                } else {
744                    None
745                }
746            }
747            _ => None,
748        }
749    }
750
751    /// Updates the selection for an item based on user interaction.
752    ///
753    /// `select` is whether the item should be selected or unselected,
754    /// `range_modifier` whether the range modifier is enabled (i.e. the
755    /// shift key), `toggle_modifier` whether the toggle modifier is enabled
756    /// (i.e. the control key) and `right_button` whether the event is a
757    /// right-click.
758    pub fn update_selection_from_container(
759        &self,
760        container: &Ref<Control>,
761        select: bool,
762        range_modifier: bool,
763        toggle_modifier: bool,
764        right_button: bool,
765    ) {
766        let Some(item) = self.tree_item_from_container(container) else { return };
767        let item = Some(item);
768
769        let mut selected_container = None;
770
771        if self.selected_item().is_some() {
772            selected_container = self.tree_container_from_item(&self.selected_item());
773        }
774
775        let mode = self.selection_mode();
776        let toggle = toggle_modifier || mode.contains(SelectionMode::TOGGLE);
777        let multi = mode.contains(SelectionMode::MULTIPLE);
778        let range = multi && range_modifier && selected_container.is_some();
779
780        if !select {
781            self.selected_items().remove(&item);
782        } else if right_button {
783            if !self.selected_items().contains(&item) {
784                self.select_single_item(item);
785            }
786        } else if !toggle && !range {
787            self.select_single_item(item);
788        } else if multi && range {
789            Self::synchronize_items(
790                &self.selected_items(),
791                &self.get_items_in_range(
792                    selected_container.and_then(|container| container.cast::<TreeViewItem>()),
793                    container.clone().cast::<TreeViewItem>(),
794                ),
795            );
796        } else {
797            let i = self.selected_items().index_of(&item);
798
799            if i.is_some() {
800                self.selected_items().remove(&item);
801            } else if multi {
802                self.selected_items().add(item);
803            } else {
804                self.set_selected_item(item);
805            }
806        }
807    }
808
809    /// Finds which node is first in the hierarchy.
810    fn find_first_node(tree_view: &TreeView, node_a: &Ref<TreeViewItem>, node_b: &Ref<TreeViewItem>) -> Option<Ref<TreeViewItem>> {
811        Self::find_in_containers(tree_view, node_a, node_b)
812    }
813
814    fn find_in_containers(
815        items_control: &ItemsControl,
816        node_a: &Ref<TreeViewItem>,
817        node_b: &Ref<TreeViewItem>,
818    ) -> Option<Ref<TreeViewItem>> {
819        for container in items_control.get_realized_containers() {
820            let node = Self::find_first_node_in(container.cast::<TreeViewItem>(), node_a, node_b);
821
822            if node.is_some() {
823                return node;
824            }
825        }
826
827        None
828    }
829
830    fn find_first_node_in(
831        node: Option<Ref<TreeViewItem>>,
832        node_a: &Ref<TreeViewItem>,
833        node_b: &Ref<TreeViewItem>,
834    ) -> Option<Ref<TreeViewItem>> {
835        let node = node?;
836
837        if node == *node_a {
838            return Some(node_a.clone());
839        }
840
841        if node == *node_b {
842            return Some(node_b.clone());
843        }
844
845        Self::find_in_containers(&node, node_a, node_b)
846    }
847
848    /// Returns all items that belong to containers between `from` and `to`.
849    /// The range is inclusive.
850    fn get_items_in_range(
851        &self,
852        from: Option<Ref<TreeViewItem>>,
853        to: Option<Ref<TreeViewItem>>,
854    ) -> Vec<Option<BoxedValue>> {
855        let mut items = Vec::new();
856
857        let (Some(mut from), Some(mut to)) = (from, to) else { return items };
858
859        let Some(first_item) = Self::find_first_node(self, &from, &to) else { return items };
860
861        let mut was_reversed = false;
862
863        if first_item == to {
864            std::mem::swap(&mut from, &mut to);
865
866            was_reversed = true;
867        }
868
869        let mut node = Some(from);
870
871        while let Some(current) = node.take() {
872            if current == to {
873                break;
874            }
875
876            let item = self.tree_item_from_container(&current.clone().upcast());
877
878            if item.is_some() {
879                items.push(item);
880            }
881
882            node = self.get_container_in_direction(Some(current), NavigationDirection::Down, true);
883        }
884
885        let to_item = self.tree_item_from_container(&to.upcast());
886
887        if to_item.is_some() {
888            items.push(to_item);
889        }
890
891        if was_reversed {
892            items.reverse();
893        }
894
895        items
896    }
897
898    /// Updates the selection based on an event that may have originated in
899    /// a container that belongs to the control.
900    ///
901    /// `event_source` is the control that raised the event; the other
902    /// arguments are those of
903    /// [`update_selection_from_container`](Self::update_selection_from_container).
904    ///
905    /// Returns true if the event originated from a container that belongs
906    /// to the control; otherwise false.
907    pub fn update_selection_from_event_source(
908        &self,
909        event_source: Option<Ref<FerroObject>>,
910        select: bool,
911        range_modifier: bool,
912        toggle_modifier: bool,
913        right_button: bool,
914    ) -> bool {
915        let container = self.get_container_from_event_source(event_source);
916
917        if let Some(container) = container {
918            self.update_selection_from_container(
919                &container.upcast(),
920                select,
921                range_modifier,
922                toggle_modifier,
923                right_button,
924            );
925            return true;
926        }
927
928        false
929    }
930
931    /// Tries to get the container that was the source of an event.
932    ///
933    /// Returns the container or `None` if the event did not originate in a
934    /// container.
935    pub fn get_container_from_event_source(&self, event_source: Option<Ref<FerroObject>>) -> Option<Ref<TreeViewItem>> {
936        let source = event_source.and_then(|source| source.cast::<Visual>())?;
937        let item = source.get_self_and_visual_ancestors().find_map(|visual| visual.cast::<TreeViewItem>())?;
938
939        if item.tree_view_owner().is_some_and(|owner| owner == self.to_ref()) {
940            Some(item)
941        } else {
942            None
943        }
944    }
945
946    /// Called when a container raises the is selected changed event.
947    fn container_selection_changed(&self, e: &RoutedEventArgs) {
948        let source = e.source();
949        let container = source.as_ref().and_then(|source| source.clone().cast::<TreeViewItem>());
950
951        if let Some(container) = container {
952            if container.tree_view_owner().is_some_and(|owner| owner == self.to_ref()) {
953                let container: Ref<Control> = container.upcast();
954
955                if let Some(item) = self.tree_item_from_container(&container) {
956                    let item = Some(item);
957                    let container_is_selected = SelectingItemsControl::get_is_selected(&container);
958                    let our_is_selected = self.selected_items().contains(&item);
959
960                    if container_is_selected != our_is_selected {
961                        if container_is_selected {
962                            self.selected_items().add(item);
963                        } else {
964                            self.selected_items().remove(&item);
965                        }
966                    }
967                }
968            }
969        }
970
971        if !e.is_source(self) {
972            e.set_handled(true);
973        }
974    }
975
976    /// Sets the selected state of a container.
977    fn mark_container_selected(&self, container: &Control, selected: bool) {
978        container.set_current_value(SelectingItemsControl::is_selected_property(), selected);
979    }
980
981    /// Makes a list of objects equal another (though doesn't preserve
982    /// order).
983    fn synchronize_items(items: &SelectedItemsList, desired: &[Option<BoxedValue>]) {
984        let items_count = items.count();
985        let desired_count = desired.len();
986
987        if items_count == 0 && desired_count > 0 {
988            // Add all desired.
989            for item in desired {
990                items.add(item.clone());
991            }
992        } else if items_count > 0 && desired_count == 0 {
993            // Remove all.
994            items.clear();
995        } else {
996            // Intersect.
997            fn except(first: &[Option<BoxedValue>], second: &[Option<BoxedValue>]) -> Vec<Option<BoxedValue>> {
998                let mut result: Vec<Option<BoxedValue>> = Vec::new();
999                for item in first {
1000                    if !second.iter().any(|other| items_equal(other, item))
1001                        && !result.iter().any(|other| items_equal(other, item))
1002                    {
1003                        result.push(item.clone());
1004                    }
1005                }
1006                result
1007            }
1008
1009            let list = items.to_vec();
1010            let to_remove = except(&list, desired);
1011            let to_add = except(desired, &list);
1012
1013            for i in &to_remove {
1014                items.remove(i);
1015            }
1016
1017            for i in to_add {
1018                items.add(i);
1019            }
1020        }
1021    }
1022}