diff options
author | 2021-09-13 11:49:06 +0700 | |
---|---|---|
committer | 2021-09-13 11:49:06 +0700 | |
commit | 93fec8d273ef8305e1c2456abe0c8ecd7a9d9407 (patch) | |
tree | c0c2445703133293b13657ab4f9c1c936e9cd688 | |
parent | 589f68df0f647d93f2b9dd7bf29cfacb0201351c (diff) | |
parent | 01b945b9814b9dc546e783a6dab66e4f7fe49786 (diff) | |
download | iced-93fec8d273ef8305e1c2456abe0c8ecd7a9d9407.tar.gz iced-93fec8d273ef8305e1c2456abe0c8ecd7a9d9407.tar.bz2 iced-93fec8d273ef8305e1c2456abe0c8ecd7a9d9407.zip |
Merge pull request #1019 from hecrj/command-actions
Platform-specific `Command` implementations
39 files changed, 556 insertions, 323 deletions
diff --git a/examples/clock/src/main.rs b/examples/clock/src/main.rs index 9bcc827b..b317ac00 100644 --- a/examples/clock/src/main.rs +++ b/examples/clock/src/main.rs @@ -1,7 +1,7 @@ use iced::{ canvas::{self, Cache, Canvas, Cursor, Geometry, LineCap, Path, Stroke}, - executor, time, Application, Clipboard, Color, Command, Container, Element, - Length, Point, Rectangle, Settings, Subscription, Vector, + executor, time, Application, Color, Command, Container, Element, Length, + Point, Rectangle, Settings, Subscription, Vector, }; pub fn main() -> iced::Result { @@ -40,11 +40,7 @@ impl Application for Clock { String::from("Clock - Iced") } - fn update( - &mut self, - message: Message, - _clipboard: &mut Clipboard, - ) -> Command<Message> { + fn update(&mut self, message: Message) -> Command<Message> { match message { Message::Tick(local_time) => { let now = local_time; diff --git a/examples/download_progress/src/main.rs b/examples/download_progress/src/main.rs index 6f844e66..cd024926 100644 --- a/examples/download_progress/src/main.rs +++ b/examples/download_progress/src/main.rs @@ -1,6 +1,6 @@ use iced::{ - button, executor, Align, Application, Button, Clipboard, Column, Command, - Container, Element, Length, ProgressBar, Settings, Subscription, Text, + button, executor, Align, Application, Button, Column, Command, Container, + Element, Length, ProgressBar, Settings, Subscription, Text, }; mod download; @@ -43,11 +43,7 @@ impl Application for Example { String::from("Download progress - Iced") } - fn update( - &mut self, - message: Message, - _clipboard: &mut Clipboard, - ) -> Command<Message> { + fn update(&mut self, message: Message) -> Command<Message> { match message { Message::Add => { self.last_id = self.last_id + 1; diff --git a/examples/events/src/main.rs b/examples/events/src/main.rs index 446c190b..911ff425 100644 --- a/examples/events/src/main.rs +++ b/examples/events/src/main.rs @@ -1,7 +1,7 @@ use iced::{ - button, executor, Align, Application, Button, Checkbox, Clipboard, Column, - Command, Container, Element, HorizontalAlignment, Length, Settings, - Subscription, Text, + button, executor, Align, Application, Button, Checkbox, Column, Command, + Container, Element, HorizontalAlignment, Length, Settings, Subscription, + Text, }; use iced_native::{window, Event}; @@ -40,11 +40,7 @@ impl Application for Events { String::from("Events - Iced") } - fn update( - &mut self, - message: Message, - _clipboard: &mut Clipboard, - ) -> Command<Message> { + fn update(&mut self, message: Message) -> Command<Message> { match message { Message::EventOccurred(event) if self.enabled => { self.last.push(event); diff --git a/examples/game_of_life/src/main.rs b/examples/game_of_life/src/main.rs index c3e16e8b..2d46634f 100644 --- a/examples/game_of_life/src/main.rs +++ b/examples/game_of_life/src/main.rs @@ -12,8 +12,8 @@ use iced::slider::{self, Slider}; use iced::time; use iced::window; use iced::{ - Align, Application, Checkbox, Clipboard, Column, Command, Container, - Element, Length, Row, Settings, Subscription, Text, + Align, Application, Checkbox, Column, Command, Container, Element, Length, + Row, Settings, Subscription, Text, }; use preset::Preset; use std::time::{Duration, Instant}; @@ -71,11 +71,7 @@ impl Application for GameOfLife { String::from("Game of Life - Iced") } - fn update( - &mut self, - message: Message, - _clipboard: &mut Clipboard, - ) -> Command<Message> { + fn update(&mut self, message: Message) -> Command<Message> { match message { Message::Grid(message, version) => { if version == self.version { diff --git a/examples/integration_opengl/src/controls.rs b/examples/integration_opengl/src/controls.rs index 13b7fbc2..ddc6827c 100644 --- a/examples/integration_opengl/src/controls.rs +++ b/examples/integration_opengl/src/controls.rs @@ -1,7 +1,7 @@ use iced_glow::Renderer; use iced_glutin::{ - slider, Align, Clipboard, Color, Column, Command, Element, Length, Program, - Row, Slider, Text, + slider, Align, Color, Column, Command, Element, Length, Program, Row, + Slider, Text, }; pub struct Controls { @@ -30,13 +30,8 @@ impl Controls { impl Program for Controls { type Renderer = Renderer; type Message = Message; - type Clipboard = Clipboard; - fn update( - &mut self, - message: Message, - _clipboard: &mut Clipboard, - ) -> Command<Message> { + fn update(&mut self, message: Message) -> Command<Message> { match message { Message::BackgroundColorChanged(color) => { self.background_color = color; diff --git a/examples/integration_wgpu/src/controls.rs b/examples/integration_wgpu/src/controls.rs index 36ee9b7e..824f9f53 100644 --- a/examples/integration_wgpu/src/controls.rs +++ b/examples/integration_wgpu/src/controls.rs @@ -1,7 +1,7 @@ use iced_wgpu::Renderer; use iced_winit::{ - slider, Align, Clipboard, Color, Column, Command, Element, Length, Program, - Row, Slider, Text, + slider, Align, Color, Column, Command, Element, Length, Program, Row, + Slider, Text, }; pub struct Controls { @@ -30,13 +30,8 @@ impl Controls { impl Program for Controls { type Renderer = Renderer; type Message = Message; - type Clipboard = Clipboard; - fn update( - &mut self, - message: Message, - _clipboard: &mut Clipboard, - ) -> Command<Message> { + fn update(&mut self, message: Message) -> Command<Message> { match message { Message::BackgroundColorChanged(color) => { self.background_color = color; diff --git a/examples/menu/src/main.rs b/examples/menu/src/main.rs index 7403713c..810afa00 100644 --- a/examples/menu/src/main.rs +++ b/examples/menu/src/main.rs @@ -1,7 +1,6 @@ use iced::menu::{self, Menu}; use iced::{ - executor, Application, Clipboard, Command, Container, Element, Length, - Settings, Text, + executor, Application, Command, Container, Element, Length, Settings, Text, }; use iced_native::keyboard::{Hotkey, KeyCode, Modifiers}; @@ -92,11 +91,7 @@ impl Application for App { ]) } - fn update( - &mut self, - message: Message, - _clipboard: &mut Clipboard, - ) -> Command<Message> { + fn update(&mut self, message: Message) -> Command<Message> { match message { Message::MenuActivated(entry) => self.selected = Some(entry), } diff --git a/examples/pane_grid/src/main.rs b/examples/pane_grid/src/main.rs index 3bd8aa25..5a134756 100644 --- a/examples/pane_grid/src/main.rs +++ b/examples/pane_grid/src/main.rs @@ -1,8 +1,7 @@ use iced::{ button, executor, keyboard, pane_grid, scrollable, Align, Application, - Button, Clipboard, Color, Column, Command, Container, Element, - HorizontalAlignment, Length, PaneGrid, Row, Scrollable, Settings, - Subscription, Text, + Button, Color, Column, Command, Container, Element, HorizontalAlignment, + Length, PaneGrid, Row, Scrollable, Settings, Subscription, Text, }; use iced_native::{event, subscription, Event}; @@ -51,11 +50,7 @@ impl Application for Example { String::from("Pane grid - Iced") } - fn update( - &mut self, - message: Message, - _clipboard: &mut Clipboard, - ) -> Command<Message> { + fn update(&mut self, message: Message) -> Command<Message> { match message { Message::Split(axis, pane) => { let result = self.panes.split( diff --git a/examples/pokedex/src/main.rs b/examples/pokedex/src/main.rs index da1d5d5d..fdf667cc 100644 --- a/examples/pokedex/src/main.rs +++ b/examples/pokedex/src/main.rs @@ -1,6 +1,6 @@ use iced::{ - button, futures, image, Align, Application, Button, Clipboard, Column, - Command, Container, Element, Length, Row, Settings, Text, + button, futures, image, Align, Application, Button, Column, Command, + Container, Element, Length, Row, Settings, Text, }; pub fn main() -> iced::Result { @@ -48,11 +48,7 @@ impl Application for Pokedex { format!("{} - Pokédex", subtitle) } - fn update( - &mut self, - message: Message, - _clipboard: &mut Clipboard, - ) -> Command<Message> { + fn update(&mut self, message: Message) -> Command<Message> { match message { Message::PokemonFound(Ok(pokemon)) => { *self = Pokedex::Loaded { diff --git a/examples/solar_system/src/main.rs b/examples/solar_system/src/main.rs index 8f844828..c8f74978 100644 --- a/examples/solar_system/src/main.rs +++ b/examples/solar_system/src/main.rs @@ -8,8 +8,8 @@ //! [1]: https://developer.mozilla.org/en-US/docs/Web/API/Canvas_API/Tutorial/Basic_animations#An_animated_solar_system use iced::{ canvas::{self, Cursor, Path, Stroke}, - executor, time, window, Application, Canvas, Clipboard, Color, Command, - Element, Length, Point, Rectangle, Settings, Size, Subscription, Vector, + executor, time, window, Application, Canvas, Color, Command, Element, + Length, Point, Rectangle, Settings, Size, Subscription, Vector, }; use std::time::Instant; @@ -48,11 +48,7 @@ impl Application for SolarSystem { String::from("Solar system - Iced") } - fn update( - &mut self, - message: Message, - _clipboard: &mut Clipboard, - ) -> Command<Message> { + fn update(&mut self, message: Message) -> Command<Message> { match message { Message::Tick(instant) => { self.state.update(instant); diff --git a/examples/stopwatch/src/main.rs b/examples/stopwatch/src/main.rs index 51972e01..983cf3e6 100644 --- a/examples/stopwatch/src/main.rs +++ b/examples/stopwatch/src/main.rs @@ -1,6 +1,6 @@ use iced::{ - button, executor, time, Align, Application, Button, Clipboard, Column, - Command, Container, Element, HorizontalAlignment, Length, Row, Settings, + button, executor, time, Align, Application, Button, Column, Command, + Container, Element, HorizontalAlignment, Length, Row, Settings, Subscription, Text, }; use std::time::{Duration, Instant}; @@ -49,11 +49,7 @@ impl Application for Stopwatch { String::from("Stopwatch - Iced") } - fn update( - &mut self, - message: Message, - _clipboard: &mut Clipboard, - ) -> Command<Message> { + fn update(&mut self, message: Message) -> Command<Message> { match message { Message::Toggle => match self.state { State::Idle => { diff --git a/examples/todos/src/main.rs b/examples/todos/src/main.rs index 97415475..7a8ecc1a 100644 --- a/examples/todos/src/main.rs +++ b/examples/todos/src/main.rs @@ -1,7 +1,7 @@ use iced::{ button, scrollable, text_input, Align, Application, Button, Checkbox, - Clipboard, Column, Command, Container, Element, Font, HorizontalAlignment, - Length, Row, Scrollable, Settings, Text, TextInput, + Column, Command, Container, Element, Font, HorizontalAlignment, Length, + Row, Scrollable, Settings, Text, TextInput, }; use serde::{Deserialize, Serialize}; @@ -58,11 +58,7 @@ impl Application for Todos { format!("Todos{} - Iced", if dirty { "*" } else { "" }) } - fn update( - &mut self, - message: Message, - _clipboard: &mut Clipboard, - ) -> Command<Message> { + fn update(&mut self, message: Message) -> Command<Message> { match self { Todos::Loading => { match message { diff --git a/examples/url_handler/src/main.rs b/examples/url_handler/src/main.rs index f14e5227..ee2d249a 100644 --- a/examples/url_handler/src/main.rs +++ b/examples/url_handler/src/main.rs @@ -1,6 +1,6 @@ use iced::{ - executor, Application, Clipboard, Command, Container, Element, Length, - Settings, Subscription, Text, + executor, Application, Command, Container, Element, Length, Settings, + Subscription, Text, }; use iced_native::{ event::{MacOS, PlatformSpecific}, @@ -34,11 +34,7 @@ impl Application for App { String::from("Url - Iced") } - fn update( - &mut self, - message: Message, - _clipboard: &mut Clipboard, - ) -> Command<Message> { + fn update(&mut self, message: Message) -> Command<Message> { match message { Message::EventOccurred(event) => { if let Event::PlatformSpecific(PlatformSpecific::MacOS( diff --git a/futures/src/command.rs b/futures/src/command.rs index b06ab3f8..d8adfe49 100644 --- a/futures/src/command.rs +++ b/futures/src/command.rs @@ -1,138 +1,69 @@ -use crate::BoxFuture; -use futures::future::{Future, FutureExt}; +/// A set of asynchronous actions to be performed by some runtime. +#[derive(Debug)] +pub struct Command<T>(Internal<T>); -/// A collection of async operations. -/// -/// You should be able to turn a future easily into a [`Command`], either by -/// using the `From` trait or [`Command::perform`]. -pub struct Command<T> { - futures: Vec<BoxFuture<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 fn none() -> Self { - Self { - futures: Vec::new(), - } + pub const fn none() -> Self { + Self(Internal::None) } - /// Creates a [`Command`] that performs the action of the given future. - #[cfg(not(target_arch = "wasm32"))] - pub fn perform<A>( - future: impl Future<Output = T> + 'static + Send, - f: impl Fn(T) -> A + 'static + Send, - ) -> Command<A> { - Command { - futures: vec![Box::pin(future.map(f))], - } - } - - /// Creates a [`Command`] that performs the action of the given future. - #[cfg(target_arch = "wasm32")] - pub fn perform<A>( - future: impl Future<Output = T> + 'static, - f: impl Fn(T) -> A + 'static + Send, - ) -> Command<A> { - Command { - futures: vec![Box::pin(future.map(f))], - } + /// Creates a [`Command`] that performs a single [`Action`]. + pub const fn single(action: T) -> Self { + Self(Internal::Single(action)) } - /// Applies a transformation to the result of a [`Command`]. - #[cfg(not(target_arch = "wasm32"))] - pub fn map<A>( - mut self, - f: impl Fn(T) -> A + 'static + Send + Sync, - ) -> Command<A> - where - T: 'static, - { - let f = std::sync::Arc::new(f); - - Command { - futures: self - .futures - .drain(..) - .map(|future| { - let f = f.clone(); + /// 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(); - Box::pin(future.map(move |result| f(result))) - as BoxFuture<A> - }) - .collect(), + 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`]. - #[cfg(target_arch = "wasm32")] - pub fn map<A>(mut self, f: impl Fn(T) -> A + 'static) -> Command<A> + pub fn map<A>(self, f: impl Fn(T) -> A) -> Command<A> where T: 'static, { - let f = std::rc::Rc::new(f); - - Command { - futures: self - .futures - .drain(..) - .map(|future| { - let f = f.clone(); + let Command(command) = self; - Box::pin(future.map(move |result| f(result))) - as BoxFuture<A> - }) - .collect(), + match command { + Internal::None => Command::none(), + Internal::Single(action) => Command::single(f(action)), + Internal::Batch(batch) => { + Command(Internal::Batch(batch.into_iter().map(f).collect())) + } } } - /// 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 { - Self { - futures: commands - .into_iter() - .flat_map(|command| command.futures) - .collect(), - } - } + /// Returns all of the actions of the [`Command`]. + pub fn actions(self) -> Vec<T> { + let Command(command) = self; - /// Converts a [`Command`] into its underlying list of futures. - pub fn futures(self) -> Vec<BoxFuture<T>> { - self.futures - } -} - -#[cfg(not(target_arch = "wasm32"))] -impl<T, A> From<A> for Command<T> -where - A: Future<Output = T> + 'static + Send, -{ - fn from(future: A) -> Self { - Self { - futures: vec![future.boxed()], - } - } -} - -#[cfg(target_arch = "wasm32")] -impl<T, A> From<A> for Command<T> -where - A: Future<Output = T> + 'static, -{ - fn from(future: A) -> Self { - Self { - futures: vec![future.boxed_local()], + match command { + Internal::None => Vec::new(), + Internal::Single(action) => vec![action], + Internal::Batch(batch) => batch, } } } - -impl<T> std::fmt::Debug for Command<T> { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.debug_struct("Command").finish() - } -} diff --git a/futures/src/runtime.rs b/futures/src/runtime.rs index e56a4eb0..7779e235 100644 --- a/futures/src/runtime.rs +++ b/futures/src/runtime.rs @@ -1,5 +1,6 @@ //! Run commands and keep track of subscriptions. -use crate::{subscription, Command, Executor, Subscription}; +use crate::BoxFuture; +use crate::{subscription, Executor, Subscription}; use futures::{channel::mpsc, Sink}; use std::marker::PhantomData; @@ -51,22 +52,18 @@ where /// /// The resulting `Message` will be forwarded to the `Sender` of the /// [`Runtime`]. - pub fn spawn(&mut self, command: Command<Message>) { + pub fn spawn(&mut self, future: BoxFuture<Message>) { use futures::{FutureExt, SinkExt}; - let futures = command.futures(); + let mut sender = self.sender.clone(); - for future in futures { - let mut sender = self.sender.clone(); - - let future = future.then(|message| async move { - let _ = sender.send(message).await; + let future = future.then(|message| async move { + let _ = sender.send(message).await; - () - }); + () + }); - self.executor.spawn(future); - } + self.executor.spawn(future); } /// Tracks a [`Subscription`] in the [`Runtime`]. diff --git a/glutin/src/application.rs b/glutin/src/application.rs index 4a5f4bd2..936f0cce 100644 --- a/glutin/src/application.rs +++ b/glutin/src/application.rs @@ -33,6 +33,8 @@ where debug.startup_started(); let event_loop = EventLoop::with_user_event(); + let mut proxy = event_loop.create_proxy(); + let mut runtime = { let executor = E::new().map_err(Error::ExecutorCreationFailed)?; let proxy = Proxy::new(event_loop.create_proxy()); @@ -48,9 +50,6 @@ where let subscription = application.subscription(); - runtime.spawn(init_command); - runtime.track(subscription); - let context = { let builder = settings .window @@ -90,6 +89,17 @@ where })? }; + let mut clipboard = Clipboard::connect(context.window()); + + application::run_command( + init_command, + &mut runtime, + &mut clipboard, + &mut proxy, + context.window(), + ); + runtime.track(subscription); + let (mut sender, receiver) = mpsc::unbounded(); let mut instance = Box::pin(run_instance::<A, E, C>( @@ -97,6 +107,8 @@ where compositor, renderer, runtime, + clipboard, + proxy, debug, receiver, context, @@ -145,6 +157,8 @@ async fn run_instance<A, E, C>( mut compositor: C, mut renderer: A::Renderer, mut runtime: Runtime<E, Proxy<A::Message>, A::Message>, + mut clipboard: Clipboard, + mut proxy: glutin::event_loop::EventLoopProxy<A::Message>, mut debug: Debug, mut receiver: mpsc::UnboundedReceiver<glutin::event::Event<'_, A::Message>>, mut context: glutin::ContextWrapper<glutin::PossiblyCurrent, Window>, @@ -157,8 +171,6 @@ async fn run_instance<A, E, C>( use glutin::event; use iced_winit::futures::stream::StreamExt; - let mut clipboard = Clipboard::connect(context.window()); - let mut state = application::State::new(&application, context.window()); let mut viewport_version = state.viewport_version(); let mut user_interface = @@ -210,9 +222,11 @@ async fn run_instance<A, E, C>( application::update( &mut application, &mut runtime, - &mut debug, &mut clipboard, + &mut proxy, + &mut debug, &mut messages, + context.window(), ); // Update window diff --git a/glutin/src/lib.rs b/glutin/src/lib.rs index 107d9556..7c0c00ca 100644 --- a/glutin/src/lib.rs +++ b/glutin/src/lib.rs @@ -16,10 +16,14 @@ pub use iced_native::*; pub mod application; +pub use iced_winit::clipboard; pub use iced_winit::settings; -pub use iced_winit::{Clipboard, Error, Mode}; +pub use iced_winit::window; +pub use iced_winit::{Error, Mode}; #[doc(no_inline)] pub use application::Application; #[doc(no_inline)] +pub use clipboard::Clipboard; +#[doc(no_inline)] pub use settings::Settings; diff --git a/native/src/clipboard.rs b/native/src/clipboard.rs index 081b4004..60703c31 100644 --- a/native/src/clipboard.rs +++ b/native/src/clipboard.rs @@ -1,4 +1,5 @@ //! Access the clipboard. +use std::fmt; /// A buffer for short-term storage and transfer within and between /// applications. @@ -21,3 +22,36 @@ impl Clipboard for Null { fn write(&mut self, _contents: String) {} } + +/// A clipboard action to be performed by some [`Command`]. +/// +/// [`Command`]: crate::Command +pub enum Action<T> { + /// Read the clipboard and produce `T` with the result. + Read(Box<dyn Fn(Option<String>) -> T>), + + /// Write the given contents to the clipboard. + Write(String), +} + +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 + Send + Sync) -> Action<A> + where + T: 'static, + { + match self { + Self::Read(o) => Action::Read(Box::new(move |s| f(o(s)))), + Self::Write(content) => Action::Write(content), + } + } +} + +impl<T> fmt::Debug for Action<T> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Self::Read(_) => write!(f, "Action::Read"), + Self::Write(_) => write!(f, "Action::Write"), + } + } +} diff --git a/native/src/command.rs b/native/src/command.rs new file mode 100644 index 00000000..6fe518d7 --- /dev/null +++ b/native/src/command.rs @@ -0,0 +1,72 @@ +//! Run asynchronous actions. +mod action; + +pub use action::Action; + +use std::fmt; +use std::future::Future; + +/// A set of asynchronous actions to be performed by some runtime. +pub struct Command<T>(iced_futures::Command<Action<T>>); + +impl<T> Command<T> { + /// Creates an empty [`Command`]. + /// + /// In other words, a [`Command`] that does nothing. + pub const fn none() -> Self { + Self(iced_futures::Command::none()) + } + + /// Creates a [`Command`] that performs a single [`Action`]. + pub const fn single(action: Action<T>) -> Self { + Self(iced_futures::Command::single(action)) + } + + /// Creates a [`Command`] that performs the action of the given future. + pub fn perform<A>( + future: impl Future<Output = T> + 'static + Send, + f: impl Fn(T) -> A + 'static + Send, + ) -> Command<A> { + use iced_futures::futures::FutureExt; + + Command::single(Action::Future(Box::pin(future.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 { + Self(iced_futures::Command::batch( + commands.into_iter().map(|Command(command)| command), + )) + } + + /// Applies a transformation to the result of a [`Command`]. + pub fn map<A>( + self, + f: impl Fn(T) -> A + 'static + Send + Sync + Clone, + ) -> Command<A> + where + T: 'static, + { + let Command(command) = self; + + Command(command.map(move |action| action.map(f.clone()))) + } + + /// Returns all of the actions of the [`Command`]. + pub fn actions(self) -> Vec<Action<T>> { + let Command(command) = self; + + command.actions() + } +} + +impl<T> fmt::Debug for Command<T> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let Command(command) = self; + + command.fmt(f) + } +} diff --git a/native/src/command/action.rs b/native/src/command/action.rs new file mode 100644 index 00000000..77be1b59 --- /dev/null +++ b/native/src/command/action.rs @@ -0,0 +1,46 @@ +use crate::clipboard; +use crate::window; + +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<T>), + + /// Run a clipboard action. + Clipboard(clipboard::Action<T>), + + /// Run a window action. + Window(window::Action), +} + +impl<T> Action<T> { + /// Applies a transformation to the result of a [`Command`]. + pub fn map<A>(self, f: impl Fn(T) -> A + 'static + Send + Sync) -> Action<A> + where + T: 'static, + { + use iced_futures::futures::FutureExt; + + match self { + Self::Future(future) => Action::Future(Box::pin(future.map(f))), + Self::Clipboard(action) => Action::Clipboard(action.map(f)), + Self::Window(window) => Action::Window(window), + } + } +} + +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::Clipboard(action) => { + write!(f, "Action::Clipboard({:?})", action) + } + Self::Window(action) => write!(f, "Action::Window({:?})", action), + } + } +} diff --git a/native/src/lib.rs b/native/src/lib.rs index cbb02506..573be51d 100644 --- a/native/src/lib.rs +++ b/native/src/lib.rs @@ -34,6 +34,7 @@ #![forbid(unsafe_code)] #![forbid(rust_2018_idioms)] pub mod clipboard; +pub mod command; pub mod event; pub mod keyboard; pub mod layout; @@ -64,12 +65,13 @@ pub use iced_core::{ menu, Align, Background, Color, Font, HorizontalAlignment, Length, Menu, Padding, Point, Rectangle, Size, Vector, VerticalAlignment, }; -pub use iced_futures::{executor, futures, Command}; +pub use iced_futures::{executor, futures}; #[doc(no_inline)] pub use executor::Executor; pub use clipboard::Clipboard; +pub use command::Command; pub use debug::Debug; pub use element::Element; pub use event::Event; diff --git a/native/src/program.rs b/native/src/program.rs index 75fab094..fa83c0b1 100644 --- a/native/src/program.rs +++ b/native/src/program.rs @@ -1,5 +1,5 @@ //! Build interactive programs using The Elm Architecture. -use crate::{Clipboard, Command, Element, Renderer}; +use crate::{Command, Element, Renderer}; mod state; @@ -13,9 +13,6 @@ pub trait Program: Sized { /// The type of __messages__ your [`Program`] will produce. type Message: std::fmt::Debug + Clone + Send; - /// The type of [`Clipboard`] your [`Program`] will use. - type Clipboard: Clipboard; - /// Handles a __message__ and updates the state of the [`Program`]. /// /// This is where you define your __update logic__. All the __messages__, @@ -24,11 +21,7 @@ pub trait Program: Sized { /// /// Any [`Command`] returned will be executed immediately in the /// background by shells. - fn update( - &mut self, - message: Self::Message, - clipboard: &mut Self::Clipboard, - ) -> Command<Self::Message>; + fn update(&mut self, message: Self::Message) -> Command<Self::Message>; /// Returns the widgets to display in the [`Program`]. /// diff --git a/native/src/program/state.rs b/native/src/program/state.rs index fd1f2b52..3f5f6069 100644 --- a/native/src/program/state.rs +++ b/native/src/program/state.rs @@ -1,5 +1,6 @@ use crate::{ - Cache, Command, Debug, Event, Point, Program, Renderer, Size, UserInterface, + Cache, Clipboard, Command, Debug, Event, Point, Program, Renderer, Size, + UserInterface, }; /// The execution state of a [`Program`]. It leverages caching, event @@ -91,7 +92,7 @@ where bounds: Size, cursor_position: Point, renderer: &mut P::Renderer, - clipboard: &mut P::Clipboard, + clipboard: &mut dyn Clipboard, debug: &mut Debug, ) -> Option<Command<P::Message>> { let mut user_interface = build_user_interface( @@ -135,7 +136,7 @@ where debug.log_message(&message); debug.update_started(); - let command = self.program.update(message, clipboard); + let command = self.program.update(message); debug.update_finished(); command diff --git a/native/src/window.rs b/native/src/window.rs index 220bb3be..62487fb9 100644 --- a/native/src/window.rs +++ b/native/src/window.rs @@ -1,4 +1,6 @@ //! Build window-based GUI applications. +mod action; mod event; +pub use action::Action; pub use event::Event; diff --git a/native/src/window/action.rs b/native/src/window/action.rs new file mode 100644 index 00000000..01294e83 --- /dev/null +++ b/native/src/window/action.rs @@ -0,0 +1,18 @@ +/// An operation to be performed on some window. +#[derive(Debug)] +pub enum Action { + /// Resize the window. + Resize { + /// The new logical width of the window + width: u32, + /// The new logical height of the window + height: u32, + }, + /// Move the window. + Move { + /// The new logical x location of the window + x: i32, + /// The new logical y location of the window + y: i32, + }, +} diff --git a/native/src/window/event.rs b/native/src/window/event.rs index 64f2b8d8..691af29a 100644 --- a/native/src/window/event.rs +++ b/native/src/window/event.rs @@ -13,10 +13,9 @@ pub enum Event { /// A window was resized. Resized { - /// The new width of the window (in units) + /// The new logical width of the window width: u32, - - /// The new height of the window (in units) + /// The new logical height of the window height: u32, }, diff --git a/src/application.rs b/src/application.rs index 78280e98..0c30a554 100644 --- a/src/application.rs +++ b/src/application.rs @@ -1,7 +1,5 @@ use crate::window; -use crate::{ - Clipboard, Color, Command, Element, Executor, Menu, Settings, Subscription, -}; +use crate::{Color, Command, Element, Executor, Menu, Settings, Subscription}; /// An interactive cross-platform application. /// @@ -59,7 +57,7 @@ use crate::{ /// says "Hello, world!": /// /// ```no_run -/// use iced::{executor, Application, Clipboard, Command, Element, Settings, Text}; +/// use iced::{executor, Application, Command, Element, Settings, Text}; /// /// pub fn main() -> iced::Result { /// Hello::run(Settings::default()) @@ -80,7 +78,7 @@ use crate::{ /// String::from("A cool application") /// } /// -/// fn update(&mut self, _message: Self::Message, _clipboard: &mut Clipboard) -> Command<Self::Message> { +/// fn update(&mut self, _message: Self::Message) -> Command<Self::Message> { /// Command::none() /// } /// @@ -129,11 +127,7 @@ pub trait Application: Sized { /// this method. /// /// Any [`Command`] returned will be executed immediately in the background. - fn update( - &mut self, - message: Self::Message, - clipboard: &mut Clipboard, - ) -> Command<Self::Message>; + fn update(&mut self, message: Self::Message) -> Command<Self::Message>; /// Returns the event [`Subscription`] for the current state of the /// application. @@ -249,14 +243,9 @@ where { type Renderer = crate::renderer::Renderer; type Message = A::Message; - type Clipboard = iced_winit::Clipboard; - - fn update( - &mut self, - message: Self::Message, - clipboard: &mut iced_winit::Clipboard, - ) -> Command<Self::Message> { - self.0.update(message, clipboard) + + fn update(&mut self, message: Self::Message) -> Command<Self::Message> { + self.0.update(message) } fn view(&mut self) -> Element<'_, Self::Message> { @@ -329,12 +318,8 @@ where self.0.title() } - fn update( - &mut self, - message: Self::Message, - clipboard: &mut Clipboard, - ) -> Command<Self::Message> { - self.0.update(message, clipboard) + fn update(&mut self, message: Self::Message) -> Command<Self::Message> { + self.0.update(message) } fn subscription(&self) -> Subscription<Self::Message> { diff --git a/src/clipboard.rs b/src/clipboard.rs new file mode 100644 index 00000000..dde17051 --- /dev/null +++ b/src/clipboard.rs @@ -0,0 +1,3 @@ +//! Access the clipboard. +#[cfg(not(target_arch = "wasm32"))] +pub use crate::runtime::clipboard::{read, write}; @@ -183,6 +183,7 @@ mod error; mod result; mod sandbox; +pub mod clipboard; pub mod executor; pub mod keyboard; pub mod mouse; @@ -245,7 +246,7 @@ pub use sandbox::Sandbox; pub use settings::Settings; pub use runtime::{ - futures, menu, Align, Background, Clipboard, Color, Command, Font, + futures, menu, Align, Background, Color, Command, Font, HorizontalAlignment, Length, Menu, Point, Rectangle, Size, Subscription, Vector, VerticalAlignment, }; diff --git a/src/sandbox.rs b/src/sandbox.rs index cb3cf624..a0bb316e 100644 --- a/src/sandbox.rs +++ b/src/sandbox.rs @@ -1,6 +1,5 @@ use crate::{ - Application, Clipboard, Color, Command, Element, Error, Settings, - Subscription, + Application, Color, Command, Element, Error, Settings, Subscription, }; /// A sandboxed [`Application`]. @@ -162,11 +161,7 @@ where T::title(self) } - fn update( - &mut self, - message: T::Message, - _clipboard: &mut Clipboard, - ) -> Command<T::Message> { + fn update(&mut self, message: T::Message) -> Command<T::Message> { T::update(self, message); Command::none() diff --git a/src/window.rs b/src/window.rs index 7d441062..71158816 100644 --- a/src/window.rs +++ b/src/window.rs @@ -9,3 +9,6 @@ pub use icon::Icon; pub use mode::Mode; pub use position::Position; pub use settings::Settings; + +#[cfg(not(target_arch = "wasm32"))] +pub use crate::runtime::window::{move_to, resize}; diff --git a/web/src/clipboard.rs b/web/src/clipboard.rs deleted file mode 100644 index 167a1e53..00000000 --- a/web/src/clipboard.rs +++ /dev/null @@ -1,21 +0,0 @@ -/// A buffer for short-term storage and transfer within and between -/// applications. -#[derive(Debug, Clone, Copy)] -pub struct Clipboard; - -impl Clipboard { - /// Creates a new [`Clipboard`]. - pub fn new() -> Self { - Self - } - - /// Reads the current content of the [`Clipboard`] as text. - pub fn read(&self) -> Option<String> { - unimplemented! {} - } - - /// Writes the given text contents to the [`Clipboard`]. - pub fn write(&mut self, _contents: String) { - unimplemented! {} - } -} diff --git a/web/src/command.rs b/web/src/command.rs new file mode 100644 index 00000000..33e49e70 --- /dev/null +++ b/web/src/command.rs @@ -0,0 +1,72 @@ +mod action; + +pub use action::Action; + +use std::fmt; + +#[cfg(target_arch = "wasm32")] +use std::future::Future; + +/// A set of asynchronous actions to be performed by some runtime. +pub struct Command<T>(iced_futures::Command<Action<T>>); + +impl<T> Command<T> { + /// Creates an empty [`Command`]. + /// + /// In other words, a [`Command`] that does nothing. + pub const fn none() -> Self { + Self(iced_futures::Command::none()) + } + + /// Creates a [`Command`] that performs a single [`Action`]. + pub const fn single(action: Action<T>) -> Self { + Self(iced_futures::Command::single(action)) + } + + /// Creates a [`Command`] that performs the action of the given future. + #[cfg(target_arch = "wasm32")] + pub fn perform<A>( + future: impl Future<Output = T> + 'static, + f: impl Fn(T) -> A + 'static + Send, + ) -> Command<A> { + use iced_futures::futures::FutureExt; + + Command::single(Action::Future(Box::pin(future.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 { + Self(iced_futures::Command::batch( + commands.into_iter().map(|Command(command)| command), + )) + } + + /// Applies a transformation to the result of a [`Command`]. + #[cfg(target_arch = "wasm32")] + pub fn map<A>(self, f: impl Fn(T) -> A + 'static + Clone) -> Command<A> + where + T: 'static, + { + let Command(command) = self; + + Command(command.map(move |action| action.map(f.clone()))) + } + + /// Returns all of the actions of the [`Command`]. + pub fn actions(self) -> Vec<Action<T>> { + let Command(command) = self; + + command.actions() + } +} + +impl<T> fmt::Debug for Command<T> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let Command(command) = self; + + command.fmt(f) + } +} diff --git a/web/src/command/action.rs b/web/src/command/action.rs new file mode 100644 index 00000000..c0223e50 --- /dev/null +++ b/web/src/command/action.rs @@ -0,0 +1,28 @@ +pub enum Action<T> { + Future(iced_futures::BoxFuture<T>), +} + +use std::fmt; + +impl<T> Action<T> { + /// Applies a transformation to the result of a [`Command`]. + #[cfg(target_arch = "wasm32")] + pub fn map<A>(self, f: impl Fn(T) -> A + 'static) -> Action<A> + where + T: 'static, + { + use iced_futures::futures::FutureExt; + + match self { + Self::Future(future) => Action::Future(Box::pin(future.map(f))), + } + } +} + +impl<T> fmt::Debug for Action<T> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Self::Future(_) => write!(f, "Action::Future"), + } + } +} diff --git a/web/src/lib.rs b/web/src/lib.rs index bb09bb0d..5bbd2ee6 100644 --- a/web/src/lib.rs +++ b/web/src/lib.rs @@ -59,7 +59,7 @@ use dodrio::bumpalo; use std::{cell::RefCell, rc::Rc}; mod bus; -mod clipboard; +mod command; mod element; mod hasher; @@ -68,7 +68,7 @@ pub mod subscription; pub mod widget; pub use bus::Bus; -pub use clipboard::Clipboard; +pub use command::Command; pub use css::Css; pub use dodrio; pub use element::Element; @@ -77,7 +77,7 @@ pub use iced_core::{ keyboard, menu, mouse, Align, Background, Color, Font, HorizontalAlignment, Length, Menu, Padding, Point, Rectangle, Size, Vector, VerticalAlignment, }; -pub use iced_futures::{executor, futures, Command}; +pub use iced_futures::{executor, futures}; pub use subscription::Subscription; #[doc(no_inline)] @@ -128,11 +128,7 @@ pub trait Application { /// this method. /// /// Any [`Command`] returned will be executed immediately in the background. - fn update( - &mut self, - message: Self::Message, - clipboard: &mut Clipboard, - ) -> Command<Self::Message>; + fn update(&mut self, message: Self::Message) -> Command<Self::Message>; /// Returns the widgets to display in the [`Application`]. /// @@ -162,8 +158,6 @@ pub trait Application { let document = window.document().unwrap(); let body = document.body().unwrap(); - let mut clipboard = Clipboard::new(); - let (sender, receiver) = iced_futures::futures::channel::mpsc::unbounded(); @@ -177,7 +171,7 @@ pub trait Application { let mut title = app.title(); document.set_title(&title); - runtime.spawn(command); + run_command(command, &mut runtime); let application = Rc::new(RefCell::new(app)); @@ -190,8 +184,7 @@ pub trait Application { let event_loop = receiver.for_each(move |message| { let (command, subscription) = runtime.enter(|| { - let command = - application.borrow_mut().update(message, &mut clipboard); + let command = application.borrow_mut().update(message); let subscription = application.borrow().subscription(); (command, subscription) @@ -199,7 +192,7 @@ pub trait Application { let new_title = application.borrow().title(); - runtime.spawn(command); + run_command(command, &mut runtime); runtime.track(subscription); if title != new_title { @@ -350,8 +343,7 @@ pub trait Embedded { ); let (app, command) = runtime.enter(|| Self::new(flags)); - - runtime.spawn(command); + run_command(command, &mut runtime); let application = Rc::new(RefCell::new(app)); @@ -370,7 +362,7 @@ pub trait Embedded { (command, subscription) }); - runtime.spawn(command); + run_command(command, &mut runtime); runtime.track(subscription); vdom.weak().schedule_render(); @@ -382,6 +374,25 @@ pub trait Embedded { } } +fn run_command<Message: 'static + Send, E: Executor>( + command: Command<Message>, + runtime: &mut iced_futures::Runtime< + Hasher, + (), + E, + iced_futures::futures::channel::mpsc::UnboundedSender<Message>, + Message, + >, +) { + for action in command.actions() { + match action { + command::Action::Future(future) => { + runtime.spawn(future); + } + } + } +} + struct EmbeddedInstance<A: Embedded> { application: Rc<RefCell<A>>, bus: Bus<A::Message>, diff --git a/winit/src/application.rs b/winit/src/application.rs index b683e592..722b4757 100644 --- a/winit/src/application.rs +++ b/winit/src/application.rs @@ -3,11 +3,12 @@ mod state; pub use state::State; +use crate::clipboard::{self, Clipboard}; use crate::conversion; use crate::mouse; use crate::{ - Clipboard, Color, Command, Debug, Error, Executor, Mode, Proxy, Runtime, - Settings, Size, Subscription, + Color, Command, Debug, Error, Executor, Mode, Proxy, Runtime, Settings, + Size, Subscription, }; use iced_futures::futures; @@ -30,7 +31,7 @@ use std::mem::ManuallyDrop; /// /// When using an [`Application`] with the `debug` feature enabled, a debug view /// can be toggled by pressing `F12`. -pub trait Application: Program<Clipboard = Clipboard> { +pub trait Application: Program { /// The data needed to initialize your [`Application`]. type Flags; @@ -127,6 +128,7 @@ where debug.startup_started(); let event_loop = EventLoop::with_user_event(); + let mut proxy = event_loop.create_proxy(); let mut runtime = { let proxy = Proxy::new(event_loop.create_proxy()); @@ -143,9 +145,6 @@ where let subscription = application.subscription(); - runtime.spawn(init_command); - runtime.track(subscription); - let window = settings .window .into_builder( @@ -158,6 +157,17 @@ where .build(&event_loop) .map_err(Error::WindowCreationFailed)?; + let mut clipboard = Clipboard::connect(&window); + + run_command( + init_command, + &mut runtime, + &mut clipboard, + &mut proxy, + &window, + ); + runtime.track(subscription); + let (compositor, renderer) = C::new(compositor_settings, Some(&window))?; let (mut sender, receiver) = mpsc::unbounded(); @@ -167,6 +177,8 @@ where compositor, renderer, runtime, + clipboard, + proxy, debug, receiver, window, @@ -215,6 +227,8 @@ async fn run_instance<A, E, C>( mut compositor: C, mut renderer: A::Renderer, mut runtime: Runtime<E, Proxy<A::Message>, A::Message>, + mut clipboard: Clipboard, + mut proxy: winit::event_loop::EventLoopProxy<A::Message>, mut debug: Debug, mut receiver: mpsc::UnboundedReceiver<winit::event::Event<'_, A::Message>>, window: winit::window::Window, @@ -228,7 +242,6 @@ async fn run_instance<A, E, C>( use winit::event; let mut surface = compositor.create_surface(&window); - let mut clipboard = Clipboard::connect(&window); let mut state = State::new(&application, &window); let mut viewport_version = state.viewport_version(); @@ -289,9 +302,11 @@ async fn run_instance<A, E, C>( update( &mut application, &mut runtime, - &mut debug, &mut clipboard, + &mut proxy, + &mut debug, &mut messages, + &window, ); // Update window @@ -491,20 +506,68 @@ pub fn build_user_interface<'a, A: Application>( pub fn update<A: Application, E: Executor>( application: &mut A, runtime: &mut Runtime<E, Proxy<A::Message>, A::Message>, + clipboard: &mut Clipboard, + proxy: &mut winit::event_loop::EventLoopProxy<A::Message>, debug: &mut Debug, - clipboard: &mut A::Clipboard, messages: &mut Vec<A::Message>, + window: &winit::window::Window, ) { for message in messages.drain(..) { debug.log_message(&message); debug.update_started(); - let command = runtime.enter(|| application.update(message, clipboard)); + let command = runtime.enter(|| application.update(message)); debug.update_finished(); - runtime.spawn(command); + run_command(command, runtime, clipboard, proxy, window); } let subscription = application.subscription(); runtime.track(subscription); } + +/// Runs the actions of a [`Command`]. +pub fn run_command<Message: 'static + std::fmt::Debug + Send, E: Executor>( + command: Command<Message>, + runtime: &mut Runtime<E, Proxy<Message>, Message>, + clipboard: &mut Clipboard, + proxy: &mut winit::event_loop::EventLoopProxy<Message>, + window: &winit::window::Window, +) { + use iced_native::command; + use iced_native::window; + + for action in command.actions() { + match action { + command::Action::Future(future) => { + runtime.spawn(future); + } + command::Action::Clipboard(action) => match action { + clipboard::Action::Read(tag) => { + let message = tag(clipboard.read()); + + proxy + .send_event(message) + .expect("Send message to event loop"); + } + clipboard::Action::Write(contents) => { + clipboard.write(contents); + } + }, + command::Action::Window(action) => match action { + window::Action::Resize { width, height } => { + window.set_inner_size(winit::dpi::LogicalSize { + width, + height, + }); + } + window::Action::Move { x, y } => { + window.set_outer_position(winit::dpi::LogicalPosition { + x, + y, + }); + } + }, + } + } +} diff --git a/winit/src/clipboard.rs b/winit/src/clipboard.rs index ca25c065..1b92b28d 100644 --- a/winit/src/clipboard.rs +++ b/winit/src/clipboard.rs @@ -1,3 +1,8 @@ +//! Access the clipboard. +pub use iced_native::clipboard::Action; + +use crate::command::{self, Command}; + /// A buffer for short-term storage and transfer within and between /// applications. #[allow(missing_debug_implementations)] @@ -52,3 +57,15 @@ impl iced_native::Clipboard for Clipboard { self.write(contents) } } + +/// 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)))) +} + +/// Write the given contents to the clipboard. +pub fn write<Message>(contents: String) -> Command<Message> { + Command::single(command::Action::Clipboard(Action::Write(contents))) +} diff --git a/winit/src/lib.rs b/winit/src/lib.rs index 1707846a..30813152 100644 --- a/winit/src/lib.rs +++ b/winit/src/lib.rs @@ -25,10 +25,11 @@ pub use iced_native::*; pub use winit; pub mod application; +pub mod clipboard; pub mod conversion; pub mod settings; +pub mod window; -mod clipboard; mod error; mod mode; mod position; diff --git a/winit/src/window.rs b/winit/src/window.rs new file mode 100644 index 00000000..f3207e68 --- /dev/null +++ b/winit/src/window.rs @@ -0,0 +1,18 @@ +//! Interact with the window of your application. +use crate::command::{self, Command}; +use iced_native::window; + +pub use window::Event; + +/// Resizes the window to the given logical dimensions. +pub fn resize<Message>(width: u32, height: u32) -> Command<Message> { + Command::single(command::Action::Window(window::Action::Resize { + width, + height, + })) +} + +/// Moves a window to the given logical coordinates. +pub fn move_to<Message>(x: i32, y: i32) -> Command<Message> { + Command::single(command::Action::Window(window::Action::Move { x, y })) +} |