desktop: Set main window as parent of file pickers

This patch makes sure that all file pickers are children of Ruffle.
This way, file pickers will hover over the main window and will have
to be dismissed before the user may interact with it.
This commit is contained in:
Kamil Jarosz 2024-08-07 14:38:15 +02:00
parent 5b3c9722d5
commit 87bf7b5b19
11 changed files with 101 additions and 22 deletions

30
Cargo.lock generated
View File

@ -251,9 +251,13 @@ dependencies = [
"futures-channel", "futures-channel",
"futures-util", "futures-util",
"rand", "rand",
"raw-window-handle",
"serde", "serde",
"serde_repr", "serde_repr",
"url", "url",
"wayland-backend",
"wayland-client",
"wayland-protocols",
"zbus", "zbus",
] ]
@ -3501,6 +3505,7 @@ checksum = "0ee638a5da3799329310ad4cfa62fbf045d5f56e3ef5ba4149e7452dcf89d5a8"
dependencies = [ dependencies = [
"bitflags 2.6.0", "bitflags 2.6.0",
"block2 0.5.1", "block2 0.5.1",
"dispatch",
"libc", "libc",
"objc2 0.5.2", "objc2 0.5.2",
] ]
@ -4199,6 +4204,27 @@ dependencies = [
"windows-sys 0.48.0", "windows-sys 0.48.0",
] ]
[[package]]
name = "rfd"
version = "0.14.1"
source = "git+https://github.com/PolyMeilex/rfd.git?rev=42dcc7d61fc5e278b4ed76bb9720ba4d89266f01#42dcc7d61fc5e278b4ed76bb9720ba4d89266f01"
dependencies = [
"ashpd",
"block2 0.5.1",
"js-sys",
"log",
"objc2 0.5.2",
"objc2-app-kit",
"objc2-foundation",
"pollster",
"raw-window-handle",
"urlencoding",
"wasm-bindgen",
"wasm-bindgen-futures",
"web-sys",
"windows-sys 0.48.0",
]
[[package]] [[package]]
name = "ring" name = "ring"
version = "0.17.8" version = "0.17.8"
@ -4308,7 +4334,7 @@ dependencies = [
"image", "image",
"os_info", "os_info",
"rand", "rand",
"rfd", "rfd 0.14.1 (git+https://github.com/PolyMeilex/rfd.git?rev=42dcc7d61fc5e278b4ed76bb9720ba4d89266f01)",
"ruffle_core", "ruffle_core",
"ruffle_frontend_utils", "ruffle_frontend_utils",
"ruffle_render", "ruffle_render",
@ -4570,7 +4596,7 @@ dependencies = [
"getrandom", "getrandom",
"gloo-net", "gloo-net",
"js-sys", "js-sys",
"rfd", "rfd 0.14.1 (registry+https://github.com/rust-lang/crates.io-index)",
"ruffle_core", "ruffle_core",
"ruffle_render", "ruffle_render",
"ruffle_render_canvas", "ruffle_render_canvas",

View File

@ -79,6 +79,8 @@ github = [
"ruffle-rs", "ruffle-rs",
# TODO: Remove once a release with https://github.com/emilk/egui/pull/4847 in it is out. # TODO: Remove once a release with https://github.com/emilk/egui/pull/4847 in it is out.
"emilk", "emilk",
# TODO: Remove once a release with https://github.com/PolyMeilex/rfd/pull/209 in it is out.
"PolyMeilex",
] ]
[advisories] [advisories]

View File

@ -32,7 +32,7 @@ winit = "0.29.15"
webbrowser = "1.0.1" webbrowser = "1.0.1"
url = { workspace = true } url = { workspace = true }
dirs = "5.0" dirs = "5.0"
rfd = "0.14.1" rfd = { git = "https://github.com/PolyMeilex/rfd.git", rev = "42dcc7d61fc5e278b4ed76bb9720ba4d89266f01" }
anyhow = { workspace = true } anyhow = { workspace = true }
bytemuck = { workspace = true } bytemuck = { workspace = true }
os_info = { version = "3", default-features = false } os_info = { version = "3", default-features = false }

View File

@ -514,8 +514,8 @@ impl App {
} }
winit::event::Event::UserEvent(RuffleEvent::BrowseAndOpen(options)) => { winit::event::Event::UserEvent(RuffleEvent::BrowseAndOpen(options)) => {
if let Some(url) = if let Some(url) = pick_file(false, None, Some(self.window.clone()))
pick_file(false, None).and_then(|p| Url::from_file_path(p).ok()) .and_then(|p| Url::from_file_path(p).ok())
{ {
self.gui self.gui
.borrow_mut() .borrow_mut()

View File

@ -25,7 +25,7 @@ use rfd::FileDialog;
use ruffle_core::debug_ui::Message as DebugMessage; use ruffle_core::debug_ui::Message as DebugMessage;
use ruffle_core::{Player, PlayerEvent}; use ruffle_core::{Player, PlayerEvent};
use std::collections::HashMap; use std::collections::HashMap;
use std::sync::MutexGuard; use std::sync::{MutexGuard, Weak};
use std::{fs, mem}; use std::{fs, mem};
use unic_langid::LanguageIdentifier; use unic_langid::LanguageIdentifier;
use winit::event_loop::EventLoopProxy; use winit::event_loop::EventLoopProxy;
@ -93,6 +93,7 @@ pub struct RuffleGui {
impl RuffleGui { impl RuffleGui {
fn new( fn new(
window: Weak<winit::window::Window>,
event_loop: EventLoopProxy<RuffleEvent>, event_loop: EventLoopProxy<RuffleEvent>,
default_path: Option<Url>, default_path: Option<Url>,
default_launch_options: LaunchOptions, default_launch_options: LaunchOptions,
@ -106,6 +107,7 @@ impl RuffleGui {
preferences.clone(), preferences.clone(),
default_launch_options.clone(), default_launch_options.clone(),
default_path, default_path,
window.clone(),
event_loop.clone(), event_loop.clone(),
), ),
menu_bar: MenuBar::new( menu_bar: MenuBar::new(

View File

@ -112,6 +112,7 @@ impl GuiController {
egui_wgpu::Renderer::new(&descriptors.device, surface_format, None, 1, true); egui_wgpu::Renderer::new(&descriptors.device, surface_format, None, 1, true);
let descriptors = Arc::new(descriptors); let descriptors = Arc::new(descriptors);
let gui = RuffleGui::new( let gui = RuffleGui::new(
Arc::downgrade(&window),
event_loop, event_loop,
initial_movie_url.clone(), initial_movie_url.clone(),
LaunchOptions::from(&preferences), LaunchOptions::from(&preferences),

View File

@ -11,12 +11,14 @@ use bookmarks_dialog::{BookmarkAddDialog, BookmarksDialog};
use open_dialog::OpenDialog; use open_dialog::OpenDialog;
use preferences_dialog::PreferencesDialog; use preferences_dialog::PreferencesDialog;
use ruffle_core::Player; use ruffle_core::Player;
use std::sync::Weak;
use unic_langid::LanguageIdentifier; use unic_langid::LanguageIdentifier;
use url::Url; use url::Url;
use volume_controls::VolumeControls; use volume_controls::VolumeControls;
use winit::event_loop::EventLoopProxy; use winit::event_loop::EventLoopProxy;
pub struct Dialogs { pub struct Dialogs {
window: Weak<winit::window::Window>,
preferences_dialog: Option<PreferencesDialog>, preferences_dialog: Option<PreferencesDialog>,
bookmarks_dialog: Option<BookmarksDialog>, bookmarks_dialog: Option<BookmarksDialog>,
bookmark_add_dialog: Option<BookmarkAddDialog>, bookmark_add_dialog: Option<BookmarkAddDialog>,
@ -37,6 +39,7 @@ impl Dialogs {
preferences: GlobalPreferences, preferences: GlobalPreferences,
player_options: LaunchOptions, player_options: LaunchOptions,
default_path: Option<Url>, default_path: Option<Url>,
window: Weak<winit::window::Window>,
event_loop: EventLoopProxy<RuffleEvent>, event_loop: EventLoopProxy<RuffleEvent>,
) -> Self { ) -> Self {
Self { Self {
@ -44,7 +47,7 @@ impl Dialogs {
bookmarks_dialog: None, bookmarks_dialog: None,
bookmark_add_dialog: None, bookmark_add_dialog: None,
open_dialog: OpenDialog::new(player_options, default_path, event_loop), open_dialog: OpenDialog::new(player_options, default_path, window.clone(), event_loop),
is_open_dialog_visible: false, is_open_dialog_visible: false,
volume_controls: VolumeControls::new(&preferences), volume_controls: VolumeControls::new(&preferences),
@ -52,6 +55,7 @@ impl Dialogs {
is_about_visible: false, is_about_visible: false,
window,
preferences, preferences,
} }
} }
@ -63,7 +67,7 @@ impl Dialogs {
event_loop: EventLoopProxy<RuffleEvent>, event_loop: EventLoopProxy<RuffleEvent>,
) { ) {
self.is_open_dialog_visible = false; self.is_open_dialog_visible = false;
self.open_dialog = OpenDialog::new(opt, url, event_loop); self.open_dialog = OpenDialog::new(opt, url, self.window.clone(), event_loop);
} }
pub fn open_file_advanced(&mut self) { pub fn open_file_advanced(&mut self) {
@ -75,13 +79,17 @@ impl Dialogs {
} }
pub fn open_bookmarks(&mut self) { pub fn open_bookmarks(&mut self) {
self.bookmarks_dialog = Some(BookmarksDialog::new(self.preferences.clone())); self.bookmarks_dialog = Some(BookmarksDialog::new(
self.preferences.clone(),
self.window.clone(),
));
} }
pub fn open_add_bookmark(&mut self, initial_url: Option<url::Url>) { pub fn open_add_bookmark(&mut self, initial_url: Option<url::Url>) {
self.bookmark_add_dialog = Some(BookmarkAddDialog::new( self.bookmark_add_dialog = Some(BookmarkAddDialog::new(
self.preferences.clone(), self.preferences.clone(),
initial_url, initial_url,
self.window.clone(),
)) ))
} }

View File

@ -4,6 +4,7 @@ use crate::preferences::GlobalPreferences;
use egui::{Align2, Button, Grid, Label, Layout, Sense, Ui, Widget, Window}; use egui::{Align2, Button, Grid, Label, Layout, Sense, Ui, Widget, Window};
use egui_extras::{Column, TableBuilder}; use egui_extras::{Column, TableBuilder};
use ruffle_frontend_utils::bookmarks::Bookmark; use ruffle_frontend_utils::bookmarks::Bookmark;
use std::sync::Weak;
use unic_langid::LanguageIdentifier; use unic_langid::LanguageIdentifier;
use url::Url; use url::Url;
@ -14,7 +15,11 @@ pub struct BookmarkAddDialog {
} }
impl BookmarkAddDialog { impl BookmarkAddDialog {
pub fn new(preferences: GlobalPreferences, initial_url: Option<Url>) -> Self { pub fn new(
preferences: GlobalPreferences,
initial_url: Option<Url>,
window: Weak<winit::window::Window>,
) -> Self {
Self { Self {
preferences, preferences,
name: initial_url name: initial_url
@ -22,7 +27,7 @@ impl BookmarkAddDialog {
.map(|x| ruffle_frontend_utils::url_to_readable_name(x).into_owned()) .map(|x| ruffle_frontend_utils::url_to_readable_name(x).into_owned())
.unwrap_or_default(), .unwrap_or_default(),
// TODO: hint. // TODO: hint.
url: PathOrUrlField::new(initial_url, ""), url: PathOrUrlField::new(initial_url, "", window),
} }
} }
@ -93,13 +98,15 @@ struct SelectedBookmark {
} }
pub struct BookmarksDialog { pub struct BookmarksDialog {
window: Weak<winit::window::Window>,
preferences: GlobalPreferences, preferences: GlobalPreferences,
selected_bookmark: Option<SelectedBookmark>, selected_bookmark: Option<SelectedBookmark>,
} }
impl BookmarksDialog { impl BookmarksDialog {
pub fn new(preferences: GlobalPreferences) -> Self { pub fn new(preferences: GlobalPreferences, window: Weak<winit::window::Window>) -> Self {
Self { Self {
window,
preferences, preferences,
selected_bookmark: None, selected_bookmark: None,
} }
@ -201,7 +208,11 @@ impl BookmarksDialog {
index, index,
// TODO: set hint // TODO: set hint
name: bookmark.name.clone(), name: bookmark.name.clone(),
url: PathOrUrlField::new(Some(bookmark.url.clone()), ""), url: PathOrUrlField::new(
Some(bookmark.url.clone()),
"",
self.window.clone(),
),
}); });
} }
}); });

View File

@ -11,6 +11,7 @@ use ruffle_core::{LoadBehavior, PlayerRuntime, StageAlign, StageScaleMode};
use ruffle_render::quality::StageQuality; use ruffle_render::quality::StageQuality;
use std::borrow::Cow; use std::borrow::Cow;
use std::ops::RangeInclusive; use std::ops::RangeInclusive;
use std::sync::Weak;
use std::time::Duration; use std::time::Duration;
use unic_langid::LanguageIdentifier; use unic_langid::LanguageIdentifier;
use url::Url; use url::Url;
@ -49,6 +50,7 @@ impl OpenDialog {
pub fn new( pub fn new(
defaults: LaunchOptions, defaults: LaunchOptions,
default_url: Option<Url>, default_url: Option<Url>,
window: Weak<winit::window::Window>,
event_loop: EventLoopProxy<RuffleEvent>, event_loop: EventLoopProxy<RuffleEvent>,
) -> Self { ) -> Self {
let spoof_url = OptionalField::new( let spoof_url = OptionalField::new(
@ -71,7 +73,7 @@ impl OpenDialog {
defaults.proxy.as_ref().map(Url::to_string), defaults.proxy.as_ref().map(Url::to_string),
UrlField::new("socks5://localhost:8080"), UrlField::new("socks5://localhost:8080"),
); );
let path = PathOrUrlField::new(default_url, "path/to/movie.swf"); let path = PathOrUrlField::new(default_url, "path/to/movie.swf", window);
let script_timeout = OptionalField::new( let script_timeout = OptionalField::new(
defaults defaults
.player .player

View File

@ -2,21 +2,28 @@ use crate::gui::text;
use crate::util::pick_file; use crate::util::pick_file;
use egui::{TextEdit, Ui}; use egui::{TextEdit, Ui};
use std::path::Path; use std::path::Path;
use std::sync::Weak;
use unic_langid::LanguageIdentifier; use unic_langid::LanguageIdentifier;
use url::Url; use url::Url;
pub struct PathOrUrlField { pub struct PathOrUrlField {
window: Weak<winit::window::Window>,
value: String, value: String,
result: Option<Url>, result: Option<Url>,
hint: &'static str, hint: &'static str,
} }
impl PathOrUrlField { impl PathOrUrlField {
pub fn new(default: Option<Url>, hint: &'static str) -> Self { pub fn new(
default: Option<Url>,
hint: &'static str,
window: Weak<winit::window::Window>,
) -> Self {
if let Some(default) = default { if let Some(default) = default {
if default.scheme() == "file" { if default.scheme() == "file" {
if let Ok(path) = default.to_file_path() { if let Ok(path) = default.to_file_path() {
return Self { return Self {
window,
value: path.to_string_lossy().to_string(), value: path.to_string_lossy().to_string(),
result: Some(default), result: Some(default),
hint, hint,
@ -25,6 +32,7 @@ impl PathOrUrlField {
} }
return Self { return Self {
window,
value: default.to_string(), value: default.to_string(),
result: Some(default), result: Some(default),
hint, hint,
@ -32,6 +40,7 @@ impl PathOrUrlField {
} }
Self { Self {
window,
value: "".to_string(), value: "".to_string(),
result: None, result: None,
hint, hint,
@ -51,7 +60,7 @@ impl PathOrUrlField {
path path
}); });
if let Some(path) = pick_file(true, dir) { if let Some(path) = pick_file(true, dir, self.window.upgrade()) {
self.value = path.to_string_lossy().to_string(); self.value = path.to_string_lossy().to_string();
} }
} }

View File

@ -4,11 +4,14 @@ use gilrs::Button;
use rfd::FileDialog; use rfd::FileDialog;
use ruffle_core::events::{GamepadButton, KeyCode, TextControlCode}; use ruffle_core::events::{GamepadButton, KeyCode, TextControlCode};
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
use std::sync::Arc;
use url::Url; use url::Url;
use wgpu::rwh::{HasDisplayHandle, HasWindowHandle};
use winit::dpi::PhysicalSize; use winit::dpi::PhysicalSize;
use winit::event::{KeyEvent, Modifiers}; use winit::event::{KeyEvent, Modifiers};
use winit::event_loop::EventLoop; use winit::event_loop::EventLoop;
use winit::keyboard::{Key, KeyLocation, NamedKey}; use winit::keyboard::{Key, KeyLocation, NamedKey};
use winit::window::Window;
/// Converts a winit event to a Ruffle `TextControlCode`. /// Converts a winit event to a Ruffle `TextControlCode`.
/// Returns `None` if there is no match. /// Returns `None` if there is no match.
@ -244,7 +247,10 @@ pub fn parse_url(path: &Path) -> Result<Url, Error> {
} }
} }
fn actually_pick_file(dir: Option<PathBuf>) -> Option<PathBuf> { fn actually_pick_file<W: HasWindowHandle + HasDisplayHandle>(
dir: Option<PathBuf>,
parent: Option<&W>,
) -> Option<PathBuf> {
let mut dialog = FileDialog::new() let mut dialog = FileDialog::new()
.add_filter("Flash Files", &["swf", "spl", "ruf"]) .add_filter("Flash Files", &["swf", "spl", "ruf"])
.add_filter("All Files", &["*"]) .add_filter("All Files", &["*"])
@ -254,6 +260,10 @@ fn actually_pick_file(dir: Option<PathBuf>) -> Option<PathBuf> {
dialog = dialog.set_directory(dir); dialog = dialog.set_directory(dir);
} }
if let Some(parent) = parent {
dialog = dialog.set_parent(parent);
}
dialog.pick_file() dialog.pick_file()
} }
@ -261,20 +271,28 @@ fn actually_pick_file(dir: Option<PathBuf>) -> Option<PathBuf> {
// We only need the workaround from within UI code, not when executing custom events // We only need the workaround from within UI code, not when executing custom events
// The workaround causes Ruffle to show as "not responding" on windows, so we don't use it if we don't need to // The workaround causes Ruffle to show as "not responding" on windows, so we don't use it if we don't need to
#[cfg(windows)] #[cfg(windows)]
pub fn pick_file(in_ui: bool, path: Option<PathBuf>) -> Option<PathBuf> { pub fn pick_file(
in_ui: bool,
path: Option<PathBuf>,
parent: Option<Arc<Window>>,
) -> Option<PathBuf> {
if in_ui { if in_ui {
std::thread::spawn(move || actually_pick_file(path)) std::thread::spawn(move || actually_pick_file(path, parent.as_ref()))
.join() .join()
.ok() .ok()
.flatten() .flatten()
} else { } else {
actually_pick_file(path) actually_pick_file(path, parent.as_ref())
} }
} }
#[cfg(not(windows))] #[cfg(not(windows))]
pub fn pick_file(_in_ui: bool, path: Option<PathBuf>) -> Option<PathBuf> { pub fn pick_file(
actually_pick_file(path) _in_ui: bool,
path: Option<PathBuf>,
parent: Option<Arc<Window>>,
) -> Option<PathBuf> {
actually_pick_file(path, parent.as_ref())
} }
#[cfg(not(feature = "tracy"))] #[cfg(not(feature = "tracy"))]