desktop: Fix context menu, use new approach rather than trying to hack activate egui's menu

This commit is contained in:
Nathan Adams 2024-01-26 21:34:49 +01:00
parent 941e60aef5
commit e778e5ed58
3 changed files with 89 additions and 47 deletions

View File

@ -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.

View File

@ -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<ruffle_core::ContextMenuItem>,
context_menu: Option<ContextMenu>,
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<ruffle_core::ContextMenuItem>) {
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();

View File

@ -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<ContextMenuItem>,
position: Option<Pos2>,
}
impl ContextMenu {
pub fn new(items: Vec<ContextMenuItem>) -> Self {
Self {
items,
position: None,
}
}
pub fn show(
&mut self,
egui_ctx: &egui::Context,
event_loop: &EventLoopProxy<RuffleEvent>,
) -> 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;
}