use makepad_widgets::makepad_derive_widget::*; use makepad_widgets::makepad_draw::*; use makepad_widgets::widget::*; live_design! { import makepad_draw::shader::std::*; import makepad_widgets::theme_desktop_dark::*; import crate::shared::styles::*; DrawBg = {{DrawBg}} { instance color: #4 instance color_selected: #5 instance border_radius: 4.0 fn pixel(self) -> vec4 { let sdf = Sdf2d::viewport(self.pos * self.rect_size) sdf.box( 1.0, 1.0, self.rect_size.x, self.rect_size.y, self.border_radius ) sdf.fill_keep(mix(self.color, self.color_selected, self.hover)) return sdf.result; } } DrawName = {{DrawName}} { text_style: {font_size: 9}, fn get_color(self) -> vec4 { return mix( mix( THEME_COLOR_TEXT_DEFAULT, THEME_COLOR_TEXT_SELECTED, self.selected ), THEME_COLOR_TEXT_HOVER, self.hover ) } } MenuItem = {{MenuItem}} { align: {y: 0.5}, padding: {left: 5., top: 10., bottom: 10., right: 5.}, spacing: 5., width: Fill, height: Fit icon_walk: {width: 15., height: Fit, margin: {bottom: 3.}} draw_icon: { color: #f2f2f2; brightness: 0.8; } animator: { hover = { default: off off = { from: {all: Snap} apply: { draw_bg: {hover: 0.0} draw_name: {hover: 0.0} } } on = { cursor: Hand from: {all: Snap} apply: { draw_bg: {hover: 1.0} draw_name: {hover: 1.0} } } } select = { default: off off = { from: {all: Snap} apply: { draw_bg: {selected: 0.0,} draw_name: {selected: 0.0,} } } on = { from: {all: Snap} apply: { draw_bg: {selected: 1.0,} draw_name: {selected: 1.0,} } } } } indent_width: 10.0 } PopupMenu = {{PopupMenu}} { menu_item: {} flow: Down, padding: 5., width: 140., height: Fit, icon_walk: {width: 20., height: Fit} draw_icon:{ instance hover: 0.0 instance pressed: 0.0 fn get_color(self) -> vec4 { return mix( mix( #9, #c, self.hover ), #9, self.pressed ) } } draw_bg: { instance color: #4 instance border_width: 0.0, instance border_color: #4, instance border_radius: 4.0 fn get_color(self) -> vec4 { return self.color } fn get_border_color(self) -> vec4 { return self.border_color } fn pixel(self) -> vec4 { let sdf = Sdf2d::viewport(self.pos * self.rect_size) // sdf.blur = 18.0; sdf.box( self.border_width, self.border_width, self.rect_size.x - (self.border_width * 2.0), self.rect_size.y - (self.border_width * 2.0), self.border_radius ) sdf.fill_keep(self.get_color()) return sdf.result; } } } } #[derive(Live, LiveHook)] #[repr(C)] struct DrawBg { #[deref] draw_super: DrawQuad, #[live] selected: f32, #[live] hover: f32, } #[derive(Live, LiveHook)] #[repr(C)] struct DrawName { #[deref] draw_super: DrawText, #[live] selected: f32, #[live] hover: f32, } #[derive(Live)] pub struct PopupMenu { #[live] draw_list: DrawList2d, #[live] menu_item: Option, #[live] draw_bg: DrawQuad, #[live] draw_icon: DrawIcon, #[live] icon_walk: Walk, #[layout] layout: Layout, #[walk] walk: Walk, #[live] labels: Vec, #[live] values: Vec, #[live] items: Vec, #[rust] menu_items: ComponentMap, #[rust] init_select_item: Option, #[rust] first_tap: bool, #[rust] count: usize, } impl LiveHook for PopupMenu { fn before_live_design(cx: &mut Cx) { register_widget!(cx, PopupMenu) } fn after_apply(&mut self, cx: &mut Cx, from: ApplyFrom, index: usize, nodes: &[LiveNode]) { if let Some(index) = nodes.child_by_name(index, live_id!(list_node).as_field()) { for (_, node) in self.menu_items.iter_mut() { node.apply(cx, from, index, nodes); } } self.draw_list.redraw(cx); } } impl Widget for PopupMenu { fn handle_widget_event_with( &mut self, _cx: &mut Cx, _event: &Event, _dispatch_action: &mut dyn FnMut(&mut Cx, WidgetActionItem), ) { } fn draw_walk_widget(&mut self, cx: &mut Cx2d, walk: Walk) -> WidgetDraw { self.draw_bg.begin(cx, walk, self.layout); self.draw_icon.draw_walk(cx, self.icon_walk); self.draw_bg.end(cx); WidgetDraw::done() } fn walk(&mut self, _cx: &mut Cx) -> Walk { self.walk } fn redraw(&mut self, cx: &mut Cx) { self.draw_bg.redraw(cx); self.draw_icon.redraw(cx); } } impl PopupMenu { pub fn menu_contains_pos(&self, cx: &mut Cx, pos: DVec2) -> bool { self.draw_bg.area().get_clipped_rect(cx).contains(pos) } pub fn begin(&mut self, cx: &mut Cx2d) { self.draw_list.begin_overlay_reuse(cx); cx.begin_pass_sized_turtle(Layout::flow_down()); self.draw_bg.begin(cx, self.walk, self.layout); self.count = 0; } pub fn end(&mut self, cx: &mut Cx2d, shift_area: Area) { self.draw_bg.end(cx); let area = self.draw_bg.area().get_rect(cx); let shift = DVec2 { x: -area.size.x + (shift_area.get_rect(cx).size.x * 0.7), y: 30., }; cx.end_pass_sized_turtle_with_shift(shift_area, shift); self.draw_list.end(cx); self.menu_items.retain_visible(); if let Some(init_select_item) = self.init_select_item.take() { self.select_item_state(cx, init_select_item); } } pub fn draw_item( &mut self, cx: &mut Cx2d, item_id: MenuItemId, label: &str, icon: LiveDependency, ) { self.count += 1; let menu_item = self.menu_item; let menu_item = self .menu_items .get_or_insert(cx, item_id, |cx| MenuItem::new_from_ptr(cx, menu_item)); menu_item.draw_item(cx, label, icon); } pub fn init_select_item(&mut self, which_id: MenuItemId) { self.init_select_item = Some(which_id); self.first_tap = true; } fn select_item_state(&mut self, cx: &mut Cx, which_id: MenuItemId) { for (id, item) in &mut *self.menu_items { if *id == which_id { item.animator_cut(cx, id!(select.on)); item.animator_cut(cx, id!(hover.on)); } else { item.animator_cut(cx, id!(select.off)); item.animator_cut(cx, id!(hover.off)); } } } pub fn handle_event_with( &mut self, cx: &mut Cx, event: &Event, sweep_area: Area, dispatch_action: &mut dyn FnMut(&mut Cx, PopupMenuAction), ) { let mut actions = Vec::new(); for (item_id, node) in self.menu_items.iter_mut() { node.handle_event_with(cx, event, sweep_area, &mut |_, e| { actions.push((*item_id, e)) }); } for (node_id, action) in actions { match action { MenuItemAction::WasSweeped => { self.select_item_state(cx, node_id); dispatch_action(cx, PopupMenuAction::WasSweeped(node_id)); } MenuItemAction::WasSelected => { self.select_item_state(cx, node_id); dispatch_action(cx, PopupMenuAction::WasSelected(node_id)); } _ => (), } } } } #[derive(Live, LiveHook)] pub struct MenuItem { #[layout] layout: Layout, #[walk] walk: Walk, #[live] draw_bg: DrawBg, #[live] draw_name: DrawName, #[live] draw_icon: DrawIcon, #[live] icon_walk: Walk, #[live] indent_width: f32, #[animator] animator: Animator, #[live] opened: f32, #[live] hover: f32, #[live] selected: f32, } #[derive(Default, Clone)] pub enum MenuItemAction { WasSweeped, WasSelected, #[default] None, } #[derive(Clone, WidgetAction, Debug)] pub enum PopupMenuAction { WasSweeped(MenuItemId), WasSelected(MenuItemId), None, } #[derive(Clone, Debug, Default, Eq, Hash, Copy, PartialEq, FromLiveId)] pub struct MenuItemId(pub LiveId); impl MenuItem { pub fn draw_item(&mut self, cx: &mut Cx2d, label: &str, icon: LiveDependency) { self.draw_bg.begin(cx, self.walk, self.layout); self.draw_icon.svg_file = icon; self.draw_icon.draw_walk(cx, self.icon_walk); self.draw_name .draw_walk(cx, Walk::fit(), Align { x: 0., y: 0.5 }, label); self.draw_bg.end(cx); } pub fn handle_event_with( &mut self, cx: &mut Cx, event: &Event, sweep_area: Area, dispatch_action: &mut dyn FnMut(&mut Cx, MenuItemAction), ) { if self.animator_handle_event(cx, event).must_redraw() { self.draw_bg.area().redraw(cx); } match event.hits_with_options( cx, self.draw_bg.area(), HitOptions::new().with_sweep_area(sweep_area), ) { Hit::FingerHoverIn(_) => { self.animator_play(cx, id!(hover.on)); } Hit::FingerHoverOut(_) => { self.animator_play(cx, id!(hover.off)); } Hit::FingerDown(_) => { dispatch_action(cx, MenuItemAction::WasSweeped); self.animator_play(cx, id!(hover.on)); self.animator_play(cx, id!(select.on)); } Hit::FingerUp(se) => { if !se.is_sweep { dispatch_action(cx, MenuItemAction::WasSelected); } else { self.animator_play(cx, id!(hover.off)); self.animator_play(cx, id!(select.off)); } } _ => {} } } }