From e308b1a0b424d0105855ed3df38903047aa108d8 Mon Sep 17 00:00:00 2001 From: Leopold Luley Date: Wed, 6 May 2020 23:08:43 +0200 Subject: [PATCH 01/19] First step at rustifying selectors. --- druid/src/command.rs | 145 ++++++++++++++++++++---------------- druid/src/ext_event.rs | 14 ++-- druid/src/menu.rs | 46 +++++++++++- druid/src/tests/helpers.rs | 4 +- druid/src/tests/mod.rs | 4 +- druid/src/widget/textbox.rs | 18 ++--- druid/src/win_handler.rs | 24 +++--- 7 files changed, 156 insertions(+), 99 deletions(-) diff --git a/druid/src/command.rs b/druid/src/command.rs index 2e2c516a53..87c01bdde0 100644 --- a/druid/src/command.rs +++ b/druid/src/command.rs @@ -15,10 +15,12 @@ //! Custom commands. use std::any::Any; -use std::sync::{Arc, Mutex}; +use std::{marker::PhantomData, sync::{Arc, Mutex}}; use crate::{WidgetId, WindowId}; +pub type SelectorSymbol = &'static str; + /// An identifier for a particular command. /// /// This should be a unique string identifier. Certain `Selector`s are defined @@ -27,7 +29,7 @@ use crate::{WidgetId, WindowId}; /// /// [`druid::commands`]: commands/index.html #[derive(Debug, Clone, PartialEq, Eq)] -pub struct Selector(&'static str); +pub struct Selector(SelectorSymbol, PhantomData); /// An arbitrary command. /// @@ -62,9 +64,8 @@ pub struct Selector(&'static str); /// [`Selector`]: struct.Selector.html #[derive(Debug, Clone)] pub struct Command { - /// The command's `Selector`. - pub selector: Selector, - object: Option, + selector: SelectorSymbol, + object: Arg, } #[derive(Debug, Clone)] @@ -76,8 +77,8 @@ enum Arg { /// Errors that can occur when attempting to retrieve the a command's argument. #[derive(Debug, Clone, PartialEq)] pub enum ArgumentError { - /// The command did not have an argument. - NoArgument, + /// The command represented a different selector. + WrongSelector, /// The argument was expected to be reusable and wasn't, or vice-versa. WrongVariant, /// The argument could not be downcast to the specified type. @@ -105,54 +106,55 @@ pub enum Target { /// [`Command`]: ../struct.Command.html pub mod sys { use super::Selector; + use crate::{FileDialogOptions, WindowId, FileInfo, menu::{AppStateContextMenu, AppStateMenuDesc}}; /// Quit the running application. This command is handled by the druid library. - pub const QUIT_APP: Selector = Selector::new("druid-builtin.quit-app"); + pub const QUIT_APP: Selector<()> = Selector::new("druid-builtin.quit-app"); /// Hide the application. (mac only?) - pub const HIDE_APPLICATION: Selector = Selector::new("druid-builtin.menu-hide-application"); + pub const HIDE_APPLICATION: Selector<()> = Selector::new("druid-builtin.menu-hide-application"); /// Hide all other applications. (mac only?) - pub const HIDE_OTHERS: Selector = Selector::new("druid-builtin.menu-hide-others"); + pub const HIDE_OTHERS: Selector<()> = Selector::new("druid-builtin.menu-hide-others"); /// The selector for a command to create a new window. - pub const NEW_WINDOW: Selector = Selector::new("druid-builtin.new-window"); + pub const NEW_WINDOW: Selector<()> = Selector::new("druid-builtin.new-window"); /// The selector for a command to close a window. The command's argument /// should be the id of the window to close. - pub const CLOSE_WINDOW: Selector = Selector::new("druid-builtin.close-window"); + pub const CLOSE_WINDOW: Selector = Selector::new("druid-builtin.close-window"); /// Close all windows. - pub const CLOSE_ALL_WINDOWS: Selector = Selector::new("druid-builtin.close-all-windows"); + pub const CLOSE_ALL_WINDOWS: Selector<()> = Selector::new("druid-builtin.close-all-windows"); /// The selector for a command to bring a window to the front, and give it focus. /// /// The command's argument should be the id of the target window. - pub const SHOW_WINDOW: Selector = Selector::new("druid-builtin.show-window"); + pub const SHOW_WINDOW: Selector = Selector::new("druid-builtin.show-window"); /// Display a context (right-click) menu. The argument must be the [`ContextMenu`]. /// object to be displayed. /// /// [`ContextMenu`]: ../struct.ContextMenu.html - pub const SHOW_CONTEXT_MENU: Selector = Selector::new("druid-builtin.show-context-menu"); + pub const SHOW_CONTEXT_MENU: Selector = Selector::new("druid-builtin.show-context-menu"); /// The selector for a command to set the window's menu. The argument should /// be a [`MenuDesc`] object. /// /// [`MenuDesc`]: ../struct.MenuDesc.html - pub const SET_MENU: Selector = Selector::new("druid-builtin.set-menu"); + pub const SET_MENU: Selector = Selector::new("druid-builtin.set-menu"); /// Show the application preferences. - pub const SHOW_PREFERENCES: Selector = Selector::new("druid-builtin.menu-show-preferences"); + pub const SHOW_PREFERENCES: Selector<()> = Selector::new("druid-builtin.menu-show-preferences"); /// Show the application about window. - pub const SHOW_ABOUT: Selector = Selector::new("druid-builtin.menu-show-about"); + pub const SHOW_ABOUT: Selector<()> = Selector::new("druid-builtin.menu-show-about"); /// Show all applications. - pub const SHOW_ALL: Selector = Selector::new("druid-builtin.menu-show-all"); + pub const SHOW_ALL: Selector<()> = Selector::new("druid-builtin.menu-show-all"); /// Show the new file dialog. - pub const NEW_FILE: Selector = Selector::new("druid-builtin.menu-file-new"); + pub const NEW_FILE: Selector<()> = Selector::new("druid-builtin.menu-file-new"); /// System command. A file picker dialog will be shown to the user, and an /// [`OPEN_FILE`] command will be sent if a file is chosen. @@ -161,14 +163,14 @@ pub mod sys { /// /// [`OPEN_FILE`]: constant.OPEN_FILE.html /// [`FileDialogOptions`]: ../struct.FileDialogOptions.html - pub const SHOW_OPEN_PANEL: Selector = Selector::new("druid-builtin.menu-file-open"); + pub const SHOW_OPEN_PANEL: Selector = Selector::new("druid-builtin.menu-file-open"); /// Open a file. /// /// The argument must be a [`FileInfo`] object for the file to be opened. /// /// [`FileInfo`]: ../struct.FileInfo.html - pub const OPEN_FILE: Selector = Selector::new("druid-builtin.open-file-path"); + pub const OPEN_FILE: Selector = Selector::new("druid-builtin.open-file-path"); /// Special command. When issued, the system will show the 'save as' panel, /// and if a path is selected the system will issue a [`SAVE_FILE`] command @@ -178,55 +180,61 @@ pub mod sys { /// /// [`SAVE_FILE`]: constant.SAVE_FILE.html /// [`FileDialogOptions`]: ../struct.FileDialogOptions.html - pub const SHOW_SAVE_PANEL: Selector = Selector::new("druid-builtin.menu-file-save-as"); + pub const SHOW_SAVE_PANEL: Selector = Selector::new("druid-builtin.menu-file-save-as"); /// Save the current file. /// /// The argument, if present, should be the path where the file should be saved. - pub const SAVE_FILE: Selector = Selector::new("druid-builtin.menu-file-save"); + pub const SAVE_FILE: Selector = Selector::new("druid-builtin.menu-file-save"); /// Show the print-setup window. - pub const PRINT_SETUP: Selector = Selector::new("druid-builtin.menu-file-print-setup"); + pub const PRINT_SETUP: Selector<()> = Selector::new("druid-builtin.menu-file-print-setup"); /// Show the print dialog. - pub const PRINT: Selector = Selector::new("druid-builtin.menu-file-print"); + pub const PRINT: Selector<()> = Selector::new("druid-builtin.menu-file-print"); /// Show the print preview. - pub const PRINT_PREVIEW: Selector = Selector::new("druid-builtin.menu-file-print"); + pub const PRINT_PREVIEW: Selector<()> = Selector::new("druid-builtin.menu-file-print"); /// Cut the current selection. - pub const CUT: Selector = Selector::new("druid-builtin.menu-cut"); + pub const CUT: Selector<()> = Selector::new("druid-builtin.menu-cut"); /// Copy the current selection. - pub const COPY: Selector = Selector::new("druid-builtin.menu-copy"); + pub const COPY: Selector<()> = Selector::new("druid-builtin.menu-copy"); /// Paste. - pub const PASTE: Selector = Selector::new("druid-builtin.menu-paste"); + pub const PASTE: Selector<()> = Selector::new("druid-builtin.menu-paste"); /// Undo. - pub const UNDO: Selector = Selector::new("druid-builtin.menu-undo"); + pub const UNDO: Selector<()> = Selector::new("druid-builtin.menu-undo"); /// Redo. - pub const REDO: Selector = Selector::new("druid-builtin.menu-redo"); + pub const REDO: Selector<()> = Selector::new("druid-builtin.menu-redo"); } -impl Selector { +impl Selector { /// A selector that does nothing. - pub const NOOP: Selector = Selector::new(""); + pub const fn noop() -> Selector { + Selector::new("") + } /// Create a new `Selector` with the given string. - pub const fn new(s: &'static str) -> Selector { - Selector(s) + pub const fn new(s: &'static str) -> Selector { + Selector(s, PhantomData) + } + + pub(crate) const fn symbol(&self) -> SelectorSymbol { + self.0 } } impl Command { /// Create a new `Command` with an argument. If you do not need /// an argument, `Selector` implements `Into`. - pub fn new(selector: Selector, arg: impl Any) -> Self { + pub fn new(selector: Selector, arg: T) -> Self { Command { - selector, - object: Some(Arg::Reusable(Arc::new(arg))), + selector: selector.symbol(), + object: Arg::Reusable(Arc::new(arg)), } } @@ -237,41 +245,50 @@ impl Command { /// [`take_object`]. /// /// [`take_object`]: #method.take_object - pub fn one_shot(selector: Selector, arg: impl Any) -> Self { + pub fn one_shot(selector: Selector, arg: T) -> Self { Command { - selector, - object: Some(Arg::OneShot(Arc::new(Mutex::new(Some(Box::new(arg)))))), + selector: selector.symbol(), + object: Arg::OneShot(Arc::new(Mutex::new(Some(Box::new(arg))))), } } /// Used to create a command from the types sent via an `ExtEventSink`. - pub(crate) fn from_ext(selector: Selector, object: Option>) -> Self { - let object: Option> = object.map(|obj| obj as Box); - let object = object.map(|o| Arg::Reusable(o.into())); + pub(crate) fn from_ext(selector: SelectorSymbol, object: Box) -> Self { + let object: Box = object; + let object = Arg::Reusable(object.into()); Command { selector, object } } + pub fn is(&self, selector: Selector) -> bool { + self.selector == selector.symbol() + } + /// Return a reference to this `Command`'s object, if it has one. /// /// This only works for 'reusable' commands; it does not work for commands /// created with [`one_shot`]. /// /// [`one_shot`]: #method.one_shot - pub fn get_object(&self) -> Result<&T, ArgumentError> { - match self.object.as_ref() { - Some(Arg::Reusable(o)) => o.downcast_ref().ok_or(ArgumentError::IncorrectType), - Some(Arg::OneShot(_)) => Err(ArgumentError::WrongVariant), - None => Err(ArgumentError::NoArgument), + pub fn get(&self, selector: Selector) -> Result<&T, ArgumentError> { + if self.selector != selector.symbol() { + return Err(ArgumentError::WrongSelector); + } + match self.object { + Arg::Reusable(o) => o.downcast_ref().ok_or(ArgumentError::IncorrectType), + Arg::OneShot(_) => Err(ArgumentError::WrongVariant), } } /// Attempt to take the object of a [`one-shot`] command. /// /// [`one-shot`]: #method.one_shot - pub fn take_object(&self) -> Result, ArgumentError> { - match self.object.as_ref() { - Some(Arg::Reusable(_)) => Err(ArgumentError::WrongVariant), - Some(Arg::OneShot(inner)) => { + pub fn take(&self, selector: Selector) -> Result, ArgumentError> { + if self.selector != selector.symbol() { + return Err(ArgumentError::WrongSelector); + } + match self.object { + Arg::Reusable(_) => Err(ArgumentError::WrongVariant), + Arg::OneShot(inner) => { let obj = inner .lock() .unwrap() @@ -285,30 +302,29 @@ impl Command { } } } - None => Err(ArgumentError::NoArgument), } } } -impl From for Command { - fn from(selector: Selector) -> Command { +impl From> for Command { + fn from(selector: Selector<()>) -> Command { Command { - selector, - object: None, + selector: selector.symbol(), + object: Arg::Reusable(Arc::new(())), } } } -impl std::fmt::Display for Selector { +impl std::fmt::Display for Selector { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { - write!(f, "Selector('{}')", self.0) + write!(f, "Selector(\"{}\", {})", self.0, std::any::type_name::()) } } impl std::fmt::Display for ArgumentError { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { match self { - ArgumentError::NoArgument => write!(f, "Command has no argument"), + ArgumentError::WrongSelector => write!(f, "Command had wrong selector"), ArgumentError::IncorrectType => write!(f, "Downcast failed: wrong concrete type"), ArgumentError::Consumed => write!(f, "One-shot command arguemnt already consumed"), ArgumentError::WrongVariant => write!( @@ -349,11 +365,12 @@ impl Into> for WidgetId { #[cfg(test)] mod tests { use super::*; + #[test] fn get_object() { - let sel = Selector::new("my-selector"); + let sel = Selector::>::new("my-selector"); let objs = vec![0, 1, 2]; let command = Command::new(sel, objs); - assert_eq!(command.get_object(), Ok(&vec![0, 1, 2])); + assert_eq!(command.get(sel), Ok(&vec![0, 1, 2])); } } diff --git a/druid/src/ext_event.rs b/druid/src/ext_event.rs index 3d1361b121..ea0a5847ee 100644 --- a/druid/src/ext_event.rs +++ b/druid/src/ext_event.rs @@ -20,9 +20,9 @@ use std::sync::{Arc, Mutex}; use crate::shell::IdleHandle; use crate::win_handler::EXT_EVENT_IDLE_TOKEN; -use crate::{Command, Selector, Target, WindowId}; +use crate::{Command, Selector, Target, WindowId, command::SelectorSymbol}; -pub(crate) type ExtCommand = (Selector, Option>, Option); +pub(crate) type ExtCommand = (SelectorSymbol, Box, Option); /// A thing that can move into other threads and be used to submit commands back /// to the running application. @@ -101,21 +101,21 @@ impl ExtEventSink { /// /// [`Command`]: struct.Command.html /// [`Selector`]: struct.Selector.html - pub fn submit_command( + pub fn submit_command( &self, - sel: Selector, - obj: impl Into>, + sel: Selector, + obj: T, target: impl Into>, ) -> Result<(), ExtEventError> { let target = target.into(); - let obj = obj.into().map(|o| Box::new(o) as Box); + let obj = Box::new(obj) as Box; if let Some(handle) = self.handle.lock().unwrap().as_mut() { handle.schedule_idle(EXT_EVENT_IDLE_TOKEN); } self.queue .lock() .map_err(|_| ExtEventError)? - .push_back((sel, obj, target)); + .push_back((sel.symbol(), obj, target)); Ok(()) } } diff --git a/druid/src/menu.rs b/druid/src/menu.rs index a66922330f..13ecaf46d1 100644 --- a/druid/src/menu.rs +++ b/druid/src/menu.rs @@ -105,7 +105,7 @@ //! [`Selector`]: ../struct.Selector.html //! [`SET_MENU`]: ../struct.Selector.html#associatedconstant.SET_MENU -use std::num::NonZeroU32; +use std::{any::Any, num::NonZeroU32}; use crate::kurbo::Point; use crate::shell::{HotKey, KeyCompare, Menu as PlatformMenu, RawMods, SysMods}; @@ -120,6 +120,27 @@ pub struct MenuDesc { items: Vec>, } +/// A platform-agnostic description of an application, window, or context +/// menu. The user has to guarantee that this represents a `MenuDesc` where `T` is the users +/// `AppState`. +pub struct AppStateMenuDesc(Box); + +impl MenuDesc { + /// This turns a typed `MenuDesc` into an untyped `AppStateMenuDesc`. + /// Doing so allows sending `MenuDesc` through `Command`s. + /// It is up to you, to ensure that this `T` represents your application + /// state that you passed to `AppLauncher::launch`. + pub fn into_app_state_menu_desc(self) -> AppStateMenuDesc { + AppStateMenuDesc(Box::new(self)) + } +} + +impl AppStateMenuDesc { + pub(crate) fn realize(&self) -> Option<&MenuDesc> { + self.0.downcast_ref() + } +} + /// An item in a menu, which may be a normal item, a submenu, or a separator. #[derive(Debug, Clone)] #[allow(clippy::large_enum_variant)] @@ -159,6 +180,27 @@ pub struct ContextMenu { pub(crate) location: Point, } +/// A platform-agnostic description of a context menu. +/// The user has to guarantee that this represents a `ContextMenu` where `T` is the users +/// `AppState`. +pub struct AppStateContextMenu(Box); + +impl ContextMenu { + /// This turns a typed `ContextMenu` into an untyped `AppStateContextMenu`. + /// Doing so allows sending `ContextMenu` through `Command`s. + /// It is up to you, to ensure that this `T` represents your application + /// state that you passed to `AppLauncher::launch`. + pub fn into_app_state_context_menu(self) -> AppStateContextMenu { + AppStateContextMenu(Box::new(self)) + } +} + +impl AppStateContextMenu { + pub(crate) fn realize(&self) -> Option<&ContextMenu> { + self.0.downcast_ref() + } +} + /// Uniquely identifies a menu item. /// /// On the druid-shell side, the id is represented as a u32. @@ -238,7 +280,7 @@ impl MenuDesc { /// Create a new menu with the given title. pub fn new(title: LocalizedString) -> Self { - let item = MenuItem::new(title, Selector::NOOP); + let item = MenuItem::new(title, Selector::noop()); MenuDesc { item, items: Vec::new(), diff --git a/druid/src/tests/helpers.rs b/druid/src/tests/helpers.rs index 19d382fdbb..4d933c4d62 100644 --- a/druid/src/tests/helpers.rs +++ b/druid/src/tests/helpers.rs @@ -39,7 +39,7 @@ pub type UpdateFn = dyn FnMut(&mut S, &mut UpdateCtx, &T, &T, &Env); pub type LayoutFn = dyn FnMut(&mut S, &mut LayoutCtx, &BoxConstraints, &T, &Env) -> Size; pub type PaintFn = dyn FnMut(&mut S, &mut PaintCtx, &T, &Env); -pub const REPLACE_CHILD: Selector = Selector::new("druid-test.replace-child"); +pub const REPLACE_CHILD: Selector<()> = Selector::new("druid-test.replace-child"); /// A widget that can be constructed from individual functions, builder-style. /// @@ -214,7 +214,7 @@ impl ReplaceChild { impl Widget for ReplaceChild { fn event(&mut self, ctx: &mut EventCtx, event: &Event, data: &mut T, env: &Env) { if let Event::Command(cmd) = event { - if cmd.selector == REPLACE_CHILD { + if cmd.is(REPLACE_CHILD) { self.inner = WidgetPod::new((self.replacer)()); ctx.children_changed(); return; diff --git a/druid/src/tests/mod.rs b/druid/src/tests/mod.rs index 7552295f8c..bd5c9a51eb 100644 --- a/druid/src/tests/mod.rs +++ b/druid/src/tests/mod.rs @@ -150,7 +150,7 @@ fn propogate_hot() { } #[test] fn take_focus() { - const TAKE_FOCUS: Selector = Selector::new("druid-tests.take-focus"); + const TAKE_FOCUS: Selector<()> = Selector::new("druid-tests.take-focus"); /// A widget that takes focus when sent a particular command. /// The widget records focus change events into the inner cell. @@ -158,7 +158,7 @@ fn take_focus() { ModularWidget::new(inner) .event_fn(|_, ctx, event, _data, _env| { if let Event::Command(cmd) = event { - if cmd.selector == TAKE_FOCUS { + if cmd.is(TAKE_FOCUS) { ctx.request_focus(); } } diff --git a/druid/src/widget/textbox.rs b/druid/src/widget/textbox.rs index 08e9962abe..d126599516 100644 --- a/druid/src/widget/textbox.rs +++ b/druid/src/widget/textbox.rs @@ -37,7 +37,7 @@ const PADDING_TOP: f64 = 5.; const PADDING_LEFT: f64 = 4.; // we send ourselves this when we want to reset blink, which must be done in event. -const RESET_BLINK: Selector = Selector::new("druid-builtin.reset-textbox-blink"); +const RESET_BLINK: Selector<()> = Selector::new("druid-builtin.reset-textbox-blink"); const CURSOR_BLINK_DRUATION: Duration = Duration::from_millis(500); /// A widget that allows user text input. @@ -53,7 +53,7 @@ pub struct TextBox { impl TextBox { /// Perform an `EditAction`. The payload *must* be an `EditAction`. - pub const PERFORM_EDIT: Selector = Selector::new("druid-builtin.textbox.perform-edit"); + pub const PERFORM_EDIT: Selector = Selector::new("druid-builtin.textbox.perform-edit"); /// Create a new TextBox widget pub fn new() -> TextBox { @@ -281,22 +281,20 @@ impl Widget for TextBox { } Event::Command(ref cmd) if ctx.is_focused() - && (cmd.selector == crate::commands::COPY - || cmd.selector == crate::commands::CUT) => + && (cmd.is(crate::commands::COPY) + || cmd.is(crate::commands::CUT)) => { if let Some(text) = data.slice(self.selection.range()) { Application::global().clipboard().put_string(text); } - if !self.selection.is_caret() && cmd.selector == crate::commands::CUT { + if !self.selection.is_caret() && cmd.is(crate::commands::CUT) { edit_action = Some(EditAction::Delete); } ctx.set_handled(); } - Event::Command(cmd) if cmd.selector == RESET_BLINK => self.reset_cursor_blink(ctx), - Event::Command(cmd) if cmd.selector == TextBox::PERFORM_EDIT => { - let edit = cmd - .get_object::() - .expect("PERFORM_EDIT contained non-edit payload"); + Event::Command(cmd) if cmd.is(RESET_BLINK) => self.reset_cursor_blink(ctx), + Event::Command(cmd) if cmd.is(TextBox::PERFORM_EDIT) => { + let edit = cmd.get(TextBox::PERFORM_EDIT).unwrap(); self.do_edit_action(edit.to_owned(), data); } Event::Paste(ref item) => { diff --git a/druid/src/win_handler.rs b/druid/src/win_handler.rs index 6513bdbb48..5bef44ca92 100644 --- a/druid/src/win_handler.rs +++ b/druid/src/win_handler.rs @@ -523,31 +523,31 @@ impl AppState { /// windows) have their logic here; other commands are passed to the window. fn handle_cmd(&mut self, target: Target, cmd: Command) { use Target as T; - match (target, &cmd.selector) { + match (target, cmd.selector_symbol()) { // these are handled the same no matter where they come from - (_, &sys_cmd::QUIT_APP) => self.quit(), - (_, &sys_cmd::HIDE_APPLICATION) => self.hide_app(), - (_, &sys_cmd::HIDE_OTHERS) => self.hide_others(), - (_, &sys_cmd::NEW_WINDOW) => { + (_, sys_cmd::QUIT_APP.symbol()) => self.quit(), + (_, sys_cmd::HIDE_APPLICATION) => self.hide_app(), + (_, sys_cmd::HIDE_OTHERS) => self.hide_others(), + (_, sys_cmd::NEW_WINDOW) => { if let Err(e) = self.new_window(cmd) { log::error!("failed to create window: '{}'", e); } } - (_, &sys_cmd::CLOSE_ALL_WINDOWS) => self.request_close_all_windows(), + (_, sys_cmd::CLOSE_ALL_WINDOWS) => self.request_close_all_windows(), // these should come from a window // FIXME: we need to be able to open a file without a window handle - (T::Window(id), &sys_cmd::SHOW_OPEN_PANEL) => self.show_open_panel(cmd, id), - (T::Window(id), &sys_cmd::SHOW_SAVE_PANEL) => self.show_save_panel(cmd, id), - (T::Window(id), &sys_cmd::CLOSE_WINDOW) => self.request_close_window(cmd, id), - (T::Window(_), &sys_cmd::SHOW_WINDOW) => self.show_window(cmd), - (T::Window(id), &sys_cmd::PASTE) => self.do_paste(id), + (T::Window(id), sys_cmd::SHOW_OPEN_PANEL) => self.show_open_panel(cmd, id), + (T::Window(id), sys_cmd::SHOW_SAVE_PANEL) => self.show_save_panel(cmd, id), + (T::Window(id), sys_cmd::CLOSE_WINDOW) => self.request_close_window(cmd, id), + (T::Window(_), sys_cmd::SHOW_WINDOW) => self.show_window(cmd), + (T::Window(id), sys_cmd::PASTE) => self.do_paste(id), _sel => self.inner.borrow_mut().dispatch_cmd(target, cmd), } } fn show_open_panel(&mut self, cmd: Command, window_id: WindowId) { let options = cmd - .get_object::() + .get(sys_cmd::SHOW_OPEN_PANEL) .map(|opts| opts.to_owned()) .unwrap_or_default(); //FIXME: this is blocking; if we hold `borrow_mut` we are likely to cause From 9eeac530bb80db8a6d685aabcce15b34a0f1bfeb Mon Sep 17 00:00:00 2001 From: Leopold Luley Date: Fri, 8 May 2020 12:26:23 +0200 Subject: [PATCH 02/19] Rebase onto current master. --- CHANGELOG.md | 1 + docs/book_examples/src/custom_widgets_md.rs | 2 +- druid/examples/blocking_function.rs | 12 +-- druid/examples/ext_event.rs | 6 +- druid/examples/identity.rs | 15 ++-- druid/examples/multiwin.rs | 41 +++++----- druid/src/app.rs | 41 +++++++++- druid/src/command.rs | 83 ++++++++++++++++----- druid/src/ext_event.rs | 2 +- druid/src/menu.rs | 63 +++++++++++----- druid/src/widget/textbox.rs | 6 +- druid/src/win_handler.rs | 83 ++++++++++++--------- 12 files changed, 237 insertions(+), 118 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 626e447d02..fb2b5a70a1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -47,6 +47,7 @@ While some features like the clipboard, menus or file dialogs are not yet availa - Timer events will only be delivered to the widgets that requested them. ([#831] by [@sjoshid]) - `Event::Wheel` now contains a `MouseEvent` structure. ([#895] by [@teddemunnik]) - `AppDelegate::command` now receives a `Target` instead of a `&Target`. ([#909] by [@xStrom]) +- `SHOW_WINDOW` and `OPEN_WINDOW` no longer require a `WindowId` as payload, but they must `Target` the correct window. ([#???] by [@finnerale]) ### Deprecated diff --git a/docs/book_examples/src/custom_widgets_md.rs b/docs/book_examples/src/custom_widgets_md.rs index 2ad864822a..9740936753 100644 --- a/docs/book_examples/src/custom_widgets_md.rs +++ b/docs/book_examples/src/custom_widgets_md.rs @@ -36,7 +36,7 @@ fn background_label() -> impl Widget { // ANCHOR_END: background_label // ANCHOR: annoying_textbox -const ACTION: Selector = Selector::new("hello.textbox-action"); +const ACTION: Selector<()> = Selector::new("hello.textbox-action"); const DELAY: Duration = Duration::from_millis(300); struct TextBoxActionController { diff --git a/druid/examples/blocking_function.rs b/druid/examples/blocking_function.rs index 0caa288dc5..af4ccde982 100644 --- a/druid/examples/blocking_function.rs +++ b/druid/examples/blocking_function.rs @@ -23,9 +23,9 @@ use druid::{ use druid::widget::{Button, Either, Flex, Label}; -const START_SLOW_FUNCTION: Selector = Selector::new("start_slow_function"); +const START_SLOW_FUNCTION: Selector = Selector::new("start_slow_function"); -const FINISH_SLOW_FUNCTION: Selector = Selector::new("finish_slow_function"); +const FINISH_SLOW_FUNCTION: Selector = Selector::new("finish_slow_function"); struct Delegate { eventsink: ExtEventSink, @@ -61,15 +61,15 @@ impl AppDelegate for Delegate { data: &mut AppState, _env: &Env, ) -> bool { - match cmd.selector { - START_SLOW_FUNCTION => { + match () { + _ if cmd.is(START_SLOW_FUNCTION) => { data.processing = true; wrapped_slow_function(self.eventsink.clone(), data.value); true } - FINISH_SLOW_FUNCTION => { + _ if cmd.is(FINISH_SLOW_FUNCTION) => { data.processing = false; - let number = cmd.get_object::().expect("api violation"); + let number = cmd.get(FINISH_SLOW_FUNCTION).unwrap(); data.value = *number; true } diff --git a/druid/examples/ext_event.rs b/druid/examples/ext_event.rs index 6ef2c9450a..ab57e80916 100644 --- a/druid/examples/ext_event.rs +++ b/druid/examples/ext_event.rs @@ -22,7 +22,7 @@ use druid::kurbo::RoundedRect; use druid::widget::prelude::*; use druid::{AppLauncher, Color, Data, LocalizedString, Rect, Selector, WidgetExt, WindowDesc}; -const SET_COLOR: Selector = Selector::new("event-example.set-color"); +const SET_COLOR: Selector = Selector::new("event-example.set-color"); /// A widget that displays a color. struct ColorWell; @@ -53,8 +53,8 @@ impl ColorWell { impl Widget for ColorWell { fn event(&mut self, ctx: &mut EventCtx, event: &Event, data: &mut MyColor, _env: &Env) { match event { - Event::Command(cmd) if cmd.selector == SET_COLOR => { - data.0 = cmd.get_object::().unwrap().clone(); + Event::Command(cmd) if cmd.is(SET_COLOR) => { + data.0 = cmd.get(SET_COLOR).unwrap().clone(); ctx.request_paint(); } _ => (), diff --git a/druid/examples/identity.rs b/druid/examples/identity.rs index 3c8c863eee..dfdabb9005 100644 --- a/druid/examples/identity.rs +++ b/druid/examples/identity.rs @@ -40,8 +40,8 @@ use druid::{ const CYCLE_DURATION: Duration = Duration::from_millis(100); -const FREEZE_COLOR: Selector = Selector::new("identity-example.freeze-color"); -const UNFREEZE_COLOR: Selector = Selector::new("identity-example.unfreeze-color"); +const FREEZE_COLOR: Selector = Selector::new("identity-example.freeze-color"); +const UNFREEZE_COLOR: Selector<()> = Selector::new("identity-example.unfreeze-color"); /// Honestly: it's just a color in fancy clothing. #[derive(Debug, Clone, Data, Lens)] @@ -114,15 +114,10 @@ impl Widget for ColorWell { self.token = ctx.request_timer(CYCLE_DURATION); } - Event::Command(cmd) if cmd.selector == FREEZE_COLOR => { - self.frozen = cmd - .get_object::() - .ok() - .cloned() - .expect("payload is always a Color") - .into(); + Event::Command(cmd) if cmd.is(FREEZE_COLOR) => { + self.frozen = cmd.get(FREEZE_COLOR).ok().cloned(); } - Event::Command(cmd) if cmd.selector == UNFREEZE_COLOR => self.frozen = None, + Event::Command(cmd) if cmd.is(UNFREEZE_COLOR) => self.frozen = None, _ => (), } } diff --git a/druid/examples/multiwin.rs b/druid/examples/multiwin.rs index 46094c4e8b..b9f6c7e5b9 100644 --- a/druid/examples/multiwin.rs +++ b/druid/examples/multiwin.rs @@ -23,10 +23,10 @@ use druid::{ use log::info; -const MENU_COUNT_ACTION: Selector = Selector::new("menu-count-action"); -const MENU_INCREMENT_ACTION: Selector = Selector::new("menu-increment-action"); -const MENU_DECREMENT_ACTION: Selector = Selector::new("menu-decrement-action"); -const MENU_SWITCH_GLOW_ACTION: Selector = Selector::new("menu-switch-glow"); +const MENU_COUNT_ACTION: Selector = Selector::new("menu-count-action"); +const MENU_INCREMENT_ACTION: Selector<()> = Selector::new("menu-increment-action"); +const MENU_DECREMENT_ACTION: Selector<()> = Selector::new("menu-decrement-action"); +const MENU_SWITCH_GLOW_ACTION: Selector<()> = Selector::new("menu-switch-glow"); #[derive(Debug, Clone, Default, Data)] struct State { @@ -56,7 +56,7 @@ trait EventCtxExt { impl EventCtxExt for EventCtx<'_> { fn set_menu(&mut self, menu: MenuDesc) { - let cmd = Command::new(druid::commands::SET_MENU, menu); + let cmd = Command::new(druid::commands::SET_MENU, menu.into_app_state_menu_desc()); let target = self.window_id(); self.submit_command(cmd, target); } @@ -155,7 +155,10 @@ impl AppDelegate for Delegate { match event { Event::MouseDown(ref mouse) if mouse.button.is_right() => { let menu = ContextMenu::new(make_context_menu::(), mouse.pos); - let cmd = Command::new(druid::commands::SHOW_CONTEXT_MENU, menu); + let cmd = Command::new( + druid::commands::SHOW_CONTEXT_MENU, + menu.into_app_state_context_menu(), + ); ctx.submit_command(cmd, Target::Window(window_id)); None } @@ -171,39 +174,40 @@ impl AppDelegate for Delegate { data: &mut State, _env: &Env, ) -> bool { - match (target, &cmd.selector) { - (_, &sys_cmds::NEW_FILE) => { - let new_win = WindowDesc::new(ui_builder) + match target { + _ if cmd.is(sys_cmds::NEW_FILE) => { + let new_win = WindowDesc::::new(ui_builder) .menu(make_menu(data)) .window_size((data.selected as f64 * 100.0 + 300.0, 500.0)); - let command = Command::one_shot(sys_cmds::NEW_WINDOW, new_win); + let command = + Command::one_shot(sys_cmds::NEW_WINDOW, new_win.into_app_state_menu_desc()); ctx.submit_command(command, Target::Global); false } - (Target::Window(id), &MENU_COUNT_ACTION) => { - data.selected = *cmd.get_object().unwrap(); + Target::Window(id) if cmd.is(MENU_COUNT_ACTION) => { + data.selected = *cmd.get(MENU_COUNT_ACTION).unwrap(); let menu = make_menu::(data); - let cmd = Command::new(druid::commands::SET_MENU, menu); + let cmd = Command::new(druid::commands::SET_MENU, menu.into_app_state_menu_desc()); ctx.submit_command(cmd, id); false } // wouldn't it be nice if a menu (like a button) could just mutate state // directly if desired? - (Target::Window(id), &MENU_INCREMENT_ACTION) => { + Target::Window(id) if cmd.is(MENU_INCREMENT_ACTION) => { data.menu_count += 1; let menu = make_menu::(data); - let cmd = Command::new(druid::commands::SET_MENU, menu); + let cmd = Command::new(druid::commands::SET_MENU, menu.into_app_state_menu_desc()); ctx.submit_command(cmd, id); false } - (Target::Window(id), &MENU_DECREMENT_ACTION) => { + Target::Window(id) if cmd.is(MENU_DECREMENT_ACTION) => { data.menu_count = data.menu_count.saturating_sub(1); let menu = make_menu::(data); - let cmd = Command::new(druid::commands::SET_MENU, menu); + let cmd = Command::new(druid::commands::SET_MENU, menu.into_app_state_menu_desc()); ctx.submit_command(cmd, id); false } - (_, &MENU_SWITCH_GLOW_ACTION) => { + _ if cmd.is(MENU_SWITCH_GLOW_ACTION) => { data.glow_hot = !data.glow_hot; false } @@ -220,6 +224,7 @@ impl AppDelegate for Delegate { ) { info!("Window added, id: {:?}", id); } + fn window_removed( &mut self, id: WindowId, diff --git a/druid/src/app.rs b/druid/src/app.rs index db6c5e8cb4..6dc8715a8f 100644 --- a/druid/src/app.rs +++ b/druid/src/app.rs @@ -21,8 +21,10 @@ use crate::widget::LabelText; use crate::win_handler::{AppHandler, AppState}; use crate::window::WindowId; use crate::{ - theme, AppDelegate, Data, DruidHandler, Env, LocalizedString, MenuDesc, Widget, WidgetExt, + command::AppStateTypeError, theme, AppDelegate, Data, DruidHandler, Env, LocalizedString, + MenuDesc, Widget, WidgetExt, }; +use std::any::{self, Any}; /// A function that modifies the initial environment. type EnvSetupFn = dyn FnOnce(&mut Env, &T); @@ -54,6 +56,43 @@ pub struct WindowDesc { pub id: WindowId, } +/// A description of a window to be instantiated. The user has to guarantee that this +/// represents a `WindowDesc` where `T` is the users `AppState`. +/// +/// This includes a function that can build the root widget, as well as other +/// window properties such as the title. +pub struct AppStateWindowDesc { + inner: Box, + type_name: &'static str, +} + +impl WindowDesc { + /// This turns a typed `WindowDesc` into an untyped `AppStateWindowDesc`. + /// Doing so allows sending `WindowDesc` through `Command`s. + /// It is up to you, to ensure that this `T` represents your application + /// state that you passed to `AppLauncher::launch`. + pub fn into_app_state_menu_desc(self) -> AppStateWindowDesc { + AppStateWindowDesc { + inner: Box::new(self), + type_name: any::type_name::(), + } + } +} + +impl AppStateWindowDesc { + pub(crate) fn realize(self) -> Result, AppStateTypeError> { + let inner: Result>, _> = self.inner.downcast(); + if let Some(inner) = inner.ok() { + Ok(*inner) + } else { + Err(AppStateTypeError::new( + any::type_name::>(), + self.type_name, + )) + } + } +} + impl AppLauncher { /// Create a new `AppLauncher` with the provided window. pub fn with_window(window: WindowDesc) -> Self { diff --git a/druid/src/command.rs b/druid/src/command.rs index 87c01bdde0..8a236089e3 100644 --- a/druid/src/command.rs +++ b/druid/src/command.rs @@ -15,7 +15,10 @@ //! Custom commands. use std::any::Any; -use std::{marker::PhantomData, sync::{Arc, Mutex}}; +use std::{ + marker::PhantomData, + sync::{Arc, Mutex}, +}; use crate::{WidgetId, WindowId}; @@ -28,7 +31,7 @@ pub type SelectorSymbol = &'static str; /// [`druid::commands`] module. /// /// [`druid::commands`]: commands/index.html -#[derive(Debug, Clone, PartialEq, Eq)] +#[derive(Debug, Clone, Copy, PartialEq, Eq)] pub struct Selector(SelectorSymbol, PhantomData); /// An arbitrary command. @@ -87,6 +90,30 @@ pub enum ArgumentError { Consumed, } +#[derive(Debug, Clone, Copy, PartialEq)] +pub struct AppStateTypeError { + expected: &'static str, + found: &'static str, +} + +impl AppStateTypeError { + pub fn new(expected: &'static str, found: &'static str) -> Self { + Self { expected, found } + } +} + +impl std::fmt::Display for AppStateTypeError { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + write!( + f, + "Promise to represent the app state was not meet. Expected {} but got {}.", + self.expected, self.found + ) + } +} + +impl std::error::Error for AppStateTypeError {} + /// The target of a command. #[derive(Clone, Copy, Debug, PartialEq)] pub enum Target { @@ -106,7 +133,11 @@ pub enum Target { /// [`Command`]: ../struct.Command.html pub mod sys { use super::Selector; - use crate::{FileDialogOptions, WindowId, FileInfo, menu::{AppStateContextMenu, AppStateMenuDesc}}; + use crate::{ + app::AppStateWindowDesc, + menu::{AppStateContextMenu, AppStateMenuDesc}, + FileDialogOptions, FileInfo, + }; /// Quit the running application. This command is handled by the druid library. pub const QUIT_APP: Selector<()> = Selector::new("druid-builtin.quit-app"); @@ -118,25 +149,31 @@ pub mod sys { pub const HIDE_OTHERS: Selector<()> = Selector::new("druid-builtin.menu-hide-others"); /// The selector for a command to create a new window. - pub const NEW_WINDOW: Selector<()> = Selector::new("druid-builtin.new-window"); + pub const NEW_WINDOW: Selector = Selector::new("druid-builtin.new-window"); - /// The selector for a command to close a window. The command's argument - /// should be the id of the window to close. - pub const CLOSE_WINDOW: Selector = Selector::new("druid-builtin.close-window"); + /// The selector for a command to close a window. + /// + /// The command must target a specific window. + /// When calling `submit_command` on a `Widget`s context, passing `None` as target + /// will automatically target the window containing the widget. + pub const CLOSE_WINDOW: Selector<()> = Selector::new("druid-builtin.close-window"); /// Close all windows. pub const CLOSE_ALL_WINDOWS: Selector<()> = Selector::new("druid-builtin.close-all-windows"); /// The selector for a command to bring a window to the front, and give it focus. /// - /// The command's argument should be the id of the target window. - pub const SHOW_WINDOW: Selector = Selector::new("druid-builtin.show-window"); + /// The command must target a specific window. + /// When calling `submit_command` on a `Widget`s context, passing `None` as target + /// will automatically target the window containing the widget. + pub const SHOW_WINDOW: Selector<()> = Selector::new("druid-builtin.show-window"); /// Display a context (right-click) menu. The argument must be the [`ContextMenu`]. /// object to be displayed. /// /// [`ContextMenu`]: ../struct.ContextMenu.html - pub const SHOW_CONTEXT_MENU: Selector = Selector::new("druid-builtin.show-context-menu"); + pub const SHOW_CONTEXT_MENU: Selector = + Selector::new("druid-builtin.show-context-menu"); /// The selector for a command to set the window's menu. The argument should /// be a [`MenuDesc`] object. @@ -163,7 +200,8 @@ pub mod sys { /// /// [`OPEN_FILE`]: constant.OPEN_FILE.html /// [`FileDialogOptions`]: ../struct.FileDialogOptions.html - pub const SHOW_OPEN_PANEL: Selector = Selector::new("druid-builtin.menu-file-open"); + pub const SHOW_OPEN_PANEL: Selector = + Selector::new("druid-builtin.menu-file-open"); /// Open a file. /// @@ -180,12 +218,13 @@ pub mod sys { /// /// [`SAVE_FILE`]: constant.SAVE_FILE.html /// [`FileDialogOptions`]: ../struct.FileDialogOptions.html - pub const SHOW_SAVE_PANEL: Selector = Selector::new("druid-builtin.menu-file-save-as"); + pub const SHOW_SAVE_PANEL: Selector = + Selector::new("druid-builtin.menu-file-save-as"); /// Save the current file. /// /// The argument, if present, should be the path where the file should be saved. - pub const SAVE_FILE: Selector = Selector::new("druid-builtin.menu-file-save"); + pub const SAVE_FILE: Selector> = Selector::new("druid-builtin.menu-file-save"); /// Show the print-setup window. pub const PRINT_SETUP: Selector<()> = Selector::new("druid-builtin.menu-file-print-setup"); @@ -273,8 +312,8 @@ impl Command { if self.selector != selector.symbol() { return Err(ArgumentError::WrongSelector); } - match self.object { - Arg::Reusable(o) => o.downcast_ref().ok_or(ArgumentError::IncorrectType), + match &self.object { + Arg::Reusable(obj) => obj.downcast_ref().ok_or(ArgumentError::IncorrectType), Arg::OneShot(_) => Err(ArgumentError::WrongVariant), } } @@ -286,7 +325,7 @@ impl Command { if self.selector != selector.symbol() { return Err(ArgumentError::WrongSelector); } - match self.object { + match &self.object { Arg::Reusable(_) => Err(ArgumentError::WrongVariant), Arg::OneShot(inner) => { let obj = inner @@ -317,7 +356,12 @@ impl From> for Command { impl std::fmt::Display for Selector { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { - write!(f, "Selector(\"{}\", {})", self.0, std::any::type_name::()) + write!( + f, + "Selector(\"{}\", {})", + self.0, + std::any::type_name::() + ) } } @@ -368,9 +412,10 @@ mod tests { #[test] fn get_object() { - let sel = Selector::>::new("my-selector"); + let sel: Selector> = Selector::new("my-selector"); let objs = vec![0, 1, 2]; - let command = Command::new(sel, objs); + // TODO: find out why this now wants a `.clone()` even tho `Selector` implements `Copy`. + let command = Command::new(sel.clone(), objs); assert_eq!(command.get(sel), Ok(&vec![0, 1, 2])); } } diff --git a/druid/src/ext_event.rs b/druid/src/ext_event.rs index ea0a5847ee..17d396da65 100644 --- a/druid/src/ext_event.rs +++ b/druid/src/ext_event.rs @@ -20,7 +20,7 @@ use std::sync::{Arc, Mutex}; use crate::shell::IdleHandle; use crate::win_handler::EXT_EVENT_IDLE_TOKEN; -use crate::{Command, Selector, Target, WindowId, command::SelectorSymbol}; +use crate::{command::SelectorSymbol, Command, Selector, Target, WindowId}; pub(crate) type ExtCommand = (SelectorSymbol, Box, Option); diff --git a/druid/src/menu.rs b/druid/src/menu.rs index 13ecaf46d1..ee230b854f 100644 --- a/druid/src/menu.rs +++ b/druid/src/menu.rs @@ -105,11 +105,16 @@ //! [`Selector`]: ../struct.Selector.html //! [`SET_MENU`]: ../struct.Selector.html#associatedconstant.SET_MENU -use std::{any::Any, num::NonZeroU32}; +use std::{ + any::{self, Any}, + num::NonZeroU32, +}; use crate::kurbo::Point; use crate::shell::{HotKey, KeyCompare, Menu as PlatformMenu, RawMods, SysMods}; -use crate::{commands, Command, Data, Env, KeyCode, LocalizedString, Selector}; +use crate::{ + command::AppStateTypeError, commands, Command, Data, Env, KeyCode, LocalizedString, Selector, +}; /// A platform-agnostic description of an application, window, or context /// menu. @@ -123,7 +128,10 @@ pub struct MenuDesc { /// A platform-agnostic description of an application, window, or context /// menu. The user has to guarantee that this represents a `MenuDesc` where `T` is the users /// `AppState`. -pub struct AppStateMenuDesc(Box); +pub struct AppStateMenuDesc { + inner: Box, + type_name: &'static str, +} impl MenuDesc { /// This turns a typed `MenuDesc` into an untyped `AppStateMenuDesc`. @@ -131,13 +139,19 @@ impl MenuDesc { /// It is up to you, to ensure that this `T` represents your application /// state that you passed to `AppLauncher::launch`. pub fn into_app_state_menu_desc(self) -> AppStateMenuDesc { - AppStateMenuDesc(Box::new(self)) + AppStateMenuDesc { + inner: Box::new(self), + type_name: any::type_name::(), + } } } impl AppStateMenuDesc { - pub(crate) fn realize(&self) -> Option<&MenuDesc> { - self.0.downcast_ref() + pub(crate) fn realize(&self) -> Result<&MenuDesc, AppStateTypeError> { + self.inner.downcast_ref().ok_or(AppStateTypeError::new( + any::type_name::>(), + self.type_name, + )) } } @@ -183,7 +197,10 @@ pub struct ContextMenu { /// A platform-agnostic description of a context menu. /// The user has to guarantee that this represents a `ContextMenu` where `T` is the users /// `AppState`. -pub struct AppStateContextMenu(Box); +pub struct AppStateContextMenu { + inner: Box, + type_name: &'static str, +} impl ContextMenu { /// This turns a typed `ContextMenu` into an untyped `AppStateContextMenu`. @@ -191,13 +208,19 @@ impl ContextMenu { /// It is up to you, to ensure that this `T` represents your application /// state that you passed to `AppLauncher::launch`. pub fn into_app_state_context_menu(self) -> AppStateContextMenu { - AppStateContextMenu(Box::new(self)) + AppStateContextMenu { + inner: Box::new(self), + type_name: any::type_name::(), + } } } impl AppStateContextMenu { - pub(crate) fn realize(&self) -> Option<&ContextMenu> { - self.0.downcast_ref() + pub(crate) fn realize(&self) -> Result<&ContextMenu, AppStateTypeError> { + self.inner.downcast_ref().ok_or(AppStateTypeError::new( + any::type_name::>(), + self.type_name, + )) } } @@ -583,7 +606,7 @@ pub mod sys { pub fn open() -> MenuItem { MenuItem::new( LocalizedString::new("common-menu-file-open"), - commands::SHOW_OPEN_PANEL, + Command::new(commands::SHOW_OPEN_PANEL, Default::default()), ) .hotkey(RawMods::Ctrl, "o") } @@ -600,16 +623,18 @@ pub mod sys { pub fn save() -> MenuItem { MenuItem::new( LocalizedString::new("common-menu-file-save"), - commands::SAVE_FILE, + Command::new(commands::SAVE_FILE, None), ) .hotkey(RawMods::Ctrl, "s") } - /// The 'Save' menu item. + /// The 'Save...' menu item. + /// For windows, this is the same as 'Save'. + /// TODO: find out why this exists. pub fn save_ellipsis() -> MenuItem { MenuItem::new( LocalizedString::new("common-menu-file-save"), - commands::SAVE_FILE, + Command::new(commands::SAVE_FILE, None), ) .hotkey(RawMods::Ctrl, "s") } @@ -618,7 +643,7 @@ pub mod sys { pub fn save_as() -> MenuItem { MenuItem::new( LocalizedString::new("common-menu-file-save-as"), - commands::SHOW_SAVE_PANEL, + Command::new(commands::SHOW_SAVE_PANEL, Default::default()), ) .hotkey(RawMods::CtrlShift, "s") } @@ -784,7 +809,7 @@ pub mod sys { pub fn open_file() -> MenuItem { MenuItem::new( LocalizedString::new("common-menu-file-open"), - commands::OPEN_FILE, + Command::new(commands::SHOW_OPEN_PANEL, Default::default()), ) .hotkey(RawMods::Meta, "o") } @@ -802,7 +827,7 @@ pub mod sys { pub fn save() -> MenuItem { MenuItem::new( LocalizedString::new("common-menu-file-save"), - commands::SAVE_FILE, + Command::new(commands::SAVE_FILE, None), ) .hotkey(RawMods::Meta, "s") } @@ -813,7 +838,7 @@ pub mod sys { pub fn save_ellipsis() -> MenuItem { MenuItem::new( LocalizedString::new("common-menu-file-save-ellipsis"), - commands::SAVE_FILE, + Command::new(commands::SHOW_SAVE_PANEL, Default::default()), ) .hotkey(RawMods::Meta, "s") } @@ -822,7 +847,7 @@ pub mod sys { pub fn save_as() -> MenuItem { MenuItem::new( LocalizedString::new("common-menu-file-save-as"), - commands::SHOW_SAVE_PANEL, + Command::new(commands::SHOW_SAVE_PANEL, Default::default()), ) .hotkey(RawMods::MetaShift, "s") } diff --git a/druid/src/widget/textbox.rs b/druid/src/widget/textbox.rs index d126599516..9ffb652030 100644 --- a/druid/src/widget/textbox.rs +++ b/druid/src/widget/textbox.rs @@ -53,7 +53,8 @@ pub struct TextBox { impl TextBox { /// Perform an `EditAction`. The payload *must* be an `EditAction`. - pub const PERFORM_EDIT: Selector = Selector::new("druid-builtin.textbox.perform-edit"); + pub const PERFORM_EDIT: Selector = + Selector::new("druid-builtin.textbox.perform-edit"); /// Create a new TextBox widget pub fn new() -> TextBox { @@ -281,8 +282,7 @@ impl Widget for TextBox { } Event::Command(ref cmd) if ctx.is_focused() - && (cmd.is(crate::commands::COPY) - || cmd.is(crate::commands::CUT)) => + && (cmd.is(crate::commands::COPY) || cmd.is(crate::commands::CUT)) => { if let Some(text) = data.slice(self.selection.range()) { Application::global().clipboard().put_string(text); diff --git a/druid/src/win_handler.rs b/druid/src/win_handler.rs index 5bef44ca92..e8abbab656 100644 --- a/druid/src/win_handler.rs +++ b/druid/src/win_handler.rs @@ -21,9 +21,7 @@ use std::rc::Rc; use crate::kurbo::{Rect, Size}; use crate::piet::Piet; -use crate::shell::{ - Application, FileDialogOptions, IdleToken, MouseEvent, WinHandler, WindowHandle, -}; +use crate::shell::{Application, IdleToken, MouseEvent, WinHandler, WindowHandle}; use crate::app_delegate::{AppDelegate, DelegateCtx}; use crate::core::CommandQueue; @@ -313,9 +311,11 @@ impl Inner { match target { Target::Window(id) => { // first handle special window-level events - match cmd.selector { - sys_cmd::SET_MENU => return self.set_menu(id, &cmd), - sys_cmd::SHOW_CONTEXT_MENU => return self.show_context_menu(id, &cmd), + match () { + _ if cmd.is(sys_cmd::SET_MENU) => return self.set_menu(id, &cmd), + _ if cmd.is(sys_cmd::SHOW_CONTEXT_MENU) => { + return self.show_context_menu(id, &cmd) + } _ => (), } if let Some(w) = self.windows.get_mut(id) { @@ -368,20 +368,26 @@ impl Inner { fn set_menu(&mut self, window_id: WindowId, cmd: &Command) { if let Some(win) = self.windows.get_mut(window_id) { - match cmd.get_object::>() { - Ok(menu) => win.set_menu(menu.to_owned(), &self.data, &self.env), - Err(e) => log::warn!("set-menu object error: '{}'", e), + match cmd.get(sys_cmd::SET_MENU) { + Ok(menu) => match menu.realize() { + Ok(menu) => win.set_menu(menu.to_owned(), &self.data, &self.env), + Err(e) => log::error!("set_menu: {}", e), + }, + Err(e) => log::error!("set-menu object error: '{}'", e), } } } fn show_context_menu(&mut self, window_id: WindowId, cmd: &Command) { if let Some(win) = self.windows.get_mut(window_id) { - match cmd.get_object::>() { - Ok(ContextMenu { menu, location }) => { - win.show_context_menu(menu.to_owned(), *location, &self.data, &self.env) - } - Err(e) => log::warn!("show-context-menu object error: '{}'", e), + match cmd.get(sys_cmd::SHOW_CONTEXT_MENU) { + Ok(menu) => match menu.realize() { + Ok(ContextMenu { menu, location }) => { + win.show_context_menu(menu.to_owned(), *location, &self.data, &self.env) + } + Err(e) => log::error!("show_context_menu: {}", e), + }, + Err(e) => log::error!("show_context_menu argument error: {}", e), } } } @@ -523,25 +529,31 @@ impl AppState { /// windows) have their logic here; other commands are passed to the window. fn handle_cmd(&mut self, target: Target, cmd: Command) { use Target as T; - match (target, cmd.selector_symbol()) { + match target { // these are handled the same no matter where they come from - (_, sys_cmd::QUIT_APP.symbol()) => self.quit(), - (_, sys_cmd::HIDE_APPLICATION) => self.hide_app(), - (_, sys_cmd::HIDE_OTHERS) => self.hide_others(), - (_, sys_cmd::NEW_WINDOW) => { + _ if cmd.is(sys_cmd::QUIT_APP) => self.quit(), + _ if cmd.is(sys_cmd::HIDE_APPLICATION) => self.hide_app(), + _ if cmd.is(sys_cmd::HIDE_OTHERS) => self.hide_others(), + _ if cmd.is(sys_cmd::NEW_WINDOW) => { if let Err(e) = self.new_window(cmd) { log::error!("failed to create window: '{}'", e); } } - (_, sys_cmd::CLOSE_ALL_WINDOWS) => self.request_close_all_windows(), + _ if cmd.is(sys_cmd::CLOSE_ALL_WINDOWS) => self.request_close_all_windows(), // these should come from a window // FIXME: we need to be able to open a file without a window handle - (T::Window(id), sys_cmd::SHOW_OPEN_PANEL) => self.show_open_panel(cmd, id), - (T::Window(id), sys_cmd::SHOW_SAVE_PANEL) => self.show_save_panel(cmd, id), - (T::Window(id), sys_cmd::CLOSE_WINDOW) => self.request_close_window(cmd, id), - (T::Window(_), sys_cmd::SHOW_WINDOW) => self.show_window(cmd), - (T::Window(id), sys_cmd::PASTE) => self.do_paste(id), - _sel => self.inner.borrow_mut().dispatch_cmd(target, cmd), + T::Window(id) if cmd.is(sys_cmd::SHOW_OPEN_PANEL) => self.show_open_panel(cmd, id), + T::Window(id) if cmd.is(sys_cmd::SHOW_SAVE_PANEL) => self.show_save_panel(cmd, id), + T::Window(id) if cmd.is(sys_cmd::CLOSE_WINDOW) => self.request_close_window(id), + T::Window(id) if cmd.is(sys_cmd::SHOW_WINDOW) => self.show_window(id), + T::Window(id) if cmd.is(sys_cmd::PASTE) => self.do_paste(id), + _ if cmd.is(sys_cmd::CLOSE_WINDOW) => { + log::warn!("CLOSE_WINDOW command must target a window.") + } + _ if cmd.is(sys_cmd::SHOW_WINDOW) => { + log::warn!("SHOW_WINDOW command must target a window.") + } + _ => self.inner.borrow_mut().dispatch_cmd(target, cmd), } } @@ -569,7 +581,7 @@ impl AppState { fn show_save_panel(&mut self, cmd: Command, window_id: WindowId) { let options = cmd - .get_object::() + .get(sys_cmd::SHOW_SAVE_PANEL) .map(|opts| opts.to_owned()) .unwrap_or_default(); let handle = self @@ -580,32 +592,29 @@ impl AppState { .map(|w| w.handle.clone()); let result = handle.and_then(|mut handle| handle.save_as_sync(options)); if let Some(info) = result { - let cmd = Command::new(sys_cmd::SAVE_FILE, info); + let cmd = Command::new(sys_cmd::SAVE_FILE, Some(info)); self.inner.borrow_mut().dispatch_cmd(window_id.into(), cmd); } } fn new_window(&mut self, cmd: Command) -> Result<(), Box> { - let desc = cmd.take_object::>()?; + let desc = cmd.take(sys_cmd::NEW_WINDOW)?; + let desc = desc.realize()?; let window = desc.build_native(self)?; window.show(); Ok(()) } - fn request_close_window(&mut self, cmd: Command, window_id: WindowId) { - let id = cmd.get_object().unwrap_or(&window_id); - self.inner.borrow_mut().request_close_window(*id); + fn request_close_window(&mut self, window_id: WindowId) { + self.inner.borrow_mut().request_close_window(window_id); } fn request_close_all_windows(&mut self) { self.inner.borrow_mut().request_close_all_windows(); } - fn show_window(&mut self, cmd: Command) { - let id: WindowId = *cmd - .get_object() - .expect("show window selector missing window id"); - self.inner.borrow_mut().show_window(id); + fn show_window(&mut self, window_id: WindowId) { + self.inner.borrow_mut().show_window(window_id); } fn do_paste(&mut self, window_id: WindowId) { From 20e19de1ade7ec0bac845be0e1e23e878a917259 Mon Sep 17 00:00:00 2001 From: Leopold Luley Date: Fri, 8 May 2020 21:49:57 +0200 Subject: [PATCH 03/19] Correct 'Save...' menu item on Windows --- druid/src/menu.rs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/druid/src/menu.rs b/druid/src/menu.rs index ee230b854f..b406770e1d 100644 --- a/druid/src/menu.rs +++ b/druid/src/menu.rs @@ -630,11 +630,10 @@ pub mod sys { /// The 'Save...' menu item. /// For windows, this is the same as 'Save'. - /// TODO: find out why this exists. pub fn save_ellipsis() -> MenuItem { MenuItem::new( - LocalizedString::new("common-menu-file-save"), - Command::new(commands::SAVE_FILE, None), + LocalizedString::new("common-menu-file-save-ellipsis"), + Command::new(commands::SHOW_OPEN_PANEL, Default::default()), ) .hotkey(RawMods::Ctrl, "s") } From f77794ae56a78105345c5ab234e5fed18271bf3a Mon Sep 17 00:00:00 2001 From: Leopold Luley Date: Sat, 9 May 2020 12:16:17 +0200 Subject: [PATCH 04/19] Fix clippy warnings. --- druid/src/app.rs | 2 +- druid/src/menu.rs | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/druid/src/app.rs b/druid/src/app.rs index 6dc8715a8f..607e9e14a1 100644 --- a/druid/src/app.rs +++ b/druid/src/app.rs @@ -82,7 +82,7 @@ impl WindowDesc { impl AppStateWindowDesc { pub(crate) fn realize(self) -> Result, AppStateTypeError> { let inner: Result>, _> = self.inner.downcast(); - if let Some(inner) = inner.ok() { + if let Ok(inner) = inner { Ok(*inner) } else { Err(AppStateTypeError::new( diff --git a/druid/src/menu.rs b/druid/src/menu.rs index b406770e1d..3300f10559 100644 --- a/druid/src/menu.rs +++ b/druid/src/menu.rs @@ -148,7 +148,7 @@ impl MenuDesc { impl AppStateMenuDesc { pub(crate) fn realize(&self) -> Result<&MenuDesc, AppStateTypeError> { - self.inner.downcast_ref().ok_or(AppStateTypeError::new( + self.inner.downcast_ref().ok_or_else(|| AppStateTypeError::new( any::type_name::>(), self.type_name, )) @@ -217,7 +217,7 @@ impl ContextMenu { impl AppStateContextMenu { pub(crate) fn realize(&self) -> Result<&ContextMenu, AppStateTypeError> { - self.inner.downcast_ref().ok_or(AppStateTypeError::new( + self.inner.downcast_ref().ok_or_else(|| AppStateTypeError::new( any::type_name::>(), self.type_name, )) From da0ed0821d3a7a4a4e3b470376b44221f46f628a Mon Sep 17 00:00:00 2001 From: Leopold Luley Date: Sat, 9 May 2020 12:44:48 +0200 Subject: [PATCH 05/19] Fix Clone and Copy impl for Selector. --- druid/src/command.rs | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/druid/src/command.rs b/druid/src/command.rs index 8a236089e3..bf681508b2 100644 --- a/druid/src/command.rs +++ b/druid/src/command.rs @@ -31,9 +31,18 @@ pub type SelectorSymbol = &'static str; /// [`druid::commands`] module. /// /// [`druid::commands`]: commands/index.html -#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[derive(Debug, PartialEq, Eq)] pub struct Selector(SelectorSymbol, PhantomData); +// This has do be done explicitly, to avoid the Copy bound on `T`. +// See https://doc.rust-lang.org/std/marker/trait.Copy.html#how-can-i-implement-copy . +impl Copy for Selector {} +impl Clone for Selector { + fn clone(&self) -> Self { + *self + } +} + /// An arbitrary command. /// /// A `Command` consists of a [`Selector`], that indicates what the command is, @@ -415,7 +424,7 @@ mod tests { let sel: Selector> = Selector::new("my-selector"); let objs = vec![0, 1, 2]; // TODO: find out why this now wants a `.clone()` even tho `Selector` implements `Copy`. - let command = Command::new(sel.clone(), objs); + let command = Command::new(sel, objs); assert_eq!(command.get(sel), Ok(&vec![0, 1, 2])); } } From 778d7d9ebb4723d629f2b5fc27bc004a402cbc5f Mon Sep 17 00:00:00 2001 From: Leopold Luley Date: Sat, 9 May 2020 12:45:34 +0200 Subject: [PATCH 06/19] Update examples in comments. --- druid/src/command.rs | 2 +- druid/src/menu.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/druid/src/command.rs b/druid/src/command.rs index bf681508b2..20a08a05df 100644 --- a/druid/src/command.rs +++ b/druid/src/command.rs @@ -68,7 +68,7 @@ impl Clone for Selector { /// let rows = vec![1, 3, 10, 12]; /// let command = Command::new(selector, rows); /// -/// assert_eq!(command.get_object(), Ok(&vec![1, 3, 10, 12])); +/// assert_eq!(command.get(selector), Ok(&vec![1, 3, 10, 12])); /// ``` /// /// [`Command::new`]: #method.new diff --git a/druid/src/menu.rs b/druid/src/menu.rs index 3300f10559..d333d8269e 100644 --- a/druid/src/menu.rs +++ b/druid/src/menu.rs @@ -333,7 +333,7 @@ impl MenuDesc { /// use druid::{Command, LocalizedString, MenuDesc, MenuItem, Selector}; /// /// let num_items: usize = 4; - /// const MENU_COUNT_ACTION: Selector = Selector::new("menu-count-action"); + /// const MENU_COUNT_ACTION: Selector = Selector::new("menu-count-action"); /// /// let my_menu: MenuDesc = MenuDesc::empty() /// .append_iter(|| (0..num_items).map(|i| { From d5553250eccd3d6352a9fc78ee64de63e7d963f0 Mon Sep 17 00:00:00 2001 From: Leopold Luley Date: Sat, 9 May 2020 12:54:23 +0200 Subject: [PATCH 07/19] Run cargo fmt. --- druid/src/menu.rs | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/druid/src/menu.rs b/druid/src/menu.rs index d333d8269e..d27c4a2ff6 100644 --- a/druid/src/menu.rs +++ b/druid/src/menu.rs @@ -148,10 +148,9 @@ impl MenuDesc { impl AppStateMenuDesc { pub(crate) fn realize(&self) -> Result<&MenuDesc, AppStateTypeError> { - self.inner.downcast_ref().ok_or_else(|| AppStateTypeError::new( - any::type_name::>(), - self.type_name, - )) + self.inner + .downcast_ref() + .ok_or_else(|| AppStateTypeError::new(any::type_name::>(), self.type_name)) } } @@ -217,10 +216,9 @@ impl ContextMenu { impl AppStateContextMenu { pub(crate) fn realize(&self) -> Result<&ContextMenu, AppStateTypeError> { - self.inner.downcast_ref().ok_or_else(|| AppStateTypeError::new( - any::type_name::>(), - self.type_name, - )) + self.inner.downcast_ref().ok_or_else(|| { + AppStateTypeError::new(any::type_name::>(), self.type_name) + }) } } From 5b60f3dcbaf5b10c66ef8a6f2f8ba647117e0006 Mon Sep 17 00:00:00 2001 From: Leopold Luley Date: Sat, 9 May 2020 13:00:30 +0200 Subject: [PATCH 08/19] Fix another doc example error. --- druid/src/menu.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/druid/src/menu.rs b/druid/src/menu.rs index d27c4a2ff6..4da962c7ec 100644 --- a/druid/src/menu.rs +++ b/druid/src/menu.rs @@ -331,9 +331,9 @@ impl MenuDesc { /// use druid::{Command, LocalizedString, MenuDesc, MenuItem, Selector}; /// /// let num_items: usize = 4; - /// const MENU_COUNT_ACTION: Selector = Selector::new("menu-count-action"); + /// const MENU_COUNT_ACTION: Selector = Selector::new("menu-count-action"); /// - /// let my_menu: MenuDesc = MenuDesc::empty() + /// let my_menu: MenuDesc = MenuDesc::empty() /// .append_iter(|| (0..num_items).map(|i| { /// MenuItem::new( /// LocalizedString::new("hello-counter").with_arg("count", move |_, _| i.into()), From 13d4ae09a3eb6040ed673dcaf3a61774bdcd89b3 Mon Sep 17 00:00:00 2001 From: Leopold Luley Date: Sat, 9 May 2020 13:48:17 +0200 Subject: [PATCH 09/19] Fix trivial copy copy pass by ref. --- druid/src/command.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/druid/src/command.rs b/druid/src/command.rs index 20a08a05df..b64ba5c997 100644 --- a/druid/src/command.rs +++ b/druid/src/command.rs @@ -271,7 +271,7 @@ impl Selector { Selector(s, PhantomData) } - pub(crate) const fn symbol(&self) -> SelectorSymbol { + pub(crate) const fn symbol(self) -> SelectorSymbol { self.0 } } From c2a55a21c1f6f8148a9aa093b2ce6c68a01ec93d Mon Sep 17 00:00:00 2001 From: Leopold Luley Date: Sat, 9 May 2020 14:25:25 +0200 Subject: [PATCH 10/19] Change Selector to honor PhantomData best practice. --- druid/src/command.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/druid/src/command.rs b/druid/src/command.rs index b64ba5c997..7fe9500d42 100644 --- a/druid/src/command.rs +++ b/druid/src/command.rs @@ -32,7 +32,7 @@ pub type SelectorSymbol = &'static str; /// /// [`druid::commands`]: commands/index.html #[derive(Debug, PartialEq, Eq)] -pub struct Selector(SelectorSymbol, PhantomData); +pub struct Selector(SelectorSymbol, PhantomData<*const T>); // This has do be done explicitly, to avoid the Copy bound on `T`. // See https://doc.rust-lang.org/std/marker/trait.Copy.html#how-can-i-implement-copy . From 9cdb7d8c6ebc8e3b56ae8706ea7c47aa18846a71 Mon Sep 17 00:00:00 2001 From: Leopold Luley Date: Sat, 9 May 2020 21:51:50 +0200 Subject: [PATCH 11/19] More doc leanups. --- druid/src/command.rs | 29 +++++++++++++++-------------- druid/src/menu.rs | 1 - 2 files changed, 15 insertions(+), 15 deletions(-) diff --git a/druid/src/command.rs b/druid/src/command.rs index 7fe9500d42..600a3ecf5d 100644 --- a/druid/src/command.rs +++ b/druid/src/command.rs @@ -22,6 +22,7 @@ use std::{ use crate::{WidgetId, WindowId}; +/// An untyped identifier for a `Selector`. pub type SelectorSymbol = &'static str; /// An identifier for a particular command. @@ -99,6 +100,9 @@ pub enum ArgumentError { Consumed, } +/// This error can occure when wrongly promising that a type ereased +/// variant of some generic item represents the application state. +/// Examples are `MenuDesc` and `AppStateMenuDesc`. #[derive(Debug, Clone, Copy, PartialEq)] pub struct AppStateTypeError { expected: &'static str, @@ -106,7 +110,7 @@ pub struct AppStateTypeError { } impl AppStateTypeError { - pub fn new(expected: &'static str, found: &'static str) -> Self { + pub(crate) fn new(expected: &'static str, found: &'static str) -> Self { Self { expected, found } } } @@ -177,15 +181,15 @@ pub mod sys { /// will automatically target the window containing the widget. pub const SHOW_WINDOW: Selector<()> = Selector::new("druid-builtin.show-window"); - /// Display a context (right-click) menu. The argument must be the [`ContextMenu`]. - /// object to be displayed. + /// Display a context (right-click) menu. + /// An `AppStateContextMenu` can be obtained using `ContextMenu::into_app_state_context_menu`. /// /// [`ContextMenu`]: ../struct.ContextMenu.html pub const SHOW_CONTEXT_MENU: Selector = Selector::new("druid-builtin.show-context-menu"); - /// The selector for a command to set the window's menu. The argument should - /// be a [`MenuDesc`] object. + /// The selector for a command to set the window's menu. + /// An `AppStateMenuDesc` can be obtained using `MenuDesc::into_app_state_menu_desc`. /// /// [`MenuDesc`]: ../struct.MenuDesc.html pub const SET_MENU: Selector = Selector::new("druid-builtin.set-menu"); @@ -205,32 +209,29 @@ pub mod sys { /// System command. A file picker dialog will be shown to the user, and an /// [`OPEN_FILE`] command will be sent if a file is chosen. /// - /// The argument should be a [`FileDialogOptions`] struct. - /// /// [`OPEN_FILE`]: constant.OPEN_FILE.html /// [`FileDialogOptions`]: ../struct.FileDialogOptions.html pub const SHOW_OPEN_PANEL: Selector = Selector::new("druid-builtin.menu-file-open"); - /// Open a file. - /// - /// The argument must be a [`FileInfo`] object for the file to be opened. + /// Commands to open a file, must be handled by the application. /// /// [`FileInfo`]: ../struct.FileInfo.html pub const OPEN_FILE: Selector = Selector::new("druid-builtin.open-file-path"); - /// Special command. When issued, the system will show the 'save as' panel, + /// Special command. When issued by the application, the system will show the 'save as' panel, /// and if a path is selected the system will issue a [`SAVE_FILE`] command /// with the selected path as the argument. /// - /// The argument should be a [`FileDialogOptions`] object. - /// /// [`SAVE_FILE`]: constant.SAVE_FILE.html /// [`FileDialogOptions`]: ../struct.FileDialogOptions.html pub const SHOW_SAVE_PANEL: Selector = Selector::new("druid-builtin.menu-file-save-as"); - /// Save the current file. + /// Commands to save a file, must be handled by the application. + /// + /// If it carries `Some`, then the application should save to that file and store the `FileInfo` for future use. + /// If it carries `None`, the appliaction should have recieved `Some` before and use the stored `FileInfo`. /// /// The argument, if present, should be the path where the file should be saved. pub const SAVE_FILE: Selector> = Selector::new("druid-builtin.menu-file-save"); diff --git a/druid/src/menu.rs b/druid/src/menu.rs index 4da962c7ec..8ba6fde952 100644 --- a/druid/src/menu.rs +++ b/druid/src/menu.rs @@ -627,7 +627,6 @@ pub mod sys { } /// The 'Save...' menu item. - /// For windows, this is the same as 'Save'. pub fn save_ellipsis() -> MenuItem { MenuItem::new( LocalizedString::new("common-menu-file-save-ellipsis"), From 9123bb54871850f7cfa01ebdadd31b57fb25118b Mon Sep 17 00:00:00 2001 From: Leopold Luley Date: Sat, 9 May 2020 21:52:18 +0200 Subject: [PATCH 12/19] Replace some matches with simpler ifs. --- druid/examples/blocking_function.rs | 21 ++++++++------------- druid/src/win_handler.rs | 11 +++++------ 2 files changed, 13 insertions(+), 19 deletions(-) diff --git a/druid/examples/blocking_function.rs b/druid/examples/blocking_function.rs index af4ccde982..ddb5854359 100644 --- a/druid/examples/blocking_function.rs +++ b/druid/examples/blocking_function.rs @@ -61,20 +61,15 @@ impl AppDelegate for Delegate { data: &mut AppState, _env: &Env, ) -> bool { - match () { - _ if cmd.is(START_SLOW_FUNCTION) => { - data.processing = true; - wrapped_slow_function(self.eventsink.clone(), data.value); - true - } - _ if cmd.is(FINISH_SLOW_FUNCTION) => { - data.processing = false; - let number = cmd.get(FINISH_SLOW_FUNCTION).unwrap(); - data.value = *number; - true - } - _ => true, + if cmd.is(START_SLOW_FUNCTION) { + data.processing = true; + wrapped_slow_function(self.eventsink.clone(), data.value); } + if let Ok(number) = cmd.get(FINISH_SLOW_FUNCTION) { + data.processing = false; + data.value = *number; + } + true } } diff --git a/druid/src/win_handler.rs b/druid/src/win_handler.rs index e8abbab656..86eef2658a 100644 --- a/druid/src/win_handler.rs +++ b/druid/src/win_handler.rs @@ -311,12 +311,11 @@ impl Inner { match target { Target::Window(id) => { // first handle special window-level events - match () { - _ if cmd.is(sys_cmd::SET_MENU) => return self.set_menu(id, &cmd), - _ if cmd.is(sys_cmd::SHOW_CONTEXT_MENU) => { - return self.show_context_menu(id, &cmd) - } - _ => (), + if cmd.is(sys_cmd::SET_MENU) { + return self.set_menu(id, &cmd); + } + if cmd.is(sys_cmd::SHOW_CONTEXT_MENU) { + return self.show_context_menu(id, &cmd); } if let Some(w) = self.windows.get_mut(id) { let event = Event::Command(cmd); From 9405582847619ae89076edea9b16f170f1358f6b Mon Sep 17 00:00:00 2001 From: Leopold Luley Date: Sat, 9 May 2020 22:01:53 +0200 Subject: [PATCH 13/19] Go back to Any bounds instead of static. --- druid/src/app.rs | 4 ++-- druid/src/command.rs | 8 ++++---- druid/src/ext_event.rs | 2 +- druid/src/menu.rs | 8 ++++---- 4 files changed, 11 insertions(+), 11 deletions(-) diff --git a/druid/src/app.rs b/druid/src/app.rs index 607e9e14a1..4ca266ac38 100644 --- a/druid/src/app.rs +++ b/druid/src/app.rs @@ -66,7 +66,7 @@ pub struct AppStateWindowDesc { type_name: &'static str, } -impl WindowDesc { +impl WindowDesc { /// This turns a typed `WindowDesc` into an untyped `AppStateWindowDesc`. /// Doing so allows sending `WindowDesc` through `Command`s. /// It is up to you, to ensure that this `T` represents your application @@ -80,7 +80,7 @@ impl WindowDesc { } impl AppStateWindowDesc { - pub(crate) fn realize(self) -> Result, AppStateTypeError> { + pub(crate) fn realize(self) -> Result, AppStateTypeError> { let inner: Result>, _> = self.inner.downcast(); if let Ok(inner) = inner { Ok(*inner) diff --git a/druid/src/command.rs b/druid/src/command.rs index 600a3ecf5d..58b2da3a70 100644 --- a/druid/src/command.rs +++ b/druid/src/command.rs @@ -280,7 +280,7 @@ impl Selector { impl Command { /// Create a new `Command` with an argument. If you do not need /// an argument, `Selector` implements `Into`. - pub fn new(selector: Selector, arg: T) -> Self { + pub fn new(selector: Selector, arg: T) -> Self { Command { selector: selector.symbol(), object: Arg::Reusable(Arc::new(arg)), @@ -294,7 +294,7 @@ impl Command { /// [`take_object`]. /// /// [`take_object`]: #method.take_object - pub fn one_shot(selector: Selector, arg: T) -> Self { + pub fn one_shot(selector: Selector, arg: T) -> Self { Command { selector: selector.symbol(), object: Arg::OneShot(Arc::new(Mutex::new(Some(Box::new(arg))))), @@ -318,7 +318,7 @@ impl Command { /// created with [`one_shot`]. /// /// [`one_shot`]: #method.one_shot - pub fn get(&self, selector: Selector) -> Result<&T, ArgumentError> { + pub fn get(&self, selector: Selector) -> Result<&T, ArgumentError> { if self.selector != selector.symbol() { return Err(ArgumentError::WrongSelector); } @@ -331,7 +331,7 @@ impl Command { /// Attempt to take the object of a [`one-shot`] command. /// /// [`one-shot`]: #method.one_shot - pub fn take(&self, selector: Selector) -> Result, ArgumentError> { + pub fn take(&self, selector: Selector) -> Result, ArgumentError> { if self.selector != selector.symbol() { return Err(ArgumentError::WrongSelector); } diff --git a/druid/src/ext_event.rs b/druid/src/ext_event.rs index 17d396da65..844fe39ca1 100644 --- a/druid/src/ext_event.rs +++ b/druid/src/ext_event.rs @@ -101,7 +101,7 @@ impl ExtEventSink { /// /// [`Command`]: struct.Command.html /// [`Selector`]: struct.Selector.html - pub fn submit_command( + pub fn submit_command( &self, sel: Selector, obj: T, diff --git a/druid/src/menu.rs b/druid/src/menu.rs index 8ba6fde952..144a638ac5 100644 --- a/druid/src/menu.rs +++ b/druid/src/menu.rs @@ -133,7 +133,7 @@ pub struct AppStateMenuDesc { type_name: &'static str, } -impl MenuDesc { +impl MenuDesc { /// This turns a typed `MenuDesc` into an untyped `AppStateMenuDesc`. /// Doing so allows sending `MenuDesc` through `Command`s. /// It is up to you, to ensure that this `T` represents your application @@ -147,7 +147,7 @@ impl MenuDesc { } impl AppStateMenuDesc { - pub(crate) fn realize(&self) -> Result<&MenuDesc, AppStateTypeError> { + pub(crate) fn realize(&self) -> Result<&MenuDesc, AppStateTypeError> { self.inner .downcast_ref() .ok_or_else(|| AppStateTypeError::new(any::type_name::>(), self.type_name)) @@ -201,7 +201,7 @@ pub struct AppStateContextMenu { type_name: &'static str, } -impl ContextMenu { +impl ContextMenu { /// This turns a typed `ContextMenu` into an untyped `AppStateContextMenu`. /// Doing so allows sending `ContextMenu` through `Command`s. /// It is up to you, to ensure that this `T` represents your application @@ -215,7 +215,7 @@ impl ContextMenu { } impl AppStateContextMenu { - pub(crate) fn realize(&self) -> Result<&ContextMenu, AppStateTypeError> { + pub(crate) fn realize(&self) -> Result<&ContextMenu, AppStateTypeError> { self.inner.downcast_ref().ok_or_else(|| { AppStateTypeError::new(any::type_name::>(), self.type_name) }) From b6f56011ba0752e72a9258689b917167fb463609 Mon Sep 17 00:00:00 2001 From: Leopold Luley Date: Mon, 11 May 2020 16:35:35 +0200 Subject: [PATCH 14/19] Introduce OneShotSelector. --- druid/examples/blocking_function.rs | 2 +- druid/examples/identity.rs | 2 +- druid/examples/multiwin.rs | 6 +- druid/src/command.rs | 107 ++++++++++++++++++++-------- druid/src/ext_event.rs | 5 +- druid/src/win_handler.rs | 14 ++-- 6 files changed, 92 insertions(+), 44 deletions(-) diff --git a/druid/examples/blocking_function.rs b/druid/examples/blocking_function.rs index ddb5854359..2700de6fd4 100644 --- a/druid/examples/blocking_function.rs +++ b/druid/examples/blocking_function.rs @@ -65,7 +65,7 @@ impl AppDelegate for Delegate { data.processing = true; wrapped_slow_function(self.eventsink.clone(), data.value); } - if let Ok(number) = cmd.get(FINISH_SLOW_FUNCTION) { + if let Some(number) = cmd.get(FINISH_SLOW_FUNCTION) { data.processing = false; data.value = *number; } diff --git a/druid/examples/identity.rs b/druid/examples/identity.rs index dfdabb9005..5c0679e962 100644 --- a/druid/examples/identity.rs +++ b/druid/examples/identity.rs @@ -115,7 +115,7 @@ impl Widget for ColorWell { } Event::Command(cmd) if cmd.is(FREEZE_COLOR) => { - self.frozen = cmd.get(FREEZE_COLOR).ok().cloned(); + self.frozen = cmd.get(FREEZE_COLOR).cloned(); } Event::Command(cmd) if cmd.is(UNFREEZE_COLOR) => self.frozen = None, _ => (), diff --git a/druid/examples/multiwin.rs b/druid/examples/multiwin.rs index b9f6c7e5b9..63395aaae3 100644 --- a/druid/examples/multiwin.rs +++ b/druid/examples/multiwin.rs @@ -187,7 +187,7 @@ impl AppDelegate for Delegate { Target::Window(id) if cmd.is(MENU_COUNT_ACTION) => { data.selected = *cmd.get(MENU_COUNT_ACTION).unwrap(); let menu = make_menu::(data); - let cmd = Command::new(druid::commands::SET_MENU, menu.into_app_state_menu_desc()); + let cmd = Command::new(sys_cmds::SET_MENU, menu.into_app_state_menu_desc()); ctx.submit_command(cmd, id); false } @@ -196,14 +196,14 @@ impl AppDelegate for Delegate { Target::Window(id) if cmd.is(MENU_INCREMENT_ACTION) => { data.menu_count += 1; let menu = make_menu::(data); - let cmd = Command::new(druid::commands::SET_MENU, menu.into_app_state_menu_desc()); + let cmd = Command::new(sys_cmds::SET_MENU, menu.into_app_state_menu_desc()); ctx.submit_command(cmd, id); false } Target::Window(id) if cmd.is(MENU_DECREMENT_ACTION) => { data.menu_count = data.menu_count.saturating_sub(1); let menu = make_menu::(data); - let cmd = Command::new(druid::commands::SET_MENU, menu.into_app_state_menu_desc()); + let cmd = Command::new(sys_cmds::SET_MENU, menu.into_app_state_menu_desc()); ctx.submit_command(cmd, id); false } diff --git a/druid/src/command.rs b/druid/src/command.rs index 58b2da3a70..522422c87a 100644 --- a/druid/src/command.rs +++ b/druid/src/command.rs @@ -44,6 +44,41 @@ impl Clone for Selector { } } +/// An identifier for a particular command. +/// +/// This should be a unique string identifier. Certain `Selector`s are defined +/// by druid, and have special meaning to the framework; these are listed in the +/// [`druid::commands`] module. +/// +/// [`druid::commands`]: commands/index.html +#[derive(Debug, PartialEq, Eq)] +pub struct OneShotSelector(SelectorSymbol, PhantomData<*const T>); + +// This has do be done explicitly, to avoid the Copy bound on `T`. +// See https://doc.rust-lang.org/std/marker/trait.Copy.html#how-can-i-implement-copy . +impl Copy for OneShotSelector {} +impl Clone for OneShotSelector { + fn clone(&self) -> Self { + *self + } +} + +pub trait AnySelector { + fn symbol(self) -> SelectorSymbol; +} + +impl AnySelector for Selector { + fn symbol(self) -> SelectorSymbol { + self.0 + } +} + +impl AnySelector for OneShotSelector { + fn symbol(self) -> SelectorSymbol { + self.0 + } +} + /// An arbitrary command. /// /// A `Command` consists of a [`Selector`], that indicates what the command is, @@ -69,7 +104,7 @@ impl Clone for Selector { /// let rows = vec![1, 3, 10, 12]; /// let command = Command::new(selector, rows); /// -/// assert_eq!(command.get(selector), Ok(&vec![1, 3, 10, 12])); +/// assert_eq!(command.get(selector), Some(&vec![1, 3, 10, 12])); /// ``` /// /// [`Command::new`]: #method.new @@ -87,15 +122,11 @@ enum Arg { OneShot(Arc>>>), } -/// Errors that can occur when attempting to retrieve the a command's argument. +/// Errors that can occur when attempting to retrieve the a `OneShotCommand`s argument. #[derive(Debug, Clone, PartialEq)] pub enum ArgumentError { /// The command represented a different selector. WrongSelector, - /// The argument was expected to be reusable and wasn't, or vice-versa. - WrongVariant, - /// The argument could not be downcast to the specified type. - IncorrectType, /// The one-shot argument has already been taken. Consumed, } @@ -145,7 +176,7 @@ pub enum Target { /// /// [`Command`]: ../struct.Command.html pub mod sys { - use super::Selector; + use super::{OneShotSelector, Selector}; use crate::{ app::AppStateWindowDesc, menu::{AppStateContextMenu, AppStateMenuDesc}, @@ -162,7 +193,8 @@ pub mod sys { pub const HIDE_OTHERS: Selector<()> = Selector::new("druid-builtin.menu-hide-others"); /// The selector for a command to create a new window. - pub const NEW_WINDOW: Selector = Selector::new("druid-builtin.new-window"); + pub const NEW_WINDOW: OneShotSelector = + OneShotSelector::new("druid-builtin.new-window"); /// The selector for a command to close a window. /// @@ -263,17 +295,25 @@ pub mod sys { impl Selector { /// A selector that does nothing. - pub const fn noop() -> Selector { + pub const fn noop() -> Self { Selector::new("") } /// Create a new `Selector` with the given string. - pub const fn new(s: &'static str) -> Selector { + pub const fn new(s: &'static str) -> Self { Selector(s, PhantomData) } +} - pub(crate) const fn symbol(self) -> SelectorSymbol { - self.0 +impl OneShotSelector { + /// A selector that does nothing. + pub const fn noop() -> Self { + OneShotSelector::new("") + } + + /// Create a new `Selector` with the given string. + pub const fn new(s: &'static str) -> Self { + OneShotSelector(s, PhantomData) } } @@ -294,7 +334,7 @@ impl Command { /// [`take_object`]. /// /// [`take_object`]: #method.take_object - pub fn one_shot(selector: Selector, arg: T) -> Self { + pub fn one_shot(selector: OneShotSelector, arg: T) -> Self { Command { selector: selector.symbol(), object: Arg::OneShot(Arc::new(Mutex::new(Some(Box::new(arg))))), @@ -308,7 +348,7 @@ impl Command { Command { selector, object } } - pub fn is(&self, selector: Selector) -> bool { + pub fn is(&self, selector: impl AnySelector) -> bool { self.selector == selector.symbol() } @@ -318,25 +358,28 @@ impl Command { /// created with [`one_shot`]. /// /// [`one_shot`]: #method.one_shot - pub fn get(&self, selector: Selector) -> Result<&T, ArgumentError> { + pub fn get(&self, selector: Selector) -> Option<&T> { if self.selector != selector.symbol() { - return Err(ArgumentError::WrongSelector); + return None; } match &self.object { - Arg::Reusable(obj) => obj.downcast_ref().ok_or(ArgumentError::IncorrectType), - Arg::OneShot(_) => Err(ArgumentError::WrongVariant), + Arg::Reusable(obj) => Some( + obj.downcast_ref() + .expect("Reusable command had wrong payload type."), + ), + Arg::OneShot(_) => panic!("Reusable command {} carried OneShot argument.", selector), } } /// Attempt to take the object of a [`one-shot`] command. /// /// [`one-shot`]: #method.one_shot - pub fn take(&self, selector: Selector) -> Result, ArgumentError> { + pub fn take(&self, selector: OneShotSelector) -> Result, ArgumentError> { if self.selector != selector.symbol() { return Err(ArgumentError::WrongSelector); } match &self.object { - Arg::Reusable(_) => Err(ArgumentError::WrongVariant), + Arg::Reusable(_) => panic!("OneShot command {} carried Reusable argument.", selector), Arg::OneShot(inner) => { let obj = inner .lock() @@ -345,9 +388,8 @@ impl Command { .ok_or(ArgumentError::Consumed)?; match obj.downcast::() { Ok(obj) => Ok(obj), - Err(obj) => { - inner.lock().unwrap().replace(obj); - Err(ArgumentError::IncorrectType) + Err(_) => { + panic!("OneShot command had wrong payload type."); } } } @@ -375,17 +417,22 @@ impl std::fmt::Display for Selector { } } +impl std::fmt::Display for OneShotSelector { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + write!( + f, + "OneShotSelector(\"{}\", {})", + self.0, + std::any::type_name::() + ) + } +} + impl std::fmt::Display for ArgumentError { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { match self { ArgumentError::WrongSelector => write!(f, "Command had wrong selector"), - ArgumentError::IncorrectType => write!(f, "Downcast failed: wrong concrete type"), ArgumentError::Consumed => write!(f, "One-shot command arguemnt already consumed"), - ArgumentError::WrongVariant => write!( - f, - "Incorrect access method for argument type; \ - check Command::one_shot docs for more detail." - ), } } } @@ -426,6 +473,6 @@ mod tests { let objs = vec![0, 1, 2]; // TODO: find out why this now wants a `.clone()` even tho `Selector` implements `Copy`. let command = Command::new(sel, objs); - assert_eq!(command.get(sel), Ok(&vec![0, 1, 2])); + assert_eq!(command.get(sel), Some(&vec![0, 1, 2])); } } diff --git a/druid/src/ext_event.rs b/druid/src/ext_event.rs index 844fe39ca1..943f5bcb8e 100644 --- a/druid/src/ext_event.rs +++ b/druid/src/ext_event.rs @@ -20,7 +20,10 @@ use std::sync::{Arc, Mutex}; use crate::shell::IdleHandle; use crate::win_handler::EXT_EVENT_IDLE_TOKEN; -use crate::{command::SelectorSymbol, Command, Selector, Target, WindowId}; +use crate::{ + command::{AnySelector, SelectorSymbol}, + Command, Selector, Target, WindowId, +}; pub(crate) type ExtCommand = (SelectorSymbol, Box, Option); diff --git a/druid/src/win_handler.rs b/druid/src/win_handler.rs index 86eef2658a..6d5881871d 100644 --- a/druid/src/win_handler.rs +++ b/druid/src/win_handler.rs @@ -367,26 +367,24 @@ impl Inner { fn set_menu(&mut self, window_id: WindowId, cmd: &Command) { if let Some(win) = self.windows.get_mut(window_id) { - match cmd.get(sys_cmd::SET_MENU) { - Ok(menu) => match menu.realize() { + if let Some(menu) = cmd.get(sys_cmd::SET_MENU) { + match menu.realize() { Ok(menu) => win.set_menu(menu.to_owned(), &self.data, &self.env), Err(e) => log::error!("set_menu: {}", e), - }, - Err(e) => log::error!("set-menu object error: '{}'", e), + } } } } fn show_context_menu(&mut self, window_id: WindowId, cmd: &Command) { if let Some(win) = self.windows.get_mut(window_id) { - match cmd.get(sys_cmd::SHOW_CONTEXT_MENU) { - Ok(menu) => match menu.realize() { + if let Some(menu) = cmd.get(sys_cmd::SHOW_CONTEXT_MENU) { + match menu.realize() { Ok(ContextMenu { menu, location }) => { win.show_context_menu(menu.to_owned(), *location, &self.data, &self.env) } Err(e) => log::error!("show_context_menu: {}", e), - }, - Err(e) => log::error!("show_context_menu argument error: {}", e), + } } } } From a42d880844d04b411a2608e8a6c7c18bcebeea69 Mon Sep 17 00:00:00 2001 From: Leopold Luley Date: Mon, 11 May 2020 16:56:37 +0200 Subject: [PATCH 15/19] Silence pointless clippy warning. --- druid/src/command.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/druid/src/command.rs b/druid/src/command.rs index 522422c87a..15825b0be9 100644 --- a/druid/src/command.rs +++ b/druid/src/command.rs @@ -386,6 +386,7 @@ impl Command { .unwrap() .take() .ok_or(ArgumentError::Consumed)?; + #[allow(clippy::match_wild_err_arm)] match obj.downcast::() { Ok(obj) => Ok(obj), Err(_) => { From 65ca89bad4fb74ba7e75b09b0253eb68b9120204 Mon Sep 17 00:00:00 2001 From: Leopold Luley Date: Tue, 12 May 2020 10:12:50 +0200 Subject: [PATCH 16/19] Default to Selector<()>. --- docs/book_examples/src/custom_widgets_md.rs | 2 +- druid/examples/identity.rs | 2 +- druid/examples/multiwin.rs | 6 ++-- druid/src/command.rs | 38 ++++++++++----------- druid/src/tests/helpers.rs | 2 +- druid/src/tests/mod.rs | 2 +- druid/src/widget/textbox.rs | 2 +- 7 files changed, 27 insertions(+), 27 deletions(-) diff --git a/docs/book_examples/src/custom_widgets_md.rs b/docs/book_examples/src/custom_widgets_md.rs index 9740936753..2ad864822a 100644 --- a/docs/book_examples/src/custom_widgets_md.rs +++ b/docs/book_examples/src/custom_widgets_md.rs @@ -36,7 +36,7 @@ fn background_label() -> impl Widget { // ANCHOR_END: background_label // ANCHOR: annoying_textbox -const ACTION: Selector<()> = Selector::new("hello.textbox-action"); +const ACTION: Selector = Selector::new("hello.textbox-action"); const DELAY: Duration = Duration::from_millis(300); struct TextBoxActionController { diff --git a/druid/examples/identity.rs b/druid/examples/identity.rs index 5c0679e962..7c080b3344 100644 --- a/druid/examples/identity.rs +++ b/druid/examples/identity.rs @@ -41,7 +41,7 @@ use druid::{ const CYCLE_DURATION: Duration = Duration::from_millis(100); const FREEZE_COLOR: Selector = Selector::new("identity-example.freeze-color"); -const UNFREEZE_COLOR: Selector<()> = Selector::new("identity-example.unfreeze-color"); +const UNFREEZE_COLOR: Selector = Selector::new("identity-example.unfreeze-color"); /// Honestly: it's just a color in fancy clothing. #[derive(Debug, Clone, Data, Lens)] diff --git a/druid/examples/multiwin.rs b/druid/examples/multiwin.rs index 63395aaae3..dda2b01be6 100644 --- a/druid/examples/multiwin.rs +++ b/druid/examples/multiwin.rs @@ -24,9 +24,9 @@ use druid::{ use log::info; const MENU_COUNT_ACTION: Selector = Selector::new("menu-count-action"); -const MENU_INCREMENT_ACTION: Selector<()> = Selector::new("menu-increment-action"); -const MENU_DECREMENT_ACTION: Selector<()> = Selector::new("menu-decrement-action"); -const MENU_SWITCH_GLOW_ACTION: Selector<()> = Selector::new("menu-switch-glow"); +const MENU_INCREMENT_ACTION: Selector = Selector::new("menu-increment-action"); +const MENU_DECREMENT_ACTION: Selector = Selector::new("menu-decrement-action"); +const MENU_SWITCH_GLOW_ACTION: Selector = Selector::new("menu-switch-glow"); #[derive(Debug, Clone, Default, Data)] struct State { diff --git a/druid/src/command.rs b/druid/src/command.rs index 15825b0be9..ef0aa69d19 100644 --- a/druid/src/command.rs +++ b/druid/src/command.rs @@ -33,7 +33,7 @@ pub type SelectorSymbol = &'static str; /// /// [`druid::commands`]: commands/index.html #[derive(Debug, PartialEq, Eq)] -pub struct Selector(SelectorSymbol, PhantomData<*const T>); +pub struct Selector(SelectorSymbol, PhantomData<*const T>); // This has do be done explicitly, to avoid the Copy bound on `T`. // See https://doc.rust-lang.org/std/marker/trait.Copy.html#how-can-i-implement-copy . @@ -184,13 +184,13 @@ pub mod sys { }; /// Quit the running application. This command is handled by the druid library. - pub const QUIT_APP: Selector<()> = Selector::new("druid-builtin.quit-app"); + pub const QUIT_APP: Selector = Selector::new("druid-builtin.quit-app"); /// Hide the application. (mac only?) - pub const HIDE_APPLICATION: Selector<()> = Selector::new("druid-builtin.menu-hide-application"); + pub const HIDE_APPLICATION: Selector = Selector::new("druid-builtin.menu-hide-application"); /// Hide all other applications. (mac only?) - pub const HIDE_OTHERS: Selector<()> = Selector::new("druid-builtin.menu-hide-others"); + pub const HIDE_OTHERS: Selector = Selector::new("druid-builtin.menu-hide-others"); /// The selector for a command to create a new window. pub const NEW_WINDOW: OneShotSelector = @@ -201,17 +201,17 @@ pub mod sys { /// The command must target a specific window. /// When calling `submit_command` on a `Widget`s context, passing `None` as target /// will automatically target the window containing the widget. - pub const CLOSE_WINDOW: Selector<()> = Selector::new("druid-builtin.close-window"); + pub const CLOSE_WINDOW: Selector = Selector::new("druid-builtin.close-window"); /// Close all windows. - pub const CLOSE_ALL_WINDOWS: Selector<()> = Selector::new("druid-builtin.close-all-windows"); + pub const CLOSE_ALL_WINDOWS: Selector = Selector::new("druid-builtin.close-all-windows"); /// The selector for a command to bring a window to the front, and give it focus. /// /// The command must target a specific window. /// When calling `submit_command` on a `Widget`s context, passing `None` as target /// will automatically target the window containing the widget. - pub const SHOW_WINDOW: Selector<()> = Selector::new("druid-builtin.show-window"); + pub const SHOW_WINDOW: Selector = Selector::new("druid-builtin.show-window"); /// Display a context (right-click) menu. /// An `AppStateContextMenu` can be obtained using `ContextMenu::into_app_state_context_menu`. @@ -227,16 +227,16 @@ pub mod sys { pub const SET_MENU: Selector = Selector::new("druid-builtin.set-menu"); /// Show the application preferences. - pub const SHOW_PREFERENCES: Selector<()> = Selector::new("druid-builtin.menu-show-preferences"); + pub const SHOW_PREFERENCES: Selector = Selector::new("druid-builtin.menu-show-preferences"); /// Show the application about window. - pub const SHOW_ABOUT: Selector<()> = Selector::new("druid-builtin.menu-show-about"); + pub const SHOW_ABOUT: Selector = Selector::new("druid-builtin.menu-show-about"); /// Show all applications. - pub const SHOW_ALL: Selector<()> = Selector::new("druid-builtin.menu-show-all"); + pub const SHOW_ALL: Selector = Selector::new("druid-builtin.menu-show-all"); /// Show the new file dialog. - pub const NEW_FILE: Selector<()> = Selector::new("druid-builtin.menu-file-new"); + pub const NEW_FILE: Selector = Selector::new("druid-builtin.menu-file-new"); /// System command. A file picker dialog will be shown to the user, and an /// [`OPEN_FILE`] command will be sent if a file is chosen. @@ -269,28 +269,28 @@ pub mod sys { pub const SAVE_FILE: Selector> = Selector::new("druid-builtin.menu-file-save"); /// Show the print-setup window. - pub const PRINT_SETUP: Selector<()> = Selector::new("druid-builtin.menu-file-print-setup"); + pub const PRINT_SETUP: Selector = Selector::new("druid-builtin.menu-file-print-setup"); /// Show the print dialog. - pub const PRINT: Selector<()> = Selector::new("druid-builtin.menu-file-print"); + pub const PRINT: Selector = Selector::new("druid-builtin.menu-file-print"); /// Show the print preview. - pub const PRINT_PREVIEW: Selector<()> = Selector::new("druid-builtin.menu-file-print"); + pub const PRINT_PREVIEW: Selector = Selector::new("druid-builtin.menu-file-print"); /// Cut the current selection. - pub const CUT: Selector<()> = Selector::new("druid-builtin.menu-cut"); + pub const CUT: Selector = Selector::new("druid-builtin.menu-cut"); /// Copy the current selection. - pub const COPY: Selector<()> = Selector::new("druid-builtin.menu-copy"); + pub const COPY: Selector = Selector::new("druid-builtin.menu-copy"); /// Paste. - pub const PASTE: Selector<()> = Selector::new("druid-builtin.menu-paste"); + pub const PASTE: Selector = Selector::new("druid-builtin.menu-paste"); /// Undo. - pub const UNDO: Selector<()> = Selector::new("druid-builtin.menu-undo"); + pub const UNDO: Selector = Selector::new("druid-builtin.menu-undo"); /// Redo. - pub const REDO: Selector<()> = Selector::new("druid-builtin.menu-redo"); + pub const REDO: Selector = Selector::new("druid-builtin.menu-redo"); } impl Selector { diff --git a/druid/src/tests/helpers.rs b/druid/src/tests/helpers.rs index 4d933c4d62..b3ef3899e8 100644 --- a/druid/src/tests/helpers.rs +++ b/druid/src/tests/helpers.rs @@ -39,7 +39,7 @@ pub type UpdateFn = dyn FnMut(&mut S, &mut UpdateCtx, &T, &T, &Env); pub type LayoutFn = dyn FnMut(&mut S, &mut LayoutCtx, &BoxConstraints, &T, &Env) -> Size; pub type PaintFn = dyn FnMut(&mut S, &mut PaintCtx, &T, &Env); -pub const REPLACE_CHILD: Selector<()> = Selector::new("druid-test.replace-child"); +pub const REPLACE_CHILD: Selector = Selector::new("druid-test.replace-child"); /// A widget that can be constructed from individual functions, builder-style. /// diff --git a/druid/src/tests/mod.rs b/druid/src/tests/mod.rs index bd5c9a51eb..2f6240039f 100644 --- a/druid/src/tests/mod.rs +++ b/druid/src/tests/mod.rs @@ -150,7 +150,7 @@ fn propogate_hot() { } #[test] fn take_focus() { - const TAKE_FOCUS: Selector<()> = Selector::new("druid-tests.take-focus"); + const TAKE_FOCUS: Selector = Selector::new("druid-tests.take-focus"); /// A widget that takes focus when sent a particular command. /// The widget records focus change events into the inner cell. diff --git a/druid/src/widget/textbox.rs b/druid/src/widget/textbox.rs index 9ffb652030..6a0987b428 100644 --- a/druid/src/widget/textbox.rs +++ b/druid/src/widget/textbox.rs @@ -37,7 +37,7 @@ const PADDING_TOP: f64 = 5.; const PADDING_LEFT: f64 = 4.; // we send ourselves this when we want to reset blink, which must be done in event. -const RESET_BLINK: Selector<()> = Selector::new("druid-builtin.reset-textbox-blink"); +const RESET_BLINK: Selector = Selector::new("druid-builtin.reset-textbox-blink"); const CURSOR_BLINK_DRUATION: Duration = Duration::from_millis(500); /// A widget that allows user text input. From 55f623e2a9e20727e466c45e45732ad64f5d5a35 Mon Sep 17 00:00:00 2001 From: Leopold Luley Date: Tue, 12 May 2020 10:21:13 +0200 Subject: [PATCH 17/19] Correct some typos. --- druid/src/command.rs | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/druid/src/command.rs b/druid/src/command.rs index ef0aa69d19..8b3d077e0e 100644 --- a/druid/src/command.rs +++ b/druid/src/command.rs @@ -131,7 +131,7 @@ pub enum ArgumentError { Consumed, } -/// This error can occure when wrongly promising that a type ereased +/// This error can occur when wrongly promising that a type erased /// variant of some generic item represents the application state. /// Examples are `MenuDesc` and `AppStateMenuDesc`. #[derive(Debug, Clone, Copy, PartialEq)] @@ -263,9 +263,7 @@ pub mod sys { /// Commands to save a file, must be handled by the application. /// /// If it carries `Some`, then the application should save to that file and store the `FileInfo` for future use. - /// If it carries `None`, the appliaction should have recieved `Some` before and use the stored `FileInfo`. - /// - /// The argument, if present, should be the path where the file should be saved. + /// If it carries `None`, the application should have received `Some` before and use the stored `FileInfo`. pub const SAVE_FILE: Selector> = Selector::new("druid-builtin.menu-file-save"); /// Show the print-setup window. From 18fa50d2fd5ee8f2d24537a9eb6c621c23b5d530 Mon Sep 17 00:00:00 2001 From: Leopold Luley Date: Tue, 12 May 2020 10:28:42 +0200 Subject: [PATCH 18/19] Rebase onto master. --- druid/examples/open_save.rs | 38 ++++++++++++++++--------------------- 1 file changed, 16 insertions(+), 22 deletions(-) diff --git a/druid/examples/open_save.rs b/druid/examples/open_save.rs index 571a3a1557..82b9a067e3 100644 --- a/druid/examples/open_save.rs +++ b/druid/examples/open_save.rs @@ -14,7 +14,7 @@ use druid::widget::{Align, Button, Flex, TextBox}; use druid::{ - AppDelegate, AppLauncher, Command, DelegateCtx, Env, FileDialogOptions, FileInfo, FileSpec, + commands, AppDelegate, AppLauncher, Command, DelegateCtx, Env, FileDialogOptions, FileSpec, LocalizedString, Target, Widget, WindowDesc, }; @@ -77,30 +77,24 @@ impl AppDelegate for Delegate { data: &mut String, _env: &Env, ) -> bool { - match cmd.selector { - druid::commands::SAVE_FILE => { - if let Ok(file_info) = cmd.get_object::() { - if let Err(e) = std::fs::write(file_info.path(), &data[..]) { - println!("Error writing file: {}", e); - } - } - true + if let Some(Some(file_info)) = cmd.get(commands::SAVE_FILE) { + if let Err(e) = std::fs::write(file_info.path(), &data[..]) { + println!("Error writing file: {}", e); } - druid::commands::OPEN_FILE => { - if let Ok(file_info) = cmd.get_object::() { - match std::fs::read_to_string(file_info.path()) { - Ok(s) => { - let first_line = s.lines().next().unwrap_or(""); - *data = first_line.to_owned(); - } - Err(e) => { - println!("Error opening file: {}", e); - } - } + return true; + } + if let Some(file_info) = cmd.get(commands::OPEN_FILE) { + match std::fs::read_to_string(file_info.path()) { + Ok(s) => { + let first_line = s.lines().next().unwrap_or(""); + *data = first_line.to_owned(); + } + Err(e) => { + println!("Error opening file: {}", e); } - true } - _ => false, + return true; } + false } } From c66836d072ef18d6daffc504524e31f464fbe743 Mon Sep 17 00:00:00 2001 From: Leopold Luley Date: Tue, 12 May 2020 10:31:01 +0200 Subject: [PATCH 19/19] Correct another typo. --- druid/src/command.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/druid/src/command.rs b/druid/src/command.rs index 8b3d077e0e..048fe72f7b 100644 --- a/druid/src/command.rs +++ b/druid/src/command.rs @@ -122,7 +122,7 @@ enum Arg { OneShot(Arc>>>), } -/// Errors that can occur when attempting to retrieve the a `OneShotCommand`s argument. +/// Errors that can occur when attempting to retrieve the `OneShotCommand`s argument. #[derive(Debug, Clone, PartialEq)] pub enum ArgumentError { /// The command represented a different selector.