diff options
author | 2021-09-01 19:21:49 +0700 | |
---|---|---|
committer | 2021-09-02 13:29:34 +0700 | |
commit | 76698ff2b5753e637b14533650c0d28e681be3c5 (patch) | |
tree | 6b3376df6ac84598b03f2885598b7908e993fe4a | |
parent | b7b7741578257bbf6a8b873c360182e2c9b920ab (diff) | |
download | iced-76698ff2b5753e637b14533650c0d28e681be3c5.tar.gz iced-76698ff2b5753e637b14533650c0d28e681be3c5.tar.bz2 iced-76698ff2b5753e637b14533650c0d28e681be3c5.zip |
Make `Command` implementations platform-specific
This allows us to introduce a platform-specific `Action` to both `iced_native`
and `iced_web` and remove the `Clipboard` from `Application::update` to maintain
purity.
Additionally, this should let us implement further actions to let users query
and modify the shell environment (e.g. window, clipboard, and more!)
34 files changed, 364 insertions, 343 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 deleted file mode 100644 index b06ab3f8..00000000 --- a/futures/src/command.rs +++ /dev/null @@ -1,138 +0,0 @@ -use crate::BoxFuture; -use futures::future::{Future, FutureExt}; - -/// 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>>, -} - -impl<T> Command<T> { - /// Creates an empty [`Command`]. - /// - /// In other words, a [`Command`] that does nothing. - pub fn none() -> Self { - Self { - futures: Vec::new(), - } - } - - /// 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))], - } - } - - /// 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(); - - Box::pin(future.map(move |result| f(result))) - as BoxFuture<A> - }) - .collect(), - } - } - - /// 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> - where - T: 'static, - { - let f = std::rc::Rc::new(f); - - Command { - futures: self - .futures - .drain(..) - .map(|future| { - let f = f.clone(); - - Box::pin(future.map(move |result| f(result))) - as BoxFuture<A> - }) - .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(), - } - } - - /// 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()], - } - } -} - -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/lib.rs b/futures/src/lib.rs index 01cf5c89..657aab37 100644 --- a/futures/src/lib.rs +++ b/futures/src/lib.rs @@ -10,7 +10,6 @@ pub use futures; -mod command; mod runtime; pub mod executor; @@ -35,7 +34,6 @@ pub mod subscription; )] pub mod time; -pub use command::Command; pub use executor::Executor; pub use runtime::Runtime; pub use subscription::Subscription; 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..1368f7f8 100644 --- a/glutin/src/application.rs +++ b/glutin/src/application.rs @@ -48,7 +48,7 @@ where let subscription = application.subscription(); - runtime.spawn(init_command); + application::run_command(init_command, &mut runtime); runtime.track(subscription); let context = { @@ -211,7 +211,6 @@ async fn run_instance<A, E, C>( &mut application, &mut runtime, &mut debug, - &mut clipboard, &mut messages, ); diff --git a/native/src/clipboard.rs b/native/src/clipboard.rs index 081b4004..62dfc130 100644 --- a/native/src/clipboard.rs +++ b/native/src/clipboard.rs @@ -21,3 +21,20 @@ impl Clipboard for Null { fn write(&mut self, _contents: String) {} } + +pub enum Action<T> { + Read(Box<dyn Fn(Option<String>) -> T>), + Write(Box<dyn Fn(String) -> T>), +} + +impl<T> Action<T> { + 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(o) => Action::Write(Box::new(move |s| f(o(s)))), + } + } +} diff --git a/native/src/command.rs b/native/src/command.rs new file mode 100644 index 00000000..16fd73ba --- /dev/null +++ b/native/src/command.rs @@ -0,0 +1,77 @@ +mod action; + +pub use action::Action; + +use std::future::Future; + +/// A set of asynchronous actions to be performed by some runtime. +pub enum Command<T> { + None, + Single(Action<T>), + Batch(Vec<Action<T>>), +} + +impl<T> Command<T> { + /// Creates an empty [`Command`]. + /// + /// In other words, a [`Command`] that does nothing. + pub fn none() -> Self { + Self::None + } + + /// 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)))) + } + + /// 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, + { + match self { + Self::None => Command::None, + Self::Single(action) => Command::Single(action.map(f)), + Self::Batch(batch) => Command::Batch( + batch + .into_iter() + .map(|action| action.map(f.clone())) + .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 { + let mut batch = Vec::new(); + + for command in commands { + match command { + Self::None => {} + Self::Single(command) => batch.push(command), + Self::Batch(commands) => batch.extend(commands), + } + } + + Self::Batch(batch) + } + + pub fn actions(self) -> Vec<Action<T>> { + match self { + Self::None => Vec::new(), + Self::Single(action) => vec![action], + Self::Batch(batch) => batch, + } + } +} diff --git a/native/src/command/action.rs b/native/src/command/action.rs new file mode 100644 index 00000000..23d6e96e --- /dev/null +++ b/native/src/command/action.rs @@ -0,0 +1,24 @@ +use crate::clipboard; +use crate::window; + +pub enum Action<T> { + Future(iced_futures::BoxFuture<T>), + Clipboard(clipboard::Action<T>), + 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), + } + } +} diff --git a/native/src/lib.rs b/native/src/lib.rs index cbb02506..f300e7b9 100644 --- a/native/src/lib.rs +++ b/native/src/lib.rs @@ -28,12 +28,13 @@ //! [`druid`]: https://github.com/xi-editor/druid //! [`raw-window-handle`]: https://github.com/rust-windowing/raw-window-handle //! [renderer]: crate::renderer -#![deny(missing_docs)] -#![deny(missing_debug_implementations)] +//#![deny(missing_docs)] +//#![deny(missing_debug_implementations)] #![deny(unused_results)] #![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..81add963 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. /// @@ -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/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/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..606097de --- /dev/null +++ b/web/src/command.rs @@ -0,0 +1,77 @@ +mod action; + +pub use action::Action; + +#[cfg(target_arch = "wasm32")] +use std::future::Future; + +/// A set of asynchronous actions to be performed by some runtime. +pub enum Command<T> { + None, + Single(Action<T>), + Batch(Vec<Action<T>>), +} + +impl<T> Command<T> { + /// Creates an empty [`Command`]. + /// + /// In other words, a [`Command`] that does nothing. + pub fn none() -> Self { + Self::None + } + + /// 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)))) + } + + /// 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 + Clone) -> Command<A> + where + T: 'static, + { + match self { + Self::None => Command::None, + Self::Single(action) => Command::Single(action.map(f)), + Self::Batch(batch) => Command::Batch( + batch + .into_iter() + .map(|action| action.map(f.clone())) + .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 { + let mut batch = Vec::new(); + + for command in commands { + match command { + Self::None => {} + Self::Single(command) => batch.push(command), + Self::Batch(commands) => batch.extend(commands), + } + } + + Self::Batch(batch) + } + + pub fn actions(self) -> Vec<Action<T>> { + match self { + Self::None => Vec::new(), + Self::Single(action) => vec![action], + Self::Batch(batch) => batch, + } + } +} diff --git a/web/src/command/action.rs b/web/src/command/action.rs new file mode 100644 index 00000000..cf384f07 --- /dev/null +++ b/web/src/command/action.rs @@ -0,0 +1,18 @@ +pub enum Action<T> { + Future(iced_futures::BoxFuture<T>), +} + +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))), + } + } +} diff --git a/web/src/lib.rs b/web/src/lib.rs index bb09bb0d..ec2bd178 100644 --- a/web/src/lib.rs +++ b/web/src/lib.rs @@ -50,8 +50,8 @@ //! [`wasm-pack`]: https://github.com/rustwasm/wasm-pack //! [`wasm-bindgen`]: https://github.com/rustwasm/wasm-bindgen //! [`tour` example]: https://github.com/hecrj/iced/tree/0.3/examples/tour -#![deny(missing_docs)] -#![deny(missing_debug_implementations)] +//#![deny(missing_docs)] +//#![deny(missing_debug_implementations)] #![deny(unused_results)] #![forbid(unsafe_code)] #![forbid(rust_2018_idioms)] @@ -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..99118ad1 100644 --- a/winit/src/application.rs +++ b/winit/src/application.rs @@ -30,7 +30,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; @@ -143,7 +143,7 @@ where let subscription = application.subscription(); - runtime.spawn(init_command); + run_command(init_command, &mut runtime); runtime.track(subscription); let window = settings @@ -290,7 +290,6 @@ async fn run_instance<A, E, C>( &mut application, &mut runtime, &mut debug, - &mut clipboard, &mut messages, ); @@ -492,19 +491,36 @@ pub fn update<A: Application, E: Executor>( application: &mut A, runtime: &mut Runtime<E, Proxy<A::Message>, A::Message>, debug: &mut Debug, - clipboard: &mut A::Clipboard, messages: &mut Vec<A::Message>, ) { 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); } let subscription = application.subscription(); runtime.track(subscription); } + +/// Runs the actions of a [`Command`]. +pub fn run_command<Message: 'static + Send, E: Executor>( + command: Command<Message>, + runtime: &mut Runtime<E, Proxy<Message>, Message>, +) { + use iced_native::command; + + for action in command.actions() { + match action { + command::Action::Future(future) => { + runtime.spawn(future); + } + command::Action::Clipboard(_action) => unimplemented! {}, + command::Action::Window(_action) => unimplemented! {}, + } + } +} diff --git a/winit/src/window.rs b/winit/src/window.rs new file mode 100644 index 00000000..8ccb13ed --- /dev/null +++ b/winit/src/window.rs @@ -0,0 +1,7 @@ +pub use iced_native::window::*; + +/// The window of an [`Application`]. +/// +/// [`Application`]: crate::Application +#[derive(Debug)] +pub struct Window {} |