From e778e5ed58c9d795260a82cb2de9031557b02fce Mon Sep 17 00:00:00 2001 From: Nathan Adams Date: Fri, 26 Jan 2024 21:34:49 +0100 Subject: [PATCH] desktop: Fix context menu, use new approach rather than trying to hack activate egui's menu --- desktop/src/app.rs | 2 +- desktop/src/gui.rs | 58 ++++++------------------- desktop/src/gui/context_menu.rs | 76 +++++++++++++++++++++++++++++++++ 3 files changed, 89 insertions(+), 47 deletions(-) create mode 100644 desktop/src/gui/context_menu.rs diff --git a/desktop/src/app.rs b/desktop/src/app.rs index b268a9302..9a12082ad 100644 --- a/desktop/src/app.rs +++ b/desktop/src/app.rs @@ -221,7 +221,7 @@ impl App { ElementState::Pressed => PlayerEvent::MouseDown { x, y, button }, ElementState::Released => PlayerEvent::MouseUp { x, y, button }, }; - if state == ElementState::Pressed && button == RuffleMouseButton::Right + if state == ElementState::Released && button == RuffleMouseButton::Right { // Show context menu. // TODO: Should be squelched if player consumes the right click event. diff --git a/desktop/src/gui.rs b/desktop/src/gui.rs index c33cffd4d..3bdb72505 100644 --- a/desktop/src/gui.rs +++ b/desktop/src/gui.rs @@ -1,3 +1,4 @@ +mod context_menu; mod controller; mod movie; mod open_dialog; @@ -8,6 +9,7 @@ use std::borrow::Cow; use url::Url; use crate::custom_event::RuffleEvent; +use crate::gui::context_menu::ContextMenu; use crate::gui::open_dialog::OpenDialog; use crate::player::PlayerOptions; use chrono::DateTime; @@ -68,7 +70,7 @@ pub struct RuffleGui { is_volume_visible: bool, volume_controls: VolumeControls, is_open_dialog_visible: bool, - context_menu: Vec, + context_menu: Option, open_dialog: OpenDialog, locale: LanguageIdentifier, default_player_options: PlayerOptions, @@ -97,7 +99,7 @@ impl RuffleGui { is_open_dialog_visible: false, was_suspended_before_debug: false, - context_menu: vec![], + context_menu: None, open_dialog: OpenDialog::new( default_player_options.clone(), default_path, @@ -159,17 +161,21 @@ impl RuffleGui { self.volume_window(egui_ctx, None); } - if !self.context_menu.is_empty() { - self.context_menu(egui_ctx); + if let Some(context_menu) = &mut self.context_menu { + if !context_menu.show(egui_ctx, &self.event_loop) { + self.context_menu = None; + } } } pub fn show_context_menu(&mut self, menu: Vec) { - self.context_menu = menu; + if !menu.is_empty() { + self.context_menu = Some(ContextMenu::new(menu)); + } } pub fn is_context_menu_visible(&self) -> bool { - !self.context_menu.is_empty() + self.context_menu.is_some() } /// Notifies the GUI that a new player was created. @@ -451,46 +457,6 @@ impl RuffleGui { }); } - /// Renders the right-click context menu. - fn context_menu(&mut self, egui_ctx: &egui::Context) { - let mut item_clicked = false; - let mut menu_visible = false; - // TODO: What is the proper way in egui to spawn a random context menu? - egui::CentralPanel::default() - .frame(Frame::none()) - .show(egui_ctx, |_| {}) - .response - .context_menu(|ui| { - menu_visible = true; - for (i, item) in self.context_menu.iter().enumerate() { - if i != 0 && item.separator_before { - ui.separator(); - } - let clicked = if item.checked { - Checkbox::new(&mut true, &item.caption).ui(ui).clicked() - } else { - let button = Button::new(&item.caption).wrap(false); - - ui.add_enabled(item.enabled, button).clicked() - }; - if clicked { - let _ = self - .event_loop - .send_event(RuffleEvent::ContextMenuItemClicked(i)); - item_clicked = true; - } - } - }); - - if item_clicked - || !menu_visible - || egui_ctx.input_mut(|input| input.consume_key(Modifiers::NONE, Key::Escape)) - { - // Hide menu. - self.context_menu.clear(); - } - } - fn open_file(&mut self, ui: &mut egui::Ui) { ui.close_menu(); diff --git a/desktop/src/gui/context_menu.rs b/desktop/src/gui/context_menu.rs new file mode 100644 index 000000000..92c74f14b --- /dev/null +++ b/desktop/src/gui/context_menu.rs @@ -0,0 +1,76 @@ +use crate::custom_event::RuffleEvent; +use egui::{ + vec2, Align, Area, Button, Checkbox, Color32, Frame, Id, Key, Layout, Modifiers, Order, Pos2, + Stroke, Style, Widget, +}; +use ruffle_core::ContextMenuItem; +use winit::event_loop::EventLoopProxy; + +pub struct ContextMenu { + items: Vec, + position: Option, +} + +impl ContextMenu { + pub fn new(items: Vec) -> Self { + Self { + items, + position: None, + } + } + + pub fn show( + &mut self, + egui_ctx: &egui::Context, + event_loop: &EventLoopProxy, + ) -> bool { + let mut item_clicked = false; + self.position = self.position.or(egui_ctx.pointer_latest_pos()); + + let area = Area::new(Id::new("context_menu")) + .order(Order::Foreground) + .fixed_pos(self.position.unwrap_or_default()) + .constrain_to(egui_ctx.screen_rect()) + .interactable(true) + .show(egui_ctx, |ui| { + set_menu_style(ui.style_mut()); + Frame::menu(ui.style()).show(ui, |ui| { + ui.set_max_width(150.0); + ui.with_layout(Layout::top_down_justified(Align::Min), |ui| { + for (i, item) in self.items.iter().enumerate() { + if i != 0 && item.separator_before { + ui.separator(); + } + let clicked = if item.checked { + Checkbox::new(&mut true, &item.caption).ui(ui).clicked() + } else { + let button = Button::new(&item.caption).wrap(false); + + ui.add_enabled(item.enabled, button).clicked() + }; + if clicked { + let _ = + event_loop.send_event(RuffleEvent::ContextMenuItemClicked(i)); + item_clicked = true; + } + } + }) + }) + }); + + let should_close = item_clicked + || area.response.clicked_elsewhere() + || egui_ctx.input_mut(|input| input.consume_key(Modifiers::NONE, Key::Escape)); + + !should_close + } +} + +// Shamelessly stolen from egui menu::set_menu_style, a private internal function +fn set_menu_style(style: &mut Style) { + style.spacing.button_padding = vec2(2.0, 0.0); + style.visuals.widgets.active.bg_stroke = Stroke::NONE; + style.visuals.widgets.hovered.bg_stroke = Stroke::NONE; + style.visuals.widgets.inactive.weak_bg_fill = Color32::TRANSPARENT; + style.visuals.widgets.inactive.bg_stroke = Stroke::NONE; +}