From 8ed1019b440df645d3f869e5fc2023f33505603f Mon Sep 17 00:00:00 2001 From: Simon Gellis Date: Tue, 11 Aug 2026 22:53:31 -0400 Subject: [PATCH] Use async file picker API --- src/filepicker.rs | 54 +++++++++++++++++++++++++++++++++++++++++++ src/main.rs | 1 + src/window/game.rs | 26 ++++++++++++--------- src/window/profile.rs | 21 ++++++++--------- 4 files changed, 80 insertions(+), 22 deletions(-) create mode 100644 src/filepicker.rs diff --git a/src/filepicker.rs b/src/filepicker.rs new file mode 100644 index 0000000..beaef71 --- /dev/null +++ b/src/filepicker.rs @@ -0,0 +1,54 @@ +use std::{ + path::PathBuf, + sync::{Arc, Weak}, +}; + +use winit::window::Window; + +pub struct FilePicker { + window: Option>, +} + +impl FilePicker { + pub fn new() -> Self { + Self { window: None } + } + + pub fn set_window(&mut self, window: &Arc) { + self.window = Some(Arc::downgrade(window)); + } + + pub fn new_dialog(&self) -> FileDialogBuilder { + let mut dialog = rfd::AsyncFileDialog::new(); + if let Some(window) = self.window.as_ref().and_then(|w| w.upgrade()) { + dialog = dialog.set_parent(&window); + } + FileDialogBuilder { dialog } + } +} + +pub struct FileDialogBuilder { + dialog: rfd::AsyncFileDialog, +} + +impl FileDialogBuilder { + pub fn add_filter(self, name: impl Into, extensions: &[impl ToString]) -> Self { + Self { + dialog: self.dialog.add_filter(name, extensions), + } + } + + pub fn set_file_name(self, file_name: impl Into) -> Self { + Self { + dialog: self.dialog.set_file_name(file_name), + } + } + + pub fn pick_file(self) -> Option { + pollster::block_on(self.dialog.pick_file()).map(Into::into) + } + + pub fn save_file(self) -> Option { + pollster::block_on(self.dialog.save_file()).map(Into::into) + } +} diff --git a/src/main.rs b/src/main.rs index 57912ea..97c6f80 100644 --- a/src/main.rs +++ b/src/main.rs @@ -23,6 +23,7 @@ mod audio; mod config; mod controller; mod emulator; +mod filepicker; mod gdbserver; mod graphics; mod images; diff --git a/src/window/game.rs b/src/window/game.rs index 71e6724..9c9af63 100644 --- a/src/window/game.rs +++ b/src/window/game.rs @@ -8,6 +8,7 @@ use crate::{ app::UserEvent, config::{COLOR_PRESETS, SimConfig}, emulator::{EmulatorClient, EmulatorCommand, EmulatorState, SimId, SimState}, + filepicker::FilePicker, images::ImageTextureLoader, input::{Command, MappingProvider, ShortcutProvider}, memory::MemoryClient, @@ -46,7 +47,7 @@ pub struct GameWindow { messages: mpsc::Receiver, message_sink: mpsc::Sender, color_picker: Option, - window: Option>, + file_picker: FilePicker, memory: Arc, images: Arc, mappings: MappingProvider, @@ -88,7 +89,7 @@ impl GameWindow { messages, message_sink, color_picker: None, - window: None, + file_picker: FilePicker::new(), memory: memory.clone(), images: images.clone(), mappings: mappings.clone(), @@ -249,7 +250,9 @@ impl GameWindow { for command in ui.input_mut(|input| self.shortcuts.consume_all(input)) { match command { Command::OpenRom => { - let rom = rfd::FileDialog::new() + let rom = self + .file_picker + .new_dialog() .add_filter("Virtual Boy ROMs", &["vb", "vbrom", "elf", "isx"]) .pick_file(); if let Some(path) = rom { @@ -305,7 +308,9 @@ impl GameWindow { .add(self.button_for(ui.ctx(), "Open ROM", Command::OpenRom)) .clicked() { - let rom = rfd::FileDialog::new() + let rom = self + .file_picker + .new_dialog() .add_filter("Virtual Boy ROMs", &["vb", "vbrom", "elf", "isx"]) .pick_file(); if let Some(path) = rom { @@ -459,13 +464,12 @@ impl GameWindow { self.client .send_command(EmulatorCommand::Screenshot(self.sim_id, tx)); let bytes = rx.await.context("Could not take screenshot")?; - let mut file_dialog = rfd::FileDialog::new() + let file = self + .file_picker + .new_dialog() .add_filter("PNG images", &["png"]) - .set_file_name("screenshot.png"); - if let Some(window) = self.window.as_ref() { - file_dialog = file_dialog.set_parent(window); - } - let file = file_dialog.save_file(); + .set_file_name("screenshot.png") + .save_file(); let Some(path) = file else { return Ok(None); }; @@ -759,7 +763,7 @@ impl AppWindow for GameWindow { )); self.screen = Some(screen); } - self.window = Some(args.window.clone()); + self.file_picker.set_window(args.window); } } diff --git a/src/window/profile.rs b/src/window/profile.rs index c498388..c57b480 100644 --- a/src/window/profile.rs +++ b/src/window/profile.rs @@ -1,12 +1,12 @@ -use std::{fs, sync::Arc, time::Duration}; +use std::{fs, time::Duration}; use anyhow::Result; use egui::{Button, CentralPanel, Checkbox, Label, ViewportBuilder}; use egui_notify::{Anchor, Toast, Toasts}; -use winit::window::Window; use crate::{ emulator::{EmulatorClient, EmulatorCommand, EmulatorState, SimId}, + filepicker::FilePicker, profiler::{Profiler, ProfilerStatus}, window::{AppWindow, InitArgs}, }; @@ -16,7 +16,7 @@ pub struct ProfileWindow { client: EmulatorClient, profiler: Profiler, toasts: Toasts, - window: Option>, + file_picker: FilePicker, } impl ProfileWindow { @@ -29,7 +29,7 @@ impl ProfileWindow { .with_anchor(Anchor::BottomLeft) .with_margin((10.0, 10.0).into()) .reverse(true), - window: None, + file_picker: FilePicker::new(), } } @@ -62,13 +62,12 @@ impl ProfileWindow { fn try_finish_recording(&mut self) -> Result> { let bytes_receiver = self.profiler.finish_recording(); - let mut file_dialog = rfd::FileDialog::new() + let file = self + .file_picker + .new_dialog() .add_filter("Profiler files", &["json"]) - .set_file_name("profile.json"); - if let Some(window) = self.window.as_ref() { - file_dialog = file_dialog.set_parent(window); - } - let file = file_dialog.save_file(); + .set_file_name("profile.json") + .save_file(); if let Some(path) = file { let bytes = pollster::block_on(bytes_receiver)?; let _ = fs::remove_file(&path); @@ -164,6 +163,6 @@ impl AppWindow for ProfileWindow { } fn on_init(&mut self, args: InitArgs) { - self.window = Some(args.window.clone()); + self.file_picker.set_window(args.window); } }