From 399a813ae1f181bf1dcdfde450b09a26c882716d Mon Sep 17 00:00:00 2001 From: Joe Neeman Date: Sun, 4 Oct 2020 22:21:10 -0500 Subject: [PATCH 1/2] First stab at GTK, which seems to work. --- druid-shell/src/lib.rs | 4 +- druid-shell/src/platform/gtk/window.rs | 98 +++++++++++++++++----- druid-shell/src/platform/mac/window.rs | 12 ++- druid-shell/src/platform/web/window.rs | 10 ++- druid-shell/src/platform/windows/window.rs | 12 ++- druid-shell/src/platform/x11/window.rs | 16 +++- druid-shell/src/window.rs | 80 +++++++++++++++++- druid/src/win_handler.rs | 80 ++++++++++++++---- 8 files changed, 265 insertions(+), 47 deletions(-) diff --git a/druid-shell/src/lib.rs b/druid-shell/src/lib.rs index 5ef1b33d53..8a661f8d9c 100644 --- a/druid-shell/src/lib.rs +++ b/druid-shell/src/lib.rs @@ -72,8 +72,8 @@ pub use region::Region; pub use scale::{Scalable, Scale, ScaledArea}; pub use screen::{Monitor, Screen}; pub use window::{ - IdleHandle, IdleToken, TimerToken, WinHandler, WindowBuilder, WindowHandle, WindowLevel, - WindowState, + FileDialogToken, IdleHandle, IdleToken, TimerToken, WinHandler, WindowBuilder, WindowHandle, + WindowLevel, WindowState, }; pub use keyboard_types; diff --git a/druid-shell/src/platform/gtk/window.rs b/druid-shell/src/platform/gtk/window.rs index 295982b35a..89bb606ed4 100644 --- a/druid-shell/src/platform/gtk/window.rs +++ b/druid-shell/src/platform/gtk/window.rs @@ -18,7 +18,6 @@ use std::any::Any; use std::cell::{Cell, RefCell}; use std::convert::TryFrom; use std::ffi::c_void; -use std::ffi::OsString; use std::os::raw::{c_int, c_uint}; use std::panic::Location; use std::ptr; @@ -45,7 +44,7 @@ use crate::piet::ImageFormat; use crate::region::Region; use crate::scale::{Scalable, Scale, ScaledArea}; use crate::window; -use crate::window::{IdleToken, TimerToken, WinHandler, WindowLevel}; +use crate::window::{DeferredOp, FileDialogToken, IdleToken, TimerToken, WinHandler, WindowLevel}; use super::application::Application; use super::dialog; @@ -130,6 +129,8 @@ pub(crate) struct WindowState { /// Used to determine whether to honor close requests from the system: we inhibit them unless /// this is true, and this gets set to true when our client requests a close. closing: Cell, + /// A counter for generating unique tokens. + next_token: Cell, drawing_area: DrawingArea, // A cairo surface for us to render to; we copy this to the drawing_area whenever necessary. // This extra buffer is necessitated by DrawingArea's painting model: when our paint callback @@ -149,6 +150,7 @@ pub(crate) struct WindowState { pub(crate) handler: RefCell>, idle_queue: Arc>>, current_keycode: Cell>, + deferred_queue: RefCell>, } #[derive(Clone)] @@ -243,6 +245,7 @@ impl WindowBuilder { scale: Cell::new(scale), area: Cell::new(area), closing: Cell::new(false), + next_token: Cell::new(0), drawing_area, surface: RefCell::new(None), surface_size: Cell::new((0, 0)), @@ -250,6 +253,7 @@ impl WindowBuilder { handler: RefCell::new(handler), idle_queue: Arc::new(Mutex::new(vec![])), current_keycode: Cell::new(None), + deferred_queue: RefCell::new(Vec::new()), }); self.app @@ -623,7 +627,10 @@ impl WindowState { return None; } - self.with_handler_and_dont_check_the_other_borrows(f) + let ret = self.with_handler_and_dont_check_the_other_borrows(f); + + self.run_deferred(); + ret } #[track_caller] @@ -640,6 +647,12 @@ impl WindowState { } } + fn next_token(&self) -> usize { + let next = self.next_token.get(); + self.next_token.set(next + 1); + next + } + fn resize_surface(&self, width: i32, height: i32) -> Result<(), anyhow::Error> { fn next_size(x: i32) -> i32 { // We round up to the nearest multiple of `accuracy`, which is between x/2 and x/4. @@ -690,6 +703,39 @@ impl WindowState { log::warn!("Not invalidating rect because region already borrowed"); } } + + /// Pushes a deferred op onto the queue. + fn defer(&self, op: DeferredOp) { + self.deferred_queue.borrow_mut().push(op); + } + + fn run_deferred(&self) { + let queue = self.deferred_queue.replace(Vec::new()); + for op in queue { + match op { + DeferredOp::Open(options, token) => { + let file_info = dialog::get_file_dialog_path( + self.window.upcast_ref(), + FileDialogType::Open, + options, + ) + .ok() + .map(|s| FileInfo { path: s.into() }); + self.with_handler(|h| h.open_file(token, file_info)); + } + DeferredOp::SaveAs(options, token) => { + let file_info = dialog::get_file_dialog_path( + self.window.upcast_ref(), + FileDialogType::Save, + options, + ) + .ok() + .map(|s| FileInfo { path: s.into() }); + self.with_handler(|h| h.save_as(token, file_info)); + } + } + } + } } impl WindowHandle { @@ -900,16 +946,34 @@ impl WindowHandle { } } - pub fn open_file_sync(&mut self, options: FileDialogOptions) -> Option { - self.file_dialog(FileDialogType::Open, options) - .ok() - .map(|s| FileInfo { path: s.into() }) + pub fn open_file(&mut self, options: FileDialogOptions) -> Option { + if let Some(state) = self.state.upgrade() { + let tok = FileDialogToken::new(state.next_token()); + state.defer(DeferredOp::Open(options, tok)); + Some(tok) + } else { + None + } } - pub fn save_as_sync(&mut self, options: FileDialogOptions) -> Option { - self.file_dialog(FileDialogType::Save, options) - .ok() - .map(|s| FileInfo { path: s.into() }) + pub fn save_as(&mut self, options: FileDialogOptions) -> Option { + if let Some(state) = self.state.upgrade() { + let tok = FileDialogToken::new(state.next_token()); + state.defer(DeferredOp::SaveAs(options, tok)); + Some(tok) + } else { + None + } + } + + pub fn save_as_sync(&mut self, _options: FileDialogOptions) -> Option { + log::error!("save as sync no longer supported on GTK"); + None + } + + pub fn open_file_sync(&mut self, _options: FileDialogOptions) -> Option { + log::error!("open file sync no longer supported on GTK"); + None } /// Get a handle that can be used to schedule an idle task. @@ -971,18 +1035,6 @@ impl WindowHandle { state.window.set_title(&(title.into())); } } - - fn file_dialog( - &self, - ty: FileDialogType, - options: FileDialogOptions, - ) -> Result { - if let Some(state) = self.state.upgrade() { - dialog::get_file_dialog_path(state.window.upcast_ref(), ty, options) - } else { - Err(anyhow!("Cannot upgrade state from weak pointer to arc").into()) - } - } } unsafe impl Send for IdleHandle {} diff --git a/druid-shell/src/platform/mac/window.rs b/druid-shell/src/platform/mac/window.rs index 1e6346e085..411dd848f9 100644 --- a/druid-shell/src/platform/mac/window.rs +++ b/druid-shell/src/platform/mac/window.rs @@ -56,7 +56,7 @@ use crate::keyboard_types::KeyState; use crate::mouse::{Cursor, CursorDesc, MouseButton, MouseButtons, MouseEvent}; use crate::region::Region; use crate::scale::Scale; -use crate::window::{IdleToken, TimerToken, WinHandler, WindowLevel, WindowState}; +use crate::window::{FileDialogToken, IdleToken, TimerToken, WinHandler, WindowLevel, WindowState}; use crate::Error; #[allow(non_upper_case_globals)] @@ -938,11 +938,21 @@ impl WindowHandle { .map(|s| FileInfo { path: s.into() }) } + pub fn open_file(&mut self, _options: FileDialogOptions) -> Option { + // TODO: implement this and get rid of open_file_sync + None + } + pub fn save_as_sync(&mut self, options: FileDialogOptions) -> Option { dialog::get_file_dialog_path(FileDialogType::Save, options) .map(|s| FileInfo { path: s.into() }) } + pub fn save_as(&mut self, _options: FileDialogOptions) -> Option { + // TODO: implement this and get rid of save_as_sync + None + } + /// Set the title for this menu. pub fn set_title(&self, title: &str) { unsafe { diff --git a/druid-shell/src/platform/web/window.rs b/druid-shell/src/platform/web/window.rs index 497f8b955c..a390aa47dc 100644 --- a/druid-shell/src/platform/web/window.rs +++ b/druid-shell/src/platform/web/window.rs @@ -42,7 +42,7 @@ use crate::keyboard::{KbKey, KeyState, Modifiers}; use crate::mouse::{Cursor, CursorDesc, MouseButton, MouseButtons, MouseEvent}; use crate::region::Region; use crate::window; -use crate::window::{IdleToken, TimerToken, WinHandler, WindowLevel}; +use crate::window::{FileDialogToken, IdleToken, TimerToken, WinHandler, WindowLevel}; // This is a macro instead of a function since KeyboardEvent and MouseEvent has identical functions // to query modifier key states. @@ -571,6 +571,10 @@ impl WindowHandle { .map(|s| FileInfo { path: s.into() }) } + pub fn open_file(&mut self, _options: FileDialogOptions) -> Option { + None + } + pub fn save_as_sync(&mut self, options: FileDialogOptions) -> Option { log::warn!("save_as_sync is currently unimplemented for web."); self.file_dialog(FileDialogType::Save, options) @@ -578,6 +582,10 @@ impl WindowHandle { .map(|s| FileInfo { path: s.into() }) } + pub fn save_as(&mut self, _options: FileDialogOptions) -> Option { + None + } + fn render_soon(&self) { if let Some(s) = self.0.upgrade() { let state = s.clone(); diff --git a/druid-shell/src/platform/windows/window.rs b/druid-shell/src/platform/windows/window.rs index bf45ed9366..95e8ce8643 100644 --- a/druid-shell/src/platform/windows/window.rs +++ b/druid-shell/src/platform/windows/window.rs @@ -66,7 +66,7 @@ use crate::mouse::{Cursor, CursorDesc, MouseButton, MouseButtons, MouseEvent}; use crate::region::Region; use crate::scale::{Scalable, Scale, ScaledArea}; use crate::window; -use crate::window::{IdleToken, TimerToken, WinHandler, WindowLevel}; +use crate::window::{FileDialogToken, IdleToken, TimerToken, WinHandler, WindowLevel}; /// The platform target DPI. /// @@ -1856,6 +1856,11 @@ impl WindowHandle { } } + pub fn open_file(&mut self, _options: FileDialogOptions) -> Option { + // TODO: implement this and remove open_file_sync + None + } + /// Prompt the user to chose a file to open. /// /// Blocks while the user picks the file. @@ -1870,6 +1875,11 @@ impl WindowHandle { } } + pub fn save_as(&mut self, _options: FileDialogOptions) -> Option { + // TODO: implement this and remove save_as_sync + None + } + /// Get the raw HWND handle, for uses that are not wrapped in /// druid_win_shell. pub fn get_hwnd(&self) -> Option { diff --git a/druid-shell/src/platform/x11/window.rs b/druid-shell/src/platform/x11/window.rs index 506cf2a7ad..abdfb86004 100644 --- a/druid-shell/src/platform/x11/window.rs +++ b/druid-shell/src/platform/x11/window.rs @@ -47,7 +47,7 @@ use crate::piet::{Piet, PietText, RenderContext}; use crate::region::Region; use crate::scale::Scale; use crate::window; -use crate::window::{IdleToken, TimerToken, WinHandler, WindowLevel}; +use crate::window::{FileDialogToken, IdleToken, TimerToken, WinHandler, WindowLevel}; use super::application::Application; use super::keycodes; @@ -1498,17 +1498,27 @@ impl WindowHandle { } pub fn open_file_sync(&mut self, _options: FileDialogOptions) -> Option { - // TODO(x11/file_dialogs): implement WindowHandle::open_file_sync log::warn!("WindowHandle::open_file_sync is currently unimplemented for X11 platforms."); None } + pub fn open_file(&mut self, _options: FileDialogOptions) -> Option { + // TODO(x11/file_dialogs): implement WindowHandle::open_file + log::warn!("WindowHandle::open_file is currently unimplemented for X11 platforms."); + None + } + pub fn save_as_sync(&mut self, _options: FileDialogOptions) -> Option { - // TODO(x11/file_dialogs): implement WindowHandle::save_as_sync log::warn!("WindowHandle::save_as_sync is currently unimplemented for X11 platforms."); None } + pub fn save_as(&mut self, _options: FileDialogOptions) -> Option { + // TODO(x11/file_dialogs): implement WindowHandle::save_as + log::warn!("WindowHandle::save_as is currently unimplemented for X11 platforms."); + None + } + pub fn show_context_menu(&self, _menu: Menu, _pos: Point) { // TODO(x11/menus): implement WindowHandle::show_context_menu log::warn!("WindowHandle::show_context_menu is currently unimplemented for X11 platforms."); diff --git a/druid-shell/src/window.rs b/druid-shell/src/window.rs index e83f02df31..54d5b5cba3 100644 --- a/druid-shell/src/window.rs +++ b/druid-shell/src/window.rs @@ -92,6 +92,42 @@ impl IdleToken { } } +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Hash)] +pub struct FileDialogToken(usize); + +impl FileDialogToken { + /// Create a new `FileDialogToken` with the given raw `usize` id. + pub const fn new(raw: usize) -> FileDialogToken { + FileDialogToken(raw) + } +} + +/// An enumeration of operations that might need to be deferred until the `WinHandler` is dropped. +/// +/// We work hard to avoid calling into `WinHandler` re-entrantly. Since most of our backends use +/// the system's event loop, and since the `WinHandler` gets a `WindowHandle` to use, this implies +/// that none of the `WindowHandle`'s methods can return control to the system's event loop +/// (because if it did, the system could call back into druid-shell with some mouse event, and then +/// we'd try to call the `WinHandler` again). +/// +/// The solution is that for every `WindowHandle` method that *wants* to return control to the +/// system's event loop, instead of doing that we queue up a deferrred operation and return +/// immediately. The deferred operations will run whenever the currently running `WinHandler` +/// method returns. +/// +/// An example call trace might look like: +/// 1. the system hands a mouse click event to druid-shell +/// 2. druid-shell calls `WinHandler::mouse_up` +/// 3. after some processing, the `WinHandler` calls `WindowHandle::save_as`, which schedules a +/// deferred op and returns immediately +/// 4. after some more processing, `WinHandler::mouse_up` returns +/// 5. druid-shell displays the "save as" dialog that was requested in step 3. +#[allow(dead_code)] +pub(crate) enum DeferredOp { + SaveAs(FileDialogOptions, FileDialogToken), + Open(FileDialogOptions, FileDialogToken), +} + /// Levels in the window system - Z order for display purposes. /// Describes the purpose of a window and should be mapped appropriately to match platform /// conventions. @@ -290,20 +326,42 @@ impl WindowHandle { self.0.make_cursor(desc) } - /// Prompt the user to chose a file to open. + /// Prompt the user to choose a file to open. /// /// Blocks while the user picks the file. pub fn open_file_sync(&mut self, options: FileDialogOptions) -> Option { self.0.open_file_sync(options) } - /// Prompt the user to chose a path for saving. + /// Prompt the user to choose a file to open. + /// + /// This won't block immediately; the file dialog will be shown whenever control returns to + /// `druid-shell`, and the [`WinHandler::open`] method will be called when the dialog is + /// closed. + /// + /// [`WinHandler::open()`]: trait.WinHandler.html#tymethod.open + pub fn open_file(&mut self, options: FileDialogOptions) -> Option { + self.0.open_file(options) + } + + /// Prompt the user to choose a path for saving. /// /// Blocks while the user picks a file. pub fn save_as_sync(&mut self, options: FileDialogOptions) -> Option { self.0.save_as_sync(options) } + /// Prompt the user to choose a path for saving. + /// + /// This won't block immediately; the file dialog will be shown whenever control returns to + /// `druid-shell`, and the [`WinHandler::save_as`] method will be called when the dialog is + /// closed. + /// + /// [`WinHandler::open()`]: trait.WinHandler.html#tymethod.open + pub fn save_as(&mut self, options: FileDialogOptions) -> Option { + self.0.save_as(options) + } + /// Display a pop-up menu at the given position. /// /// `Point` is in the coordinate space of the window. @@ -484,6 +542,24 @@ pub trait WinHandler { #[allow(unused_variables)] fn command(&mut self, id: u32) {} + /// Called when a "Save As" dialog is closed. + /// + /// `token` is the value returned by [`WindowHandle::save_as`]. `file` contains the information + /// of the chosen path, or `None` if the save dialog was cancelled. + /// + /// [`WindowHandle::save_as`]: trait.WindowHandle:#tymethod.save_as + #[allow(unused_variables)] + fn save_as(&mut self, token: FileDialogToken, file: Option) {} + + /// Called when an "Open" dialog is closed. + /// + /// `token` is the value returned by [`WindowHandle::open_file`]. `file` contains the information + /// of the chosen path, or `None` if the save dialog was cancelled. + /// + /// [`WindowHandle::open_file`]: trait.WindowHandle:#tymethod.open_file + #[allow(unused_variables)] + fn open_file(&mut self, token: FileDialogToken, file: Option) {} + /// Called on a key down event. /// /// Return `true` if the event is handled. diff --git a/druid/src/win_handler.rs b/druid/src/win_handler.rs index 4751334b4e..e7c10cba65 100644 --- a/druid/src/win_handler.rs +++ b/druid/src/win_handler.rs @@ -21,7 +21,10 @@ use std::rc::Rc; use crate::kurbo::Size; use crate::piet::Piet; -use crate::shell::{Application, IdleToken, MouseEvent, Region, Scale, WinHandler, WindowHandle}; +use crate::shell::{ + Application, FileDialogToken, FileInfo, IdleToken, MouseEvent, Region, Scale, WinHandler, + WindowHandle, +}; use crate::app_delegate::{AppDelegate, DelegateCtx}; use crate::core::CommandQueue; @@ -75,6 +78,7 @@ struct Inner { app: Application, delegate: Option>>, command_queue: CommandQueue, + file_dialogs: HashMap, ext_event_host: ExtEventHost, windows: Windows, /// the application-level menu, only set on macos and only if there @@ -143,6 +147,7 @@ impl AppState { app, delegate, command_queue: VecDeque::new(), + file_dialogs: HashMap::new(), root_menu: None, ext_event_host, data, @@ -606,9 +611,6 @@ impl AppState { fn show_open_panel(&mut self, cmd: Command, window_id: WindowId) { let options = cmd.get_unchecked(sys_cmd::SHOW_OPEN_PANEL).to_owned(); - //FIXME: this is blocking; if we hold `borrow_mut` we are likely to cause - //a crash. as a workaround we take a clone of the window handle. - //it's less clear what the better solution would be. let handle = self .inner .borrow_mut() @@ -616,14 +618,25 @@ impl AppState { .get_mut(window_id) .map(|w| w.handle.clone()); - let result = handle.and_then(|mut handle| handle.open_file_sync(options)); - self.inner.borrow_mut().dispatch_cmd({ - if let Some(info) = result { + let token = handle + .clone() + .and_then(|mut handle| handle.open_file(options.clone())); + if let Some(token) = token { + self.inner + .borrow_mut() + .file_dialogs + .insert(token, window_id); + } else { + // TODO: remove this (and also some spurious clones above) once all platforms support + // the non-sync version + let file_info = handle.and_then(|mut handle| handle.open_file_sync(options)); + let cmd = if let Some(info) = file_info { sys_cmd::OPEN_FILE.with(info).to(window_id) } else { sys_cmd::OPEN_PANEL_CANCELLED.to(window_id) - } - }); + }; + self.inner.borrow_mut().dispatch_cmd(cmd); + } } fn show_save_panel(&mut self, cmd: Command, window_id: WindowId) { @@ -635,14 +648,25 @@ impl AppState { .get_mut(window_id) .map(|w| w.handle.clone()); - let result = handle.and_then(|mut handle| handle.save_as_sync(options)); - self.inner.borrow_mut().dispatch_cmd({ - if let Some(info) = result { + let token = handle + .clone() + .and_then(|mut handle| handle.save_as(options.clone())); + if let Some(token) = token { + self.inner + .borrow_mut() + .file_dialogs + .insert(token, window_id); + } else { + // TODO: remove this (and also some spurious clones above) once all platforms support + // the non-sync version + let file_info = handle.and_then(|mut handle| handle.save_as_sync(options)); + let cmd = if let Some(info) = file_info { sys_cmd::SAVE_FILE.with(Some(info)).to(window_id) } else { sys_cmd::SAVE_PANEL_CANCELLED.to(window_id) - } - }); + }; + self.inner.borrow_mut().dispatch_cmd(cmd); + } } fn new_window(&mut self, cmd: Command) -> Result<(), Box> { @@ -759,6 +783,34 @@ impl WinHandler for DruidHandler { self.app_state.handle_system_cmd(id, Some(self.window_id)); } + fn save_as(&mut self, token: FileDialogToken, file_info: Option) { + let mut inner = self.app_state.inner.borrow_mut(); + if let Some(window_id) = inner.file_dialogs.remove(&token) { + let cmd = if let Some(info) = file_info { + sys_cmd::SAVE_FILE.with(Some(info)).to(window_id) + } else { + sys_cmd::SAVE_PANEL_CANCELLED.to(window_id) + }; + inner.dispatch_cmd(cmd); + } else { + log::error!("unknown save dialog token"); + } + } + + fn open_file(&mut self, token: FileDialogToken, file_info: Option) { + let mut inner = self.app_state.inner.borrow_mut(); + if let Some(window_id) = inner.file_dialogs.remove(&token) { + let cmd = if let Some(info) = file_info { + sys_cmd::OPEN_FILE.with(info).to(window_id) + } else { + sys_cmd::OPEN_PANEL_CANCELLED.to(window_id) + }; + inner.dispatch_cmd(cmd); + } else { + log::error!("unknown open dialog token"); + } + } + fn mouse_down(&mut self, event: &MouseEvent) { // TODO: double-click detection (or is this done in druid-shell?) let event = Event::MouseDown(event.clone().into()); From 46839b3fbaf83e7e0c52f4b9ea3a9cf22d17011c Mon Sep 17 00:00:00 2001 From: Joe Neeman Date: Mon, 12 Oct 2020 18:02:25 -0500 Subject: [PATCH 2/2] Use the existing counter infrastructure. --- druid-shell/src/platform/gtk/window.rs | 13 ++----------- druid-shell/src/window.rs | 23 +++++++++++++++++++---- 2 files changed, 21 insertions(+), 15 deletions(-) diff --git a/druid-shell/src/platform/gtk/window.rs b/druid-shell/src/platform/gtk/window.rs index 89bb606ed4..b028dfbfa1 100644 --- a/druid-shell/src/platform/gtk/window.rs +++ b/druid-shell/src/platform/gtk/window.rs @@ -129,8 +129,6 @@ pub(crate) struct WindowState { /// Used to determine whether to honor close requests from the system: we inhibit them unless /// this is true, and this gets set to true when our client requests a close. closing: Cell, - /// A counter for generating unique tokens. - next_token: Cell, drawing_area: DrawingArea, // A cairo surface for us to render to; we copy this to the drawing_area whenever necessary. // This extra buffer is necessitated by DrawingArea's painting model: when our paint callback @@ -245,7 +243,6 @@ impl WindowBuilder { scale: Cell::new(scale), area: Cell::new(area), closing: Cell::new(false), - next_token: Cell::new(0), drawing_area, surface: RefCell::new(None), surface_size: Cell::new((0, 0)), @@ -647,12 +644,6 @@ impl WindowState { } } - fn next_token(&self) -> usize { - let next = self.next_token.get(); - self.next_token.set(next + 1); - next - } - fn resize_surface(&self, width: i32, height: i32) -> Result<(), anyhow::Error> { fn next_size(x: i32) -> i32 { // We round up to the nearest multiple of `accuracy`, which is between x/2 and x/4. @@ -948,7 +939,7 @@ impl WindowHandle { pub fn open_file(&mut self, options: FileDialogOptions) -> Option { if let Some(state) = self.state.upgrade() { - let tok = FileDialogToken::new(state.next_token()); + let tok = FileDialogToken::next(); state.defer(DeferredOp::Open(options, tok)); Some(tok) } else { @@ -958,7 +949,7 @@ impl WindowHandle { pub fn save_as(&mut self, options: FileDialogOptions) -> Option { if let Some(state) = self.state.upgrade() { - let tok = FileDialogToken::new(state.next_token()); + let tok = FileDialogToken::next(); state.defer(DeferredOp::SaveAs(options, tok)); Some(tok) } else { diff --git a/druid-shell/src/window.rs b/druid-shell/src/window.rs index 54d5b5cba3..33a1f0b161 100644 --- a/druid-shell/src/window.rs +++ b/druid-shell/src/window.rs @@ -92,13 +92,28 @@ impl IdleToken { } } +/// A token that uniquely identifies a file dialog request. #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Hash)] -pub struct FileDialogToken(usize); +pub struct FileDialogToken(u64); impl FileDialogToken { - /// Create a new `FileDialogToken` with the given raw `usize` id. - pub const fn new(raw: usize) -> FileDialogToken { - FileDialogToken(raw) + /// A token that does not correspond to any file dialog. + pub const INVALID: FileDialogToken = FileDialogToken(0); + + /// Create a new token. + pub fn next() -> FileDialogToken { + static COUNTER: Counter = Counter::new(); + FileDialogToken(COUNTER.next()) + } + + /// Create a new token from a raw value. + pub const fn from_raw(id: u64) -> FileDialogToken { + FileDialogToken(id) + } + + /// Get the raw value for a token. + pub const fn into_raw(self) -> u64 { + self.0 } }