ferroui_controls/
list_box.rs1use 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#[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 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 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 pub fn selected_items_property() -> &'static DirectProperty<SelectingItemsControl, Option<SelectedItemsList>> {
116 SelectingItemsControl::selected_items_property()
117 }
118
119 pub fn selection_property() -> &'static DirectProperty<SelectingItemsControl, Option<Rc<dyn ISelectionModel>>> {
121 SelectingItemsControl::selection_property()
122 }
123
124 pub fn selection_mode_property() -> &'static StyledProperty<SelectionMode> {
126 SelectingItemsControl::selection_mode_property()
127 }
128
129 fn static_constructor() {
130 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 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 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 pub fn select_all(&self) {
159 self.selection().select_all()
160 }
161
162 pub fn unselect_all(&self) {
164 self.selection().clear()
165 }
166}