Skip to main content

fui_controls/controls/
drop_down.rs

1use 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//
14// DropDown.
15//
16
17#[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}