makepad_widgets/
popup_menu.rs

1use {
2    crate::{
3        makepad_derive_widget::*,
4        makepad_draw::*,
5        widget::*,
6    },
7};
8
9live_design!{
10    PopupMenuItemBase = {{PopupMenuItem}} {}
11    PopupMenuBase = {{PopupMenu}} {}
12}
13
14
15#[derive(Live, LiveHook)]
16pub struct PopupMenuItem {
17    
18    #[live] draw_bg: DrawQuad,
19    #[live] draw_name: DrawText,
20    
21    #[layout] layout: Layout,
22    #[animator] animator: Animator,
23    #[walk] walk: Walk,
24    
25    #[live] indent_width: f32,
26    #[live] icon_walk: Walk,
27    
28    #[live] opened: f32,
29    #[live] hover: f32,
30    #[live] selected: f32,
31}
32
33#[derive(Live)]
34pub struct PopupMenu {
35    #[live] draw_list: DrawList2d,
36    #[live] menu_item: Option<LivePtr>,
37    
38    #[live] draw_bg: DrawQuad,
39    #[layout] layout: Layout,
40    #[walk] walk: Walk,
41    #[live] items: Vec<String>,
42    #[rust] first_tap: bool,
43    #[rust] menu_items: ComponentMap<PopupMenuItemId, PopupMenuItem>,
44    #[rust] init_select_item: Option<PopupMenuItemId>,
45    
46    #[rust] count: usize,
47}
48
49impl LiveHook for PopupMenu {
50    fn after_apply(&mut self, cx: &mut Cx, from: ApplyFrom, index: usize, nodes: &[LiveNode]) {
51        if let Some(index) = nodes.child_by_name(index, live_id!(list_node).as_field()) {
52            for (_, node) in self.menu_items.iter_mut() {
53                node.apply(cx, from, index, nodes);
54            }
55        }
56        self.draw_list.redraw(cx);
57    }
58}
59
60pub enum PopupMenuItemAction {
61    WasSweeped,
62    WasSelected,
63    MightBeSelected,
64    None
65}
66
67#[derive(Clone, WidgetAction)]
68pub enum PopupMenuAction {
69    WasSweeped(PopupMenuItemId),
70    WasSelected(PopupMenuItemId),
71    None,
72}
73
74#[derive(Clone, Debug, Default, Eq, Hash, Copy, PartialEq, FromLiveId)]
75pub struct PopupMenuItemId(pub LiveId);
76
77impl PopupMenuItem {
78    
79    pub fn draw_item(
80        &mut self,
81        cx: &mut Cx2d,
82        label: &str,
83    ) {
84        self.draw_bg.begin(cx, self.walk, self.layout);
85        self.draw_name.draw_walk(cx, Walk::fit(), Align::default(), label);
86        self.draw_bg.end(cx);
87    }
88    
89    pub fn handle_event_with(
90        &mut self,
91        cx: &mut Cx,
92        event: &Event,
93        sweep_area: Area,
94        dispatch_action: &mut dyn FnMut(&mut Cx, PopupMenuItemAction),
95    ) {
96        if self.animator_handle_event(cx, event).must_redraw() {
97            self.draw_bg.area().redraw(cx);
98        }
99        
100        match event.hits_with_options(
101            cx,
102            self.draw_bg.area(),
103            HitOptions::new().with_sweep_area(sweep_area)
104        ) {
105            Hit::FingerHoverIn(_) => {
106                self.animator_play(cx, id!(hover.on));
107            }
108            Hit::FingerHoverOut(_) => {
109                self.animator_play(cx, id!(hover.off));
110            }
111            Hit::FingerDown(_) => {
112                dispatch_action(cx, PopupMenuItemAction::WasSweeped);
113                self.animator_play(cx, id!(hover.on));
114                self.animator_play(cx, id!(select.on));
115            }
116            Hit::FingerUp(se) => {
117                if !se.is_sweep {
118                    //if se.was_tap() { // ok this only goes for the first time
119                    //    dispatch_action(cx, PopupMenuItemAction::MightBeSelected);
120                    //    println!("MIGHTBESELECTED");
121                    // }
122                    //else {
123                    dispatch_action(cx, PopupMenuItemAction::WasSelected);
124                    //}
125                }
126                else {
127                    self.animator_play(cx, id!(hover.off));
128                    self.animator_play(cx, id!(select.off));
129                }
130            }
131            _ => {}
132        }
133    }
134}
135
136impl PopupMenu {
137    
138    pub fn menu_contains_pos(&self, cx: &mut Cx, pos: DVec2) -> bool {
139        self.draw_bg.area().get_clipped_rect(cx).contains(pos)
140    }
141    
142    pub fn begin(&mut self, cx: &mut Cx2d) {
143        self.draw_list.begin_overlay_reuse(cx);
144        
145        cx.begin_pass_sized_turtle(Layout::flow_down());
146        
147        // ok so. this thing needs a complete position reset
148        self.draw_bg.begin(cx, self.walk, self.layout);
149        self.count = 0;
150    }
151    
152    pub fn end(&mut self, cx: &mut Cx2d, shift_area: Area, shift: DVec2) {
153        // ok so.
154        /*
155        let menu_rect1 = cx.turtle().padded_rect_used();
156        let pass_rect = Rect {pos: dvec2(0.0, 0.0), size: cx.current_pass_size()};
157        let menu_rect2 = pass_rect.add_margin(-dvec2(10.0, 10.0)).contain(menu_rect1);
158        */
159        //cx.turtle_mut().set_shift(shift + (menu_rect2.pos - menu_rect1.pos));
160        //let menu_rect1 = cx.turtle().padded_rect_used();
161        self.draw_bg.end(cx);
162        
163        cx.end_pass_sized_turtle_with_shift(shift_area, shift);
164        //cx.debug.rect_r(self.draw_bg.area().get_rect(cx));
165        self.draw_list.end(cx);
166        self.menu_items.retain_visible();
167        if let Some(init_select_item) = self.init_select_item.take() {
168            self.select_item_state(cx, init_select_item);
169        }
170    }
171    
172    pub fn redraw(&mut self, cx: &mut Cx) {
173        self.draw_list.redraw(cx);
174    }
175    
176    pub fn draw_item(
177        &mut self,
178        cx: &mut Cx2d,
179        item_id: PopupMenuItemId,
180        label: &str,
181    ) {
182        self.count += 1;
183        
184        let menu_item = self.menu_item;
185        let menu_item = self.menu_items.get_or_insert(cx, item_id, | cx | {
186            PopupMenuItem::new_from_ptr(cx, menu_item)
187        });
188        menu_item.draw_item(cx, label);
189    }
190    
191    pub fn init_select_item(&mut self, which_id: PopupMenuItemId) {
192        self.init_select_item = Some(which_id);
193        self.first_tap = true;
194    }
195    
196    fn select_item_state(&mut self, cx: &mut Cx, which_id: PopupMenuItemId) {
197        for (id, item) in &mut *self.menu_items {
198            if *id == which_id {
199                item.animator_cut(cx, id!(select.on));
200                item.animator_cut(cx, id!(hover.on));
201            }
202            else {
203                item.animator_cut(cx, id!(select.off));
204                item.animator_cut(cx, id!(hover.off));
205            }
206        }
207    }
208    
209    pub fn handle_event_with(
210        &mut self,
211        cx: &mut Cx,
212        event: &Event,
213        sweep_area: Area,
214        dispatch_action: &mut dyn FnMut(&mut Cx, PopupMenuAction),
215    ) {
216        let mut actions = Vec::new();
217        for (item_id, node) in self.menu_items.iter_mut() {
218            node.handle_event_with(cx, event, sweep_area, &mut | _, e | actions.push((*item_id, e)));
219        }
220        
221        for (node_id, action) in actions {
222            match action {
223                PopupMenuItemAction::MightBeSelected => {
224                    if self.first_tap {
225                        self.first_tap = false;
226                    }
227                    else {
228                        self.select_item_state(cx, node_id);
229                        dispatch_action(cx, PopupMenuAction::WasSelected(node_id));
230                    }
231                }
232                PopupMenuItemAction::WasSweeped => {
233                    self.select_item_state(cx, node_id);
234                    dispatch_action(cx, PopupMenuAction::WasSweeped(node_id));
235                }
236                PopupMenuItemAction::WasSelected => {
237                    self.select_item_state(cx, node_id);
238                    dispatch_action(cx, PopupMenuAction::WasSelected(node_id));
239                }
240                _ => ()
241            }
242        }
243    }
244}
245