Skip to main content

ferroui_controls/
list_box.rs

1use crate::generators::RecycleKey;
2use crate::metadata::TemplatePartAttribute;
3use crate::primitives::{
4    SelectedItemsList, SelectingItemsControl, SelectingItemsControlImpl, TemplateAppliedEventArgs,
5    TemplatedControlImpl, TemplatedControlImplExt,
6};
7use crate::selection::ISelectionModel;
8use crate::templates::{FuncTemplate, ITemplateOf};
9use crate::{
10    Control, ControlImpl, ItemsControl, ItemsControlImpl, ListBoxItem, Panel, SelectionMode, VirtualizingStackPanel,
11};
12use ferroui_base::input::{
13    InputElement, InputElementImpl, InputElementImplExt, KeyEventArgs, KeyModifiers, KeyboardNavigation,
14    KeyboardNavigationMode,
15};
16use ferroui_base::interactivity::InteractiveImpl;
17use ferroui_base::layout::LayoutableImpl;
18use ferroui_base::{
19    ferro_class, ferro_impl_classes, ferro_property, instantiate, BoxedValue, DirectProperty, FerroObjectImpl, FerroProperty, Ref, StaticType, StyledElementImpl, StyledProperty, VisualImpl,
20};
21use std::cell::RefCell;
22use std::rc::Rc;
23
24/// An [`ItemsControl`] in which individual items can be selected.
25#[repr(C)]
26pub struct ListBox {
27    base: SelectingItemsControl,
28    scroll: RefCell<Option<Ref<InputElement>>>,
29}
30
31ferro_class!(ListBox: SelectingItemsControl);
32ferroui_base::ferro_class_info!(ListBox { new: ListBox::new });
33ferro_impl_classes!(
34    ListBox: StyledElementImpl,
35    VisualImpl,
36    LayoutableImpl,
37    InteractiveImpl,
38    SelectingItemsControlImpl
39);
40
41ferroui_base::ferro_impl_classes!(ListBox: FerroObjectImpl);
42
43impl InputElementImpl for ListBox {
44    fn on_key_down(this: &Self, e: &KeyEventArgs) {
45        let hotkeys = this.get_platform_settings().map(|settings| settings.hotkey_configuration());
46        let ctrl = hotkeys.as_ref().is_some_and(|hotkeys| e.key_modifiers.contains(hotkeys.command_modifiers));
47        let direction = e.key.to_navigation_direction(KeyModifiers::NONE).filter(|direction| direction.is_directional());
48
49        if let (false, Some(direction)) = (ctrl, direction) {
50            let moved =
51                this.move_selection(direction, this.wrap_selection(), e.key_modifiers.contains(KeyModifiers::SHIFT));
52            e.set_handled(e.handled() | moved);
53        } else if this.selection_mode().contains(SelectionMode::MULTIPLE)
54            && hotkeys.as_ref().is_some_and(|hotkeys| hotkeys.select_all.iter().any(|x| x.matches(Some(e))))
55        {
56            this.selection().select_all();
57            e.set_handled(true);
58        }
59
60        Self::parent_on_key_down(this, e);
61    }
62}
63
64impl TemplatedControlImpl for ListBox {
65    fn on_apply_template(this: &Self, e: &TemplateAppliedEventArgs) {
66        Self::parent_on_apply_template(this, e);
67        let scroll = e
68            .name_scope()
69            .find("PART_ScrollViewer")
70            .and_then(|element| element.cast::<InputElement>())
71            .filter(|element| element.as_scrollable().is_some());
72        this.set_scroll(scroll);
73    }
74}
75
76impl ItemsControlImpl for ListBox {
77    fn create_container_for_item_override(
78        _this: &Self,
79        _item: &Option<BoxedValue>,
80        _index: i32,
81        _recycle_key: Option<RecycleKey>,
82    ) -> Ref<Control> {
83        ListBoxItem::new().upcast()
84    }
85
86    fn needs_container_override(this: &Self, item: &Option<BoxedValue>, _index: i32) -> (bool, Option<RecycleKey>) {
87        this.needs_container::<ListBoxItem>(item)
88    }
89}
90
91impl ControlImpl for ListBox {
92    fn on_create_automation_peer(this: &Self) -> Ref<crate::automation::peers::AutomationPeer> {
93        crate::automation::peers::ListBoxAutomationPeer::new(this).upcast()
94    }
95}
96
97impl ListBox {
98    /// The named parts expected in the control template: the scrollable
99    /// that scrolls the items.
100    pub const TEMPLATE_PARTS: &'static [TemplatePartAttribute] =
101        &[TemplatePartAttribute::new("PART_ScrollViewer", <InputElement as StaticType>::TYPE)];
102}
103
104ferroui_base::ferro_properties! { impl ListBox {
105    ferro_property!(
106        /// Defines the `Scroll` property.
107        pub fn scroll_property() -> DirectProperty<ListBox, Option<Ref<InputElement>>> {
108            FerroProperty::register_direct::<ListBox, _>("Scroll", |o| o.scroll(), None, None)
109        }
110    );
111} }
112
113impl ListBox {
114    /// Defines the `SelectedItems` property.
115    pub fn selected_items_property() -> &'static DirectProperty<SelectingItemsControl, Option<SelectedItemsList>> {
116        SelectingItemsControl::selected_items_property()
117    }
118
119    /// Defines the `Selection` property.
120    pub fn selection_property() -> &'static DirectProperty<SelectingItemsControl, Option<Rc<dyn ISelectionModel>>> {
121        SelectingItemsControl::selection_property()
122    }
123
124    /// Defines the `SelectionMode` property.
125    pub fn selection_mode_property() -> &'static StyledProperty<SelectionMode> {
126        SelectingItemsControl::selection_mode_property()
127    }
128
129    fn static_constructor() {
130        // The default value for the `ItemsPanel` property.
131        let default_panel: Rc<dyn ITemplateOf<Option<Ref<Panel>>>> =
132            FuncTemplate::new(|| Some(VirtualizingStackPanel::new().upcast::<Panel>()));
133        ItemsControl::items_panel_property().override_default_value::<ListBox>(default_panel);
134        KeyboardNavigation::tab_navigation_property().override_default_value::<ListBox>(KeyboardNavigationMode::Once);
135    }
136
137    /// Creates the class data; see [`ferroui_base::FerroObject::construct`].
138    pub fn construct() -> Self {
139        Self { base: SelectingItemsControl::construct(), scroll: RefCell::new(None) }
140    }
141
142    pub fn new() -> Ref<Self> {
143        instantiate(Self::construct())
144    }
145
146    /// Gets the scroll information for the [`ListBox`]: the element of the
147    /// template that implements the scrollable contract (see
148    /// `InputElement::as_scrollable`).
149    pub fn scroll(&self) -> Option<Ref<InputElement>> {
150        self.scroll.borrow().clone()
151    }
152
153    fn set_scroll(&self, value: Option<Ref<InputElement>>) {
154        self.set_and_raise(Self::scroll_property(), &self.scroll, value);
155    }
156
157    /// Selects all items in the [`ListBox`].
158    pub fn select_all(&self) {
159        self.selection().select_all()
160    }
161
162    /// Deselects all items in the [`ListBox`].
163    pub fn unselect_all(&self) {
164        self.selection().clear()
165    }
166}