fui_controls/controls/
drop_down.rs1use std::cell::{Cell, RefCell};
2use std::rc::Rc;
3
4use typed_builder::TypedBuilder;
5use typemap::TypeMap;
6
7use fui_core::*;
8use fui_macros::ui;
9
10use crate::controls::*;
11use crate::{DataHolder, RadioController};
12
13#[derive(TypedBuilder)]
18pub struct DropDown<V>
19where
20 V: ViewModel + PartialEq + 'static,
21{
22 #[builder(default = Property::new(None))]
23 pub selected_item: Property<Option<Rc<V>>>,
24 #[builder(default = Box::new(Vec::<Rc<V>>::new()))]
25 pub items: Box<dyn ObservableCollection<Rc<V>>>,
26}
27
28impl<V> DropDown<V>
29where
30 V: ViewModel + PartialEq + 'static,
31{
32 pub fn to_view(
33 self,
34 _style: Option<Box<dyn Style<Self>>>,
35 context: ViewContext,
36 ) -> Rc<RefCell<dyn ControlObject>> {
37 let is_popup_open_property = Property::new(false);
38
39 let is_popup_open_property_clone = is_popup_open_property.clone();
40 let mut show_callback = Callback::empty();
41 show_callback.set_sync(move |_| {
42 is_popup_open_property_clone.set(true);
43 });
44
45 let is_popup_open_property_clone = is_popup_open_property.clone();
46 let mut hide_callback = Callback::empty();
47 hide_callback.set_sync(move |_| {
48 is_popup_open_property_clone.set(false);
49 });
50
51 let selected_item_prop_clone = self.selected_item.clone();
52 let hide_callback_clone = hide_callback.clone();
53 let menu_item_vms = self.items.map(move |v| {
54 MenuItemViewModel::new(
55 v.clone(),
56 selected_item_prop_clone.clone(),
57 hide_callback_clone.clone(),
58 )
59 });
60 let menu_item_controls = (&menu_item_vms
61 as &dyn ObservableCollection<Rc<MenuItemViewModel<V>>>)
62 .map(|vm| vm.create_view());
63
64 let content = ui! {
65 Button {
66 clicked: show_callback.clone(),
67
68 &self.selected_item,
69
70 Popup {
71 is_open: is_popup_open_property,
72 auto_hide: PopupAutoHide::ClickedOutside,
73 placement: PopupPlacement::BelowOrAboveParent,
74
75 Shadow {
76 ScrollViewer {
77 horizontal_scroll_bar_visibility: ScrollBarVisibility::Hidden,
78 vertical_scroll_bar_visibility: ScrollBarVisibility::Auto,
79
80 Grid {
81 VerticalAlignment: Alignment::Start,
82 columns: 1,
83
84 &menu_item_controls,
85 }
86 }
87 }
88 }
89 }
90 };
91
92 let radio_controller =
93 RadioController::<StyledControl<ToggleButton>>::new(menu_item_controls);
94
95 let data_holder = DataHolder {
96 data: (
97 self.selected_item,
98 self.items,
99 radio_controller,
100 menu_item_vms,
101 ),
102 };
103 data_holder.to_view(
104 None,
105 ViewContext {
106 attached_values: context.attached_values,
107 children: Children::SingleStatic(content),
108 },
109 )
110 }
111}
112
113struct MenuItemViewModel<V>
114where
115 V: ViewModel + PartialEq + 'static,
116{
117 pub is_checked: Property<bool>,
118 pub clicked_callback: Callback<()>,
119 pub source_vm: Rc<V>,
120 pub selected_item: Property<Option<Rc<V>>>,
121 pub event_subscription: Cell<Option<Subscription>>,
122}
123
124impl<V> MenuItemViewModel<V>
125where
126 V: ViewModel + PartialEq + 'static,
127{
128 pub fn new(
129 source_vm: Rc<V>,
130 selected_item: Property<Option<Rc<V>>>,
131 clicked_callback: Callback<()>,
132 ) -> Rc<Self> {
133 let is_checked = match &selected_item.get() {
134 None => false,
135 Some(vm) => vm == &source_vm,
136 };
137 let vm = Rc::new(MenuItemViewModel {
138 is_checked: Property::new(is_checked),
139 clicked_callback,
140 source_vm,
141 selected_item,
142 event_subscription: Cell::new(None),
143 });
144
145 {
146 let weak_vm = Rc::downgrade(&vm);
147 let clicked_callback = vm.clicked_callback.clone();
148 vm.event_subscription
149 .set(Some(vm.is_checked.on_changed(move |is_checked| {
150 if is_checked {
151 weak_vm.upgrade().map(|vm| {
152 let source_vm_clone = vm.source_vm.clone();
153 vm.selected_item.set(Some(source_vm_clone));
154 });
155 clicked_callback.emit(());
156 }
157 })));
158 }
159
160 vm
161 }
162}
163
164impl<V> ViewModel for MenuItemViewModel<V>
165where
166 V: ViewModel + PartialEq + 'static,
167{
168 fn create_view(self: &Rc<Self>) -> Rc<RefCell<dyn ControlObject>> {
169 let clicked_callback = self.clicked_callback.clone();
170 let content = self.source_vm.create_view();
171 ui! {
172 ToggleButton {
173 Style: DropDown {
174 clicked: clicked_callback,
175 },
176 is_checked: self.is_checked.clone(),
177 content,
178 }
179 }
180 }
181}