diff --git a/.github/workflows/document.yml b/.github/workflows/document.yml index 827a2ca874..a213e590d4 100644 --- a/.github/workflows/document.yml +++ b/.github/workflows/document.yml @@ -8,7 +8,7 @@ jobs: steps: - uses: hecrj/setup-rust-action@v2 with: - rust-version: nightly-2023-12-11 + rust-version: nightly - uses: actions/checkout@v2 - name: Generate documentation run: | diff --git a/core/Cargo.toml b/core/Cargo.toml index 3c557bca0f..a12289099c 100644 --- a/core/Cargo.toml +++ b/core/Cargo.toml @@ -33,8 +33,5 @@ web-time.workspace = true dark-light.workspace = true dark-light.optional = true -[target.'cfg(windows)'.dependencies] -raw-window-handle.workspace = true - [dev-dependencies] approx = "0.5" diff --git a/core/src/element.rs b/core/src/element.rs index 7d918a2ea7..385d829523 100644 --- a/core/src/element.rs +++ b/core/src/element.rs @@ -10,7 +10,6 @@ use crate::{ Widget, }; -use std::any::Any; use std::borrow::Borrow; /// A generic [`Widget`]. @@ -305,63 +304,9 @@ where tree: &mut Tree, layout: Layout<'_>, renderer: &Renderer, - operation: &mut dyn widget::Operation, + operation: &mut dyn widget::Operation<()>, ) { - struct MapOperation<'a, B> { - operation: &'a mut dyn widget::Operation, - } - - impl<'a, T, B> widget::Operation for MapOperation<'a, B> { - fn container( - &mut self, - id: Option<&widget::Id>, - bounds: Rectangle, - operate_on_children: &mut dyn FnMut( - &mut dyn widget::Operation, - ), - ) { - self.operation.container(id, bounds, &mut |operation| { - operate_on_children(&mut MapOperation { operation }); - }); - } - - fn focusable( - &mut self, - state: &mut dyn widget::operation::Focusable, - id: Option<&widget::Id>, - ) { - self.operation.focusable(state, id); - } - - fn scrollable( - &mut self, - state: &mut dyn widget::operation::Scrollable, - id: Option<&widget::Id>, - bounds: Rectangle, - translation: Vector, - ) { - self.operation.scrollable(state, id, bounds, translation); - } - - fn text_input( - &mut self, - state: &mut dyn widget::operation::TextInput, - id: Option<&widget::Id>, - ) { - self.operation.text_input(state, id); - } - - fn custom(&mut self, state: &mut dyn Any, id: Option<&widget::Id>) { - self.operation.custom(state, id); - } - } - - self.widget.operate( - tree, - layout, - renderer, - &mut MapOperation { operation }, - ); + self.widget.operate(tree, layout, renderer, operation); } fn on_event( @@ -495,7 +440,7 @@ where state: &mut Tree, layout: Layout<'_>, renderer: &Renderer, - operation: &mut dyn widget::Operation, + operation: &mut dyn widget::Operation<()>, ) { self.element .widget diff --git a/core/src/overlay.rs b/core/src/overlay.rs index 3a57fe1641..16f867daf9 100644 --- a/core/src/overlay.rs +++ b/core/src/overlay.rs @@ -41,7 +41,7 @@ where &mut self, _layout: Layout<'_>, _renderer: &Renderer, - _operation: &mut dyn widget::Operation, + _operation: &mut dyn widget::Operation<()>, ) { } diff --git a/core/src/overlay/element.rs b/core/src/overlay/element.rs index 695b88b3a5..61e75e8aa3 100644 --- a/core/src/overlay/element.rs +++ b/core/src/overlay/element.rs @@ -5,9 +5,7 @@ use crate::layout; use crate::mouse; use crate::renderer; use crate::widget; -use crate::{Clipboard, Layout, Point, Rectangle, Shell, Size, Vector}; - -use std::any::Any; +use crate::{Clipboard, Layout, Point, Rectangle, Shell, Size}; /// A generic [`Overlay`]. #[allow(missing_debug_implementations)] @@ -94,7 +92,7 @@ where &mut self, layout: Layout<'_>, renderer: &Renderer, - operation: &mut dyn widget::Operation, + operation: &mut dyn widget::Operation<()>, ) { self.overlay.operate(layout, renderer, operation); } @@ -146,59 +144,9 @@ where &mut self, layout: Layout<'_>, renderer: &Renderer, - operation: &mut dyn widget::Operation, + operation: &mut dyn widget::Operation<()>, ) { - struct MapOperation<'a, B> { - operation: &'a mut dyn widget::Operation, - } - - impl<'a, T, B> widget::Operation for MapOperation<'a, B> { - fn container( - &mut self, - id: Option<&widget::Id>, - bounds: Rectangle, - operate_on_children: &mut dyn FnMut( - &mut dyn widget::Operation, - ), - ) { - self.operation.container(id, bounds, &mut |operation| { - operate_on_children(&mut MapOperation { operation }); - }); - } - - fn focusable( - &mut self, - state: &mut dyn widget::operation::Focusable, - id: Option<&widget::Id>, - ) { - self.operation.focusable(state, id); - } - - fn scrollable( - &mut self, - state: &mut dyn widget::operation::Scrollable, - id: Option<&widget::Id>, - bounds: Rectangle, - translation: Vector, - ) { - self.operation.scrollable(state, id, bounds, translation); - } - - fn text_input( - &mut self, - state: &mut dyn widget::operation::TextInput, - id: Option<&widget::Id>, - ) { - self.operation.text_input(state, id); - } - - fn custom(&mut self, state: &mut dyn Any, id: Option<&widget::Id>) { - self.operation.custom(state, id); - } - } - - self.content - .operate(layout, renderer, &mut MapOperation { operation }); + self.content.operate(layout, renderer, operation); } fn on_event( diff --git a/core/src/overlay/group.rs b/core/src/overlay/group.rs index 7e4bebd078..cd12eac9c5 100644 --- a/core/src/overlay/group.rs +++ b/core/src/overlay/group.rs @@ -132,7 +132,7 @@ where &mut self, layout: Layout<'_>, renderer: &Renderer, - operation: &mut dyn widget::Operation, + operation: &mut dyn widget::Operation<()>, ) { operation.container(None, layout.bounds(), &mut |operation| { self.children.iter_mut().zip(layout.children()).for_each( diff --git a/core/src/widget.rs b/core/src/widget.rs index b02e3a4f8f..0d12deba2f 100644 --- a/core/src/widget.rs +++ b/core/src/widget.rs @@ -105,7 +105,7 @@ where _state: &mut Tree, _layout: Layout<'_>, _renderer: &Renderer, - _operation: &mut dyn Operation, + _operation: &mut dyn Operation<()>, ) { } diff --git a/core/src/widget/operation.rs b/core/src/widget/operation.rs index b91cf9ac94..3e4ed618e4 100644 --- a/core/src/widget/operation.rs +++ b/core/src/widget/operation.rs @@ -12,11 +12,11 @@ use crate::{Rectangle, Vector}; use std::any::Any; use std::fmt; -use std::rc::Rc; +use std::sync::Arc; /// A piece of logic that can traverse the widget tree of an application in /// order to query or update some widget state. -pub trait Operation { +pub trait Operation: Send { /// Operates on a widget that contains other widgets. /// /// The `operate_on_children` function can be called to return control to @@ -81,7 +81,7 @@ where /// Maps the output of an [`Operation`] using the given function. pub fn map( operation: Box>, - f: impl Fn(A) -> B + 'static, + f: impl Fn(A) -> B + Send + Sync + 'static, ) -> impl Operation where A: 'static, @@ -90,7 +90,7 @@ where #[allow(missing_debug_implementations)] struct Map { operation: Box>, - f: Rc B>, + f: Arc B + Send + Sync>, } impl Operation for Map @@ -197,7 +197,7 @@ where Map { operation, - f: Rc::new(f), + f: Arc::new(f), } } diff --git a/core/src/window/settings/windows.rs b/core/src/window/settings/windows.rs index d3bda25915..88fe2fbd6a 100644 --- a/core/src/window/settings/windows.rs +++ b/core/src/window/settings/windows.rs @@ -1,12 +1,8 @@ //! Platform specific settings for Windows. -use raw_window_handle::RawWindowHandle; /// The platform specific window settings of an application. #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub struct PlatformSpecific { - /// Parent window - pub parent: Option, - /// Drag and drop support pub drag_and_drop: bool, @@ -17,7 +13,6 @@ pub struct PlatformSpecific { impl Default for PlatformSpecific { fn default() -> Self { Self { - parent: None, drag_and_drop: true, skip_taskbar: false, } diff --git a/examples/editor/src/main.rs b/examples/editor/src/main.rs index c20a7d67e5..ec65e2fa60 100644 --- a/examples/editor/src/main.rs +++ b/examples/editor/src/main.rs @@ -4,7 +4,7 @@ use iced::widget::{ button, column, container, horizontal_space, pick_list, row, text, text_editor, tooltip, }; -use iced::{Alignment, Command, Element, Font, Length, Subscription, Theme}; +use iced::{Alignment, Element, Font, Length, Subscription, Task, Theme}; use std::ffi; use std::io; @@ -51,26 +51,26 @@ impl Editor { } } - fn load() -> Command { - Command::perform( + fn load() -> Task { + Task::perform( load_file(format!("{}/src/main.rs", env!("CARGO_MANIFEST_DIR"))), Message::FileOpened, ) } - fn update(&mut self, message: Message) -> Command { + fn update(&mut self, message: Message) -> Task { match message { Message::ActionPerformed(action) => { self.is_dirty = self.is_dirty || action.is_edit(); self.content.perform(action); - Command::none() + Task::none() } Message::ThemeSelected(theme) => { self.theme = theme; - Command::none() + Task::none() } Message::NewFile => { if !self.is_loading { @@ -78,15 +78,15 @@ impl Editor { self.content = text_editor::Content::new(); } - Command::none() + Task::none() } Message::OpenFile => { if self.is_loading { - Command::none() + Task::none() } else { self.is_loading = true; - Command::perform(open_file(), Message::FileOpened) + Task::perform(open_file(), Message::FileOpened) } } Message::FileOpened(result) => { @@ -98,15 +98,15 @@ impl Editor { self.content = text_editor::Content::with_text(&contents); } - Command::none() + Task::none() } Message::SaveFile => { if self.is_loading { - Command::none() + Task::none() } else { self.is_loading = true; - Command::perform( + Task::perform( save_file(self.file.clone(), self.content.text()), Message::FileSaved, ) @@ -120,7 +120,7 @@ impl Editor { self.is_dirty = false; } - Command::none() + Task::none() } } } diff --git a/examples/events/src/main.rs b/examples/events/src/main.rs index bacd8e6e95..504ed5d89b 100644 --- a/examples/events/src/main.rs +++ b/examples/events/src/main.rs @@ -2,7 +2,7 @@ use iced::alignment; use iced::event::{self, Event}; use iced::widget::{button, center, checkbox, text, Column}; use iced::window; -use iced::{Alignment, Command, Element, Length, Subscription}; +use iced::{Alignment, Element, Length, Subscription, Task}; pub fn main() -> iced::Result { iced::program("Events - Iced", Events::update, Events::view) @@ -25,7 +25,7 @@ enum Message { } impl Events { - fn update(&mut self, message: Message) -> Command { + fn update(&mut self, message: Message) -> Task { match message { Message::EventOccurred(event) if self.enabled => { self.last.push(event); @@ -34,19 +34,19 @@ impl Events { let _ = self.last.remove(0); } - Command::none() + Task::none() } Message::EventOccurred(event) => { if let Event::Window(window::Event::CloseRequested) = event { window::close(window::Id::MAIN) } else { - Command::none() + Task::none() } } Message::Toggled(enabled) => { self.enabled = enabled; - Command::none() + Task::none() } Message::Exit => window::close(window::Id::MAIN), } diff --git a/examples/exit/src/main.rs b/examples/exit/src/main.rs index 2de97e2020..8ba180a5be 100644 --- a/examples/exit/src/main.rs +++ b/examples/exit/src/main.rs @@ -1,6 +1,6 @@ use iced::widget::{button, center, column}; use iced::window; -use iced::{Alignment, Command, Element}; +use iced::{Alignment, Element, Task}; pub fn main() -> iced::Result { iced::program("Exit - Iced", Exit::update, Exit::view).run() @@ -18,13 +18,13 @@ enum Message { } impl Exit { - fn update(&mut self, message: Message) -> Command { + fn update(&mut self, message: Message) -> Task { match message { Message::Confirm => window::close(window::Id::MAIN), Message::Exit => { self.show_confirm = true; - Command::none() + Task::none() } } } diff --git a/examples/game_of_life/src/main.rs b/examples/game_of_life/src/main.rs index 8f1f7a5409..7e6d461d3c 100644 --- a/examples/game_of_life/src/main.rs +++ b/examples/game_of_life/src/main.rs @@ -9,7 +9,7 @@ use iced::time; use iced::widget::{ button, checkbox, column, container, pick_list, row, slider, text, }; -use iced::{Alignment, Command, Element, Length, Subscription, Theme}; +use iced::{Alignment, Element, Length, Subscription, Task, Theme}; use std::time::Duration; pub fn main() -> iced::Result { @@ -56,7 +56,7 @@ impl GameOfLife { } } - fn update(&mut self, message: Message) -> Command { + fn update(&mut self, message: Message) -> Task { match message { Message::Grid(message, version) => { if version == self.version { @@ -75,7 +75,7 @@ impl GameOfLife { let version = self.version; - return Command::perform(task, move |message| { + return Task::perform(task, move |message| { Message::Grid(message, version) }); } @@ -103,7 +103,7 @@ impl GameOfLife { } } - Command::none() + Task::none() } fn subscription(&self) -> Subscription { diff --git a/examples/integration/src/controls.rs b/examples/integration/src/controls.rs index 1958b2f324..d06549967e 100644 --- a/examples/integration/src/controls.rs +++ b/examples/integration/src/controls.rs @@ -2,7 +2,7 @@ use iced_wgpu::Renderer; use iced_widget::{column, container, row, slider, text, text_input}; use iced_winit::core::alignment; use iced_winit::core::{Color, Element, Length, Theme}; -use iced_winit::runtime::{Command, Program}; +use iced_winit::runtime::{Program, Task}; pub struct Controls { background_color: Color, @@ -33,7 +33,7 @@ impl Program for Controls { type Message = Message; type Renderer = Renderer; - fn update(&mut self, message: Message) -> Command { + fn update(&mut self, message: Message) -> Task { match message { Message::BackgroundColorChanged(color) => { self.background_color = color; @@ -43,7 +43,7 @@ impl Program for Controls { } } - Command::none() + Task::none() } fn view(&self) -> Element { diff --git a/examples/loading_spinners/src/easing.rs b/examples/loading_spinners/src/easing.rs index 665b332931..45089ef66e 100644 --- a/examples/loading_spinners/src/easing.rs +++ b/examples/loading_spinners/src/easing.rs @@ -119,10 +119,7 @@ impl Builder { fn point(p: impl Into) -> lyon_algorithms::geom::Point { let p: Point = p.into(); - lyon_algorithms::geom::point( - p.x.min(1.0).max(0.0), - p.y.min(1.0).max(0.0), - ) + lyon_algorithms::geom::point(p.x.clamp(0.0, 1.0), p.y.clamp(0.0, 1.0)) } } diff --git a/examples/modal/src/main.rs b/examples/modal/src/main.rs index a012c310dd..d185cf3b45 100644 --- a/examples/modal/src/main.rs +++ b/examples/modal/src/main.rs @@ -5,7 +5,7 @@ use iced::widget::{ self, button, center, column, container, horizontal_space, mouse_area, opaque, pick_list, row, stack, text, text_input, }; -use iced::{Alignment, Color, Command, Element, Length, Subscription}; +use iced::{Alignment, Color, Element, Length, Subscription, Task}; use std::fmt; @@ -39,7 +39,7 @@ impl App { event::listen().map(Message::Event) } - fn update(&mut self, message: Message) -> Command { + fn update(&mut self, message: Message) -> Task { match message { Message::ShowModal => { self.show_modal = true; @@ -47,26 +47,26 @@ impl App { } Message::HideModal => { self.hide_modal(); - Command::none() + Task::none() } Message::Email(email) => { self.email = email; - Command::none() + Task::none() } Message::Password(password) => { self.password = password; - Command::none() + Task::none() } Message::Plan(plan) => { self.plan = plan; - Command::none() + Task::none() } Message::Submit => { if !self.email.is_empty() && !self.password.is_empty() { self.hide_modal(); } - Command::none() + Task::none() } Message::Event(event) => match event { Event::Keyboard(keyboard::Event::KeyPressed { @@ -85,9 +85,9 @@ impl App { .. }) => { self.hide_modal(); - Command::none() + Task::none() } - _ => Command::none(), + _ => Task::none(), }, } } diff --git a/examples/multi_window/src/main.rs b/examples/multi_window/src/main.rs index eb74c94a0c..b82ad1f379 100644 --- a/examples/multi_window/src/main.rs +++ b/examples/multi_window/src/main.rs @@ -1,16 +1,15 @@ -use iced::event; use iced::executor; use iced::multi_window::{self, Application}; use iced::widget::{ - button, center, column, container, scrollable, text, text_input, + button, center, column, container, horizontal_space, scrollable, text, + text_input, }; use iced::window; use iced::{ - Alignment, Command, Element, Length, Point, Settings, Subscription, Theme, - Vector, + Alignment, Element, Length, Settings, Subscription, Task, Theme, Vector, }; -use std::collections::HashMap; +use std::collections::BTreeMap; fn main() -> iced::Result { Example::run(Settings::default()) @@ -18,8 +17,7 @@ fn main() -> iced::Result { #[derive(Default)] struct Example { - windows: HashMap, - next_window_pos: window::Position, + windows: BTreeMap, } #[derive(Debug)] @@ -33,13 +31,12 @@ struct Window { #[derive(Debug, Clone)] enum Message { + OpenWindow, + WindowOpened(window::Id), + WindowClosed(window::Id), ScaleInputChanged(window::Id, String), ScaleChanged(window::Id, String), TitleChanged(window::Id, String), - CloseWindow(window::Id), - WindowOpened(window::Id, Option), - WindowClosed(window::Id), - NewWindow, } impl multi_window::Application for Example { @@ -48,13 +45,12 @@ impl multi_window::Application for Example { type Theme = Theme; type Flags = (); - fn new(_flags: ()) -> (Self, Command) { + fn new(_flags: ()) -> (Self, Task) { ( Example { - windows: HashMap::from([(window::Id::MAIN, Window::new(1))]), - next_window_pos: window::Position::Default, + windows: BTreeMap::from([(window::Id::MAIN, Window::new(1))]), }, - Command::none(), + Task::none(), ) } @@ -62,79 +58,88 @@ impl multi_window::Application for Example { self.windows .get(&window) .map(|window| window.title.clone()) - .unwrap_or("Example".to_string()) + .unwrap_or_default() } - fn update(&mut self, message: Message) -> Command { + fn update(&mut self, message: Message) -> Task { match message { - Message::ScaleInputChanged(id, scale) => { - let window = - self.windows.get_mut(&id).expect("Window not found!"); - window.scale_input = scale; - - Command::none() - } - Message::ScaleChanged(id, scale) => { - let window = - self.windows.get_mut(&id).expect("Window not found!"); - - window.current_scale = scale - .parse::() - .unwrap_or(window.current_scale) - .clamp(0.5, 5.0); - - Command::none() + Message::OpenWindow => { + let Some(last_window) = self.windows.keys().last() else { + return Task::none(); + }; + + window::fetch_position(*last_window) + .then(|last_position| { + let position = last_position.map_or( + window::Position::Default, + |last_position| { + window::Position::Specific( + last_position + Vector::new(20.0, 20.0), + ) + }, + ); + + window::open(window::Settings { + position, + ..window::Settings::default() + }) + }) + .map(Message::WindowOpened) } - Message::TitleChanged(id, title) => { - let window = - self.windows.get_mut(&id).expect("Window not found."); + Message::WindowOpened(id) => { + let window = Window::new(self.windows.len() + 1); + let focus_input = text_input::focus(window.input_id.clone()); - window.title = title; + self.windows.insert(id, window); - Command::none() + focus_input } - Message::CloseWindow(id) => window::close(id), Message::WindowClosed(id) => { self.windows.remove(&id); - Command::none() + + Task::none() } - Message::WindowOpened(id, position) => { - if let Some(position) = position { - self.next_window_pos = window::Position::Specific( - position + Vector::new(20.0, 20.0), - ); + Message::ScaleInputChanged(id, scale) => { + if let Some(window) = self.windows.get_mut(&id) { + window.scale_input = scale; } - if let Some(window) = self.windows.get(&id) { - text_input::focus(window.input_id.clone()) - } else { - Command::none() - } + Task::none() } - Message::NewWindow => { - let count = self.windows.len() + 1; - - let (id, spawn_window) = window::spawn(window::Settings { - position: self.next_window_pos, - exit_on_close_request: count % 2 == 0, - ..Default::default() - }); + Message::ScaleChanged(id, scale) => { + if let Some(window) = self.windows.get_mut(&id) { + window.current_scale = scale + .parse::() + .unwrap_or(window.current_scale) + .clamp(0.5, 5.0); + } - self.windows.insert(id, Window::new(count)); + Task::none() + } + Message::TitleChanged(id, title) => { + if let Some(window) = self.windows.get_mut(&id) { + window.title = title; + } - spawn_window + Task::none() } } } - fn view(&self, window: window::Id) -> Element { - let content = self.windows.get(&window).unwrap().view(window); - - center(content).into() + fn view(&self, window_id: window::Id) -> Element { + if let Some(window) = self.windows.get(&window_id) { + center(window.view(window_id)).into() + } else { + horizontal_space().into() + } } - fn theme(&self, window: window::Id) -> Self::Theme { - self.windows.get(&window).unwrap().theme.clone() + fn theme(&self, window: window::Id) -> Theme { + if let Some(window) = self.windows.get(&window) { + window.theme.clone() + } else { + Theme::default() + } } fn scale_factor(&self, window: window::Id) -> f64 { @@ -145,24 +150,7 @@ impl multi_window::Application for Example { } fn subscription(&self) -> Subscription { - event::listen_with(|event, _, window| { - if let iced::Event::Window(window_event) = event { - match window_event { - window::Event::CloseRequested => { - Some(Message::CloseWindow(window)) - } - window::Event::Opened { position, .. } => { - Some(Message::WindowOpened(window, position)) - } - window::Event::Closed => { - Some(Message::WindowClosed(window)) - } - _ => None, - } - } else { - None - } - }) + window::close_events().map(Message::WindowClosed) } } @@ -172,11 +160,7 @@ impl Window { title: format!("Window_{}", count), scale_input: "1.0".to_string(), current_scale: 1.0, - theme: if count % 2 == 0 { - Theme::Light - } else { - Theme::Dark - }, + theme: Theme::ALL[count % Theme::ALL.len()].clone(), input_id: text_input::Id::unique(), } } @@ -200,7 +184,7 @@ impl Window { ]; let new_window_button = - button(text("New Window")).on_press(Message::NewWindow); + button(text("New Window")).on_press(Message::OpenWindow); let content = scrollable( column![scale_input, title_input, new_window_button] diff --git a/examples/pokedex/src/main.rs b/examples/pokedex/src/main.rs index cffa37276b..e62ed70b24 100644 --- a/examples/pokedex/src/main.rs +++ b/examples/pokedex/src/main.rs @@ -1,6 +1,6 @@ use iced::futures; use iced::widget::{self, center, column, image, row, text}; -use iced::{Alignment, Command, Element, Length}; +use iced::{Alignment, Element, Length, Task}; pub fn main() -> iced::Result { iced::program(Pokedex::title, Pokedex::update, Pokedex::view) @@ -25,8 +25,8 @@ enum Message { } impl Pokedex { - fn search() -> Command { - Command::perform(Pokemon::search(), Message::PokemonFound) + fn search() -> Task { + Task::perform(Pokemon::search(), Message::PokemonFound) } fn title(&self) -> String { @@ -39,20 +39,20 @@ impl Pokedex { format!("{subtitle} - Pokédex") } - fn update(&mut self, message: Message) -> Command { + fn update(&mut self, message: Message) -> Task { match message { Message::PokemonFound(Ok(pokemon)) => { *self = Pokedex::Loaded { pokemon }; - Command::none() + Task::none() } Message::PokemonFound(Err(_error)) => { *self = Pokedex::Errored; - Command::none() + Task::none() } Message::Search => match self { - Pokedex::Loading => Command::none(), + Pokedex::Loading => Task::none(), _ => { *self = Pokedex::Loading; diff --git a/examples/screenshot/src/main.rs b/examples/screenshot/src/main.rs index fb19e55637..78d3e9ffc6 100644 --- a/examples/screenshot/src/main.rs +++ b/examples/screenshot/src/main.rs @@ -4,7 +4,7 @@ use iced::widget::{button, column, container, image, row, text, text_input}; use iced::window; use iced::window::screenshot::{self, Screenshot}; use iced::{ - Alignment, Command, ContentFit, Element, Length, Rectangle, Subscription, + Alignment, ContentFit, Element, Length, Rectangle, Subscription, Task, }; use ::image as img; @@ -34,7 +34,7 @@ struct Example { enum Message { Crop, Screenshot, - ScreenshotData(Screenshot), + Screenshotted(Screenshot), Png, PngSaved(Result), XInputChanged(Option), @@ -44,22 +44,20 @@ enum Message { } impl Example { - fn update(&mut self, message: Message) -> Command { + fn update(&mut self, message: Message) -> Task { match message { Message::Screenshot => { - return iced::window::screenshot( - window::Id::MAIN, - Message::ScreenshotData, - ); + return iced::window::screenshot(window::Id::MAIN) + .map(Message::Screenshotted); } - Message::ScreenshotData(screenshot) => { + Message::Screenshotted(screenshot) => { self.screenshot = Some(screenshot); } Message::Png => { if let Some(screenshot) = &self.screenshot { self.png_saving = true; - return Command::perform( + return Task::perform( save_to_png(screenshot.clone()), Message::PngSaved, ); @@ -103,7 +101,7 @@ impl Example { } } - Command::none() + Task::none() } fn view(&self) -> Element<'_, Message> { diff --git a/examples/scrollable/src/main.rs b/examples/scrollable/src/main.rs index bbb6497fcc..a0dcf82c6a 100644 --- a/examples/scrollable/src/main.rs +++ b/examples/scrollable/src/main.rs @@ -3,7 +3,7 @@ use iced::widget::{ button, column, container, horizontal_space, progress_bar, radio, row, scrollable, slider, text, vertical_space, Scrollable, }; -use iced::{Alignment, Border, Color, Command, Element, Length, Theme}; +use iced::{Alignment, Border, Color, Element, Length, Task, Theme}; use once_cell::sync::Lazy; @@ -59,7 +59,7 @@ impl ScrollableDemo { } } - fn update(&mut self, message: Message) -> Command { + fn update(&mut self, message: Message) -> Task { match message { Message::SwitchDirection(direction) => { self.current_scroll_offset = scrollable::RelativeOffset::START; @@ -82,17 +82,17 @@ impl ScrollableDemo { Message::ScrollbarWidthChanged(width) => { self.scrollbar_width = width; - Command::none() + Task::none() } Message::ScrollbarMarginChanged(margin) => { self.scrollbar_margin = margin; - Command::none() + Task::none() } Message::ScrollerWidthChanged(width) => { self.scroller_width = width; - Command::none() + Task::none() } Message::ScrollToBeginning => { self.current_scroll_offset = scrollable::RelativeOffset::START; @@ -113,7 +113,7 @@ impl ScrollableDemo { Message::Scrolled(viewport) => { self.current_scroll_offset = viewport.relative_offset(); - Command::none() + Task::none() } } } diff --git a/examples/system_information/src/main.rs b/examples/system_information/src/main.rs index 8ce12e1cfa..e2808edd17 100644 --- a/examples/system_information/src/main.rs +++ b/examples/system_information/src/main.rs @@ -1,5 +1,5 @@ use iced::widget::{button, center, column, text}; -use iced::{system, Command, Element}; +use iced::{system, Element, Task}; pub fn main() -> iced::Result { iced::program("System Information - Iced", Example::update, Example::view) @@ -24,19 +24,20 @@ enum Message { } impl Example { - fn update(&mut self, message: Message) -> Command { + fn update(&mut self, message: Message) -> Task { match message { Message::Refresh => { *self = Self::Loading; - return system::fetch_information(Message::InformationReceived); + return system::fetch_information() + .map(Message::InformationReceived); } Message::InformationReceived(information) => { *self = Self::Loaded { information }; } } - Command::none() + Task::none() } fn view(&self) -> Element { diff --git a/examples/toast/src/main.rs b/examples/toast/src/main.rs index 700b6b1084..aee2479e52 100644 --- a/examples/toast/src/main.rs +++ b/examples/toast/src/main.rs @@ -4,7 +4,7 @@ use iced::keyboard::key; use iced::widget::{ self, button, center, column, pick_list, row, slider, text, text_input, }; -use iced::{Alignment, Command, Element, Length, Subscription}; +use iced::{Alignment, Element, Length, Subscription, Task}; use toast::{Status, Toast}; @@ -49,7 +49,7 @@ impl App { event::listen().map(Message::Event) } - fn update(&mut self, message: Message) -> Command { + fn update(&mut self, message: Message) -> Task { match message { Message::Add => { if !self.editing.title.is_empty() @@ -57,27 +57,27 @@ impl App { { self.toasts.push(std::mem::take(&mut self.editing)); } - Command::none() + Task::none() } Message::Close(index) => { self.toasts.remove(index); - Command::none() + Task::none() } Message::Title(title) => { self.editing.title = title; - Command::none() + Task::none() } Message::Body(body) => { self.editing.body = body; - Command::none() + Task::none() } Message::Status(status) => { self.editing.status = status; - Command::none() + Task::none() } Message::Timeout(timeout) => { self.timeout_secs = timeout as u64; - Command::none() + Task::none() } Message::Event(Event::Keyboard(keyboard::Event::KeyPressed { key: keyboard::Key::Named(key::Named::Tab), @@ -88,7 +88,7 @@ impl App { key: keyboard::Key::Named(key::Named::Tab), .. })) => widget::focus_next(), - Message::Event(_) => Command::none(), + Message::Event(_) => Task::none(), } } @@ -347,7 +347,7 @@ mod toast { state: &mut Tree, layout: Layout<'_>, renderer: &Renderer, - operation: &mut dyn Operation, + operation: &mut dyn Operation<()>, ) { operation.container(None, layout.bounds(), &mut |operation| { self.content.as_widget().operate( @@ -589,7 +589,7 @@ mod toast { &mut self, layout: Layout<'_>, renderer: &Renderer, - operation: &mut dyn widget::Operation, + operation: &mut dyn widget::Operation<()>, ) { operation.container(None, layout.bounds(), &mut |operation| { self.toasts diff --git a/examples/todos/src/main.rs b/examples/todos/src/main.rs index dd1e521388..c21e1a9674 100644 --- a/examples/todos/src/main.rs +++ b/examples/todos/src/main.rs @@ -5,7 +5,7 @@ use iced::widget::{ scrollable, text, text_input, Text, }; use iced::window; -use iced::{Command, Element, Font, Length, Subscription}; +use iced::{Element, Font, Length, Subscription, Task as Command}; use once_cell::sync::Lazy; use serde::{Deserialize, Serialize}; diff --git a/examples/visible_bounds/src/main.rs b/examples/visible_bounds/src/main.rs index 8030f5b490..b43c0cca24 100644 --- a/examples/visible_bounds/src/main.rs +++ b/examples/visible_bounds/src/main.rs @@ -5,8 +5,8 @@ use iced::widget::{ }; use iced::window; use iced::{ - Alignment, Color, Command, Element, Font, Length, Point, Rectangle, - Subscription, Theme, + Alignment, Color, Element, Font, Length, Point, Rectangle, Subscription, + Task, Theme, }; pub fn main() -> iced::Result { @@ -33,14 +33,14 @@ enum Message { } impl Example { - fn update(&mut self, message: Message) -> Command { + fn update(&mut self, message: Message) -> Task { match message { Message::MouseMoved(position) => { self.mouse_position = Some(position); - Command::none() + Task::none() } - Message::Scrolled | Message::WindowResized => Command::batch(vec![ + Message::Scrolled | Message::WindowResized => Task::batch(vec![ container::visible_bounds(OUTER_CONTAINER.clone()) .map(Message::OuterBoundsFetched), container::visible_bounds(INNER_CONTAINER.clone()) @@ -49,12 +49,12 @@ impl Example { Message::OuterBoundsFetched(outer_bounds) => { self.outer_bounds = outer_bounds; - Command::none() + Task::none() } Message::InnerBoundsFetched(inner_bounds) => { self.inner_bounds = inner_bounds; - Command::none() + Task::none() } } } diff --git a/examples/websocket/src/main.rs b/examples/websocket/src/main.rs index ba1e102992..8c0fa1d075 100644 --- a/examples/websocket/src/main.rs +++ b/examples/websocket/src/main.rs @@ -4,7 +4,7 @@ use iced::alignment::{self, Alignment}; use iced::widget::{ self, button, center, column, row, scrollable, text, text_input, }; -use iced::{color, Command, Element, Length, Subscription}; +use iced::{color, Element, Length, Subscription, Task}; use once_cell::sync::Lazy; pub fn main() -> iced::Result { @@ -30,19 +30,19 @@ enum Message { } impl WebSocket { - fn load() -> Command { - Command::batch([ - Command::perform(echo::server::run(), |_| Message::Server), + fn load() -> Task { + Task::batch([ + Task::perform(echo::server::run(), |_| Message::Server), widget::focus_next(), ]) } - fn update(&mut self, message: Message) -> Command { + fn update(&mut self, message: Message) -> Task { match message { Message::NewMessageChanged(new_message) => { self.new_message = new_message; - Command::none() + Task::none() } Message::Send(message) => match &mut self.state { State::Connected(connection) => { @@ -50,9 +50,9 @@ impl WebSocket { connection.send(message); - Command::none() + Task::none() } - State::Disconnected => Command::none(), + State::Disconnected => Task::none(), }, Message::Echo(event) => match event { echo::Event::Connected(connection) => { @@ -60,14 +60,14 @@ impl WebSocket { self.messages.push(echo::Message::connected()); - Command::none() + Task::none() } echo::Event::Disconnected => { self.state = State::Disconnected; self.messages.push(echo::Message::disconnected()); - Command::none() + Task::none() } echo::Event::MessageReceived(message) => { self.messages.push(message); @@ -78,7 +78,7 @@ impl WebSocket { ) } }, - Message::Server => Command::none(), + Message::Server => Task::none(), } } diff --git a/futures/src/executor.rs b/futures/src/executor.rs index 5ac76081c3..3b0d4af168 100644 --- a/futures/src/executor.rs +++ b/futures/src/executor.rs @@ -1,5 +1,6 @@ //! Choose your preferred executor to power a runtime. use crate::MaybeSend; + use futures::Future; /// A type that can run futures. diff --git a/runtime/src/clipboard.rs b/runtime/src/clipboard.rs index dd47c47d63..19950d01d4 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 { +/// [`Task`]: crate::Task +#[derive(Debug)] +pub enum Action { /// Read the clipboard and produce `T` with the result. - Read(Box) -> T>, Kind), + Read { + /// The clipboard target. + target: Kind, + /// The channel to send the read contents. + channel: oneshot::Sender>, + }, /// Write the given contents to the clipboard. - Write(String, Kind), -} - -impl Action { - /// Maps the output of a clipboard [`Action`] using the provided closure. - pub fn map( - self, - f: impl Fn(T) -> A + 'static + MaybeSend + Sync, - ) -> Action - 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 fmt::Debug for Action { - 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( - f: impl Fn(Option) -> Message + 'static, -) -> Command { - Command::single(command::Action::Clipboard(Action::Read( - Box::new(f), - Kind::Standard, - ))) +pub fn read() -> Task> { + Task::oneshot(|channel| { + crate::Action::Clipboard(Action::Read { + target: Kind::Standard, + channel, + }) + }) } /// Read the current contents of the primary clipboard. -pub fn read_primary( - f: impl Fn(Option) -> Message + 'static, -) -> Command { - Command::single(command::Action::Clipboard(Action::Read( - Box::new(f), - Kind::Primary, - ))) +pub fn read_primary() -> Task> { + Task::oneshot(|channel| { + crate::Action::Clipboard(Action::Read { + target: Kind::Primary, + channel, + }) + }) } /// Write the given contents to the clipboard. -pub fn write(contents: String) -> Command { - Command::single(command::Action::Clipboard(Action::Write( +pub fn write(contents: String) -> Task { + 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(contents: String) -> Command { - Command::single(command::Action::Clipboard(Action::Write( +pub fn write_primary(contents: String) -> Task { + 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 f7a746feea..0000000000 --- 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(Internal>); - -#[derive(Debug)] -enum Internal { - None, - Single(T), - Batch(Vec), -} - -impl Command { - /// 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) -> Self { - Self(Internal::Single(action)) - } - - /// Creates a [`Command`] that performs a [`widget::Operation`]. - pub fn widget(operation: impl widget::Operation + 'static) -> Self { - Self::single(Action::Widget(Box::new(operation))) - } - - /// Creates a [`Command`] that performs the action of the given future. - pub fn perform( - future: impl Future + 'static + MaybeSend, - f: impl FnOnce(A) -> T + 'static + MaybeSend, - ) -> Command { - 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( - stream: impl Stream + 'static + MaybeSend, - f: impl Fn(A) -> T + 'static + MaybeSend, - ) -> Command { - 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>) -> 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( - self, - f: impl Fn(T) -> A + 'static + MaybeSend + Sync + Clone, - ) -> Command - 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> { - let Command(command) = self; - - match command { - Internal::None => Vec::new(), - Internal::Single(action) => vec![action], - Internal::Batch(batch) => batch, - } - } -} - -impl From<()> for Command { - fn from(_value: ()) -> Self { - Self::none() - } -} - -impl fmt::Debug for Command { - 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( - size: usize, - f: impl FnOnce(mpsc::Sender) -> Fut + MaybeSend + 'static, -) -> Command -where - Fut: Future + 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 c9ffe801c4..0000000000 --- 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 { - /// Run a [`Future`] to completion. - /// - /// [`Future`]: iced_futures::BoxFuture - Future(iced_futures::BoxFuture), - - /// Run a [`Stream`] to completion. - /// - /// [`Stream`]: iced_futures::BoxStream - Stream(iced_futures::BoxStream), - - /// Run a clipboard action. - Clipboard(clipboard::Action), - - /// Run a window action. - Window(window::Action), - - /// Run a system action. - System(system::Action), - - /// Run a widget action. - Widget(Box>), - - /// 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) -> T>, - }, - - /// A custom action supported by a specific runtime. - Custom(Box), -} - -impl Action { - /// Applies a transformation to the result of a [`Command`]. - /// - /// [`Command`]: crate::Command - pub fn map( - self, - f: impl Fn(T) -> A + 'static + MaybeSend + Sync, - ) -> Action - 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 fmt::Debug for Action { - 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 1535969473..d54eb6a837 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>, -) -> Command> { - Command::single(command::Action::LoadFont { +pub fn load(bytes: impl Into>) -> Task> { + 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 5f054c4638..5fde3039b8 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 { + /// 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>, + }, + + /// Run a widget operation. + Widget(Box + Send>), + + /// Run a clipboard action. + Clipboard(clipboard::Action), + + /// Run a window action. + Window(window::Action), + + /// Run a system action. + System(system::Action), +} + +impl Action { + /// 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(self) -> Result> { + 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 fmt::Debug for Action +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 963a09d748..e8c71b268a 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; + /// Any [`Task`] returned will be executed immediately in the background by the + /// runtime. + fn update(&mut self, message: Self::Message) -> Task; /// 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 10366ec05b..72ce6933d7 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, Option>) { + ) -> (Vec, Option>) { 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>>, + operations: impl Iterator>>, 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 ddb9532b9f..11eee41c44 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, + operation: &mut dyn widget::Operation<()>, ) { fn recurse( element: &mut overlay::Element<'_, Message, Theme, Renderer>, layout: Layout<'_>, renderer: &Renderer, - operation: &mut dyn widget::Operation, + operation: &mut dyn widget::Operation<()>, ) where Renderer: renderer::Renderer, { diff --git a/runtime/src/program.rs b/runtime/src/program.rs index 0ea94d3b02..77acf4979e 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; + fn update(&mut self, message: Self::Message) -> Task; /// Returns the widgets to display in the [`Program`]. /// diff --git a/runtime/src/program/state.rs b/runtime/src/program/state.rs index c6589c22be..e51ad0cb4b 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, Option>) { + ) -> (Vec, Option>) { 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>>, + operations: impl Iterator>>, 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 61c8ff297f..b6fb4fdf11 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), +} + +/// 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, + /// Operating system kernel version + pub system_kernel: Option, + /// Long operating system version + /// + /// Examples: + /// - MacOS 10.15 Catalina + /// - Windows 10 Pro + /// - Ubuntu 20.04 LTS (Focal Fossa) + pub system_version: Option, + /// Short operating system version number + pub system_short_version: Option, + /// Detailed processor model information + pub cpu_brand: String, + /// The number of physical cores on the processor + pub cpu_cores: Option, + /// Total RAM size, in bytes + pub memory_total: u64, + /// Memory used by this process, in bytes + pub memory_used: Option, + /// 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 dea9536f42..0000000000 --- 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 { - /// Query system information and produce `T` with the result. - QueryInformation(Box>), -} - -pub trait Closure: Fn(system::Information) -> T + MaybeSend {} - -impl Closure for T where T: Fn(system::Information) -> O + MaybeSend {} - -impl Action { - /// Maps the output of a system [`Action`] using the provided closure. - pub fn map( - self, - f: impl Fn(T) -> A + 'static + MaybeSend + Sync, - ) -> Action - where - T: 'static, - { - match self { - Self::QueryInformation(o) => { - Action::QueryInformation(Box::new(move |s| f(o(s)))) - } - } - } -} - -impl fmt::Debug for Action { - 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 0f78f5e926..0000000000 --- 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, - /// Operating system kernel version - pub system_kernel: Option, - /// Long operating system version - /// - /// Examples: - /// - MacOS 10.15 Catalina - /// - Windows 10 Pro - /// - Ubuntu 20.04 LTS (Focal Fossa) - pub system_version: Option, - /// Short operating system version number - pub system_short_version: Option, - /// Detailed processor model information - pub cpu_brand: String, - /// The number of physical cores on the processor - pub cpu_cores: Option, - /// Total RAM size, in bytes - pub memory_total: u64, - /// Memory used by this process, in bytes - pub memory_used: Option, - /// 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 0000000000..740360acba --- /dev/null +++ b/runtime/src/task.rs @@ -0,0 +1,252 @@ +use crate::core::widget; +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, MaybeSend}; +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(Option>>); + +impl Task { + /// 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 + 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 + MaybeSend + 'static) -> Self + where + T: 'static, + { + Self(Some(boxed_stream(stream.map(Action::Output)))) + } + + /// Creates a [`Task`] that runs the given [`Future`] to completion and maps its + /// output with the given closure. + pub fn perform( + future: impl Future + 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 and maps each + /// item with the given closure. + pub fn run( + stream: impl Stream + MaybeSend + 'static, + f: impl Fn(A) -> T + MaybeSend + 'static, + ) -> 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) -> Self + where + T: 'static, + { + Self(Some(boxed_stream(stream::select_all( + tasks.into_iter().filter_map(|task| task.0), + )))) + } + + /// Creates a new [`Task`] that runs the given [`widget::Operation`] and produces + /// its output. + pub fn widget(operation: impl widget::Operation + 'static) -> Task + where + T: Send + '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) -> Action) -> Task + 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) -> Action) -> Task + 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>) -> 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( + self, + mut f: impl FnMut(T) -> O + MaybeSend + 'static, + ) -> Task + 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( + self, + mut f: impl FnMut(T) -> Task + MaybeSend + 'static, + ) -> Task + 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 new [`Task`] that collects all the output of the current one into a [`Vec`]. + pub fn collect(self) -> Task> + where + T: MaybeSend + 'static, + { + match self.0 { + None => Task::done(Vec::new()), + Some(stream) => Task(Some(boxed_stream( + stream::unfold( + (stream, Some(Vec::new())), + move |(mut stream, outputs)| async move { + let mut outputs = outputs?; + + let Some(action) = stream.next().await else { + return Some(( + Some(Action::Output(outputs)), + (stream, None), + )); + }; + + match action.output() { + Ok(output) => { + outputs.push(output); + + Some((None, (stream, Some(outputs)))) + } + Err(action) => { + Some((Some(action), (stream, Some(outputs)))) + } + } + }, + ) + .filter_map(future::ready), + ))), + } + } + + /// Returns the underlying [`Stream`] of the [`Task`]. + pub fn into_stream(self) -> Option>> { + self.0 + } +} + +impl From<()> for Task +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 006225ed01..858b1a2d0d 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, + 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 b68c9a7130..956a20e1bd 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::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, oneshot::Sender), + + /// 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), + + /// Fetch if the current window is maximized or not. + FetchMaximized(Id, oneshot::Sender), + + /// 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>), + + /// Set the window to minimized or back + Minimize(Id, bool), + + /// Fetch the current logical coordinates of the window. + FetchPosition(Id, oneshot::Sender>), + + /// 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), + + /// 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), + + /// 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), + + /// 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) + Send>), + + /// Screenshot the viewport of the window. + Screenshot(Id, oneshot::Sender), +} + /// 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,138 @@ pub fn frames() -> Subscription { }) } -/// Spawns a new window with the given `settings`. -/// -/// Returns the new window [`Id`] alongside the [`Command`]. -pub fn spawn(settings: Settings) -> (Id, Command) { +/// Subscribes to all window events of the running application. +pub fn events() -> Subscription<(Id, Event)> { + event::listen_with(|event, _status, id| { + if let crate::core::Event::Window(event) = event { + Some((id, event)) + } else { + None + } + }) +} + +/// Subscribes to all [`Event::Closed`] occurrences in the running application. +pub fn open_events() -> Subscription { + event::listen_with(|event, _status, id| { + if let crate::core::Event::Window(Event::Closed) = event { + Some(id) + } else { + None + } + }) +} + +/// Subscribes to all [`Event::Closed`] occurrences in the running application. +pub fn close_events() -> Subscription { + event::listen_with(|event, _status, id| { + if let crate::core::Event::Window(Event::Closed) = event { + Some(id) + } else { + None + } + }) +} + +/// Subscribes to all [`Event::CloseRequested`] occurences in the running application. +pub fn close_requests() -> Subscription { + event::listen_with(|event, _status, id| { + if let crate::core::Event::Window(Event::CloseRequested) = event { + Some(id) + } else { + None + } + }) +} + +/// Opens a new window with the given [`Settings`]; producing the [`Id`] +/// of the new window on completion. +pub fn open(settings: Settings) -> Task { let id = Id::unique(); - ( - id, - Command::single(command::Action::Window(Action::Spawn(id, settings))), - ) + Task::oneshot(|channel| { + crate::Action::Window(Action::Open(id, settings, channel)) + }) } /// Closes the window with `id`. -pub fn close(id: Id) -> Command { - Command::single(command::Action::Window(Action::Close(id))) +pub fn close(id: Id) -> Task { + Task::effect(crate::Action::Window(Action::Close(id))) } /// Begins dragging the window while the left mouse button is held. -pub fn drag(id: Id) -> Command { - Command::single(command::Action::Window(Action::Drag(id))) +pub fn drag(id: Id) -> Task { + Task::effect(crate::Action::Window(Action::Drag(id))) } /// Resizes the window to the given logical dimensions. -pub fn resize(id: Id, new_size: Size) -> Command { - Command::single(command::Action::Window(Action::Resize(id, new_size))) +pub fn resize(id: Id, new_size: Size) -> Task { + Task::effect(crate::Action::Window(Action::Resize(id, new_size))) } /// Fetches the window's size in logical dimensions. -pub fn fetch_size( - id: Id, - f: impl FnOnce(Size) -> Message + 'static, -) -> Command { - Command::single(command::Action::Window(Action::FetchSize(id, Box::new(f)))) +pub fn fetch_size(id: Id) -> Task { + Task::oneshot(move |channel| { + crate::Action::Window(Action::FetchSize(id, channel)) + }) } /// Fetches if the window is maximized. -pub fn fetch_maximized( - id: Id, - f: impl FnOnce(bool) -> Message + 'static, -) -> Command { - Command::single(command::Action::Window(Action::FetchMaximized( - id, - Box::new(f), - ))) +pub fn fetch_maximized(id: Id) -> Task { + Task::oneshot(move |channel| { + crate::Action::Window(Action::FetchMaximized(id, channel)) + }) } /// Maximizes the window. -pub fn maximize(id: Id, maximized: bool) -> Command { - Command::single(command::Action::Window(Action::Maximize(id, maximized))) +pub fn maximize(id: Id, maximized: bool) -> Task { + Task::effect(crate::Action::Window(Action::Maximize(id, maximized))) } /// Fetches if the window is minimized. -pub fn fetch_minimized( - id: Id, - f: impl FnOnce(Option) -> Message + 'static, -) -> Command { - Command::single(command::Action::Window(Action::FetchMinimized( - id, - Box::new(f), - ))) +pub fn fetch_minimized(id: Id) -> Task> { + Task::oneshot(move |channel| { + crate::Action::Window(Action::FetchMinimized(id, channel)) + }) } /// Minimizes the window. -pub fn minimize(id: Id, minimized: bool) -> Command { - Command::single(command::Action::Window(Action::Minimize(id, minimized))) +pub fn minimize(id: Id, minimized: bool) -> Task { + Task::effect(crate::Action::Window(Action::Minimize(id, minimized))) } /// Fetches the current window position in logical coordinates. -pub fn fetch_position( - id: Id, - f: impl FnOnce(Option) -> Message + 'static, -) -> Command { - Command::single(command::Action::Window(Action::FetchPosition( - id, - Box::new(f), - ))) +pub fn fetch_position(id: Id) -> Task> { + Task::oneshot(move |channel| { + crate::Action::Window(Action::FetchPosition(id, channel)) + }) } /// Moves the window to the given logical coordinates. -pub fn move_to(id: Id, position: Point) -> Command { - Command::single(command::Action::Window(Action::Move(id, position))) +pub fn move_to(id: Id, position: Point) -> Task { + Task::effect(crate::Action::Window(Action::Move(id, position))) } /// Changes the [`Mode`] of the window. -pub fn change_mode(id: Id, mode: Mode) -> Command { - Command::single(command::Action::Window(Action::ChangeMode(id, mode))) +pub fn change_mode(id: Id, mode: Mode) -> Task { + Task::effect(crate::Action::Window(Action::ChangeMode(id, mode))) } /// Fetches the current [`Mode`] of the window. -pub fn fetch_mode( - id: Id, - f: impl FnOnce(Mode) -> Message + 'static, -) -> Command { - Command::single(command::Action::Window(Action::FetchMode(id, Box::new(f)))) +pub fn fetch_mode(id: Id) -> Task { + Task::oneshot(move |channel| { + crate::Action::Window(Action::FetchMode(id, channel)) + }) } /// Toggles the window to maximized or back. -pub fn toggle_maximize(id: Id) -> Command { - Command::single(command::Action::Window(Action::ToggleMaximize(id))) +pub fn toggle_maximize(id: Id) -> Task { + Task::effect(crate::Action::Window(Action::ToggleMaximize(id))) } /// Toggles the window decorations. -pub fn toggle_decorations(id: Id) -> Command { - Command::single(command::Action::Window(Action::ToggleDecorations(id))) +pub fn toggle_decorations(id: Id) -> Task { + Task::effect(crate::Action::Window(Action::ToggleDecorations(id))) } /// Request user attention to the window. This has no effect if the application @@ -146,11 +295,11 @@ pub fn toggle_decorations(id: Id) -> Command { /// /// 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( +pub fn request_user_attention( id: Id, user_attention: Option, -) -> Command { - Command::single(command::Action::Window(Action::RequestUserAttention( +) -> Task { + Task::effect(crate::Action::Window(Action::RequestUserAttention( id, user_attention, ))) @@ -159,59 +308,61 @@ pub fn request_user_attention( /// 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(id: Id) -> Command { - Command::single(command::Action::Window(Action::GainFocus(id))) +pub fn gain_focus(id: Id) -> Task { + Task::effect(crate::Action::Window(Action::GainFocus(id))) } /// Changes the window [`Level`]. -pub fn change_level(id: Id, level: Level) -> Command { - Command::single(command::Action::Window(Action::ChangeLevel(id, level))) +pub fn change_level(id: Id, level: Level) -> Task { + 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(id: Id) -> Command { - Command::single(command::Action::Window(Action::ShowSystemMenu(id))) +pub fn show_system_menu(id: Id) -> Task { + 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( - id: Id, - f: impl FnOnce(u64) -> Message + 'static, -) -> Command { - Command::single(command::Action::Window(Action::FetchId(id, Box::new(f)))) +pub fn fetch_raw_id(id: Id) -> Task { + Task::oneshot(|channel| { + crate::Action::Window(Action::FetchRawId(id, channel)) + }) } /// Changes the [`Icon`] of the window. -pub fn change_icon(id: Id, icon: Icon) -> Command { - Command::single(command::Action::Window(Action::ChangeIcon(id, icon))) +pub fn change_icon(id: Id, icon: Icon) -> Task { + 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( +pub fn run_with_handle( id: Id, - f: impl FnOnce(WindowHandle<'_>) -> Message + 'static, -) -> Command { - Command::single(command::Action::Window(Action::RunWithHandle( - id, - Box::new(f), - ))) + f: impl FnOnce(WindowHandle<'_>) -> T + Send + 'static, +) -> Task +where + T: Send + '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( - id: Id, - f: impl FnOnce(Screenshot) -> Message + Send + 'static, -) -> Command { - Command::single(command::Action::Window(Action::Screenshot( - id, - Box::new(f), - ))) +pub fn screenshot(id: Id) -> Task { + 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 07e7787231..0000000000 --- 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 { - /// 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 T + 'static>), - /// Fetch if the current window is maximized or not. - /// - /// ## Platform-specific - /// - **iOS / Android / Web:** Unsupported. - FetchMaximized(Id, Box 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) -> T + 'static>), - /// Set the window to minimized or back - Minimize(Id, bool), - /// Fetch the current logical coordinates of the window. - FetchPosition(Id, Box) -> 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 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), - /// 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 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) -> T + 'static>), - /// Screenshot the viewport of the window. - Screenshot(Id, Box T + 'static>), -} - -impl Action { - /// Maps the output of a window [`Action`] using the provided closure. - pub fn map( - self, - f: impl Fn(T) -> A + 'static + MaybeSend + Sync, - ) -> Action - 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 fmt::Debug for Action { - 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:?})"), - } - } -} diff --git a/src/application.rs b/src/application.rs index d12ba73dcc..4cd4a87d07 100644 --- a/src/application.rs +++ b/src/application.rs @@ -2,7 +2,7 @@ use crate::core::text; use crate::graphics::compositor; use crate::shell::application; -use crate::{Command, Element, Executor, Settings, Subscription}; +use crate::{Element, Executor, Settings, Subscription, Task}; pub use application::{Appearance, DefaultStyle}; @@ -16,7 +16,7 @@ pub use application::{Appearance, DefaultStyle}; /// document. /// /// An [`Application`] can execute asynchronous actions by returning a -/// [`Command`] in some of its methods. +/// [`Task`] in some of its methods. /// /// When using an [`Application`] with the `debug` feature enabled, a debug view /// can be toggled by pressing `F12`. @@ -62,7 +62,7 @@ pub use application::{Appearance, DefaultStyle}; /// ```no_run /// use iced::advanced::Application; /// use iced::executor; -/// use iced::{Command, Element, Settings, Theme, Renderer}; +/// use iced::{Task, Element, Settings, Theme, Renderer}; /// /// pub fn main() -> iced::Result { /// Hello::run(Settings::default()) @@ -77,16 +77,16 @@ pub use application::{Appearance, DefaultStyle}; /// type Theme = Theme; /// type Renderer = Renderer; /// -/// fn new(_flags: ()) -> (Hello, Command) { -/// (Hello, Command::none()) +/// fn new(_flags: ()) -> (Hello, Task) { +/// (Hello, Task::none()) /// } /// /// fn title(&self) -> String { /// String::from("A cool application") /// } /// -/// fn update(&mut self, _message: Self::Message) -> Command { -/// Command::none() +/// fn update(&mut self, _message: Self::Message) -> Task { +/// Task::none() /// } /// /// fn view(&self) -> Element { @@ -123,12 +123,12 @@ where /// /// Here is where you should return the initial state of your app. /// - /// Additionally, you can return a [`Command`] if you need to perform some + /// Additionally, you can return a [`Task`] if you need to perform some /// async action in the background on startup. This is useful if you want to /// load state from a file, perform an initial HTTP request, etc. /// /// [`run`]: Self::run - fn new(flags: Self::Flags) -> (Self, Command); + fn new(flags: Self::Flags) -> (Self, Task); /// Returns the current title of the [`Application`]. /// @@ -142,8 +142,8 @@ where /// produced by either user interactions or commands, will be handled by /// this method. /// - /// Any [`Command`] returned will be executed immediately in the background. - fn update(&mut self, message: Self::Message) -> Command; + /// Any [`Task`] returned will be executed immediately in the background. + fn update(&mut self, message: Self::Message) -> Task; /// Returns the widgets to display in the [`Application`]. /// @@ -234,7 +234,7 @@ where type Theme = A::Theme; type Renderer = A::Renderer; - fn update(&mut self, message: Self::Message) -> Command { + fn update(&mut self, message: Self::Message) -> Task { self.0.update(message) } @@ -250,7 +250,7 @@ where { type Flags = A::Flags; - fn new(flags: Self::Flags) -> (Self, Command) { + fn new(flags: Self::Flags) -> (Self, Task) { let (app, command) = A::new(flags); (Instance(app), command) diff --git a/src/lib.rs b/src/lib.rs index 317d25a6d7..cf0bc7d732 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -203,6 +203,7 @@ pub use crate::core::{ Length, Padding, Pixels, Point, Radians, Rectangle, Rotation, Shadow, Size, Theme, Transformation, Vector, }; +pub use crate::runtime::Task; pub mod clipboard { //! Access the clipboard. @@ -256,11 +257,6 @@ pub mod mouse { }; } -pub mod command { - //! Run asynchronous actions. - pub use crate::runtime::command::{channel, Command}; -} - pub mod subscription { //! Listen to external events in your application. pub use iced_futures::subscription::{ @@ -312,7 +308,6 @@ pub mod widget { mod runtime {} } -pub use command::Command; pub use error::Error; pub use event::Event; pub use executor::Executor; diff --git a/src/multi_window.rs b/src/multi_window.rs index b81297dc53..4900bb85f5 100644 --- a/src/multi_window.rs +++ b/src/multi_window.rs @@ -1,6 +1,6 @@ //! Leverage multi-window support in your application. use crate::window; -use crate::{Command, Element, Executor, Settings, Subscription}; +use crate::{Element, Executor, Settings, Subscription, Task}; pub use crate::application::{Appearance, DefaultStyle}; @@ -14,7 +14,7 @@ pub use crate::application::{Appearance, DefaultStyle}; /// document and display only the contents of the `window::Id::MAIN` window. /// /// An [`Application`] can execute asynchronous actions by returning a -/// [`Command`] in some of its methods. +/// [`Task`] in some of its methods. /// /// When using an [`Application`] with the `debug` feature enabled, a debug view /// can be toggled by pressing `F12`. @@ -29,7 +29,7 @@ pub use crate::application::{Appearance, DefaultStyle}; /// /// ```no_run /// use iced::{executor, window}; -/// use iced::{Command, Element, Settings, Theme}; +/// use iced::{Task, Element, Settings, Theme}; /// use iced::multi_window::{self, Application}; /// /// pub fn main() -> iced::Result { @@ -44,16 +44,16 @@ pub use crate::application::{Appearance, DefaultStyle}; /// type Message = (); /// type Theme = Theme; /// -/// fn new(_flags: ()) -> (Hello, Command) { -/// (Hello, Command::none()) +/// fn new(_flags: ()) -> (Hello, Task) { +/// (Hello, Task::none()) /// } /// /// fn title(&self, _window: window::Id) -> String { /// String::from("A cool application") /// } /// -/// fn update(&mut self, _message: Self::Message) -> Command { -/// Command::none() +/// fn update(&mut self, _message: Self::Message) -> Task { +/// Task::none() /// } /// /// fn view(&self, _window: window::Id) -> Element { @@ -89,12 +89,12 @@ where /// /// Here is where you should return the initial state of your app. /// - /// Additionally, you can return a [`Command`] if you need to perform some + /// Additionally, you can return a [`Task`] if you need to perform some /// async action in the background on startup. This is useful if you want to /// load state from a file, perform an initial HTTP request, etc. /// /// [`run`]: Self::run - fn new(flags: Self::Flags) -> (Self, Command); + fn new(flags: Self::Flags) -> (Self, Task); /// Returns the current title of the `window` of the [`Application`]. /// @@ -108,8 +108,8 @@ where /// produced by either user interactions or commands, will be handled by /// this method. /// - /// Any [`Command`] returned will be executed immediately in the background. - fn update(&mut self, message: Self::Message) -> Command; + /// Any [`Task`] returned will be executed immediately in the background. + fn update(&mut self, message: Self::Message) -> Task; /// Returns the widgets to display in the `window` of the [`Application`]. /// @@ -207,7 +207,7 @@ where type Theme = A::Theme; type Renderer = crate::Renderer; - fn update(&mut self, message: Self::Message) -> Command { + fn update(&mut self, message: Self::Message) -> Task { self.0.update(message) } @@ -226,7 +226,7 @@ where { type Flags = A::Flags; - fn new(flags: Self::Flags) -> (Self, Command) { + fn new(flags: Self::Flags) -> (Self, Task) { let (app, command) = A::new(flags); (Instance(app), command) diff --git a/src/program.rs b/src/program.rs index d4c2a26650..ea6b0e8e03 100644 --- a/src/program.rs +++ b/src/program.rs @@ -35,7 +35,7 @@ use crate::core::text; use crate::executor::{self, Executor}; use crate::graphics::compositor; use crate::window; -use crate::{Command, Element, Font, Result, Settings, Size, Subscription}; +use crate::{Element, Font, Result, Settings, Size, Subscription, Task}; pub use crate::application::{Appearance, DefaultStyle}; @@ -76,7 +76,7 @@ pub fn program( ) -> Program> where State: 'static, - Message: Send + std::fmt::Debug, + Message: Send + std::fmt::Debug + 'static, Theme: Default + DefaultStyle, Renderer: self::Renderer, { @@ -94,7 +94,7 @@ where impl Definition for Application where - Message: Send + std::fmt::Debug, + Message: Send + std::fmt::Debug + 'static, Theme: Default + DefaultStyle, Renderer: self::Renderer, Update: self::Update, @@ -106,15 +106,15 @@ where type Renderer = Renderer; type Executor = executor::Default; - fn load(&self) -> Command { - Command::none() + fn load(&self) -> Task { + Task::none() } fn update( &self, state: &mut Self::State, message: Self::Message, - ) -> Command { + ) -> Task { self.update.update(state, message).into() } @@ -197,7 +197,7 @@ impl Program

{ fn new( (program, initialize): Self::Flags, - ) -> (Self, Command) { + ) -> (Self, Task) { let state = initialize(); let command = program.load(); @@ -218,7 +218,7 @@ impl Program

{ fn update( &mut self, message: Self::Message, - ) -> Command { + ) -> Task { self.program.update(&mut self.state, message) } @@ -357,10 +357,10 @@ impl Program

{ } } - /// Runs the [`Command`] produced by the closure at startup. + /// Runs the [`Task`] produced by the closure at startup. pub fn load( self, - f: impl Fn() -> Command, + f: impl Fn() -> Task, ) -> Program< impl Definition, > { @@ -420,7 +420,7 @@ pub trait Definition: Sized { type State; /// The message of the program. - type Message: Send + std::fmt::Debug; + type Message: Send + std::fmt::Debug + 'static; /// The theme of the program. type Theme: Default + DefaultStyle; @@ -431,13 +431,13 @@ pub trait Definition: Sized { /// The executor of the program. type Executor: Executor; - fn load(&self) -> Command; + fn load(&self) -> Task; fn update( &self, state: &mut Self::State, message: Self::Message, - ) -> Command; + ) -> Task; fn view<'a>( &self, @@ -484,7 +484,7 @@ fn with_title( type Renderer = P::Renderer; type Executor = P::Executor; - fn load(&self) -> Command { + fn load(&self) -> Task { self.program.load() } @@ -496,7 +496,7 @@ fn with_title( &self, state: &mut Self::State, message: Self::Message, - ) -> Command { + ) -> Task { self.program.update(state, message) } @@ -532,7 +532,7 @@ fn with_title( fn with_load( program: P, - f: impl Fn() -> Command, + f: impl Fn() -> Task, ) -> impl Definition { struct WithLoad { program: P, @@ -541,7 +541,7 @@ fn with_load( impl Definition for WithLoad where - F: Fn() -> Command, + F: Fn() -> Task, { type State = P::State; type Message = P::Message; @@ -549,15 +549,15 @@ fn with_load( type Renderer = P::Renderer; type Executor = executor::Default; - fn load(&self) -> Command { - Command::batch([self.program.load(), (self.load)()]) + fn load(&self) -> Task { + Task::batch([self.program.load(), (self.load)()]) } fn update( &self, state: &mut Self::State, message: Self::Message, - ) -> Command { + ) -> Task { self.program.update(state, message) } @@ -621,7 +621,7 @@ fn with_subscription( (self.subscription)(state) } - fn load(&self) -> Command { + fn load(&self) -> Task { self.program.load() } @@ -629,7 +629,7 @@ fn with_subscription( &self, state: &mut Self::State, message: Self::Message, - ) -> Command { + ) -> Task { self.program.update(state, message) } @@ -686,7 +686,7 @@ fn with_theme( (self.theme)(state) } - fn load(&self) -> Command { + fn load(&self) -> Task { self.program.load() } @@ -698,7 +698,7 @@ fn with_theme( &self, state: &mut Self::State, message: Self::Message, - ) -> Command { + ) -> Task { self.program.update(state, message) } @@ -755,7 +755,7 @@ fn with_style( (self.style)(state, theme) } - fn load(&self) -> Command { + fn load(&self) -> Task { self.program.load() } @@ -767,7 +767,7 @@ fn with_style( &self, state: &mut Self::State, message: Self::Message, - ) -> Command { + ) -> Task { self.program.update(state, message) } @@ -822,26 +822,26 @@ where /// The update logic of some [`Program`]. /// /// This trait allows the [`program`] builder to take any closure that -/// returns any `Into>`. +/// returns any `Into>`. pub trait Update { /// Processes the message and updates the state of the [`Program`]. fn update( &self, state: &mut State, message: Message, - ) -> impl Into>; + ) -> impl Into>; } impl Update for T where T: Fn(&mut State, Message) -> C, - C: Into>, + C: Into>, { fn update( &self, state: &mut State, message: Message, - ) -> impl Into> { + ) -> impl Into> { self(state, message) } } diff --git a/widget/src/button.rs b/widget/src/button.rs index dc9496713c..5d446fea37 100644 --- a/widget/src/button.rs +++ b/widget/src/button.rs @@ -205,7 +205,7 @@ where tree: &mut Tree, layout: Layout<'_>, renderer: &Renderer, - operation: &mut dyn Operation, + operation: &mut dyn Operation<()>, ) { operation.container(None, layout.bounds(), &mut |operation| { self.content.as_widget().operate( diff --git a/widget/src/column.rs b/widget/src/column.rs index df7829b3df..4699164c9c 100644 --- a/widget/src/column.rs +++ b/widget/src/column.rs @@ -208,7 +208,7 @@ where tree: &mut Tree, layout: Layout<'_>, renderer: &Renderer, - operation: &mut dyn Operation, + operation: &mut dyn Operation<()>, ) { operation.container(None, layout.bounds(), &mut |operation| { self.children diff --git a/widget/src/container.rs b/widget/src/container.rs index 51967707d5..e917471f5c 100644 --- a/widget/src/container.rs +++ b/widget/src/container.rs @@ -13,7 +13,7 @@ use crate::core::{ Padding, Pixels, Point, Rectangle, Shadow, Shell, Size, Theme, Vector, Widget, }; -use crate::runtime::Command; +use crate::runtime::Task; /// An element decorating some content. /// @@ -258,7 +258,7 @@ where tree: &mut Tree, layout: Layout<'_>, renderer: &Renderer, - operation: &mut dyn Operation, + operation: &mut dyn Operation<()>, ) { operation.container( self.id.as_ref().map(|id| &id.0), @@ -457,9 +457,9 @@ impl From for widget::Id { } } -/// Produces a [`Command`] that queries the visible screen bounds of the +/// Produces a [`Task`] that queries the visible screen bounds of the /// [`Container`] with the given [`Id`]. -pub fn visible_bounds(id: Id) -> Command> { +pub fn visible_bounds(id: Id) -> Task> { struct VisibleBounds { target: widget::Id, depth: usize, @@ -538,7 +538,7 @@ pub fn visible_bounds(id: Id) -> Command> { } } - Command::widget(VisibleBounds { + Task::widget(VisibleBounds { target: id.into(), depth: 0, scrollables: Vec::new(), diff --git a/widget/src/helpers.rs b/widget/src/helpers.rs index 016bafbb26..62343a55c8 100644 --- a/widget/src/helpers.rs +++ b/widget/src/helpers.rs @@ -12,7 +12,7 @@ use crate::pick_list::{self, PickList}; use crate::progress_bar::{self, ProgressBar}; use crate::radio::{self, Radio}; use crate::rule::{self, Rule}; -use crate::runtime::Command; +use crate::runtime::{Action, Task}; use crate::scrollable::{self, Scrollable}; use crate::slider::{self, Slider}; use crate::text::{self, Text}; @@ -275,7 +275,7 @@ where state: &mut Tree, layout: Layout<'_>, renderer: &Renderer, - operation: &mut dyn operation::Operation, + operation: &mut dyn operation::Operation<()>, ) { self.content .as_widget() @@ -477,7 +477,7 @@ where tree: &mut Tree, layout: Layout<'_>, renderer: &Renderer, - operation: &mut dyn operation::Operation, + operation: &mut dyn operation::Operation<()>, ) { let children = [&self.base, &self.top] .into_iter() @@ -929,19 +929,13 @@ where } /// Focuses the previous focusable widget. -pub fn focus_previous() -> Command -where - Message: 'static, -{ - Command::widget(operation::focusable::focus_previous()) +pub fn focus_previous() -> Task { + Task::effect(Action::widget(operation::focusable::focus_previous())) } /// Focuses the next focusable widget. -pub fn focus_next() -> Command -where - Message: 'static, -{ - Command::widget(operation::focusable::focus_next()) +pub fn focus_next() -> Task { + Task::effect(Action::widget(operation::focusable::focus_next())) } /// A container intercepting mouse events. diff --git a/widget/src/keyed/column.rs b/widget/src/keyed/column.rs index fdaadefaa7..69991d1f34 100644 --- a/widget/src/keyed/column.rs +++ b/widget/src/keyed/column.rs @@ -265,7 +265,7 @@ where tree: &mut Tree, layout: Layout<'_>, renderer: &Renderer, - operation: &mut dyn Operation, + operation: &mut dyn Operation<()>, ) { operation.container(None, layout.bounds(), &mut |operation| { self.children diff --git a/widget/src/lazy.rs b/widget/src/lazy.rs index 04783dbe0f..606da22dc9 100644 --- a/widget/src/lazy.rs +++ b/widget/src/lazy.rs @@ -182,7 +182,7 @@ where tree: &mut Tree, layout: Layout<'_>, renderer: &Renderer, - operation: &mut dyn widget::Operation, + operation: &mut dyn widget::Operation<()>, ) { self.with_element(|element| { element.as_widget().operate( diff --git a/widget/src/lazy/component.rs b/widget/src/lazy/component.rs index 7ba71a027f..f079c0df18 100644 --- a/widget/src/lazy/component.rs +++ b/widget/src/lazy/component.rs @@ -59,7 +59,7 @@ pub trait Component { fn operate( &self, _state: &mut Self::State, - _operation: &mut dyn widget::Operation, + _operation: &mut dyn widget::Operation<()>, ) { } @@ -172,7 +172,7 @@ where fn rebuild_element_with_operation( &self, - operation: &mut dyn widget::Operation, + operation: &mut dyn widget::Operation<()>, ) { let heads = self.state.borrow_mut().take().unwrap().into_heads(); @@ -358,70 +358,17 @@ where tree: &mut Tree, layout: Layout<'_>, renderer: &Renderer, - operation: &mut dyn widget::Operation, + operation: &mut dyn widget::Operation<()>, ) { self.rebuild_element_with_operation(operation); - struct MapOperation<'a, B> { - operation: &'a mut dyn widget::Operation, - } - - impl<'a, T, B> widget::Operation for MapOperation<'a, B> { - fn container( - &mut self, - id: Option<&widget::Id>, - bounds: Rectangle, - operate_on_children: &mut dyn FnMut( - &mut dyn widget::Operation, - ), - ) { - self.operation.container(id, bounds, &mut |operation| { - operate_on_children(&mut MapOperation { operation }); - }); - } - - fn focusable( - &mut self, - state: &mut dyn widget::operation::Focusable, - id: Option<&widget::Id>, - ) { - self.operation.focusable(state, id); - } - - fn text_input( - &mut self, - state: &mut dyn widget::operation::TextInput, - id: Option<&widget::Id>, - ) { - self.operation.text_input(state, id); - } - - fn scrollable( - &mut self, - state: &mut dyn widget::operation::Scrollable, - id: Option<&widget::Id>, - bounds: Rectangle, - translation: Vector, - ) { - self.operation.scrollable(state, id, bounds, translation); - } - - fn custom( - &mut self, - state: &mut dyn std::any::Any, - id: Option<&widget::Id>, - ) { - self.operation.custom(state, id); - } - } - let tree = tree.state.downcast_mut::>>>(); self.with_element(|element| { element.as_widget().operate( &mut tree.borrow_mut().as_mut().unwrap().children[0], layout, renderer, - &mut MapOperation { operation }, + operation, ); }); } diff --git a/widget/src/lazy/responsive.rs b/widget/src/lazy/responsive.rs index f612102e84..27f5261771 100644 --- a/widget/src/lazy/responsive.rs +++ b/widget/src/lazy/responsive.rs @@ -161,7 +161,7 @@ where tree: &mut Tree, layout: Layout<'_>, renderer: &Renderer, - operation: &mut dyn widget::Operation, + operation: &mut dyn widget::Operation<()>, ) { let state = tree.state.downcast_mut::(); let mut content = self.content.borrow_mut(); diff --git a/widget/src/mouse_area.rs b/widget/src/mouse_area.rs index d7235cf602..17cae53bda 100644 --- a/widget/src/mouse_area.rs +++ b/widget/src/mouse_area.rs @@ -178,7 +178,7 @@ where tree: &mut Tree, layout: Layout<'_>, renderer: &Renderer, - operation: &mut dyn Operation, + operation: &mut dyn Operation<()>, ) { self.content.as_widget().operate( &mut tree.children[0], diff --git a/widget/src/pane_grid.rs b/widget/src/pane_grid.rs index acfa9d44d6..c3da387934 100644 --- a/widget/src/pane_grid.rs +++ b/widget/src/pane_grid.rs @@ -324,7 +324,7 @@ where tree: &mut Tree, layout: Layout<'_>, renderer: &Renderer, - operation: &mut dyn widget::Operation, + operation: &mut dyn widget::Operation<()>, ) { operation.container(None, layout.bounds(), &mut |operation| { self.contents diff --git a/widget/src/pane_grid/content.rs b/widget/src/pane_grid/content.rs index 30ad52ca41..d45fc0cd7c 100644 --- a/widget/src/pane_grid/content.rs +++ b/widget/src/pane_grid/content.rs @@ -214,7 +214,7 @@ where tree: &mut Tree, layout: Layout<'_>, renderer: &Renderer, - operation: &mut dyn widget::Operation, + operation: &mut dyn widget::Operation<()>, ) { let body_layout = if let Some(title_bar) = &self.title_bar { let mut children = layout.children(); diff --git a/widget/src/pane_grid/title_bar.rs b/widget/src/pane_grid/title_bar.rs index c2eeebb76d..c05f1252a6 100644 --- a/widget/src/pane_grid/title_bar.rs +++ b/widget/src/pane_grid/title_bar.rs @@ -278,7 +278,7 @@ where tree: &mut Tree, layout: Layout<'_>, renderer: &Renderer, - operation: &mut dyn widget::Operation, + operation: &mut dyn widget::Operation<()>, ) { let mut children = layout.children(); let padded = children.next().unwrap(); diff --git a/widget/src/row.rs b/widget/src/row.rs index fa35217163..00bcf60164 100644 --- a/widget/src/row.rs +++ b/widget/src/row.rs @@ -197,7 +197,7 @@ where tree: &mut Tree, layout: Layout<'_>, renderer: &Renderer, - operation: &mut dyn Operation, + operation: &mut dyn Operation<()>, ) { operation.container(None, layout.bounds(), &mut |operation| { self.children diff --git a/widget/src/scrollable.rs b/widget/src/scrollable.rs index 6fc00f877d..c3d08223bb 100644 --- a/widget/src/scrollable.rs +++ b/widget/src/scrollable.rs @@ -15,7 +15,7 @@ use crate::core::{ self, Background, Border, Clipboard, Color, Element, Layout, Length, Pixels, Point, Rectangle, Shell, Size, Theme, Vector, Widget, }; -use crate::runtime::Command; +use crate::runtime::{Action, Task}; pub use operation::scrollable::{AbsoluteOffset, RelativeOffset}; @@ -295,7 +295,7 @@ where tree: &mut Tree, layout: Layout<'_>, renderer: &Renderer, - operation: &mut dyn Operation, + operation: &mut dyn Operation<()>, ) { let state = tree.state.downcast_mut::(); @@ -952,22 +952,18 @@ impl From for widget::Id { } } -/// Produces a [`Command`] that snaps the [`Scrollable`] with the given [`Id`] +/// Produces a [`Task`] that snaps the [`Scrollable`] with the given [`Id`] /// to the provided `percentage` along the x & y axis. -pub fn snap_to( - id: Id, - offset: RelativeOffset, -) -> Command { - Command::widget(operation::scrollable::snap_to(id.0, offset)) +pub fn snap_to(id: Id, offset: RelativeOffset) -> Task { + Task::effect(Action::widget(operation::scrollable::snap_to(id.0, offset))) } -/// Produces a [`Command`] that scrolls the [`Scrollable`] with the given [`Id`] +/// Produces a [`Task`] that scrolls the [`Scrollable`] with the given [`Id`] /// to the provided [`AbsoluteOffset`] along the x & y axis. -pub fn scroll_to( - id: Id, - offset: AbsoluteOffset, -) -> Command { - Command::widget(operation::scrollable::scroll_to(id.0, offset)) +pub fn scroll_to(id: Id, offset: AbsoluteOffset) -> Task { + Task::effect(Action::widget(operation::scrollable::scroll_to( + id.0, offset, + ))) } /// Returns [`true`] if the viewport actually changed. diff --git a/widget/src/stack.rs b/widget/src/stack.rs index 5035541b0b..efa9711db3 100644 --- a/widget/src/stack.rs +++ b/widget/src/stack.rs @@ -189,7 +189,7 @@ where tree: &mut Tree, layout: Layout<'_>, renderer: &Renderer, - operation: &mut dyn Operation, + operation: &mut dyn Operation<()>, ) { operation.container(None, layout.bounds(), &mut |operation| { self.children diff --git a/widget/src/text_input.rs b/widget/src/text_input.rs index dc4f83e024..4e89236b57 100644 --- a/widget/src/text_input.rs +++ b/widget/src/text_input.rs @@ -30,7 +30,7 @@ use crate::core::{ Background, Border, Color, Element, Layout, Length, Padding, Pixels, Point, Rectangle, Shell, Size, Theme, Vector, Widget, }; -use crate::runtime::Command; +use crate::runtime::{Action, Task}; /// A field that can be filled with text. /// @@ -540,7 +540,7 @@ where tree: &mut Tree, _layout: Layout<'_>, _renderer: &Renderer, - operation: &mut dyn Operation, + operation: &mut dyn Operation<()>, ) { let state = tree.state.downcast_mut::>(); @@ -1140,35 +1140,38 @@ impl From for widget::Id { } } -/// Produces a [`Command`] that focuses the [`TextInput`] with the given [`Id`]. -pub fn focus(id: Id) -> Command { - Command::widget(operation::focusable::focus(id.0)) +/// Produces a [`Task`] that focuses the [`TextInput`] with the given [`Id`]. +pub fn focus(id: Id) -> Task { + Task::effect(Action::widget(operation::focusable::focus(id.0))) } -/// Produces a [`Command`] that moves the cursor of the [`TextInput`] with the given [`Id`] to the +/// Produces a [`Task`] that moves the cursor of the [`TextInput`] with the given [`Id`] to the /// end. -pub fn move_cursor_to_end(id: Id) -> Command { - Command::widget(operation::text_input::move_cursor_to_end(id.0)) +pub fn move_cursor_to_end(id: Id) -> Task { + Task::effect(Action::widget(operation::text_input::move_cursor_to_end( + id.0, + ))) } -/// Produces a [`Command`] that moves the cursor of the [`TextInput`] with the given [`Id`] to the +/// Produces a [`Task`] that moves the cursor of the [`TextInput`] with the given [`Id`] to the /// front. -pub fn move_cursor_to_front(id: Id) -> Command { - Command::widget(operation::text_input::move_cursor_to_front(id.0)) +pub fn move_cursor_to_front(id: Id) -> Task { + Task::effect(Action::widget(operation::text_input::move_cursor_to_front( + id.0, + ))) } -/// Produces a [`Command`] that moves the cursor of the [`TextInput`] with the given [`Id`] to the +/// Produces a [`Task`] that moves the cursor of the [`TextInput`] with the given [`Id`] to the /// provided position. -pub fn move_cursor_to( - id: Id, - position: usize, -) -> Command { - Command::widget(operation::text_input::move_cursor_to(id.0, position)) +pub fn move_cursor_to(id: Id, position: usize) -> Task { + Task::effect(Action::widget(operation::text_input::move_cursor_to( + id.0, position, + ))) } -/// Produces a [`Command`] that selects all the content of the [`TextInput`] with the given [`Id`]. -pub fn select_all(id: Id) -> Command { - Command::widget(operation::text_input::select_all(id.0)) +/// Produces a [`Task`] that selects all the content of the [`TextInput`] with the given [`Id`]. +pub fn select_all(id: Id) -> Task { + Task::effect(Action::widget(operation::text_input::select_all(id.0))) } /// The state of a [`TextInput`]. diff --git a/widget/src/themer.rs b/widget/src/themer.rs index f4597458e7..9eb47d844a 100644 --- a/widget/src/themer.rs +++ b/widget/src/themer.rs @@ -104,7 +104,7 @@ where tree: &mut Tree, layout: Layout<'_>, renderer: &Renderer, - operation: &mut dyn Operation, + operation: &mut dyn Operation<()>, ) { self.content .as_widget() @@ -236,7 +236,7 @@ where &mut self, layout: Layout<'_>, renderer: &Renderer, - operation: &mut dyn Operation, + operation: &mut dyn Operation<()>, ) { self.content.operate(layout, renderer, operation); } diff --git a/winit/src/application.rs b/winit/src/application.rs index d93ea42e60..a93878ead9 100644 --- a/winit/src/application.rs +++ b/winit/src/application.rs @@ -19,7 +19,7 @@ use crate::graphics::compositor::{self, Compositor}; use crate::runtime::clipboard; use crate::runtime::program::Program; use crate::runtime::user_interface::{self, UserInterface}; -use crate::runtime::{Command, Debug}; +use crate::runtime::{Action, Debug, Task}; use crate::{Clipboard, Error, Proxy, Settings}; use futures::channel::mpsc; @@ -36,7 +36,7 @@ use std::sync::Arc; /// its own window. /// /// An [`Application`] can execute asynchronous actions by returning a -/// [`Command`] in some of its methods. +/// [`Task`] in some of its methods. /// /// When using an [`Application`] with the `debug` feature enabled, a debug view /// can be toggled by pressing `F12`. @@ -52,10 +52,10 @@ where /// /// Here is where you should return the initial state of your app. /// - /// Additionally, you can return a [`Command`] if you need to perform some + /// Additionally, you can return a [`Task`] if you need to perform some /// async action in the background on startup. This is useful if you want to /// load state from a file, perform an initial HTTP request, etc. - fn new(flags: Self::Flags) -> (Self, Command); + fn new(flags: Self::Flags) -> (Self, Task); /// Returns the current title of the [`Application`]. /// @@ -155,19 +155,23 @@ where let (proxy, worker) = Proxy::new(event_loop.create_proxy()); - let runtime = { + let mut runtime = { let executor = E::new().map_err(Error::ExecutorCreationFailed)?; executor.spawn(worker); Runtime::new(executor, proxy.clone()) }; - let (application, init_command) = { + let (application, task) = { let flags = settings.flags; runtime.enter(|| A::new(flags)) }; + if let Some(stream) = task.into_stream() { + runtime.run(stream); + } + let id = settings.id; let title = application.title(); @@ -183,7 +187,6 @@ where boot_receiver, event_receiver, control_sender, - init_command, settings.fonts, )); @@ -193,13 +196,13 @@ where instance: std::pin::Pin>, context: task::Context<'static>, boot: Option>, - sender: mpsc::UnboundedSender>, + sender: mpsc::UnboundedSender>>, receiver: mpsc::UnboundedReceiver, error: Option, #[cfg(target_arch = "wasm32")] is_booted: std::rc::Rc>, #[cfg(target_arch = "wasm32")] - queued_events: Vec>, + queued_events: Vec>>, } struct BootConfig { @@ -229,7 +232,7 @@ where queued_events: Vec::new(), }; - impl winit::application::ApplicationHandler + impl winit::application::ApplicationHandler> for Runner where F: Future, @@ -393,11 +396,11 @@ where fn user_event( &mut self, event_loop: &winit::event_loop::ActiveEventLoop, - message: Message, + action: Action, ) { self.process_event( event_loop, - winit::event::Event::UserEvent(message), + winit::event::Event::UserEvent(action), ); } @@ -416,7 +419,7 @@ where fn process_event( &mut self, event_loop: &winit::event_loop::ActiveEventLoop, - event: winit::event::Event, + event: winit::event::Event>, ) { // On Wasm, events may start being processed before the compositor // boots up. We simply queue them and process them once ready. @@ -480,15 +483,14 @@ struct Boot { async fn run_instance( mut application: A, - mut runtime: Runtime, A::Message>, + mut runtime: Runtime, Action>, mut proxy: Proxy, mut debug: Debug, mut boot: oneshot::Receiver>, mut event_receiver: mpsc::UnboundedReceiver< - winit::event::Event, + winit::event::Event>, >, mut control_sender: mpsc::UnboundedSender, - init_command: Command, fonts: Vec>, ) where A: Application + 'static, @@ -518,7 +520,7 @@ async fn run_instance( let physical_size = state.physical_size(); let mut clipboard = Clipboard::connect(&window); - let mut cache = user_interface::Cache::default(); + let cache = user_interface::Cache::default(); let mut surface = compositor.create_surface( window.clone(), physical_size.width, @@ -530,22 +532,12 @@ async fn run_instance( window.set_visible(true); } - run_command( - &application, - &mut compositor, - &mut surface, - &mut cache, - &state, - &mut renderer, - init_command, - &mut runtime, - &mut clipboard, - &mut should_exit, - &mut proxy, - &mut debug, - &window, + runtime.track( + application + .subscription() + .map(Action::Output) + .into_recipes(), ); - runtime.track(application.subscription().into_recipes()); let mut user_interface = ManuallyDrop::new(build_user_interface( &application, @@ -581,8 +573,21 @@ async fn run_instance( ), )); } - event::Event::UserEvent(message) => { - messages.push(message); + event::Event::UserEvent(action) => { + run_action( + action, + &mut user_interface, + &mut compositor, + &mut surface, + &state, + &mut renderer, + &mut messages, + &mut clipboard, + &mut should_exit, + &mut debug, + &window, + ); + user_events += 1; } event::Event::WindowEvent { @@ -756,21 +761,14 @@ async fn run_instance( user_interface::State::Outdated ) { - let mut cache = + let cache = ManuallyDrop::into_inner(user_interface).into_cache(); // Update application update( &mut application, - &mut compositor, - &mut surface, - &mut cache, &mut state, - &mut renderer, &mut runtime, - &mut clipboard, - &mut should_exit, - &mut proxy, &mut debug, &mut messages, &window, @@ -855,282 +853,230 @@ where } /// Updates an [`Application`] by feeding it the provided messages, spawning any -/// resulting [`Command`], and tracking its [`Subscription`]. -pub fn update( +/// resulting [`Task`], and tracking its [`Subscription`]. +pub fn update( application: &mut A, - compositor: &mut C, - surface: &mut C::Surface, - cache: &mut user_interface::Cache, state: &mut State, - renderer: &mut A::Renderer, - runtime: &mut Runtime, A::Message>, - clipboard: &mut Clipboard, - should_exit: &mut bool, - proxy: &mut Proxy, + runtime: &mut Runtime, Action>, debug: &mut Debug, messages: &mut Vec, window: &winit::window::Window, ) where - C: Compositor + 'static, A::Theme: DefaultStyle, { for message in messages.drain(..) { debug.log_message(&message); debug.update_started(); - let command = runtime.enter(|| application.update(message)); + let task = runtime.enter(|| application.update(message)); debug.update_finished(); - run_command( - application, - compositor, - surface, - cache, - state, - renderer, - command, - runtime, - clipboard, - should_exit, - proxy, - debug, - window, - ); + if let Some(stream) = task.into_stream() { + runtime.run(stream); + } } state.synchronize(application, window); let subscription = application.subscription(); - runtime.track(subscription.into_recipes()); + runtime.track(subscription.map(Action::Output).into_recipes()); } -/// Runs the actions of a [`Command`]. -pub fn run_command( - application: &A, +/// Runs the actions of a [`Task`]. +pub fn run_action( + action: Action, + user_interface: &mut UserInterface<'_, A::Message, A::Theme, C::Renderer>, compositor: &mut C, surface: &mut C::Surface, - cache: &mut user_interface::Cache, state: &State, renderer: &mut A::Renderer, - command: Command, - runtime: &mut Runtime, A::Message>, + messages: &mut Vec, clipboard: &mut Clipboard, should_exit: &mut bool, - proxy: &mut Proxy, debug: &mut Debug, window: &winit::window::Window, ) where A: Application, - E: Executor, C: Compositor + 'static, A::Theme: DefaultStyle, { - use crate::runtime::command; use crate::runtime::system; use crate::runtime::window; - for action in command.actions() { - match action { - command::Action::Future(future) => { - runtime.spawn(future); + match action { + Action::Clipboard(action) => match action { + clipboard::Action::Read { target, channel } => { + let _ = channel.send(clipboard.read(target)); } - command::Action::Stream(stream) => { - runtime.run(stream); + clipboard::Action::Write { target, contents } => { + clipboard.write(target, contents); } - command::Action::Clipboard(action) => match action { - clipboard::Action::Read(tag, kind) => { - let message = tag(clipboard.read(kind)); - - proxy.send(message); - } - clipboard::Action::Write(contents, kind) => { - clipboard.write(kind, contents); - } - }, - command::Action::Window(action) => match action { - window::Action::Close(_id) => { - *should_exit = true; - } - window::Action::Drag(_id) => { - let _res = window.drag_window(); - } - window::Action::Spawn { .. } => { - log::warn!( - "Spawning a window is only available with \ + }, + Action::Window(action) => match action { + window::Action::Close(_id) => { + *should_exit = true; + } + window::Action::Drag(_id) => { + let _res = window.drag_window(); + } + window::Action::Open { .. } => { + log::warn!( + "Spawning a window is only available with \ multi-window applications." - ); - } - window::Action::Resize(_id, size) => { - let _ = - window.request_inner_size(winit::dpi::LogicalSize { - width: size.width, - height: size.height, - }); - } - window::Action::FetchSize(_id, callback) => { - let size = - window.inner_size().to_logical(window.scale_factor()); + ); + } + window::Action::Resize(_id, size) => { + let _ = window.request_inner_size(winit::dpi::LogicalSize { + width: size.width, + height: size.height, + }); + } + window::Action::FetchSize(_id, channel) => { + let size = + window.inner_size().to_logical(window.scale_factor()); - proxy.send(callback(Size::new(size.width, size.height))); - } - window::Action::FetchMaximized(_id, callback) => { - proxy.send(callback(window.is_maximized())); - } - window::Action::Maximize(_id, maximized) => { - window.set_maximized(maximized); - } - window::Action::FetchMinimized(_id, callback) => { - proxy.send(callback(window.is_minimized())); - } - window::Action::Minimize(_id, minimized) => { - window.set_minimized(minimized); - } - window::Action::FetchPosition(_id, callback) => { - let position = window - .inner_position() - .map(|position| { - let position = position - .to_logical::(window.scale_factor()); - - Point::new(position.x, position.y) - }) - .ok(); + let _ = channel.send(Size::new(size.width, size.height)); + } + window::Action::FetchMaximized(_id, channel) => { + let _ = channel.send(window.is_maximized()); + } + window::Action::Maximize(_id, maximized) => { + window.set_maximized(maximized); + } + window::Action::FetchMinimized(_id, channel) => { + let _ = channel.send(window.is_minimized()); + } + window::Action::Minimize(_id, minimized) => { + window.set_minimized(minimized); + } + window::Action::FetchPosition(_id, channel) => { + let position = window + .inner_position() + .map(|position| { + let position = + position.to_logical::(window.scale_factor()); + + Point::new(position.x, position.y) + }) + .ok(); + + let _ = channel.send(position); + } + window::Action::Move(_id, position) => { + window.set_outer_position(winit::dpi::LogicalPosition { + x: position.x, + y: position.y, + }); + } + window::Action::ChangeMode(_id, mode) => { + window.set_visible(conversion::visible(mode)); + window.set_fullscreen(conversion::fullscreen( + window.current_monitor(), + mode, + )); + } + window::Action::ChangeIcon(_id, icon) => { + window.set_window_icon(conversion::icon(icon)); + } + window::Action::FetchMode(_id, channel) => { + let mode = if window.is_visible().unwrap_or(true) { + conversion::mode(window.fullscreen()) + } else { + core::window::Mode::Hidden + }; - proxy.send(callback(position)); - } - window::Action::Move(_id, position) => { - window.set_outer_position(winit::dpi::LogicalPosition { - x: position.x, - y: position.y, + let _ = channel.send(mode); + } + window::Action::ToggleMaximize(_id) => { + window.set_maximized(!window.is_maximized()); + } + window::Action::ToggleDecorations(_id) => { + window.set_decorations(!window.is_decorated()); + } + window::Action::RequestUserAttention(_id, user_attention) => { + window.request_user_attention( + user_attention.map(conversion::user_attention), + ); + } + window::Action::GainFocus(_id) => { + window.focus_window(); + } + window::Action::ChangeLevel(_id, level) => { + window.set_window_level(conversion::window_level(level)); + } + window::Action::ShowSystemMenu(_id) => { + if let mouse::Cursor::Available(point) = state.cursor() { + window.show_window_menu(winit::dpi::LogicalPosition { + x: point.x, + y: point.y, }); } - window::Action::ChangeMode(_id, mode) => { - window.set_visible(conversion::visible(mode)); - window.set_fullscreen(conversion::fullscreen( - window.current_monitor(), - mode, - )); - } - window::Action::ChangeIcon(_id, icon) => { - window.set_window_icon(conversion::icon(icon)); - } - window::Action::FetchMode(_id, tag) => { - let mode = if window.is_visible().unwrap_or(true) { - conversion::mode(window.fullscreen()) - } else { - core::window::Mode::Hidden - }; - - proxy.send(tag(mode)); - } - window::Action::ToggleMaximize(_id) => { - window.set_maximized(!window.is_maximized()); - } - window::Action::ToggleDecorations(_id) => { - window.set_decorations(!window.is_decorated()); - } - window::Action::RequestUserAttention(_id, user_attention) => { - window.request_user_attention( - user_attention.map(conversion::user_attention), - ); - } - window::Action::GainFocus(_id) => { - window.focus_window(); - } - window::Action::ChangeLevel(_id, level) => { - window.set_window_level(conversion::window_level(level)); - } - window::Action::ShowSystemMenu(_id) => { - if let mouse::Cursor::Available(point) = state.cursor() { - window.show_window_menu(winit::dpi::LogicalPosition { - x: point.x, - y: point.y, - }); - } - } - window::Action::FetchId(_id, tag) => { - proxy.send(tag(window.id().into())); - } - window::Action::RunWithHandle(_id, tag) => { - use window::raw_window_handle::HasWindowHandle; + } + window::Action::FetchRawId(_id, channel) => { + let _ = channel.send(window.id().into()); + } + window::Action::RunWithHandle(_id, f) => { + use window::raw_window_handle::HasWindowHandle; - if let Ok(handle) = window.window_handle() { - proxy.send(tag(handle)); - } + if let Ok(handle) = window.window_handle() { + f(handle); } + } - window::Action::Screenshot(_id, tag) => { - let bytes = compositor.screenshot( - renderer, - surface, - state.viewport(), - state.background_color(), - &debug.overlay(), - ); - - proxy.send(tag(window::Screenshot::new( - bytes, - state.physical_size(), - state.viewport().scale_factor(), - ))); - } - }, - command::Action::System(action) => match action { - system::Action::QueryInformation(_tag) => { - #[cfg(feature = "system")] - { - let graphics_info = compositor.fetch_information(); - let mut proxy = proxy.clone(); - - let _ = std::thread::spawn(move || { - let information = - crate::system::information(graphics_info); - - let message = _tag(information); - - proxy.send(message); - }); - } - } - }, - command::Action::Widget(action) => { - let mut current_cache = std::mem::take(cache); - let mut current_operation = Some(action); - - let mut user_interface = build_user_interface( - application, - current_cache, + window::Action::Screenshot(_id, channel) => { + let bytes = compositor.screenshot( renderer, - state.logical_size(), - debug, + surface, + state.viewport(), + state.background_color(), + &debug.overlay(), ); - while let Some(mut operation) = current_operation.take() { - user_interface.operate(renderer, operation.as_mut()); + let _ = channel.send(window::Screenshot::new( + bytes, + state.physical_size(), + state.viewport().scale_factor(), + )); + } + }, + Action::System(action) => match action { + system::Action::QueryInformation(_channel) => { + #[cfg(feature = "system")] + { + let graphics_info = compositor.fetch_information(); - match operation.finish() { - operation::Outcome::None => {} - operation::Outcome::Some(message) => { - proxy.send(message); - } - operation::Outcome::Chain(next) => { - current_operation = Some(next); - } + let _ = std::thread::spawn(move || { + let information = + crate::system::information(graphics_info); + + let _ = _channel.send(information); + }); + } + } + }, + Action::Widget(operation) => { + let mut current_operation = Some(operation); + + while let Some(mut operation) = current_operation.take() { + user_interface.operate(renderer, operation.as_mut()); + + match operation.finish() { + operation::Outcome::None => {} + operation::Outcome::Some(()) => {} + operation::Outcome::Chain(next) => { + current_operation = Some(next); } } - - current_cache = user_interface.into_cache(); - *cache = current_cache; } - command::Action::LoadFont { bytes, tagger } => { - // TODO: Error handling (?) - compositor.load_font(bytes); + } + Action::LoadFont { bytes, channel } => { + // TODO: Error handling (?) + compositor.load_font(bytes); - proxy.send(tagger(Ok(()))); - } - command::Action::Custom(_) => { - log::warn!("Unsupported custom action in `iced_winit` shell"); - } + let _ = channel.send(Ok(())); + } + Action::Output(message) => { + messages.push(message); } } } diff --git a/winit/src/conversion.rs b/winit/src/conversion.rs index 79fcf92ece..0ed10c88d8 100644 --- a/winit/src/conversion.rs +++ b/winit/src/conversion.rs @@ -73,11 +73,7 @@ pub fn window_attributes( #[cfg(target_os = "windows")] { use winit::platform::windows::WindowAttributesExtWindows; - #[allow(unsafe_code)] - unsafe { - attributes = attributes - .with_parent_window(settings.platform_specific.parent); - } + attributes = attributes .with_drag_and_drop(settings.platform_specific.drag_and_drop); diff --git a/winit/src/multi_window.rs b/winit/src/multi_window.rs index 2eaf9241b5..8bd8a64d40 100644 --- a/winit/src/multi_window.rs +++ b/winit/src/multi_window.rs @@ -21,10 +21,10 @@ use crate::futures::{Executor, Runtime}; use crate::graphics; use crate::graphics::{compositor, Compositor}; use crate::multi_window::window_manager::WindowManager; -use crate::runtime::command::{self, Command}; use crate::runtime::multi_window::Program; use crate::runtime::user_interface::{self, UserInterface}; use crate::runtime::Debug; +use crate::runtime::{Action, Task}; use crate::{Clipboard, Error, Proxy, Settings}; pub use crate::application::{default, Appearance, DefaultStyle}; @@ -41,7 +41,7 @@ use std::time::Instant; /// its own window. /// /// An [`Application`] can execute asynchronous actions by returning a -/// [`Command`] in some of its methods. +/// [`Task`] in some of its methods. /// /// When using an [`Application`] with the `debug` feature enabled, a debug view /// can be toggled by pressing `F12`. @@ -57,10 +57,10 @@ where /// /// Here is where you should return the initial state of your app. /// - /// Additionally, you can return a [`Command`] if you need to perform some + /// Additionally, you can return a [`Task`] if you need to perform some /// async action in the background on startup. This is useful if you want to /// load state from a file, perform an initial HTTP request, etc. - fn new(flags: Self::Flags) -> (Self, Command); + fn new(flags: Self::Flags) -> (Self, Task); /// Returns the current title of the [`Application`]. /// @@ -127,19 +127,23 @@ where let (proxy, worker) = Proxy::new(event_loop.create_proxy()); - let runtime = { + let mut runtime = { let executor = E::new().map_err(Error::ExecutorCreationFailed)?; executor.spawn(worker); Runtime::new(executor, proxy.clone()) }; - let (application, init_command) = { + let (application, task) = { let flags = settings.flags; runtime.enter(|| A::new(flags)) }; + if let Some(stream) = task.into_stream() { + runtime.run(stream); + } + let id = settings.id; let title = application.title(window::Id::MAIN); @@ -155,7 +159,6 @@ where boot_receiver, event_receiver, control_sender, - init_command, )); let context = task::Context::from_waker(task::noop_waker_ref()); @@ -448,13 +451,12 @@ enum Control { async fn run_instance( mut application: A, - mut runtime: Runtime, A::Message>, + mut runtime: Runtime, Action>, mut proxy: Proxy, mut debug: Debug, mut boot: oneshot::Receiver>, - mut event_receiver: mpsc::UnboundedReceiver>, + mut event_receiver: mpsc::UnboundedReceiver>>, mut control_sender: mpsc::UnboundedSender, - init_command: Command, ) where A: Application + 'static, E: Executor + 'static, @@ -511,21 +513,13 @@ async fn run_instance( )]), )); - run_command( - &application, - &mut compositor, - init_command, - &mut runtime, - &mut clipboard, - &mut control_sender, - &mut proxy, - &mut debug, - &mut window_manager, - &mut ui_caches, + runtime.track( + application + .subscription() + .map(Action::Output) + .into_recipes(), ); - runtime.track(application.subscription().into_recipes()); - let mut messages = Vec::new(); let mut user_events = 0; @@ -594,8 +588,19 @@ async fn run_instance( ), ); } - event::Event::UserEvent(message) => { - messages.push(message); + event::Event::UserEvent(action) => { + run_action( + action, + &application, + &mut compositor, + &mut messages, + &mut clipboard, + &mut control_sender, + &mut debug, + &mut user_interfaces, + &mut window_manager, + &mut ui_caches, + ); user_events += 1; } event::Event::WindowEvent { @@ -888,7 +893,7 @@ async fn run_instance( // TODO mw application update returns which window IDs to update if !messages.is_empty() || uis_stale { - let mut cached_interfaces: FxHashMap< + let cached_interfaces: FxHashMap< window::Id, user_interface::Cache, > = ManuallyDrop::into_inner(user_interfaces) @@ -899,15 +904,9 @@ async fn run_instance( // Update application update( &mut application, - &mut compositor, &mut runtime, - &mut clipboard, - &mut control_sender, - &mut proxy, &mut debug, &mut messages, - &mut window_manager, - &mut cached_interfaces, ); // we must synchronize all window states with application state after an @@ -971,63 +970,46 @@ where user_interface } -/// Updates a multi-window [`Application`] by feeding it messages, spawning any -/// resulting [`Command`], and tracking its [`Subscription`]. -fn update( +fn update( application: &mut A, - compositor: &mut C, - runtime: &mut Runtime, A::Message>, - clipboard: &mut Clipboard, - control_sender: &mut mpsc::UnboundedSender, - proxy: &mut Proxy, + runtime: &mut Runtime, Action>, debug: &mut Debug, messages: &mut Vec, - window_manager: &mut WindowManager, - ui_caches: &mut FxHashMap, ) where - C: Compositor + 'static, A::Theme: DefaultStyle, { for message in messages.drain(..) { debug.log_message(&message); debug.update_started(); - let command = runtime.enter(|| application.update(message)); + let task = runtime.enter(|| application.update(message)); debug.update_finished(); - run_command( - application, - compositor, - command, - runtime, - clipboard, - control_sender, - proxy, - debug, - window_manager, - ui_caches, - ); + if let Some(stream) = task.into_stream() { + runtime.run(stream); + } } let subscription = application.subscription(); - runtime.track(subscription.into_recipes()); + runtime.track(subscription.map(Action::Output).into_recipes()); } -/// Runs the actions of a [`Command`]. -fn run_command( +fn run_action( + action: Action, application: &A, compositor: &mut C, - command: Command, - runtime: &mut Runtime, A::Message>, + messages: &mut Vec, clipboard: &mut Clipboard, control_sender: &mut mpsc::UnboundedSender, - proxy: &mut Proxy, debug: &mut Debug, + interfaces: &mut FxHashMap< + window::Id, + UserInterface<'_, A::Message, A::Theme, A::Renderer>, + >, window_manager: &mut WindowManager, ui_caches: &mut FxHashMap, ) where A: Application, - E: Executor, C: Compositor + 'static, A::Theme: DefaultStyle, { @@ -1035,279 +1017,254 @@ fn run_command( use crate::runtime::system; use crate::runtime::window; - for action in command.actions() { - match action { - command::Action::Future(future) => { - runtime.spawn(Box::pin(future)); + match action { + Action::Output(message) => { + messages.push(message); + } + Action::Clipboard(action) => match action { + clipboard::Action::Read { target, channel } => { + let _ = channel.send(clipboard.read(target)); } - command::Action::Stream(stream) => { - runtime.run(Box::pin(stream)); + clipboard::Action::Write { target, contents } => { + clipboard.write(target, contents); } - command::Action::Clipboard(action) => match action { - clipboard::Action::Read(tag, kind) => { - let message = tag(clipboard.read(kind)); + }, + Action::Window(action) => match action { + window::Action::Open(id, settings, channel) => { + let monitor = window_manager.last_monitor(); - proxy.send(message); - } - clipboard::Action::Write(contents, kind) => { - clipboard.write(kind, contents); - } - }, - command::Action::Window(action) => match action { - window::Action::Spawn(id, settings) => { - let monitor = window_manager.last_monitor(); + control_sender + .start_send(Control::CreateWindow { + id, + settings, + title: application.title(id), + monitor, + }) + .expect("Send control action"); + + let _ = channel.send(id); + } + window::Action::Close(id) => { + let _ = window_manager.remove(id); + let _ = ui_caches.remove(&id); + if window_manager.is_empty() { control_sender - .start_send(Control::CreateWindow { - id, - settings, - title: application.title(id), - monitor, - }) + .start_send(Control::Exit) .expect("Send control action"); } - window::Action::Close(id) => { - let _ = window_manager.remove(id); - let _ = ui_caches.remove(&id); - - if window_manager.is_empty() { - control_sender - .start_send(Control::Exit) - .expect("Send control action"); - } - } - window::Action::Drag(id) => { - if let Some(window) = window_manager.get_mut(id) { - let _ = window.raw.drag_window(); - } + } + window::Action::Drag(id) => { + if let Some(window) = window_manager.get_mut(id) { + let _ = window.raw.drag_window(); } - window::Action::Resize(id, size) => { - if let Some(window) = window_manager.get_mut(id) { - let _ = window.raw.request_inner_size( - winit::dpi::LogicalSize { - width: size.width, - height: size.height, - }, - ); - } + } + window::Action::Resize(id, size) => { + if let Some(window) = window_manager.get_mut(id) { + let _ = window.raw.request_inner_size( + winit::dpi::LogicalSize { + width: size.width, + height: size.height, + }, + ); } - window::Action::FetchSize(id, callback) => { - if let Some(window) = window_manager.get_mut(id) { - let size = window - .raw - .inner_size() - .to_logical(window.raw.scale_factor()); - - proxy - .send(callback(Size::new(size.width, size.height))); - } + } + window::Action::FetchSize(id, channel) => { + if let Some(window) = window_manager.get_mut(id) { + let size = window + .raw + .inner_size() + .to_logical(window.raw.scale_factor()); + + let _ = channel.send(Size::new(size.width, size.height)); } - window::Action::FetchMaximized(id, callback) => { - if let Some(window) = window_manager.get_mut(id) { - proxy.send(callback(window.raw.is_maximized())); - } + } + window::Action::FetchMaximized(id, channel) => { + if let Some(window) = window_manager.get_mut(id) { + let _ = channel.send(window.raw.is_maximized()); } - window::Action::Maximize(id, maximized) => { - if let Some(window) = window_manager.get_mut(id) { - window.raw.set_maximized(maximized); - } + } + window::Action::Maximize(id, maximized) => { + if let Some(window) = window_manager.get_mut(id) { + window.raw.set_maximized(maximized); } - window::Action::FetchMinimized(id, callback) => { - if let Some(window) = window_manager.get_mut(id) { - proxy.send(callback(window.raw.is_minimized())); - } + } + window::Action::FetchMinimized(id, channel) => { + if let Some(window) = window_manager.get_mut(id) { + let _ = channel.send(window.raw.is_minimized()); } - window::Action::Minimize(id, minimized) => { - if let Some(window) = window_manager.get_mut(id) { - window.raw.set_minimized(minimized); - } + } + window::Action::Minimize(id, minimized) => { + if let Some(window) = window_manager.get_mut(id) { + window.raw.set_minimized(minimized); } - window::Action::FetchPosition(id, callback) => { - if let Some(window) = window_manager.get_mut(id) { - let position = window - .raw - .inner_position() - .map(|position| { - let position = position.to_logical::( - window.raw.scale_factor(), - ); - - Point::new(position.x, position.y) - }) - .ok(); + } + window::Action::FetchPosition(id, channel) => { + if let Some(window) = window_manager.get_mut(id) { + let position = window + .raw + .inner_position() + .map(|position| { + let position = position + .to_logical::(window.raw.scale_factor()); + + Point::new(position.x, position.y) + }) + .ok(); - proxy.send(callback(position)); - } + let _ = channel.send(position); } - window::Action::Move(id, position) => { - if let Some(window) = window_manager.get_mut(id) { - window.raw.set_outer_position( - winit::dpi::LogicalPosition { - x: position.x, - y: position.y, - }, - ); - } + } + window::Action::Move(id, position) => { + if let Some(window) = window_manager.get_mut(id) { + window.raw.set_outer_position( + winit::dpi::LogicalPosition { + x: position.x, + y: position.y, + }, + ); } - window::Action::ChangeMode(id, mode) => { - if let Some(window) = window_manager.get_mut(id) { - window.raw.set_visible(conversion::visible(mode)); - window.raw.set_fullscreen(conversion::fullscreen( - window.raw.current_monitor(), - mode, - )); - } + } + window::Action::ChangeMode(id, mode) => { + if let Some(window) = window_manager.get_mut(id) { + window.raw.set_visible(conversion::visible(mode)); + window.raw.set_fullscreen(conversion::fullscreen( + window.raw.current_monitor(), + mode, + )); } - window::Action::ChangeIcon(id, icon) => { - if let Some(window) = window_manager.get_mut(id) { - window.raw.set_window_icon(conversion::icon(icon)); - } + } + window::Action::ChangeIcon(id, icon) => { + if let Some(window) = window_manager.get_mut(id) { + window.raw.set_window_icon(conversion::icon(icon)); } - window::Action::FetchMode(id, tag) => { - if let Some(window) = window_manager.get_mut(id) { - let mode = if window.raw.is_visible().unwrap_or(true) { - conversion::mode(window.raw.fullscreen()) - } else { - core::window::Mode::Hidden - }; - - proxy.send(tag(mode)); - } + } + window::Action::FetchMode(id, channel) => { + if let Some(window) = window_manager.get_mut(id) { + let mode = if window.raw.is_visible().unwrap_or(true) { + conversion::mode(window.raw.fullscreen()) + } else { + core::window::Mode::Hidden + }; + + let _ = channel.send(mode); } - window::Action::ToggleMaximize(id) => { - if let Some(window) = window_manager.get_mut(id) { - window.raw.set_maximized(!window.raw.is_maximized()); - } + } + window::Action::ToggleMaximize(id) => { + if let Some(window) = window_manager.get_mut(id) { + window.raw.set_maximized(!window.raw.is_maximized()); } - window::Action::ToggleDecorations(id) => { - if let Some(window) = window_manager.get_mut(id) { - window.raw.set_decorations(!window.raw.is_decorated()); - } + } + window::Action::ToggleDecorations(id) => { + if let Some(window) = window_manager.get_mut(id) { + window.raw.set_decorations(!window.raw.is_decorated()); } - window::Action::RequestUserAttention(id, attention_type) => { - if let Some(window) = window_manager.get_mut(id) { - window.raw.request_user_attention( - attention_type.map(conversion::user_attention), - ); - } + } + window::Action::RequestUserAttention(id, attention_type) => { + if let Some(window) = window_manager.get_mut(id) { + window.raw.request_user_attention( + attention_type.map(conversion::user_attention), + ); } - window::Action::GainFocus(id) => { - if let Some(window) = window_manager.get_mut(id) { - window.raw.focus_window(); - } + } + window::Action::GainFocus(id) => { + if let Some(window) = window_manager.get_mut(id) { + window.raw.focus_window(); } - window::Action::ChangeLevel(id, level) => { - if let Some(window) = window_manager.get_mut(id) { - window - .raw - .set_window_level(conversion::window_level(level)); - } + } + window::Action::ChangeLevel(id, level) => { + if let Some(window) = window_manager.get_mut(id) { + window + .raw + .set_window_level(conversion::window_level(level)); } - window::Action::ShowSystemMenu(id) => { - if let Some(window) = window_manager.get_mut(id) { - if let mouse::Cursor::Available(point) = - window.state.cursor() - { - window.raw.show_window_menu( - winit::dpi::LogicalPosition { - x: point.x, - y: point.y, - }, - ); - } + } + window::Action::ShowSystemMenu(id) => { + if let Some(window) = window_manager.get_mut(id) { + if let mouse::Cursor::Available(point) = + window.state.cursor() + { + window.raw.show_window_menu( + winit::dpi::LogicalPosition { + x: point.x, + y: point.y, + }, + ); } } - window::Action::FetchId(id, tag) => { - if let Some(window) = window_manager.get_mut(id) { - proxy.send(tag(window.raw.id().into())); - } + } + window::Action::FetchRawId(id, channel) => { + if let Some(window) = window_manager.get_mut(id) { + let _ = channel.send(window.raw.id().into()); } - window::Action::RunWithHandle(id, tag) => { - use window::raw_window_handle::HasWindowHandle; - - if let Some(handle) = window_manager - .get_mut(id) - .and_then(|window| window.raw.window_handle().ok()) - { - proxy.send(tag(handle)); - } + } + window::Action::RunWithHandle(id, f) => { + use window::raw_window_handle::HasWindowHandle; + + if let Some(handle) = window_manager + .get_mut(id) + .and_then(|window| window.raw.window_handle().ok()) + { + f(handle); } - window::Action::Screenshot(id, tag) => { - if let Some(window) = window_manager.get_mut(id) { - let bytes = compositor.screenshot( - &mut window.renderer, - &mut window.surface, - window.state.viewport(), - window.state.background_color(), - &debug.overlay(), - ); + } + window::Action::Screenshot(id, channel) => { + if let Some(window) = window_manager.get_mut(id) { + let bytes = compositor.screenshot( + &mut window.renderer, + &mut window.surface, + window.state.viewport(), + window.state.background_color(), + &debug.overlay(), + ); - proxy.send(tag(window::Screenshot::new( - bytes, - window.state.physical_size(), - window.state.viewport().scale_factor(), - ))); - } + let _ = channel.send(window::Screenshot::new( + bytes, + window.state.physical_size(), + window.state.viewport().scale_factor(), + )); } - }, - command::Action::System(action) => match action { - system::Action::QueryInformation(_tag) => { - #[cfg(feature = "system")] - { - let graphics_info = compositor.fetch_information(); - let mut proxy = proxy.clone(); - - let _ = std::thread::spawn(move || { - let information = - crate::system::information(graphics_info); - - let message = _tag(information); - - proxy.send(message); - }); - } + } + }, + Action::System(action) => match action { + system::Action::QueryInformation(_channel) => { + #[cfg(feature = "system")] + { + let graphics_info = compositor.fetch_information(); + + let _ = std::thread::spawn(move || { + let information = + crate::system::information(graphics_info); + + let _ = _channel.send(information); + }); } - }, - command::Action::Widget(action) => { - let mut current_operation = Some(action); - - let mut uis = build_user_interfaces( - application, - debug, - window_manager, - std::mem::take(ui_caches), - ); - - while let Some(mut operation) = current_operation.take() { - for (id, ui) in uis.iter_mut() { - if let Some(window) = window_manager.get_mut(*id) { - ui.operate(&window.renderer, operation.as_mut()); - } + } + }, + Action::Widget(operation) => { + let mut current_operation = Some(operation); + + while let Some(mut operation) = current_operation.take() { + for (id, ui) in interfaces.iter_mut() { + if let Some(window) = window_manager.get_mut(*id) { + ui.operate(&window.renderer, operation.as_mut()); } + } - match operation.finish() { - operation::Outcome::None => {} - operation::Outcome::Some(message) => { - proxy.send(message); - } - operation::Outcome::Chain(next) => { - current_operation = Some(next); - } + match operation.finish() { + operation::Outcome::None => {} + operation::Outcome::Some(()) => {} + operation::Outcome::Chain(next) => { + current_operation = Some(next); } } - - *ui_caches = - uis.drain().map(|(id, ui)| (id, ui.into_cache())).collect(); } - command::Action::LoadFont { bytes, tagger } => { - // TODO: Error handling (?) - compositor.load_font(bytes.clone()); + } + Action::LoadFont { bytes, channel } => { + // TODO: Error handling (?) + compositor.load_font(bytes.clone()); - proxy.send(tagger(Ok(()))); - } - command::Action::Custom(_) => { - log::warn!("Unsupported custom action in `iced_winit` shell"); - } + let _ = channel.send(Ok(())); } } } diff --git a/winit/src/proxy.rs b/winit/src/proxy.rs index 3edc30ad5e..0ab61375db 100644 --- a/winit/src/proxy.rs +++ b/winit/src/proxy.rs @@ -4,17 +4,18 @@ use crate::futures::futures::{ task::{Context, Poll}, Future, Sink, StreamExt, }; +use crate::runtime::Action; use std::pin::Pin; /// An event loop proxy with backpressure that implements `Sink`. #[derive(Debug)] -pub struct Proxy { - raw: winit::event_loop::EventLoopProxy, - sender: mpsc::Sender, +pub struct Proxy { + raw: winit::event_loop::EventLoopProxy>, + sender: mpsc::Sender>, notifier: mpsc::Sender, } -impl Clone for Proxy { +impl Clone for Proxy { fn clone(&self) -> Self { Self { raw: self.raw.clone(), @@ -24,12 +25,12 @@ impl Clone for Proxy { } } -impl Proxy { +impl Proxy { const MAX_SIZE: usize = 100; /// Creates a new [`Proxy`] from an `EventLoopProxy`. pub fn new( - raw: winit::event_loop::EventLoopProxy, + raw: winit::event_loop::EventLoopProxy>, ) -> (Self, impl Future) { let (notifier, mut processed) = mpsc::channel(Self::MAX_SIZE); let (sender, mut receiver) = mpsc::channel(Self::MAX_SIZE); @@ -72,16 +73,16 @@ impl Proxy { ) } - /// Sends a `Message` to the event loop. + /// Sends a value to the event loop. /// /// Note: This skips the backpressure mechanism with an unbounded /// channel. Use sparingly! - pub fn send(&mut self, message: Message) + pub fn send(&mut self, value: T) where - Message: std::fmt::Debug, + T: std::fmt::Debug, { self.raw - .send_event(message) + .send_event(Action::Output(value)) .expect("Send message to event loop"); } @@ -92,7 +93,7 @@ impl Proxy { } } -impl Sink for Proxy { +impl Sink> for Proxy { type Error = mpsc::SendError; fn poll_ready( @@ -104,9 +105,9 @@ impl Sink for Proxy { fn start_send( mut self: Pin<&mut Self>, - message: Message, + action: Action, ) -> Result<(), Self::Error> { - self.sender.start_send(message) + self.sender.start_send(action) } fn poll_flush( diff --git a/winit/src/system.rs b/winit/src/system.rs index c5a5b21922..7997f311c1 100644 --- a/winit/src/system.rs +++ b/winit/src/system.rs @@ -1,15 +1,13 @@ //! Access the native system. use crate::graphics::compositor; -use crate::runtime::command::{self, Command}; use crate::runtime::system::{Action, Information}; +use crate::runtime::{self, Task}; /// Query for available system information. -pub fn fetch_information( - f: impl Fn(Information) -> Message + Send + 'static, -) -> Command { - Command::single(command::Action::System(Action::QueryInformation( - Box::new(f), - ))) +pub fn fetch_information() -> Task { + Task::oneshot(|channel| { + runtime::Action::System(Action::QueryInformation(channel)) + }) } pub(crate) fn information(