use std::any::{self, Any};
use std::{
marker::PhantomData,
sync::{Arc, Mutex},
};
use crate::{WidgetId, WindowId};
pub(crate) type SelectorSymbol = &'static str;
#[derive(Debug, PartialEq, Eq)]
pub struct Selector<T = ()>(SelectorSymbol, PhantomData<T>);
#[derive(Debug, Clone)]
pub struct Command {
symbol: SelectorSymbol,
payload: Arc<dyn Any>,
target: Target,
}
#[derive(Clone)]
pub struct Notification {
symbol: SelectorSymbol,
payload: Arc<dyn Any>,
source: WidgetId,
route: WidgetId,
warn_if_unused: bool,
}
pub struct SingleUse<T>(Mutex<Option<T>>);
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum Target {
Global,
Window(WindowId),
Widget(WidgetId),
Auto,
}
pub mod sys {
use std::any::Any;
use super::Selector;
use crate::{
sub_window::{SubWindowDesc, SubWindowUpdate},
FileDialogOptions, FileInfo, Rect, SingleUse, WidgetId, WindowConfig,
};
pub const QUIT_APP: Selector = Selector::new("druid-builtin.quit-app");
#[cfg_attr(
not(target_os = "macos"),
deprecated = "HIDE_APPLICATION is only supported on macOS"
)]
pub const HIDE_APPLICATION: Selector = Selector::new("druid-builtin.menu-hide-application");
#[cfg_attr(
not(target_os = "macos"),
deprecated = "HIDE_OTHERS is only supported on macOS"
)]
pub const HIDE_OTHERS: Selector = Selector::new("druid-builtin.menu-hide-others");
pub(crate) const NEW_WINDOW: Selector<SingleUse<Box<dyn Any>>> =
Selector::new("druid-builtin.new-window");
pub const CLOSE_WINDOW: Selector = Selector::new("druid-builtin.close-window");
pub const CLOSE_ALL_WINDOWS: Selector = Selector::new("druid-builtin.close-all-windows");
pub const SHOW_WINDOW: Selector = Selector::new("druid-builtin.show-window");
pub const HIDE_WINDOW: Selector = Selector::new("druid-builtin.hide-window");
pub const CONFIGURE_WINDOW: Selector<WindowConfig> =
Selector::new("druid-builtin.configure-window");
pub(crate) const SHOW_CONTEXT_MENU: Selector<SingleUse<Box<dyn Any>>> =
Selector::new("druid-builtin.show-context-menu");
pub(crate) const NEW_SUB_WINDOW: Selector<SingleUse<SubWindowDesc>> =
Selector::new("druid-builtin.new-sub-window");
pub(crate) const SUB_WINDOW_PARENT_TO_HOST: Selector<SubWindowUpdate> =
Selector::new("druid-builtin.parent_to_host");
pub(crate) const SUB_WINDOW_HOST_TO_PARENT: Selector<Box<dyn Any>> =
Selector::new("druid-builtin.host_to_parent");
pub const SHOW_PREFERENCES: Selector = Selector::new("druid-builtin.menu-show-preferences");
pub const SHOW_ABOUT: Selector = Selector::new("druid-builtin.menu-show-about");
pub const SHOW_ALL: Selector = Selector::new("druid-builtin.menu-show-all");
pub const NEW_FILE: Selector = Selector::new("druid-builtin.menu-file-new");
pub const SHOW_OPEN_PANEL: Selector<FileDialogOptions> =
Selector::new("druid-builtin.menu-file-open");
pub const OPEN_PANEL_CANCELLED: Selector = Selector::new("druid-builtin.open-panel-cancelled");
pub const OPEN_FILE: Selector<FileInfo> = Selector::new("druid-builtin.open-file-path");
pub const OPEN_FILES: Selector<Vec<FileInfo>> = Selector::new("druid-builtin.open-files-path");
pub const SHOW_SAVE_PANEL: Selector<FileDialogOptions> =
Selector::new("druid-builtin.menu-file-save-as");
pub const SAVE_PANEL_CANCELLED: Selector = Selector::new("druid-builtin.save-panel-cancelled");
pub const SAVE_FILE: Selector<()> = Selector::new("druid-builtin.save-file");
pub const SAVE_FILE_AS: Selector<FileInfo> = Selector::new("druid-builtin.save-file-as");
pub const PRINT_SETUP: Selector = Selector::new("druid-builtin.menu-file-print-setup");
pub const PRINT: Selector = Selector::new("druid-builtin.menu-file-print");
pub const PRINT_PREVIEW: Selector = Selector::new("druid-builtin.menu-file-print");
pub const CUT: Selector = Selector::new("druid-builtin.menu-cut");
pub const COPY: Selector = Selector::new("druid-builtin.menu-copy");
pub const PASTE: Selector = Selector::new("druid-builtin.menu-paste");
pub const UNDO: Selector = Selector::new("druid-builtin.menu-undo");
pub const REDO: Selector = Selector::new("druid-builtin.menu-redo");
pub const SELECT_ALL: Selector = Selector::new("druid-builtin.menu-select-all");
pub(crate) const INVALIDATE_IME: Selector<ImeInvalidation> =
Selector::new("druid-builtin.invalidate-ime");
pub const SCROLL_TO_VIEW: Selector<Rect> = Selector::new("druid-builtin.scroll-to");
pub(crate) struct ImeInvalidation {
pub widget: WidgetId,
pub event: crate::shell::text::Event,
}
}
impl Selector<()> {
pub const NOOP: Selector = Selector::new("");
pub fn to(self, target: impl Into<Target>) -> Command {
Command::from(self).to(target.into())
}
}
impl<T> Selector<T> {
pub const fn new(s: &'static str) -> Selector<T> {
Selector(s, PhantomData)
}
pub(crate) const fn symbol(self) -> SelectorSymbol {
self.0
}
}
impl<T: Any> Selector<T> {
pub fn with(self, payload: T) -> Command {
Command::new(self, payload, Target::Auto)
}
}
impl Command {
pub fn new<T: Any>(selector: Selector<T>, payload: T, target: impl Into<Target>) -> Self {
Command {
symbol: selector.symbol(),
payload: Arc::new(payload),
target: target.into(),
}
}
pub(crate) fn from_ext(symbol: SelectorSymbol, payload: Box<dyn Any>, target: Target) -> Self {
Command {
symbol,
payload: payload.into(),
target,
}
.default_to(Target::Global)
}
pub(crate) fn into_notification(self, source: WidgetId) -> Notification {
Notification {
symbol: self.symbol,
payload: self.payload,
source,
route: source,
warn_if_unused: true,
}
}
pub fn to(mut self, target: impl Into<Target>) -> Self {
self.target = target.into();
self
}
pub(crate) fn default_to(mut self, target: Target) -> Self {
self.target.default(target);
self
}
pub fn target(&self) -> Target {
self.target
}
pub fn is<T>(&self, selector: Selector<T>) -> bool {
self.symbol == selector.symbol()
}
pub fn get<T: Any>(&self, selector: Selector<T>) -> Option<&T> {
if self.symbol == selector.symbol() {
Some(self.payload.downcast_ref().unwrap_or_else(|| {
panic!(
"The selector \"{}\" exists twice with different types. See druid::Command::get for more information",
selector.symbol()
);
}))
} else {
None
}
}
pub fn get_unchecked<T: Any>(&self, selector: Selector<T>) -> &T {
self.get(selector).unwrap_or_else(|| {
panic!(
"Expected selector \"{}\" but the command was \"{}\".",
selector.symbol(),
self.symbol
)
})
}
}
impl Notification {
pub fn is<T>(&self, selector: Selector<T>) -> bool {
self.symbol == selector.symbol()
}
pub fn get<T: Any>(&self, selector: Selector<T>) -> Option<&T> {
if self.symbol == selector.symbol() {
Some(self.payload.downcast_ref().unwrap_or_else(|| {
panic!(
"The selector \"{}\" exists twice with different types. \
See druid::Command::get for more information",
selector.symbol()
);
}))
} else {
None
}
}
pub fn source(&self) -> WidgetId {
self.source
}
pub fn warn_if_unused(mut self, warn_if_unused: bool) -> Self {
self.warn_if_unused = warn_if_unused;
self
}
pub fn warn_if_unused_set(&self) -> bool {
self.warn_if_unused
}
pub(crate) fn with_route(mut self, widget_id: WidgetId) -> Self {
self.route = widget_id;
self
}
pub fn route(&self) -> WidgetId {
self.route
}
}
impl<T: Any> SingleUse<T> {
pub fn new(data: T) -> Self {
SingleUse(Mutex::new(Some(data)))
}
pub fn take(&self) -> Option<T> {
self.0.lock().unwrap().take()
}
}
impl From<Selector> for Command {
fn from(selector: Selector) -> Command {
Command {
symbol: selector.symbol(),
payload: Arc::new(()),
target: Target::Auto,
}
}
}
impl<T> std::fmt::Display for Selector<T> {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
write!(f, "Selector(\"{}\", {})", self.0, any::type_name::<T>())
}
}
impl<T> Copy for Selector<T> {}
impl<T> Clone for Selector<T> {
fn clone(&self) -> Self {
*self
}
}
impl Target {
pub(crate) fn default(&mut self, target: Target) {
if self == &Target::Auto {
*self = target;
}
}
}
impl From<WindowId> for Target {
fn from(id: WindowId) -> Target {
Target::Window(id)
}
}
impl From<WidgetId> for Target {
fn from(id: WidgetId) -> Target {
Target::Widget(id)
}
}
impl From<WindowId> for Option<Target> {
fn from(id: WindowId) -> Self {
Some(Target::Window(id))
}
}
impl From<WidgetId> for Option<Target> {
fn from(id: WidgetId) -> Self {
Some(Target::Widget(id))
}
}
impl std::fmt::Debug for Notification {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
write!(
f,
"Notification: Selector {} from {:?}",
self.symbol, self.source
)
}
}
#[cfg(test)]
mod tests {
use super::*;
use test_log::test;
#[test]
fn get_payload() {
let sel = Selector::new("my-selector");
let payload = vec![0, 1, 2];
let command = sel.with(payload);
assert_eq!(command.get(sel), Some(&vec![0, 1, 2]));
}
#[test]
fn selector_is_send_and_sync() {
fn assert_send_sync<T: Send + Sync>() {}
assert_send_sync::<Selector>();
}
}