use std::any::Any;
use std::sync::{Arc, Mutex};
use crate::{WidgetId, WindowId};
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Selector(&'static str);
#[derive(Debug, Clone)]
pub struct Command {
pub selector: Selector,
object: Option<Arg>,
}
#[derive(Debug, Clone)]
enum Arg {
Reusable(Arc<dyn Any>),
OneShot(Arc<Mutex<Option<Box<dyn Any>>>>),
}
#[derive(Debug, Clone, PartialEq)]
pub enum ArgumentError {
NoArgument,
WrongVariant,
IncorrectType,
Consumed,
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub enum Target {
Global,
Window(WindowId),
Widget(WidgetId),
}
pub mod sys {
use super::Selector;
pub const QUIT_APP: Selector = Selector::new("druid-builtin.quit-app");
pub const HIDE_APPLICATION: Selector = Selector::new("druid-builtin.menu-hide-application");
pub const HIDE_OTHERS: Selector = Selector::new("druid-builtin.menu-hide-others");
pub const NEW_WINDOW: Selector = Selector::new("druid-builtin.new-window");
pub const CLOSE_WINDOW: Selector = Selector::new("druid-builtin.close-window");
pub const SHOW_WINDOW: Selector = Selector::new("druid-builtin.show-window");
pub const SHOW_CONTEXT_MENU: Selector = Selector::new("druid-builtin.show-context-menu");
pub const SET_MENU: Selector = Selector::new("druid-builtin.set-menu");
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 = Selector::new("druid-builtin.menu-file-open");
pub const OPEN_FILE: Selector = Selector::new("druid-builtin.open-file-path");
pub const SHOW_SAVE_PANEL: Selector = Selector::new("druid-builtin.menu-file-save-as");
pub const SAVE_FILE: Selector = Selector::new("druid-builtin.menu-file-save");
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");
}
impl Selector {
pub const NOOP: Selector = Selector::new("");
pub const fn new(s: &'static str) -> Selector {
Selector(s)
}
}
impl Command {
pub fn new(selector: Selector, arg: impl Any) -> Self {
Command {
selector,
object: Some(Arg::Reusable(Arc::new(arg))),
}
}
pub fn one_shot(selector: Selector, arg: impl Any) -> Self {
Command {
selector,
object: Some(Arg::OneShot(Arc::new(Mutex::new(Some(Box::new(arg)))))),
}
}
pub(crate) fn from_ext(selector: Selector, object: Option<Box<dyn Any + Send>>) -> Self {
let object: Option<Box<dyn Any>> = object.map(|obj| obj as Box<dyn Any>);
let object = object.map(|o| Arg::Reusable(o.into()));
Command { selector, object }
}
pub fn get_object<T: Any>(&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 take_object<T: Any>(&self) -> Result<Box<T>, ArgumentError> {
match self.object.as_ref() {
Some(Arg::Reusable(_)) => Err(ArgumentError::WrongVariant),
Some(Arg::OneShot(inner)) => {
let obj = inner
.lock()
.unwrap()
.take()
.ok_or(ArgumentError::Consumed)?;
match obj.downcast::<T>() {
Ok(obj) => Ok(obj),
Err(obj) => {
inner.lock().unwrap().replace(obj);
Err(ArgumentError::IncorrectType)
}
}
}
None => Err(ArgumentError::NoArgument),
}
}
}
impl From<Selector> for Command {
fn from(selector: Selector) -> Command {
Command {
selector,
object: None,
}
}
}
impl std::fmt::Display for Selector {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
write!(f, "Selector('{}')", self.0)
}
}
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::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."
),
}
}
}
impl std::error::Error for ArgumentError {}
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 Into<Option<Target>> for WindowId {
fn into(self) -> Option<Target> {
Some(Target::Window(self))
}
}
impl Into<Option<Target>> for WidgetId {
fn into(self) -> Option<Target> {
Some(Target::Widget(self))
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn get_object() {
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]));
}
}