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 dispatch_action(cx, PopupMenuItemAction::WasSelected);
124 }
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 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 self.draw_bg.end(cx);
162
163 cx.end_pass_sized_turtle_with_shift(shift_area, shift);
164 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