diff options
| author | 2024-06-14 01:47:39 +0200 | |
|---|---|---|
| committer | 2024-06-14 01:47:39 +0200 | |
| commit | a25b1af45690bdd8e1cbb20ee3a5b1c4342de455 (patch) | |
| tree | 432044cf682dd73d1019a2f964749e78db178865 /runtime | |
| parent | e6d0b3bda5042a1017a5944a5227c97e0ed6caf9 (diff) | |
| download | iced-a25b1af45690bdd8e1cbb20ee3a5b1c4342de455.tar.gz iced-a25b1af45690bdd8e1cbb20ee3a5b1c4342de455.tar.bz2 iced-a25b1af45690bdd8e1cbb20ee3a5b1c4342de455.zip  | |
Replace `Command` with a new `Task` API with chain support
Diffstat (limited to 'runtime')
| -rw-r--r-- | runtime/src/clipboard.rs | 98 | ||||
| -rw-r--r-- | runtime/src/command.rs | 147 | ||||
| -rw-r--r-- | runtime/src/command/action.rs | 100 | ||||
| -rw-r--r-- | runtime/src/font.rs | 12 | ||||
| -rw-r--r-- | runtime/src/lib.rs | 80 | ||||
| -rw-r--r-- | runtime/src/multi_window/program.rs | 8 | ||||
| -rw-r--r-- | runtime/src/multi_window/state.rs | 18 | ||||
| -rw-r--r-- | runtime/src/overlay/nested.rs | 4 | ||||
| -rw-r--r-- | runtime/src/program.rs | 6 | ||||
| -rw-r--r-- | runtime/src/program/state.rs | 33 | ||||
| -rw-r--r-- | runtime/src/system.rs | 41 | ||||
| -rw-r--r-- | runtime/src/system/action.rs | 39 | ||||
| -rw-r--r-- | runtime/src/system/information.rs | 29 | ||||
| -rw-r--r-- | runtime/src/task.rs | 214 | ||||
| -rw-r--r-- | runtime/src/user_interface.rs | 2 | ||||
| -rw-r--r-- | runtime/src/window.rs | 295 | ||||
| -rw-r--r-- | runtime/src/window/action.rs | 230 | 
17 files changed, 607 insertions, 749 deletions
diff --git a/runtime/src/clipboard.rs b/runtime/src/clipboard.rs index dd47c47d..19950d01 100644 --- a/runtime/src/clipboard.rs +++ b/runtime/src/clipboard.rs @@ -1,80 +1,62 @@  //! Access the clipboard. -use crate::command::{self, Command};  use crate::core::clipboard::Kind; -use crate::futures::MaybeSend; +use crate::futures::futures::channel::oneshot; +use crate::Task; -use std::fmt; - -/// A clipboard action to be performed by some [`Command`]. +/// A clipboard action to be performed by some [`Task`].  /// -/// [`Command`]: crate::Command -pub enum Action<T> { +/// [`Task`]: crate::Task +#[derive(Debug)] +pub enum Action {      /// Read the clipboard and produce `T` with the result. -    Read(Box<dyn Fn(Option<String>) -> T>, Kind), +    Read { +        /// The clipboard target. +        target: Kind, +        /// The channel to send the read contents. +        channel: oneshot::Sender<Option<String>>, +    },      /// Write the given contents to the clipboard. -    Write(String, Kind), -} - -impl<T> Action<T> { -    /// Maps the output of a clipboard [`Action`] using the provided closure. -    pub fn map<A>( -        self, -        f: impl Fn(T) -> A + 'static + MaybeSend + Sync, -    ) -> Action<A> -    where -        T: 'static, -    { -        match self { -            Self::Read(o, target) => { -                Action::Read(Box::new(move |s| f(o(s))), target) -            } -            Self::Write(content, target) => Action::Write(content, target), -        } -    } -} - -impl<T> fmt::Debug for Action<T> { -    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { -        match self { -            Self::Read(_, target) => write!(f, "Action::Read{target:?}"), -            Self::Write(_, target) => write!(f, "Action::Write({target:?})"), -        } -    } +    Write { +        /// The clipboard target. +        target: Kind, +        /// The contents to be written. +        contents: String, +    },  }  /// Read the current contents of the clipboard. -pub fn read<Message>( -    f: impl Fn(Option<String>) -> Message + 'static, -) -> Command<Message> { -    Command::single(command::Action::Clipboard(Action::Read( -        Box::new(f), -        Kind::Standard, -    ))) +pub fn read() -> Task<Option<String>> { +    Task::oneshot(|channel| { +        crate::Action::Clipboard(Action::Read { +            target: Kind::Standard, +            channel, +        }) +    })  }  /// Read the current contents of the primary clipboard. -pub fn read_primary<Message>( -    f: impl Fn(Option<String>) -> Message + 'static, -) -> Command<Message> { -    Command::single(command::Action::Clipboard(Action::Read( -        Box::new(f), -        Kind::Primary, -    ))) +pub fn read_primary() -> Task<Option<String>> { +    Task::oneshot(|channel| { +        crate::Action::Clipboard(Action::Read { +            target: Kind::Primary, +            channel, +        }) +    })  }  /// Write the given contents to the clipboard. -pub fn write<Message>(contents: String) -> Command<Message> { -    Command::single(command::Action::Clipboard(Action::Write( +pub fn write<T>(contents: String) -> Task<T> { +    Task::effect(crate::Action::Clipboard(Action::Write { +        target: Kind::Standard,          contents, -        Kind::Standard, -    ))) +    }))  }  /// Write the given contents to the primary clipboard. -pub fn write_primary<Message>(contents: String) -> Command<Message> { -    Command::single(command::Action::Clipboard(Action::Write( +pub fn write_primary<Message>(contents: String) -> Task<Message> { +    Task::effect(crate::Action::Clipboard(Action::Write { +        target: Kind::Primary,          contents, -        Kind::Primary, -    ))) +    }))  } diff --git a/runtime/src/command.rs b/runtime/src/command.rs deleted file mode 100644 index f7a746fe..00000000 --- a/runtime/src/command.rs +++ /dev/null @@ -1,147 +0,0 @@ -//! Run asynchronous actions. -mod action; - -pub use action::Action; - -use crate::core::widget; -use crate::futures::futures; -use crate::futures::MaybeSend; - -use futures::channel::mpsc; -use futures::Stream; -use std::fmt; -use std::future::Future; - -/// A set of asynchronous actions to be performed by some runtime. -#[must_use = "`Command` must be returned to runtime to take effect"] -pub struct Command<T>(Internal<Action<T>>); - -#[derive(Debug)] -enum Internal<T> { -    None, -    Single(T), -    Batch(Vec<T>), -} - -impl<T> Command<T> { -    /// Creates an empty [`Command`]. -    /// -    /// In other words, a [`Command`] that does nothing. -    pub const fn none() -> Self { -        Self(Internal::None) -    } - -    /// Creates a [`Command`] that performs a single [`Action`]. -    pub const fn single(action: Action<T>) -> Self { -        Self(Internal::Single(action)) -    } - -    /// Creates a [`Command`] that performs a [`widget::Operation`]. -    pub fn widget(operation: impl widget::Operation<T> + 'static) -> Self { -        Self::single(Action::Widget(Box::new(operation))) -    } - -    /// Creates a [`Command`] that performs the action of the given future. -    pub fn perform<A>( -        future: impl Future<Output = A> + 'static + MaybeSend, -        f: impl FnOnce(A) -> T + 'static + MaybeSend, -    ) -> Command<T> { -        use futures::FutureExt; - -        Command::single(Action::Future(Box::pin(future.map(f)))) -    } - -    /// Creates a [`Command`] that runs the given stream to completion. -    pub fn run<A>( -        stream: impl Stream<Item = A> + 'static + MaybeSend, -        f: impl Fn(A) -> T + 'static + MaybeSend, -    ) -> Command<T> { -        use futures::StreamExt; - -        Command::single(Action::Stream(Box::pin(stream.map(f)))) -    } - -    /// Creates a [`Command`] that performs the actions of all the given -    /// commands. -    /// -    /// Once this command is run, all the commands will be executed at once. -    pub fn batch(commands: impl IntoIterator<Item = Command<T>>) -> Self { -        let mut batch = Vec::new(); - -        for Command(command) in commands { -            match command { -                Internal::None => {} -                Internal::Single(command) => batch.push(command), -                Internal::Batch(commands) => batch.extend(commands), -            } -        } - -        Self(Internal::Batch(batch)) -    } - -    /// Applies a transformation to the result of a [`Command`]. -    pub fn map<A>( -        self, -        f: impl Fn(T) -> A + 'static + MaybeSend + Sync + Clone, -    ) -> Command<A> -    where -        T: 'static, -        A: 'static, -    { -        match self.0 { -            Internal::None => Command::none(), -            Internal::Single(action) => Command::single(action.map(f)), -            Internal::Batch(batch) => Command(Internal::Batch( -                batch -                    .into_iter() -                    .map(|action| action.map(f.clone())) -                    .collect(), -            )), -        } -    } - -    /// Returns all of the actions of the [`Command`]. -    pub fn actions(self) -> Vec<Action<T>> { -        let Command(command) = self; - -        match command { -            Internal::None => Vec::new(), -            Internal::Single(action) => vec![action], -            Internal::Batch(batch) => batch, -        } -    } -} - -impl<Message> From<()> for Command<Message> { -    fn from(_value: ()) -> Self { -        Self::none() -    } -} - -impl<T> fmt::Debug for Command<T> { -    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { -        let Command(command) = self; - -        command.fmt(f) -    } -} - -/// Creates a [`Command`] that produces the `Message`s published from a [`Future`] -/// to an [`mpsc::Sender`] with the given bounds. -pub fn channel<Fut, Message>( -    size: usize, -    f: impl FnOnce(mpsc::Sender<Message>) -> Fut + MaybeSend + 'static, -) -> Command<Message> -where -    Fut: Future<Output = ()> + MaybeSend + 'static, -    Message: 'static + MaybeSend, -{ -    use futures::future; -    use futures::stream::{self, StreamExt}; - -    let (sender, receiver) = mpsc::channel(size); - -    let runner = stream::once(f(sender)).filter_map(|_| future::ready(None)); - -    Command::single(Action::Stream(Box::pin(stream::select(receiver, runner)))) -} diff --git a/runtime/src/command/action.rs b/runtime/src/command/action.rs deleted file mode 100644 index c9ffe801..00000000 --- a/runtime/src/command/action.rs +++ /dev/null @@ -1,100 +0,0 @@ -use crate::clipboard; -use crate::core::widget; -use crate::font; -use crate::futures::MaybeSend; -use crate::system; -use crate::window; - -use std::any::Any; -use std::borrow::Cow; -use std::fmt; - -/// An action that a [`Command`] can perform. -/// -/// [`Command`]: crate::Command -pub enum Action<T> { -    /// Run a [`Future`] to completion. -    /// -    /// [`Future`]: iced_futures::BoxFuture -    Future(iced_futures::BoxFuture<T>), - -    /// Run a [`Stream`] to completion. -    /// -    /// [`Stream`]: iced_futures::BoxStream -    Stream(iced_futures::BoxStream<T>), - -    /// Run a clipboard action. -    Clipboard(clipboard::Action<T>), - -    /// Run a window action. -    Window(window::Action<T>), - -    /// Run a system action. -    System(system::Action<T>), - -    /// Run a widget action. -    Widget(Box<dyn widget::Operation<T>>), - -    /// Load a font from its bytes. -    LoadFont { -        /// The bytes of the font to load. -        bytes: Cow<'static, [u8]>, - -        /// The message to produce when the font has been loaded. -        tagger: Box<dyn Fn(Result<(), font::Error>) -> T>, -    }, - -    /// A custom action supported by a specific runtime. -    Custom(Box<dyn Any>), -} - -impl<T> Action<T> { -    /// Applies a transformation to the result of a [`Command`]. -    /// -    /// [`Command`]: crate::Command -    pub fn map<A>( -        self, -        f: impl Fn(T) -> A + 'static + MaybeSend + Sync, -    ) -> Action<A> -    where -        A: 'static, -        T: 'static, -    { -        use iced_futures::futures::{FutureExt, StreamExt}; - -        match self { -            Self::Future(future) => Action::Future(Box::pin(future.map(f))), -            Self::Stream(stream) => Action::Stream(Box::pin(stream.map(f))), -            Self::Clipboard(action) => Action::Clipboard(action.map(f)), -            Self::Window(window) => Action::Window(window.map(f)), -            Self::System(system) => Action::System(system.map(f)), -            Self::Widget(operation) => { -                Action::Widget(Box::new(widget::operation::map(operation, f))) -            } -            Self::LoadFont { bytes, tagger } => Action::LoadFont { -                bytes, -                tagger: Box::new(move |result| f(tagger(result))), -            }, -            Self::Custom(custom) => Action::Custom(custom), -        } -    } -} - -impl<T> fmt::Debug for Action<T> { -    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { -        match self { -            Self::Future(_) => write!(f, "Action::Future"), -            Self::Stream(_) => write!(f, "Action::Stream"), -            Self::Clipboard(action) => { -                write!(f, "Action::Clipboard({action:?})") -            } -            Self::Window(action) => { -                write!(f, "Action::Window({action:?})") -            } -            Self::System(action) => write!(f, "Action::System({action:?})"), -            Self::Widget(_action) => write!(f, "Action::Widget"), -            Self::LoadFont { .. } => write!(f, "Action::LoadFont"), -            Self::Custom(_) => write!(f, "Action::Custom"), -        } -    } -} diff --git a/runtime/src/font.rs b/runtime/src/font.rs index 15359694..d54eb6a8 100644 --- a/runtime/src/font.rs +++ b/runtime/src/font.rs @@ -1,7 +1,5 @@  //! Load and use fonts. -pub use iced_core::font::*; - -use crate::command::{self, Command}; +use crate::{Action, Task};  use std::borrow::Cow;  /// An error while loading a font. @@ -9,11 +7,9 @@ use std::borrow::Cow;  pub enum Error {}  /// Load a font from its bytes. -pub fn load( -    bytes: impl Into<Cow<'static, [u8]>>, -) -> Command<Result<(), Error>> { -    Command::single(command::Action::LoadFont { +pub fn load(bytes: impl Into<Cow<'static, [u8]>>) -> Task<Result<(), Error>> { +    Task::oneshot(|channel| Action::LoadFont {          bytes: bytes.into(), -        tagger: Box::new(std::convert::identity), +        channel,      })  } diff --git a/runtime/src/lib.rs b/runtime/src/lib.rs index 5f054c46..5fde3039 100644 --- a/runtime/src/lib.rs +++ b/runtime/src/lib.rs @@ -10,7 +10,6 @@  )]  #![cfg_attr(docsrs, feature(doc_auto_cfg))]  pub mod clipboard; -pub mod command;  pub mod font;  pub mod keyboard;  pub mod overlay; @@ -22,6 +21,8 @@ pub mod window;  #[cfg(feature = "multi-window")]  pub mod multi_window; +mod task; +  // We disable debug capabilities on release builds unless the `debug` feature  // is explicitly enabled.  #[cfg(feature = "debug")] @@ -34,8 +35,81 @@ mod debug;  pub use iced_core as core;  pub use iced_futures as futures; -pub use command::Command;  pub use debug::Debug; -pub use font::Font;  pub use program::Program; +pub use task::Task;  pub use user_interface::UserInterface; + +use crate::core::widget; +use crate::futures::futures::channel::oneshot; + +use std::borrow::Cow; +use std::fmt; + +/// An action that the iced runtime can perform. +pub enum Action<T> { +    /// Output some value. +    Output(T), + +    /// Load a font from its bytes. +    LoadFont { +        /// The bytes of the font to load. +        bytes: Cow<'static, [u8]>, +        /// The channel to send back the load result. +        channel: oneshot::Sender<Result<(), font::Error>>, +    }, + +    /// Run a widget operation. +    Widget(Box<dyn widget::Operation<()> + Send>), + +    /// Run a clipboard action. +    Clipboard(clipboard::Action), + +    /// Run a window action. +    Window(window::Action), + +    /// Run a system action. +    System(system::Action), +} + +impl<T> Action<T> { +    /// Creates a new [`Action::Widget`] with the given [`widget::Operation`]. +    pub fn widget(operation: impl widget::Operation<()> + 'static) -> Self { +        Self::Widget(Box::new(operation)) +    } + +    fn output<O>(self) -> Result<T, Action<O>> { +        match self { +            Action::Output(output) => Ok(output), +            Action::LoadFont { bytes, channel } => { +                Err(Action::LoadFont { bytes, channel }) +            } +            Action::Widget(operation) => Err(Action::Widget(operation)), +            Action::Clipboard(action) => Err(Action::Clipboard(action)), +            Action::Window(action) => Err(Action::Window(action)), +            Action::System(action) => Err(Action::System(action)), +        } +    } +} + +impl<T> fmt::Debug for Action<T> +where +    T: fmt::Debug, +{ +    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { +        match self { +            Action::Output(output) => write!(f, "Action::Output({output:?})"), +            Action::LoadFont { .. } => { +                write!(f, "Action::LoadFont") +            } +            Action::Widget { .. } => { +                write!(f, "Action::Widget") +            } +            Action::Clipboard(action) => { +                write!(f, "Action::Clipboard({action:?})") +            } +            Action::Window(_) => write!(f, "Action::Window"), +            Action::System(action) => write!(f, "Action::System({action:?})"), +        } +    } +} diff --git a/runtime/src/multi_window/program.rs b/runtime/src/multi_window/program.rs index 963a09d7..e8c71b26 100644 --- a/runtime/src/multi_window/program.rs +++ b/runtime/src/multi_window/program.rs @@ -2,7 +2,7 @@  use crate::core::text;  use crate::core::window;  use crate::core::{Element, Renderer}; -use crate::Command; +use crate::Task;  /// The core of a user interface for a multi-window application following The Elm Architecture.  pub trait Program: Sized { @@ -21,9 +21,9 @@ pub trait Program: Sized {      /// produced by either user interactions or commands, will be handled by      /// this method.      /// -    /// Any [`Command`] returned will be executed immediately in the -    /// background by shells. -    fn update(&mut self, message: Self::Message) -> Command<Self::Message>; +    /// Any [`Task`] returned will be executed immediately in the background by the +    /// runtime. +    fn update(&mut self, message: Self::Message) -> Task<Self::Message>;      /// Returns the widgets to display in the [`Program`] for the `window`.      /// diff --git a/runtime/src/multi_window/state.rs b/runtime/src/multi_window/state.rs index 10366ec0..72ce6933 100644 --- a/runtime/src/multi_window/state.rs +++ b/runtime/src/multi_window/state.rs @@ -5,7 +5,7 @@ use crate::core::renderer;  use crate::core::widget::operation::{self, Operation};  use crate::core::{Clipboard, Size};  use crate::user_interface::{self, UserInterface}; -use crate::{Command, Debug, Program}; +use crate::{Debug, Program, Task};  /// The execution state of a multi-window [`Program`]. It leverages caching, event  /// processing, and rendering primitive storage. @@ -85,7 +85,7 @@ where      /// the widgets of the linked [`Program`] if necessary.      ///      /// Returns a list containing the instances of [`Event`] that were not -    /// captured by any widget, and the [`Command`] obtained from [`Program`] +    /// captured by any widget, and the [`Task`] obtained from [`Program`]      /// after updating it, only if an update was necessary.      pub fn update(          &mut self, @@ -96,7 +96,7 @@ where          style: &renderer::Style,          clipboard: &mut dyn Clipboard,          debug: &mut Debug, -    ) -> (Vec<Event>, Option<Command<P::Message>>) { +    ) -> (Vec<Event>, Option<Task<P::Message>>) {          let mut user_interfaces = build_user_interfaces(              &self.program,              self.caches.take().unwrap(), @@ -163,14 +163,14 @@ where              drop(user_interfaces); -            let commands = Command::batch(messages.into_iter().map(|msg| { +            let commands = Task::batch(messages.into_iter().map(|msg| {                  debug.log_message(&msg);                  debug.update_started(); -                let command = self.program.update(msg); +                let task = self.program.update(msg);                  debug.update_finished(); -                command +                task              }));              let mut user_interfaces = build_user_interfaces( @@ -205,7 +205,7 @@ where      pub fn operate(          &mut self,          renderer: &mut P::Renderer, -        operations: impl Iterator<Item = Box<dyn Operation<P::Message>>>, +        operations: impl Iterator<Item = Box<dyn Operation<()>>>,          bounds: Size,          debug: &mut Debug,      ) { @@ -227,9 +227,7 @@ where                  match operation.finish() {                      operation::Outcome::None => {} -                    operation::Outcome::Some(message) => { -                        self.queued_messages.push(message); -                    } +                    operation::Outcome::Some(()) => {}                      operation::Outcome::Chain(next) => {                          current_operation = Some(next);                      } diff --git a/runtime/src/overlay/nested.rs b/runtime/src/overlay/nested.rs index ddb9532b..11eee41c 100644 --- a/runtime/src/overlay/nested.rs +++ b/runtime/src/overlay/nested.rs @@ -131,13 +131,13 @@ where          &mut self,          layout: Layout<'_>,          renderer: &Renderer, -        operation: &mut dyn widget::Operation<Message>, +        operation: &mut dyn widget::Operation<()>,      ) {          fn recurse<Message, Theme, Renderer>(              element: &mut overlay::Element<'_, Message, Theme, Renderer>,              layout: Layout<'_>,              renderer: &Renderer, -            operation: &mut dyn widget::Operation<Message>, +            operation: &mut dyn widget::Operation<()>,          ) where              Renderer: renderer::Renderer,          { diff --git a/runtime/src/program.rs b/runtime/src/program.rs index 0ea94d3b..77acf497 100644 --- a/runtime/src/program.rs +++ b/runtime/src/program.rs @@ -1,5 +1,5 @@  //! Build interactive programs using The Elm Architecture. -use crate::Command; +use crate::Task;  use iced_core::text;  use iced_core::Element; @@ -25,9 +25,9 @@ pub trait Program: Sized {      /// produced by either user interactions or commands, will be handled by      /// this method.      /// -    /// Any [`Command`] returned will be executed immediately in the +    /// Any [`Task`] returned will be executed immediately in the      /// background by shells. -    fn update(&mut self, message: Self::Message) -> Command<Self::Message>; +    fn update(&mut self, message: Self::Message) -> Task<Self::Message>;      /// Returns the widgets to display in the [`Program`].      /// diff --git a/runtime/src/program/state.rs b/runtime/src/program/state.rs index c6589c22..e51ad0cb 100644 --- a/runtime/src/program/state.rs +++ b/runtime/src/program/state.rs @@ -4,7 +4,7 @@ use crate::core::renderer;  use crate::core::widget::operation::{self, Operation};  use crate::core::{Clipboard, Size};  use crate::user_interface::{self, UserInterface}; -use crate::{Command, Debug, Program}; +use crate::{Debug, Program, Task};  /// The execution state of a [`Program`]. It leverages caching, event  /// processing, and rendering primitive storage. @@ -84,7 +84,7 @@ where      /// the widgets of the linked [`Program`] if necessary.      ///      /// Returns a list containing the instances of [`Event`] that were not -    /// captured by any widget, and the [`Command`] obtained from [`Program`] +    /// captured by any widget, and the [`Task`] obtained from [`Program`]      /// after updating it, only if an update was necessary.      pub fn update(          &mut self, @@ -95,7 +95,7 @@ where          style: &renderer::Style,          clipboard: &mut dyn Clipboard,          debug: &mut Debug, -    ) -> (Vec<Event>, Option<Command<P::Message>>) { +    ) -> (Vec<Event>, Option<Task<P::Message>>) {          let mut user_interface = build_user_interface(              &mut self.program,              self.cache.take().unwrap(), @@ -129,7 +129,7 @@ where          messages.append(&mut self.queued_messages);          debug.event_processing_finished(); -        let command = if messages.is_empty() { +        let task = if messages.is_empty() {              debug.draw_started();              self.mouse_interaction =                  user_interface.draw(renderer, theme, style, cursor); @@ -143,16 +143,15 @@ where              // for now :^)              let temp_cache = user_interface.into_cache(); -            let commands = -                Command::batch(messages.into_iter().map(|message| { -                    debug.log_message(&message); +            let tasks = Task::batch(messages.into_iter().map(|message| { +                debug.log_message(&message); -                    debug.update_started(); -                    let command = self.program.update(message); -                    debug.update_finished(); +                debug.update_started(); +                let task = self.program.update(message); +                debug.update_finished(); -                    command -                })); +                task +            }));              let mut user_interface = build_user_interface(                  &mut self.program, @@ -169,17 +168,17 @@ where              self.cache = Some(user_interface.into_cache()); -            Some(commands) +            Some(tasks)          }; -        (uncaptured_events, command) +        (uncaptured_events, task)      }      /// Applies [`Operation`]s to the [`State`]      pub fn operate(          &mut self,          renderer: &mut P::Renderer, -        operations: impl Iterator<Item = Box<dyn Operation<P::Message>>>, +        operations: impl Iterator<Item = Box<dyn Operation<()>>>,          bounds: Size,          debug: &mut Debug,      ) { @@ -199,9 +198,7 @@ where                  match operation.finish() {                      operation::Outcome::None => {} -                    operation::Outcome::Some(message) => { -                        self.queued_messages.push(message); -                    } +                    operation::Outcome::Some(()) => {}                      operation::Outcome::Chain(next) => {                          current_operation = Some(next);                      } diff --git a/runtime/src/system.rs b/runtime/src/system.rs index 61c8ff29..b6fb4fdf 100644 --- a/runtime/src/system.rs +++ b/runtime/src/system.rs @@ -1,6 +1,39 @@  //! Access the native system. -mod action; -mod information; +use crate::futures::futures::channel::oneshot; -pub use action::Action; -pub use information::Information; +/// An operation to be performed on the system. +#[derive(Debug)] +pub enum Action { +    /// Query system information and produce `T` with the result. +    QueryInformation(oneshot::Sender<Information>), +} + +/// Contains informations about the system (e.g. system name, processor, memory, graphics adapter). +#[derive(Clone, Debug)] +pub struct Information { +    /// The operating system name +    pub system_name: Option<String>, +    /// Operating system kernel version +    pub system_kernel: Option<String>, +    /// Long operating system version +    /// +    /// Examples: +    /// - MacOS 10.15 Catalina +    /// - Windows 10 Pro +    /// - Ubuntu 20.04 LTS (Focal Fossa) +    pub system_version: Option<String>, +    /// Short operating system version number +    pub system_short_version: Option<String>, +    /// Detailed processor model information +    pub cpu_brand: String, +    /// The number of physical cores on the processor +    pub cpu_cores: Option<usize>, +    /// Total RAM size, in bytes +    pub memory_total: u64, +    /// Memory used by this process, in bytes +    pub memory_used: Option<u64>, +    /// Underlying graphics backend for rendering +    pub graphics_backend: String, +    /// Model information for the active graphics adapter +    pub graphics_adapter: String, +} diff --git a/runtime/src/system/action.rs b/runtime/src/system/action.rs deleted file mode 100644 index dea9536f..00000000 --- a/runtime/src/system/action.rs +++ /dev/null @@ -1,39 +0,0 @@ -use crate::system; - -use iced_futures::MaybeSend; -use std::fmt; - -/// An operation to be performed on the system. -pub enum Action<T> { -    /// Query system information and produce `T` with the result. -    QueryInformation(Box<dyn Closure<T>>), -} - -pub trait Closure<T>: Fn(system::Information) -> T + MaybeSend {} - -impl<T, O> Closure<O> for T where T: Fn(system::Information) -> O + MaybeSend {} - -impl<T> Action<T> { -    /// Maps the output of a system [`Action`] using the provided closure. -    pub fn map<A>( -        self, -        f: impl Fn(T) -> A + 'static + MaybeSend + Sync, -    ) -> Action<A> -    where -        T: 'static, -    { -        match self { -            Self::QueryInformation(o) => { -                Action::QueryInformation(Box::new(move |s| f(o(s)))) -            } -        } -    } -} - -impl<T> fmt::Debug for Action<T> { -    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { -        match self { -            Self::QueryInformation(_) => write!(f, "Action::QueryInformation"), -        } -    } -} diff --git a/runtime/src/system/information.rs b/runtime/src/system/information.rs deleted file mode 100644 index 0f78f5e9..00000000 --- a/runtime/src/system/information.rs +++ /dev/null @@ -1,29 +0,0 @@ -/// Contains informations about the system (e.g. system name, processor, memory, graphics adapter). -#[derive(Clone, Debug)] -pub struct Information { -    /// The operating system name -    pub system_name: Option<String>, -    /// Operating system kernel version -    pub system_kernel: Option<String>, -    /// Long operating system version -    /// -    /// Examples: -    /// - MacOS 10.15 Catalina -    /// - Windows 10 Pro -    /// - Ubuntu 20.04 LTS (Focal Fossa) -    pub system_version: Option<String>, -    /// Short operating system version number -    pub system_short_version: Option<String>, -    /// Detailed processor model information -    pub cpu_brand: String, -    /// The number of physical cores on the processor -    pub cpu_cores: Option<usize>, -    /// Total RAM size, in bytes -    pub memory_total: u64, -    /// Memory used by this process, in bytes -    pub memory_used: Option<u64>, -    /// Underlying graphics backend for rendering -    pub graphics_backend: String, -    /// Model information for the active graphics adapter -    pub graphics_adapter: String, -} diff --git a/runtime/src/task.rs b/runtime/src/task.rs new file mode 100644 index 00000000..ac28a4e7 --- /dev/null +++ b/runtime/src/task.rs @@ -0,0 +1,214 @@ +use crate::core::widget; +use crate::core::MaybeSend; +use crate::futures::futures::channel::mpsc; +use crate::futures::futures::channel::oneshot; +use crate::futures::futures::future::{self, FutureExt}; +use crate::futures::futures::never::Never; +use crate::futures::futures::stream::{self, Stream, StreamExt}; +use crate::futures::{boxed_stream, BoxStream}; +use crate::Action; + +use std::future::Future; + +/// A set of concurrent actions to be performed by the iced runtime. +/// +/// A [`Task`] _may_ produce a bunch of values of type `T`. +#[allow(missing_debug_implementations)] +pub struct Task<T>(Option<BoxStream<Action<T>>>); + +impl<T> Task<T> { +    /// Creates a [`Task`] that does nothing. +    pub fn none() -> Self { +        Self(None) +    } + +    /// Creates a new [`Task`] that instantly produces the given value. +    pub fn done(value: T) -> Self +    where +        T: MaybeSend + 'static, +    { +        Self::future(future::ready(value)) +    } + +    /// Creates a new [`Task`] that runs the given [`Future`] and produces +    /// its output. +    pub fn future(future: impl Future<Output = T> + MaybeSend + 'static) -> Self +    where +        T: 'static, +    { +        Self::stream(stream::once(future)) +    } + +    /// Creates a new [`Task`] that runs the given [`Stream`] and produces +    /// each of its items. +    pub fn stream(stream: impl Stream<Item = T> + MaybeSend + 'static) -> Self +    where +        T: 'static, +    { +        Self(Some(boxed_stream(stream.map(Action::Output)))) +    } + +    /// Creates a new [`Task`] that runs the given [`widget::Operation`] and produces +    /// its output. +    pub fn widget(operation: impl widget::Operation<T> + 'static) -> Task<T> +    where +        T: MaybeSend + 'static, +    { +        Self::channel(move |sender| { +            let operation = +                widget::operation::map(Box::new(operation), move |value| { +                    let _ = sender.clone().try_send(value); +                }); + +            Action::Widget(Box::new(operation)) +        }) +    } + +    /// Creates a new [`Task`] that executes the [`Action`] returned by the closure and +    /// produces the value fed to the [`oneshot::Sender`]. +    pub fn oneshot(f: impl FnOnce(oneshot::Sender<T>) -> Action<T>) -> Task<T> +    where +        T: MaybeSend + 'static, +    { +        let (sender, receiver) = oneshot::channel(); + +        let action = f(sender); + +        Self(Some(boxed_stream( +            stream::once(async move { action }).chain( +                receiver.into_stream().filter_map(|result| async move { +                    Some(Action::Output(result.ok()?)) +                }), +            ), +        ))) +    } + +    /// Creates a new [`Task`] that executes the [`Action`] returned by the closure and +    /// produces the values fed to the [`mpsc::Sender`]. +    pub fn channel(f: impl FnOnce(mpsc::Sender<T>) -> Action<T>) -> Task<T> +    where +        T: MaybeSend + 'static, +    { +        let (sender, receiver) = mpsc::channel(1); + +        let action = f(sender); + +        Self(Some(boxed_stream( +            stream::once(async move { action }) +                .chain(receiver.map(|result| Action::Output(result))), +        ))) +    } + +    /// Creates a new [`Task`] that executes the given [`Action`] and produces no output. +    pub fn effect(action: impl Into<Action<Never>>) -> Self { +        let action = action.into(); + +        Self(Some(boxed_stream(stream::once(async move { +            action.output().expect_err("no output") +        })))) +    } + +    /// Maps the output of a [`Task`] with the given closure. +    pub fn map<O>( +        self, +        mut f: impl FnMut(T) -> O + MaybeSend + 'static, +    ) -> Task<O> +    where +        T: MaybeSend + 'static, +        O: MaybeSend + 'static, +    { +        self.then(move |output| Task::done(f(output))) +    } + +    /// Performs a new [`Task`] for every output of the current [`Task`] using the +    /// given closure. +    /// +    /// This is the monadic interface of [`Task`]—analogous to [`Future`] and +    /// [`Stream`]. +    pub fn then<O>( +        self, +        mut f: impl FnMut(T) -> Task<O> + MaybeSend + 'static, +    ) -> Task<O> +    where +        T: MaybeSend + 'static, +        O: MaybeSend + 'static, +    { +        Task(match self.0 { +            None => None, +            Some(stream) => { +                Some(boxed_stream(stream.flat_map(move |action| { +                    match action.output() { +                        Ok(output) => f(output) +                            .0 +                            .unwrap_or_else(|| boxed_stream(stream::empty())), +                        Err(action) => { +                            boxed_stream(stream::once(async move { action })) +                        } +                    } +                }))) +            } +        }) +    } + +    /// Chains a new [`Task`] to be performed once the current one finishes completely. +    pub fn chain(self, task: Self) -> Self +    where +        T: 'static, +    { +        match self.0 { +            None => task, +            Some(first) => match task.0 { +                None => Task::none(), +                Some(second) => Task(Some(boxed_stream(first.chain(second)))), +            }, +        } +    } + +    /// Creates a [`Task`] that runs the given [`Future`] to completion. +    pub fn perform<A>( +        future: impl Future<Output = A> + MaybeSend + 'static, +        f: impl Fn(A) -> T + MaybeSend + 'static, +    ) -> Self +    where +        T: MaybeSend + 'static, +        A: MaybeSend + 'static, +    { +        Self::future(future.map(f)) +    } + +    /// Creates a [`Task`] that runs the given [`Stream`] to completion. +    pub fn run<A>( +        stream: impl Stream<Item = A> + MaybeSend + 'static, +        f: impl Fn(A) -> T + 'static + MaybeSend, +    ) -> Self +    where +        T: 'static, +    { +        Self::stream(stream.map(f)) +    } + +    /// Combines the given tasks and produces a single [`Task`] that will run all of them +    /// in parallel. +    pub fn batch(tasks: impl IntoIterator<Item = Self>) -> Self +    where +        T: 'static, +    { +        Self(Some(boxed_stream(stream::select_all( +            tasks.into_iter().filter_map(|task| task.0), +        )))) +    } + +    /// Returns the underlying [`Stream`] of the [`Task`]. +    pub fn into_stream(self) -> Option<BoxStream<Action<T>>> { +        self.0 +    } +} + +impl<T> From<()> for Task<T> +where +    T: MaybeSend + 'static, +{ +    fn from(_value: ()) -> Self { +        Self::none() +    } +} diff --git a/runtime/src/user_interface.rs b/runtime/src/user_interface.rs index 006225ed..858b1a2d 100644 --- a/runtime/src/user_interface.rs +++ b/runtime/src/user_interface.rs @@ -566,7 +566,7 @@ where      pub fn operate(          &mut self,          renderer: &Renderer, -        operation: &mut dyn widget::Operation<Message>, +        operation: &mut dyn widget::Operation<()>,      ) {          self.root.as_widget().operate(              &mut self.state, diff --git a/runtime/src/window.rs b/runtime/src/window.rs index b68c9a71..0876ab69 100644 --- a/runtime/src/window.rs +++ b/runtime/src/window.rs @@ -1,24 +1,145 @@  //! Build window-based GUI applications. -mod action; -  pub mod screenshot; -pub use action::Action;  pub use screenshot::Screenshot; -use crate::command::{self, Command};  use crate::core::time::Instant;  use crate::core::window::{      Event, Icon, Id, Level, Mode, Settings, UserAttention,  }; -use crate::core::{Point, Size}; +use crate::core::{MaybeSend, Point, Size};  use crate::futures::event; +use crate::futures::futures::channel::oneshot;  use crate::futures::Subscription; +use crate::Task;  pub use raw_window_handle;  use raw_window_handle::WindowHandle; +/// An operation to be performed on some window. +#[allow(missing_debug_implementations)] +pub enum Action { +    /// Opens a new window with some [`Settings`]. +    Open(Id, Settings), + +    /// Close the window and exits the application. +    Close(Id), + +    /// Move the window with the left mouse button until the button is +    /// released. +    /// +    /// There’s no guarantee that this will work unless the left mouse +    /// button was pressed immediately before this function is called. +    Drag(Id), + +    /// Resize the window to the given logical dimensions. +    Resize(Id, Size), + +    /// Fetch the current logical dimensions of the window. +    FetchSize(Id, oneshot::Sender<Size>), + +    /// Fetch if the current window is maximized or not. +    FetchMaximized(Id, oneshot::Sender<bool>), + +    /// Set the window to maximized or back +    Maximize(Id, bool), + +    /// Fetch if the current window is minimized or not. +    /// +    /// ## Platform-specific +    /// - **Wayland:** Always `None`. +    FetchMinimized(Id, oneshot::Sender<Option<bool>>), + +    /// Set the window to minimized or back +    Minimize(Id, bool), + +    /// Fetch the current logical coordinates of the window. +    FetchPosition(Id, oneshot::Sender<Option<Point>>), + +    /// Move the window to the given logical coordinates. +    /// +    /// Unsupported on Wayland. +    Move(Id, Point), + +    /// Change the [`Mode`] of the window. +    ChangeMode(Id, Mode), + +    /// Fetch the current [`Mode`] of the window. +    FetchMode(Id, oneshot::Sender<Mode>), + +    /// Toggle the window to maximized or back +    ToggleMaximize(Id), + +    /// Toggle whether window has decorations. +    /// +    /// ## Platform-specific +    /// - **X11:** Not implemented. +    /// - **Web:** Unsupported. +    ToggleDecorations(Id), + +    /// Request user attention to the window, this has no effect if the application +    /// is already focused. How requesting for user attention manifests is platform dependent, +    /// see [`UserAttention`] for details. +    /// +    /// Providing `None` will unset the request for user attention. Unsetting the request for +    /// user attention might not be done automatically by the WM when the window receives input. +    /// +    /// ## Platform-specific +    /// +    /// - **iOS / Android / Web:** Unsupported. +    /// - **macOS:** `None` has no effect. +    /// - **X11:** Requests for user attention must be manually cleared. +    /// - **Wayland:** Requires `xdg_activation_v1` protocol, `None` has no effect. +    RequestUserAttention(Id, Option<UserAttention>), + +    /// Bring the window to the front and sets input focus. Has no effect if the window is +    /// already in focus, minimized, or not visible. +    /// +    /// This method steals input focus from other applications. Do not use this method unless +    /// you are certain that's what the user wants. Focus stealing can cause an extremely disruptive +    /// user experience. +    /// +    /// ## Platform-specific +    /// +    /// - **Web / Wayland:** Unsupported. +    GainFocus(Id), + +    /// Change the window [`Level`]. +    ChangeLevel(Id, Level), + +    /// Show the system menu at cursor position. +    /// +    /// ## Platform-specific +    /// Android / iOS / macOS / Orbital / Web / X11: Unsupported. +    ShowSystemMenu(Id), + +    /// Fetch the raw identifier unique to the window. +    FetchRawId(Id, oneshot::Sender<u64>), + +    /// Change the window [`Icon`]. +    /// +    /// On Windows and X11, this is typically the small icon in the top-left +    /// corner of the titlebar. +    /// +    /// ## Platform-specific +    /// +    /// - **Web / Wayland / macOS:** Unsupported. +    /// +    /// - **Windows:** Sets `ICON_SMALL`. The base size for a window icon is 16x16, but it's +    ///   recommended to account for screen scaling and pick a multiple of that, i.e. 32x32. +    /// +    /// - **X11:** Has no universal guidelines for icon sizes, so you're at the whims of the WM. That +    ///   said, it's usually in the same ballpark as on Windows. +    ChangeIcon(Id, Icon), + +    /// Runs the closure with the native window handle of the window with the given [`Id`]. +    RunWithHandle(Id, Box<dyn FnOnce(WindowHandle<'_>) + Send>), + +    /// Screenshot the viewport of the window. +    Screenshot(Id, oneshot::Sender<Screenshot>), +} +  /// Subscribes to the frames of the window of the running application.  ///  /// The resulting [`Subscription`] will produce items at a rate equal to the @@ -34,110 +155,96 @@ pub fn frames() -> Subscription<Instant> {      })  } -/// Spawns a new window with the given `settings`. +/// Opens a new window with the given `settings`.  /// -/// Returns the new window [`Id`] alongside the [`Command`]. -pub fn spawn<Message>(settings: Settings) -> (Id, Command<Message>) { +/// Returns the new window [`Id`] alongside the [`Task`]. +pub fn open<T>(settings: Settings) -> (Id, Task<T>) {      let id = Id::unique();      (          id, -        Command::single(command::Action::Window(Action::Spawn(id, settings))), +        Task::effect(crate::Action::Window(Action::Open(id, settings))),      )  }  /// Closes the window with `id`. -pub fn close<Message>(id: Id) -> Command<Message> { -    Command::single(command::Action::Window(Action::Close(id))) +pub fn close<T>(id: Id) -> Task<T> { +    Task::effect(crate::Action::Window(Action::Close(id)))  }  /// Begins dragging the window while the left mouse button is held. -pub fn drag<Message>(id: Id) -> Command<Message> { -    Command::single(command::Action::Window(Action::Drag(id))) +pub fn drag<T>(id: Id) -> Task<T> { +    Task::effect(crate::Action::Window(Action::Drag(id)))  }  /// Resizes the window to the given logical dimensions. -pub fn resize<Message>(id: Id, new_size: Size) -> Command<Message> { -    Command::single(command::Action::Window(Action::Resize(id, new_size))) +pub fn resize<T>(id: Id, new_size: Size) -> Task<T> { +    Task::effect(crate::Action::Window(Action::Resize(id, new_size)))  }  /// Fetches the window's size in logical dimensions. -pub fn fetch_size<Message>( -    id: Id, -    f: impl FnOnce(Size) -> Message + 'static, -) -> Command<Message> { -    Command::single(command::Action::Window(Action::FetchSize(id, Box::new(f)))) +pub fn fetch_size(id: Id) -> Task<Size> { +    Task::oneshot(move |channel| { +        crate::Action::Window(Action::FetchSize(id, channel)) +    })  }  /// Fetches if the window is maximized. -pub fn fetch_maximized<Message>( -    id: Id, -    f: impl FnOnce(bool) -> Message + 'static, -) -> Command<Message> { -    Command::single(command::Action::Window(Action::FetchMaximized( -        id, -        Box::new(f), -    ))) +pub fn fetch_maximized(id: Id) -> Task<bool> { +    Task::oneshot(move |channel| { +        crate::Action::Window(Action::FetchMaximized(id, channel)) +    })  }  /// Maximizes the window. -pub fn maximize<Message>(id: Id, maximized: bool) -> Command<Message> { -    Command::single(command::Action::Window(Action::Maximize(id, maximized))) +pub fn maximize<T>(id: Id, maximized: bool) -> Task<T> { +    Task::effect(crate::Action::Window(Action::Maximize(id, maximized)))  }  /// Fetches if the window is minimized. -pub fn fetch_minimized<Message>( -    id: Id, -    f: impl FnOnce(Option<bool>) -> Message + 'static, -) -> Command<Message> { -    Command::single(command::Action::Window(Action::FetchMinimized( -        id, -        Box::new(f), -    ))) +pub fn fetch_minimized(id: Id) -> Task<Option<bool>> { +    Task::oneshot(move |channel| { +        crate::Action::Window(Action::FetchMinimized(id, channel)) +    })  }  /// Minimizes the window. -pub fn minimize<Message>(id: Id, minimized: bool) -> Command<Message> { -    Command::single(command::Action::Window(Action::Minimize(id, minimized))) +pub fn minimize<T>(id: Id, minimized: bool) -> Task<T> { +    Task::effect(crate::Action::Window(Action::Minimize(id, minimized)))  }  /// Fetches the current window position in logical coordinates. -pub fn fetch_position<Message>( -    id: Id, -    f: impl FnOnce(Option<Point>) -> Message + 'static, -) -> Command<Message> { -    Command::single(command::Action::Window(Action::FetchPosition( -        id, -        Box::new(f), -    ))) +pub fn fetch_position(id: Id) -> Task<Option<Point>> { +    Task::oneshot(move |channel| { +        crate::Action::Window(Action::FetchPosition(id, channel)) +    })  }  /// Moves the window to the given logical coordinates. -pub fn move_to<Message>(id: Id, position: Point) -> Command<Message> { -    Command::single(command::Action::Window(Action::Move(id, position))) +pub fn move_to<T>(id: Id, position: Point) -> Task<T> { +    Task::effect(crate::Action::Window(Action::Move(id, position)))  }  /// Changes the [`Mode`] of the window. -pub fn change_mode<Message>(id: Id, mode: Mode) -> Command<Message> { -    Command::single(command::Action::Window(Action::ChangeMode(id, mode))) +pub fn change_mode<T>(id: Id, mode: Mode) -> Task<T> { +    Task::effect(crate::Action::Window(Action::ChangeMode(id, mode)))  }  /// Fetches the current [`Mode`] of the window. -pub fn fetch_mode<Message>( -    id: Id, -    f: impl FnOnce(Mode) -> Message + 'static, -) -> Command<Message> { -    Command::single(command::Action::Window(Action::FetchMode(id, Box::new(f)))) +pub fn fetch_mode(id: Id) -> Task<Mode> { +    Task::oneshot(move |channel| { +        crate::Action::Window(Action::FetchMode(id, channel)) +    })  }  /// Toggles the window to maximized or back. -pub fn toggle_maximize<Message>(id: Id) -> Command<Message> { -    Command::single(command::Action::Window(Action::ToggleMaximize(id))) +pub fn toggle_maximize<T>(id: Id) -> Task<T> { +    Task::effect(crate::Action::Window(Action::ToggleMaximize(id)))  }  /// Toggles the window decorations. -pub fn toggle_decorations<Message>(id: Id) -> Command<Message> { -    Command::single(command::Action::Window(Action::ToggleDecorations(id))) +pub fn toggle_decorations<T>(id: Id) -> Task<T> { +    Task::effect(crate::Action::Window(Action::ToggleDecorations(id)))  }  /// Request user attention to the window. This has no effect if the application @@ -146,11 +253,11 @@ pub fn toggle_decorations<Message>(id: Id) -> Command<Message> {  ///  /// Providing `None` will unset the request for user attention. Unsetting the request for  /// user attention might not be done automatically by the WM when the window receives input. -pub fn request_user_attention<Message>( +pub fn request_user_attention<T>(      id: Id,      user_attention: Option<UserAttention>, -) -> Command<Message> { -    Command::single(command::Action::Window(Action::RequestUserAttention( +) -> Task<T> { +    Task::effect(crate::Action::Window(Action::RequestUserAttention(          id,          user_attention,      ))) @@ -159,59 +266,61 @@ pub fn request_user_attention<Message>(  /// Brings the window to the front and sets input focus. Has no effect if the window is  /// already in focus, minimized, or not visible.  /// -/// This [`Command`] steals input focus from other applications. Do not use this method unless +/// This [`Task`] steals input focus from other applications. Do not use this method unless  /// you are certain that's what the user wants. Focus stealing can cause an extremely disruptive  /// user experience. -pub fn gain_focus<Message>(id: Id) -> Command<Message> { -    Command::single(command::Action::Window(Action::GainFocus(id))) +pub fn gain_focus<T>(id: Id) -> Task<T> { +    Task::effect(crate::Action::Window(Action::GainFocus(id)))  }  /// Changes the window [`Level`]. -pub fn change_level<Message>(id: Id, level: Level) -> Command<Message> { -    Command::single(command::Action::Window(Action::ChangeLevel(id, level))) +pub fn change_level<T>(id: Id, level: Level) -> Task<T> { +    Task::effect(crate::Action::Window(Action::ChangeLevel(id, level)))  }  /// Show the [system menu] at cursor position.  ///  /// [system menu]: https://en.wikipedia.org/wiki/Common_menus_in_Microsoft_Windows#System_menu -pub fn show_system_menu<Message>(id: Id) -> Command<Message> { -    Command::single(command::Action::Window(Action::ShowSystemMenu(id))) +pub fn show_system_menu<T>(id: Id) -> Task<T> { +    Task::effect(crate::Action::Window(Action::ShowSystemMenu(id)))  }  /// Fetches an identifier unique to the window, provided by the underlying windowing system. This is  /// not to be confused with [`Id`]. -pub fn fetch_id<Message>( -    id: Id, -    f: impl FnOnce(u64) -> Message + 'static, -) -> Command<Message> { -    Command::single(command::Action::Window(Action::FetchId(id, Box::new(f)))) +pub fn fetch_raw_id<Message>(id: Id) -> Task<u64> { +    Task::oneshot(|channel| { +        crate::Action::Window(Action::FetchRawId(id, channel)) +    })  }  /// Changes the [`Icon`] of the window. -pub fn change_icon<Message>(id: Id, icon: Icon) -> Command<Message> { -    Command::single(command::Action::Window(Action::ChangeIcon(id, icon))) +pub fn change_icon<T>(id: Id, icon: Icon) -> Task<T> { +    Task::effect(crate::Action::Window(Action::ChangeIcon(id, icon)))  }  /// Runs the given callback with the native window handle for the window with the given id.  ///  /// Note that if the window closes before this call is processed the callback will not be run. -pub fn run_with_handle<Message>( +pub fn run_with_handle<T>(      id: Id, -    f: impl FnOnce(WindowHandle<'_>) -> Message + 'static, -) -> Command<Message> { -    Command::single(command::Action::Window(Action::RunWithHandle( -        id, -        Box::new(f), -    ))) +    f: impl FnOnce(WindowHandle<'_>) -> T + MaybeSend + 'static, +) -> Task<T> +where +    T: MaybeSend + 'static, +{ +    Task::oneshot(move |channel| { +        crate::Action::Window(Action::RunWithHandle( +            id, +            Box::new(move |handle| { +                let _ = channel.send(f(handle)); +            }), +        )) +    })  }  /// Captures a [`Screenshot`] from the window. -pub fn screenshot<Message>( -    id: Id, -    f: impl FnOnce(Screenshot) -> Message + Send + 'static, -) -> Command<Message> { -    Command::single(command::Action::Window(Action::Screenshot( -        id, -        Box::new(f), -    ))) +pub fn screenshot(id: Id) -> Task<Screenshot> { +    Task::oneshot(move |channel| { +        crate::Action::Window(Action::Screenshot(id, channel)) +    })  } diff --git a/runtime/src/window/action.rs b/runtime/src/window/action.rs deleted file mode 100644 index 07e77872..00000000 --- a/runtime/src/window/action.rs +++ /dev/null @@ -1,230 +0,0 @@ -use crate::core::window::{Icon, Id, Level, Mode, Settings, UserAttention}; -use crate::core::{Point, Size}; -use crate::futures::MaybeSend; -use crate::window::Screenshot; - -use raw_window_handle::WindowHandle; - -use std::fmt; - -/// An operation to be performed on some window. -pub enum Action<T> { -    /// Spawns a new window with some [`Settings`]. -    Spawn(Id, Settings), -    /// Close the window and exits the application. -    Close(Id), -    /// Move the window with the left mouse button until the button is -    /// released. -    /// -    /// There’s no guarantee that this will work unless the left mouse -    /// button was pressed immediately before this function is called. -    Drag(Id), -    /// Resize the window to the given logical dimensions. -    Resize(Id, Size), -    /// Fetch the current logical dimensions of the window. -    FetchSize(Id, Box<dyn FnOnce(Size) -> T + 'static>), -    /// Fetch if the current window is maximized or not. -    /// -    /// ## Platform-specific -    /// - **iOS / Android / Web:** Unsupported. -    FetchMaximized(Id, Box<dyn FnOnce(bool) -> T + 'static>), -    /// Set the window to maximized or back -    Maximize(Id, bool), -    /// Fetch if the current window is minimized or not. -    /// -    /// ## Platform-specific -    /// - **Wayland:** Always `None`. -    /// - **iOS / Android / Web:** Unsupported. -    FetchMinimized(Id, Box<dyn FnOnce(Option<bool>) -> T + 'static>), -    /// Set the window to minimized or back -    Minimize(Id, bool), -    /// Fetch the current logical coordinates of the window. -    FetchPosition(Id, Box<dyn FnOnce(Option<Point>) -> T + 'static>), -    /// Move the window to the given logical coordinates. -    /// -    /// Unsupported on Wayland. -    Move(Id, Point), -    /// Change the [`Mode`] of the window. -    ChangeMode(Id, Mode), -    /// Fetch the current [`Mode`] of the window. -    FetchMode(Id, Box<dyn FnOnce(Mode) -> T + 'static>), -    /// Toggle the window to maximized or back -    ToggleMaximize(Id), -    /// Toggle whether window has decorations. -    /// -    /// ## Platform-specific -    /// - **X11:** Not implemented. -    /// - **Web:** Unsupported. -    ToggleDecorations(Id), -    /// Request user attention to the window, this has no effect if the application -    /// is already focused. How requesting for user attention manifests is platform dependent, -    /// see [`UserAttention`] for details. -    /// -    /// Providing `None` will unset the request for user attention. Unsetting the request for -    /// user attention might not be done automatically by the WM when the window receives input. -    /// -    /// ## Platform-specific -    /// -    /// - **iOS / Android / Web:** Unsupported. -    /// - **macOS:** `None` has no effect. -    /// - **X11:** Requests for user attention must be manually cleared. -    /// - **Wayland:** Requires `xdg_activation_v1` protocol, `None` has no effect. -    RequestUserAttention(Id, Option<UserAttention>), -    /// Bring the window to the front and sets input focus. Has no effect if the window is -    /// already in focus, minimized, or not visible. -    /// -    /// This method steals input focus from other applications. Do not use this method unless -    /// you are certain that's what the user wants. Focus stealing can cause an extremely disruptive -    /// user experience. -    /// -    /// ## Platform-specific -    /// -    /// - **Web / Wayland:** Unsupported. -    GainFocus(Id), -    /// Change the window [`Level`]. -    ChangeLevel(Id, Level), -    /// Show the system menu at cursor position. -    /// -    /// ## Platform-specific -    /// Android / iOS / macOS / Orbital / Web / X11: Unsupported. -    ShowSystemMenu(Id), -    /// Fetch the raw identifier unique to the window. -    FetchId(Id, Box<dyn FnOnce(u64) -> T + 'static>), -    /// Change the window [`Icon`]. -    /// -    /// On Windows and X11, this is typically the small icon in the top-left -    /// corner of the titlebar. -    /// -    /// ## Platform-specific -    /// -    /// - **Web / Wayland / macOS:** Unsupported. -    /// -    /// - **Windows:** Sets `ICON_SMALL`. The base size for a window icon is 16x16, but it's -    ///   recommended to account for screen scaling and pick a multiple of that, i.e. 32x32. -    /// -    /// - **X11:** Has no universal guidelines for icon sizes, so you're at the whims of the WM. That -    ///   said, it's usually in the same ballpark as on Windows. -    ChangeIcon(Id, Icon), -    /// Runs the closure with the native window handle of the window with the given [`Id`]. -    RunWithHandle(Id, Box<dyn FnOnce(WindowHandle<'_>) -> T + 'static>), -    /// Screenshot the viewport of the window. -    Screenshot(Id, Box<dyn FnOnce(Screenshot) -> T + 'static>), -} - -impl<T> Action<T> { -    /// Maps the output of a window [`Action`] using the provided closure. -    pub fn map<A>( -        self, -        f: impl Fn(T) -> A + 'static + MaybeSend + Sync, -    ) -> Action<A> -    where -        T: 'static, -    { -        match self { -            Self::Spawn(id, settings) => Action::Spawn(id, settings), -            Self::Close(id) => Action::Close(id), -            Self::Drag(id) => Action::Drag(id), -            Self::Resize(id, size) => Action::Resize(id, size), -            Self::FetchSize(id, o) => { -                Action::FetchSize(id, Box::new(move |s| f(o(s)))) -            } -            Self::FetchMaximized(id, o) => { -                Action::FetchMaximized(id, Box::new(move |s| f(o(s)))) -            } -            Self::Maximize(id, maximized) => Action::Maximize(id, maximized), -            Self::FetchMinimized(id, o) => { -                Action::FetchMinimized(id, Box::new(move |s| f(o(s)))) -            } -            Self::Minimize(id, minimized) => Action::Minimize(id, minimized), -            Self::FetchPosition(id, o) => { -                Action::FetchPosition(id, Box::new(move |s| f(o(s)))) -            } -            Self::Move(id, position) => Action::Move(id, position), -            Self::ChangeMode(id, mode) => Action::ChangeMode(id, mode), -            Self::FetchMode(id, o) => { -                Action::FetchMode(id, Box::new(move |s| f(o(s)))) -            } -            Self::ToggleMaximize(id) => Action::ToggleMaximize(id), -            Self::ToggleDecorations(id) => Action::ToggleDecorations(id), -            Self::RequestUserAttention(id, attention_type) => { -                Action::RequestUserAttention(id, attention_type) -            } -            Self::GainFocus(id) => Action::GainFocus(id), -            Self::ChangeLevel(id, level) => Action::ChangeLevel(id, level), -            Self::ShowSystemMenu(id) => Action::ShowSystemMenu(id), -            Self::FetchId(id, o) => { -                Action::FetchId(id, Box::new(move |s| f(o(s)))) -            } -            Self::ChangeIcon(id, icon) => Action::ChangeIcon(id, icon), -            Self::RunWithHandle(id, o) => { -                Action::RunWithHandle(id, Box::new(move |s| f(o(s)))) -            } -            Self::Screenshot(id, tag) => Action::Screenshot( -                id, -                Box::new(move |screenshot| f(tag(screenshot))), -            ), -        } -    } -} - -impl<T> fmt::Debug for Action<T> { -    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { -        match self { -            Self::Spawn(id, settings) => { -                write!(f, "Action::Spawn({id:?}, {settings:?})") -            } -            Self::Close(id) => write!(f, "Action::Close({id:?})"), -            Self::Drag(id) => write!(f, "Action::Drag({id:?})"), -            Self::Resize(id, size) => { -                write!(f, "Action::Resize({id:?}, {size:?})") -            } -            Self::FetchSize(id, _) => write!(f, "Action::FetchSize({id:?})"), -            Self::FetchMaximized(id, _) => { -                write!(f, "Action::FetchMaximized({id:?})") -            } -            Self::Maximize(id, maximized) => { -                write!(f, "Action::Maximize({id:?}, {maximized})") -            } -            Self::FetchMinimized(id, _) => { -                write!(f, "Action::FetchMinimized({id:?})") -            } -            Self::Minimize(id, minimized) => { -                write!(f, "Action::Minimize({id:?}, {minimized}") -            } -            Self::FetchPosition(id, _) => { -                write!(f, "Action::FetchPosition({id:?})") -            } -            Self::Move(id, position) => { -                write!(f, "Action::Move({id:?}, {position})") -            } -            Self::ChangeMode(id, mode) => { -                write!(f, "Action::SetMode({id:?}, {mode:?})") -            } -            Self::FetchMode(id, _) => write!(f, "Action::FetchMode({id:?})"), -            Self::ToggleMaximize(id) => { -                write!(f, "Action::ToggleMaximize({id:?})") -            } -            Self::ToggleDecorations(id) => { -                write!(f, "Action::ToggleDecorations({id:?})") -            } -            Self::RequestUserAttention(id, _) => { -                write!(f, "Action::RequestUserAttention({id:?})") -            } -            Self::GainFocus(id) => write!(f, "Action::GainFocus({id:?})"), -            Self::ChangeLevel(id, level) => { -                write!(f, "Action::ChangeLevel({id:?}, {level:?})") -            } -            Self::ShowSystemMenu(id) => { -                write!(f, "Action::ShowSystemMenu({id:?})") -            } -            Self::FetchId(id, _) => write!(f, "Action::FetchId({id:?})"), -            Self::ChangeIcon(id, _icon) => { -                write!(f, "Action::ChangeIcon({id:?})") -            } -            Self::RunWithHandle(id, _) => { -                write!(f, "Action::RunWithHandle({id:?})") -            } -            Self::Screenshot(id, _) => write!(f, "Action::Screenshot({id:?})"), -        } -    } -}  | 
