makepad_widgets/
drop_down.rs

1use {
2    std::rc::Rc,
3    std::cell::RefCell,
4    crate::{
5        makepad_derive_widget::*,
6        popup_menu::{PopupMenu, PopupMenuAction},
7        makepad_draw::*,
8        widget::*,
9    }
10};
11
12live_design!{
13    DrawLabelText = {{DrawLabelText}} {}
14    DropDownBase = {{DropDown}} {}
15}
16
17#[derive(Live)]
18pub struct DropDown {
19    #[animator] animator: Animator,
20    
21    #[live] draw_bg: DrawQuad,
22    #[live] draw_text: DrawLabelText,
23    
24    #[walk] walk: Walk,
25    
26    #[live] bind: String,
27    #[live] bind_enum: String,
28    
29    #[live] popup_menu: Option<LivePtr>,
30    
31    #[live] labels: Vec<String>,
32    #[live] values: Vec<LiveValue>,
33    
34    #[live] popup_shift: DVec2,
35    
36    #[rust] is_open: bool,
37    
38    #[live] selected_item: usize,
39    
40    #[layout] layout: Layout,
41}
42
43#[derive(Default, Clone)]
44struct PopupMenuGlobal {
45    map: Rc<RefCell<ComponentMap<LivePtr, PopupMenu >> >
46}
47
48#[derive(Live, LiveHook)]#[repr(C)]
49struct DrawLabelText {
50    #[deref] draw_super: DrawText,
51    #[live] focus: f32,
52    #[live] hover: f32,
53    #[live] pressed: f32,
54}
55
56impl LiveHook for DropDown {
57    fn before_live_design(cx: &mut Cx) {
58        register_widget!(cx, DropDown)
59    }
60    
61    fn after_apply(&mut self, cx: &mut Cx, from: ApplyFrom, _index: usize, _nodes: &[LiveNode]) {
62        if self.popup_menu.is_none() || !from.is_from_doc() {
63            return
64        }
65        let global = cx.global::<PopupMenuGlobal>().clone();
66        let mut map = global.map.borrow_mut();
67        
68        // when live styling clean up old style references
69        map.retain( | k, _ | cx.live_registry.borrow().generation_valid(*k));
70        
71        let list_box = self.popup_menu.unwrap();
72        map.get_or_insert(cx, list_box, | cx | {
73            PopupMenu::new_from_ptr(cx, Some(list_box))
74        });
75        
76    }
77}
78#[derive(Clone, WidgetAction)]
79pub enum DropDownAction {
80    Select(usize, LiveValue),
81    None
82}
83
84
85impl DropDown {
86    
87    pub fn set_open(&mut self, cx: &mut Cx) {
88        self.is_open = true;
89        self.draw_bg.redraw(cx);
90        let global = cx.global::<PopupMenuGlobal>().clone();
91        let mut map = global.map.borrow_mut();
92        let lb = map.get_mut(&self.popup_menu.unwrap()).unwrap();
93        let node_id = LiveId(self.selected_item as u64).into();
94        lb.init_select_item(node_id);
95        cx.sweep_lock(self.draw_bg.area());
96    }
97    
98    pub fn set_closed(&mut self, cx: &mut Cx) {
99        self.is_open = false;
100        self.draw_bg.redraw(cx);
101        cx.sweep_unlock(self.draw_bg.area());
102    }
103    
104    pub fn handle_event_with(&mut self, cx: &mut Cx, event: &Event, dispatch_action: &mut dyn FnMut(&mut Cx, DropDownAction)) {
105        self.animator_handle_event(cx, event);
106        
107        if self.is_open && self.popup_menu.is_some() {
108            // ok so how will we solve this one
109            let global = cx.global::<PopupMenuGlobal>().clone();
110            let mut map = global.map.borrow_mut();
111            let menu = map.get_mut(&self.popup_menu.unwrap()).unwrap();
112            let mut close = false;
113            menu.handle_event_with(cx, event, self.draw_bg.area(), &mut | cx, action | {
114                match action {
115                    PopupMenuAction::WasSweeped(_node_id) => {
116                        //dispatch_action(cx, PopupMenuAction::WasSweeped(node_id));
117                    }
118                    PopupMenuAction::WasSelected(node_id) => {
119                        //dispatch_action(cx, PopupMenuAction::WasSelected(node_id));
120                        self.selected_item = node_id.0.0 as usize;
121                        dispatch_action(cx, DropDownAction::Select(self.selected_item, self.values.get(self.selected_item).cloned().unwrap_or(LiveValue::None)));
122                        self.draw_bg.redraw(cx);
123                        close = true;
124                    }
125                    _ => ()
126                }
127            });
128            if close {
129                self.set_closed(cx);
130            }
131            
132            // check if we clicked outside of the popup menu
133            if let Event::MouseDown(e) = event {
134                if !menu.menu_contains_pos(cx, e.abs) {
135                    self.set_closed(cx);
136                    self.animator_play(cx, id!(hover.off));
137                }
138            }
139        }
140        
141        match event.hits_with_sweep_area(cx, self.draw_bg.area(), self.draw_bg.area()) {
142            Hit::KeyFocusLost(_) => {
143                self.animator_play(cx, id!(focus.off));
144                self.set_closed(cx);
145                self.animator_play(cx, id!(hover.off));
146                self.draw_bg.redraw(cx);
147            }
148            Hit::KeyFocus(_) => {
149                self.animator_play(cx, id!(focus.on));
150            }
151            Hit::KeyDown(ke) => match ke.key_code {
152                KeyCode::ArrowUp => {
153                    if self.selected_item > 0 {
154                        self.selected_item -= 1;
155                        dispatch_action(cx, DropDownAction::Select(self.selected_item, self.values.get(self.selected_item).cloned().unwrap_or(LiveValue::None)));
156                        self.set_closed(cx);
157                        self.draw_bg.redraw(cx);
158                    }
159                }
160                KeyCode::ArrowDown => {
161                    if self.values.len() > 0 && self.selected_item < self.values.len() - 1 {
162                        self.selected_item += 1;
163                        dispatch_action(cx, DropDownAction::Select(self.selected_item, self.values.get(self.selected_item).cloned().unwrap_or(LiveValue::None)));
164                        self.set_closed(cx);
165                        self.draw_bg.redraw(cx);
166                    }
167                },
168                _ => ()
169            }
170            Hit::FingerDown(_fe) => {
171                cx.set_key_focus(self.draw_bg.area());
172                self.set_open(cx);
173                self.animator_play(cx, id!(hover.pressed));
174            },
175            Hit::FingerHoverIn(_) => {
176                cx.set_cursor(MouseCursor::Hand);
177                self.animator_play(cx, id!(hover.on));
178            }
179            Hit::FingerHoverOut(_) => {
180                self.animator_play(cx, id!(hover.off));
181            }
182            Hit::FingerUp(fe) => {
183                if fe.is_over {
184                    if fe.device.has_hovers() {
185                        self.animator_play(cx, id!(hover.on));
186                    }
187                }
188                else {
189                    self.animator_play(cx, id!(hover.off));
190                }
191            }
192            _ => ()
193        };
194    }
195    
196    pub fn draw_text(&mut self, cx: &mut Cx2d, label: &str) {
197        self.draw_bg.begin(cx, self.walk, self.layout);
198        self.draw_text.draw_walk(cx, Walk::fit(), Align::default(), label);
199        self.draw_bg.end(cx);
200    }
201    
202    pub fn draw_walk(&mut self, cx: &mut Cx2d, walk: Walk) {
203        //cx.clear_sweep_lock(self.draw_bg.area());
204        
205        self.draw_bg.begin(cx, walk, self.layout);
206        //let start_pos = cx.turtle().rect().pos;
207        
208        if let Some(val) = self.labels.get(self.selected_item) {
209            self.draw_text.draw_walk(cx, Walk::fit(), Align::default(), val);
210        }
211        else {
212            self.draw_text.draw_walk(cx, Walk::fit(), Align::default(), " ");
213        }
214        self.draw_bg.end(cx);
215        
216        cx.add_nav_stop(self.draw_bg.area(), NavRole::DropDown, Margin::default());
217        
218        if self.is_open && self.popup_menu.is_some() {
219            //cx.set_sweep_lock(self.draw_bg.area());
220            // ok so if self was not open, we need to
221            // ok so how will we solve this one
222            let global = cx.global::<PopupMenuGlobal>().clone();
223            let mut map = global.map.borrow_mut();
224            let popup_menu = map.get_mut(&self.popup_menu.unwrap()).unwrap();
225            let mut item_pos = None;
226            
227            // we kinda need to draw it twice.
228            
229            popup_menu.begin(cx);
230            
231            for (i, item) in self.labels.iter().enumerate() {
232                let node_id = LiveId(i as u64).into();
233                if i == self.selected_item {
234                    item_pos = Some(cx.turtle().pos());
235                }
236                popup_menu.draw_item(cx, node_id, &item);
237            }
238            
239            // ok we shift the entire menu. however we shouldnt go outside the screen area
240            popup_menu.end(cx, self.draw_bg.area(), -item_pos.unwrap_or(dvec2(0.0, 0.0)));
241        }
242    }
243}
244
245impl Widget for DropDown {
246    
247    fn widget_to_data(&self, _cx: &mut Cx, actions: &WidgetActions, nodes: &mut LiveNodeVec, path: &[LiveId]) -> bool {
248        match actions.single_action(self.widget_uid()) {
249            DropDownAction::Select(_, value) => {
250                nodes.write_field_value(path, value.clone());
251                true
252            }
253            _ => false
254        }
255    }
256    
257    fn data_to_widget(&mut self, cx: &mut Cx, nodes: &[LiveNode], path: &[LiveId]) {
258        if let Some(value) = nodes.read_field_value(path) {
259            if let Some(index) = self.values.iter().position( | v | v == value) {
260                if self.selected_item != index {
261                    self.selected_item = index;
262                    self.redraw(cx);
263                }
264            }
265            else {
266                error!("Value not in values list {:?}", value);
267            }
268        }
269    }
270    
271    fn redraw(&mut self, cx: &mut Cx) {
272        self.draw_bg.redraw(cx);
273    }
274    
275    fn handle_widget_event_with(&mut self, cx: &mut Cx, event: &Event, dispatch_action: &mut dyn FnMut(&mut Cx, WidgetActionItem)) {
276        let uid = self.widget_uid();
277        self.handle_event_with(cx, event, &mut | cx, action | {
278            dispatch_action(cx, WidgetActionItem::new(action.into(), uid))
279        });
280    }
281    
282    fn walk(&mut self, _cx:&mut Cx) -> Walk {self.walk}
283    
284    fn draw_walk_widget(&mut self, cx: &mut Cx2d, walk: Walk) -> WidgetDraw {
285        self.draw_walk(cx, walk);
286        WidgetDraw::done()
287    }
288}
289
290#[derive(Clone, PartialEq, WidgetRef)]
291pub struct DropDownRef(WidgetRef);
292
293impl DropDownRef {
294    pub fn set_labels(&self, labels: Vec<String>) {
295        if let Some(mut inner) = self.borrow_mut() {
296            inner.labels = labels
297        }
298    }
299    
300    pub fn set_labels_and_redraw(&self, cx: &mut Cx, labels: Vec<String>) {
301        if let Some(mut inner) = self.borrow_mut() {
302            inner.labels = labels;
303            inner.draw_bg.redraw(cx);
304        }
305    }
306    
307    pub fn selected(&self, actions: &WidgetActions) -> Option<usize> {
308        if let Some(item) = actions.find_single_action(self.widget_uid()) {
309            if let DropDownAction::Select(id, _) = item.action() {
310                return Some(id)
311            }
312        }
313        None
314    }
315    
316    pub fn set_selected_item(&self, item: usize) {
317        if let Some(mut inner) = self.borrow_mut() {
318            inner.selected_item = item.min(inner.labels.len().max(1) - 1)
319        }
320    }
321    
322    pub fn set_selected_item_and_redraw(&self, cx: &mut Cx, item: usize) {
323        if let Some(mut inner) = self.borrow_mut() {
324            let new_selected = item.min(inner.labels.len().max(1) - 1);
325            if new_selected != inner.selected_item{
326                inner.selected_item = new_selected;
327                inner.draw_bg.redraw(cx);
328            }
329        }
330    }
331    pub fn selected_item(&self) -> usize {
332        if let Some(inner) = self.borrow() {
333            return inner.selected_item
334        }
335        0
336    }
337    
338    pub fn selected_label(&self) -> String {
339        if let Some(inner) = self.borrow() {
340            return inner.labels[inner.selected_item].clone()
341        }
342        "".to_string()
343    }
344    
345    pub fn set_selected_by_label(&self, label: &str) {
346        if let Some(mut inner) = self.borrow_mut() {
347            if let Some(index) = inner.labels.iter().position( | v | v == label) {
348                inner.selected_item = index
349            }
350        }
351    }
352    
353    pub fn set_selected_by_label_and_redraw(&self, label: &str, cx: &mut Cx) {
354        if let Some(mut inner) = self.borrow_mut() {
355            if let Some(index) = inner.labels.iter().position( | v | v == label) {
356                if inner.selected_item != index{
357                    inner.selected_item = index;
358                    inner.draw_bg.redraw(cx);
359                }
360            }
361        }
362    }
363    
364}