diff --git a/Cargo.toml b/Cargo.toml index 44b3d30706..b85900cf61 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -66,7 +66,7 @@ iced_core.workspace = true iced_futures.workspace = true iced_renderer.workspace = true iced_widget.workspace = true -iced_winit.features = ["application"] +iced_winit.features = ["program"] iced_winit.workspace = true iced_highlighter.workspace = true diff --git a/examples/arc/src/main.rs b/examples/arc/src/main.rs index 4576404fde..b1e8402a89 100644 --- a/examples/arc/src/main.rs +++ b/examples/arc/src/main.rs @@ -7,7 +7,7 @@ use iced::widget::canvas::{ use iced::{Element, Length, Point, Rectangle, Renderer, Subscription, Theme}; pub fn main() -> iced::Result { - iced::program("Arc - Iced", Arc::update, Arc::view) + iced::application("Arc - Iced", Arc::update, Arc::view) .subscription(Arc::subscription) .theme(|_| Theme::Dark) .antialiasing(true) diff --git a/examples/bezier_tool/src/main.rs b/examples/bezier_tool/src/main.rs index 29df3eeba9..eaf84b97c6 100644 --- a/examples/bezier_tool/src/main.rs +++ b/examples/bezier_tool/src/main.rs @@ -4,7 +4,7 @@ use iced::widget::{button, container, horizontal_space, hover}; use iced::{Element, Length, Theme}; pub fn main() -> iced::Result { - iced::program("Bezier Tool - Iced", Example::update, Example::view) + iced::application("Bezier Tool - Iced", Example::update, Example::view) .theme(|_| Theme::CatppuccinMocha) .antialiasing(true) .run() diff --git a/examples/checkbox/src/main.rs b/examples/checkbox/src/main.rs index bec4a9542e..f06557f86f 100644 --- a/examples/checkbox/src/main.rs +++ b/examples/checkbox/src/main.rs @@ -4,7 +4,7 @@ use iced::{Element, Font}; const ICON_FONT: Font = Font::with_name("icons"); pub fn main() -> iced::Result { - iced::program("Checkbox - Iced", Example::update, Example::view) + iced::application("Checkbox - Iced", Example::update, Example::view) .font(include_bytes!("../fonts/icons.ttf").as_slice()) .run() } diff --git a/examples/clock/src/main.rs b/examples/clock/src/main.rs index 7c4685c428..4584a0c708 100644 --- a/examples/clock/src/main.rs +++ b/examples/clock/src/main.rs @@ -11,7 +11,7 @@ use iced::{ pub fn main() -> iced::Result { tracing_subscriber::fmt::init(); - iced::program("Clock - Iced", Clock::update, Clock::view) + iced::application("Clock - Iced", Clock::update, Clock::view) .subscription(Clock::subscription) .theme(Clock::theme) .antialiasing(true) diff --git a/examples/color_palette/src/main.rs b/examples/color_palette/src/main.rs index d9325edb29..e4b19731aa 100644 --- a/examples/color_palette/src/main.rs +++ b/examples/color_palette/src/main.rs @@ -11,7 +11,7 @@ use std::marker::PhantomData; use std::ops::RangeInclusive; pub fn main() -> iced::Result { - iced::program( + iced::application( "Color Palette - Iced", ColorPalette::update, ColorPalette::view, diff --git a/examples/custom_shader/src/main.rs b/examples/custom_shader/src/main.rs index 463b2df939..b04a818362 100644 --- a/examples/custom_shader/src/main.rs +++ b/examples/custom_shader/src/main.rs @@ -9,9 +9,13 @@ use iced::window; use iced::{Alignment, Color, Element, Length, Subscription}; fn main() -> iced::Result { - iced::program("Custom Shader - Iced", IcedCubes::update, IcedCubes::view) - .subscription(IcedCubes::subscription) - .run() + iced::application( + "Custom Shader - Iced", + IcedCubes::update, + IcedCubes::view, + ) + .subscription(IcedCubes::subscription) + .run() } struct IcedCubes { diff --git a/examples/download_progress/src/main.rs b/examples/download_progress/src/main.rs index 7974d5a08e..d91e5eabfe 100644 --- a/examples/download_progress/src/main.rs +++ b/examples/download_progress/src/main.rs @@ -4,9 +4,13 @@ use iced::widget::{button, center, column, progress_bar, text, Column}; use iced::{Alignment, Element, Subscription}; pub fn main() -> iced::Result { - iced::program("Download Progress - Iced", Example::update, Example::view) - .subscription(Example::subscription) - .run() + iced::application( + "Download Progress - Iced", + Example::update, + Example::view, + ) + .subscription(Example::subscription) + .run() } #[derive(Debug)] diff --git a/examples/editor/src/main.rs b/examples/editor/src/main.rs index ec65e2fa60..bed9d94a9c 100644 --- a/examples/editor/src/main.rs +++ b/examples/editor/src/main.rs @@ -12,7 +12,7 @@ use std::path::{Path, PathBuf}; use std::sync::Arc; pub fn main() -> iced::Result { - iced::program("Editor - Iced", Editor::update, Editor::view) + iced::application("Editor - Iced", Editor::update, Editor::view) .load(Editor::load) .subscription(Editor::subscription) .theme(Editor::theme) diff --git a/examples/events/src/main.rs b/examples/events/src/main.rs index 504ed5d89b..4f0f07b065 100644 --- a/examples/events/src/main.rs +++ b/examples/events/src/main.rs @@ -5,7 +5,7 @@ use iced::window; use iced::{Alignment, Element, Length, Subscription, Task}; pub fn main() -> iced::Result { - iced::program("Events - Iced", Events::update, Events::view) + iced::application("Events - Iced", Events::update, Events::view) .subscription(Events::subscription) .exit_on_close_request(false) .run() diff --git a/examples/exit/src/main.rs b/examples/exit/src/main.rs index 8ba180a5be..b998016ee0 100644 --- a/examples/exit/src/main.rs +++ b/examples/exit/src/main.rs @@ -3,7 +3,7 @@ use iced::window; use iced::{Alignment, Element, Task}; pub fn main() -> iced::Result { - iced::program("Exit - Iced", Exit::update, Exit::view).run() + iced::application("Exit - Iced", Exit::update, Exit::view).run() } #[derive(Default)] diff --git a/examples/ferris/src/main.rs b/examples/ferris/src/main.rs index 0400c3765d..88006898a5 100644 --- a/examples/ferris/src/main.rs +++ b/examples/ferris/src/main.rs @@ -9,7 +9,7 @@ use iced::{ }; pub fn main() -> iced::Result { - iced::program("Ferris - Iced", Image::update, Image::view) + iced::application("Ferris - Iced", Image::update, Image::view) .subscription(Image::subscription) .theme(|_| Theme::TokyoNight) .run() diff --git a/examples/game_of_life/src/main.rs b/examples/game_of_life/src/main.rs index 7e6d461d3c..421f862a29 100644 --- a/examples/game_of_life/src/main.rs +++ b/examples/game_of_life/src/main.rs @@ -15,12 +15,16 @@ use std::time::Duration; pub fn main() -> iced::Result { tracing_subscriber::fmt::init(); - iced::program("Game of Life - Iced", GameOfLife::update, GameOfLife::view) - .subscription(GameOfLife::subscription) - .theme(|_| Theme::Dark) - .antialiasing(true) - .centered() - .run() + iced::application( + "Game of Life - Iced", + GameOfLife::update, + GameOfLife::view, + ) + .subscription(GameOfLife::subscription) + .theme(|_| Theme::Dark) + .antialiasing(true) + .centered() + .run() } struct GameOfLife { diff --git a/examples/gradient/src/main.rs b/examples/gradient/src/main.rs index 2b906c3260..e5b194438e 100644 --- a/examples/gradient/src/main.rs +++ b/examples/gradient/src/main.rs @@ -1,5 +1,5 @@ +use iced::application; use iced::gradient; -use iced::program; use iced::widget::{ checkbox, column, container, horizontal_space, row, slider, text, }; @@ -8,7 +8,7 @@ use iced::{Alignment, Color, Element, Length, Radians, Theme}; pub fn main() -> iced::Result { tracing_subscriber::fmt::init(); - iced::program("Gradient - Iced", Gradient::update, Gradient::view) + iced::application("Gradient - Iced", Gradient::update, Gradient::view) .style(Gradient::style) .transparent(true) .run() @@ -95,11 +95,11 @@ impl Gradient { .into() } - fn style(&self, theme: &Theme) -> program::Appearance { - use program::DefaultStyle; + fn style(&self, theme: &Theme) -> application::Appearance { + use application::DefaultStyle; if self.transparent { - program::Appearance { + application::Appearance { background_color: Color::TRANSPARENT, text_color: theme.palette().text, } diff --git a/examples/layout/src/main.rs b/examples/layout/src/main.rs index c40ac82017..2e77441560 100644 --- a/examples/layout/src/main.rs +++ b/examples/layout/src/main.rs @@ -10,7 +10,7 @@ use iced::{ }; pub fn main() -> iced::Result { - iced::program(Layout::title, Layout::update, Layout::view) + iced::application(Layout::title, Layout::update, Layout::view) .subscription(Layout::subscription) .theme(Layout::theme) .run() diff --git a/examples/loading_spinners/src/main.rs b/examples/loading_spinners/src/main.rs index a63c51d47e..503f2d7ae9 100644 --- a/examples/loading_spinners/src/main.rs +++ b/examples/loading_spinners/src/main.rs @@ -11,7 +11,7 @@ use circular::Circular; use linear::Linear; pub fn main() -> iced::Result { - iced::program( + iced::application( "Loading Spinners - Iced", LoadingSpinners::update, LoadingSpinners::view, diff --git a/examples/modal/src/main.rs b/examples/modal/src/main.rs index d185cf3b45..413485e724 100644 --- a/examples/modal/src/main.rs +++ b/examples/modal/src/main.rs @@ -10,7 +10,7 @@ use iced::{Alignment, Color, Element, Length, Subscription, Task}; use std::fmt; pub fn main() -> iced::Result { - iced::program("Modal - Iced", App::update, App::view) + iced::application("Modal - Iced", App::update, App::view) .subscription(App::subscription) .run() } diff --git a/examples/multi_window/src/main.rs b/examples/multi_window/src/main.rs index b82ad1f379..dfb816cfe3 100644 --- a/examples/multi_window/src/main.rs +++ b/examples/multi_window/src/main.rs @@ -1,18 +1,21 @@ -use iced::executor; -use iced::multi_window::{self, Application}; use iced::widget::{ button, center, column, container, horizontal_space, scrollable, text, text_input, }; use iced::window; -use iced::{ - Alignment, Element, Length, Settings, Subscription, Task, Theme, Vector, -}; +use iced::{Alignment, Element, Length, Subscription, Task, Theme, Vector}; use std::collections::BTreeMap; fn main() -> iced::Result { - Example::run(Settings::default()) + iced::daemon(Example::title, Example::update, Example::view) + .load(|| { + window::open(window::Settings::default()).map(Message::WindowOpened) + }) + .subscription(Example::subscription) + .theme(Example::theme) + .scale_factor(Example::scale_factor) + .run() } #[derive(Default)] @@ -39,21 +42,7 @@ enum Message { TitleChanged(window::Id, String), } -impl multi_window::Application for Example { - type Executor = executor::Default; - type Message = Message; - type Theme = Theme; - type Flags = (); - - fn new(_flags: ()) -> (Self, Task) { - ( - Example { - windows: BTreeMap::from([(window::Id::MAIN, Window::new(1))]), - }, - Task::none(), - ) - } - +impl Example { fn title(&self, window: window::Id) -> String { self.windows .get(&window) @@ -97,7 +86,11 @@ impl multi_window::Application for Example { Message::WindowClosed(id) => { self.windows.remove(&id); - Task::none() + if self.windows.is_empty() { + iced::exit() + } else { + Task::none() + } } Message::ScaleInputChanged(id, scale) => { if let Some(window) = self.windows.get_mut(&id) { @@ -149,7 +142,7 @@ impl multi_window::Application for Example { .unwrap_or(1.0) } - fn subscription(&self) -> Subscription { + fn subscription(&self) -> Subscription { window::close_events().map(Message::WindowClosed) } } diff --git a/examples/multitouch/src/main.rs b/examples/multitouch/src/main.rs index 2453c7f5c2..6971731067 100644 --- a/examples/multitouch/src/main.rs +++ b/examples/multitouch/src/main.rs @@ -13,7 +13,7 @@ use std::collections::HashMap; pub fn main() -> iced::Result { tracing_subscriber::fmt::init(); - iced::program("Multitouch - Iced", Multitouch::update, Multitouch::view) + iced::application("Multitouch - Iced", Multitouch::update, Multitouch::view) .antialiasing(true) .centered() .run() diff --git a/examples/pane_grid/src/main.rs b/examples/pane_grid/src/main.rs index 6b5bd332fe..db9f7a058a 100644 --- a/examples/pane_grid/src/main.rs +++ b/examples/pane_grid/src/main.rs @@ -7,7 +7,7 @@ use iced::widget::{ use iced::{Color, Element, Length, Size, Subscription}; pub fn main() -> iced::Result { - iced::program("Pane Grid - Iced", Example::update, Example::view) + iced::application("Pane Grid - Iced", Example::update, Example::view) .subscription(Example::subscription) .run() } diff --git a/examples/pokedex/src/main.rs b/examples/pokedex/src/main.rs index e62ed70b24..b22ffe7f18 100644 --- a/examples/pokedex/src/main.rs +++ b/examples/pokedex/src/main.rs @@ -3,7 +3,7 @@ use iced::widget::{self, center, column, image, row, text}; use iced::{Alignment, Element, Length, Task}; pub fn main() -> iced::Result { - iced::program(Pokedex::title, Pokedex::update, Pokedex::view) + iced::application(Pokedex::title, Pokedex::update, Pokedex::view) .load(Pokedex::search) .run() } diff --git a/examples/qr_code/src/main.rs b/examples/qr_code/src/main.rs index c6a904581c..b30ecf152e 100644 --- a/examples/qr_code/src/main.rs +++ b/examples/qr_code/src/main.rs @@ -2,7 +2,7 @@ use iced::widget::{center, column, pick_list, qr_code, row, text, text_input}; use iced::{Alignment, Element, Theme}; pub fn main() -> iced::Result { - iced::program( + iced::application( "QR Code Generator - Iced", QRGenerator::update, QRGenerator::view, diff --git a/examples/screenshot/src/main.rs b/examples/screenshot/src/main.rs index 78d3e9ffc6..1ea53e8f7f 100644 --- a/examples/screenshot/src/main.rs +++ b/examples/screenshot/src/main.rs @@ -13,7 +13,7 @@ use ::image::ColorType; fn main() -> iced::Result { tracing_subscriber::fmt::init(); - iced::program("Screenshot - Iced", Example::update, Example::view) + iced::application("Screenshot - Iced", Example::update, Example::view) .subscription(Example::subscription) .run() } diff --git a/examples/scrollable/src/main.rs b/examples/scrollable/src/main.rs index a0dcf82c6a..f2a853e1e6 100644 --- a/examples/scrollable/src/main.rs +++ b/examples/scrollable/src/main.rs @@ -10,7 +10,7 @@ use once_cell::sync::Lazy; static SCROLLABLE_ID: Lazy = Lazy::new(scrollable::Id::unique); pub fn main() -> iced::Result { - iced::program( + iced::application( "Scrollable - Iced", ScrollableDemo::update, ScrollableDemo::view, diff --git a/examples/sierpinski_triangle/src/main.rs b/examples/sierpinski_triangle/src/main.rs index 7dd7be5e53..4c7519374a 100644 --- a/examples/sierpinski_triangle/src/main.rs +++ b/examples/sierpinski_triangle/src/main.rs @@ -8,7 +8,7 @@ use rand::Rng; use std::fmt::Debug; fn main() -> iced::Result { - iced::program( + iced::application( "Sierpinski Triangle - Iced", SierpinskiEmulator::update, SierpinskiEmulator::view, diff --git a/examples/solar_system/src/main.rs b/examples/solar_system/src/main.rs index deb211d89c..2a67e23ed3 100644 --- a/examples/solar_system/src/main.rs +++ b/examples/solar_system/src/main.rs @@ -22,7 +22,7 @@ use std::time::Instant; pub fn main() -> iced::Result { tracing_subscriber::fmt::init(); - iced::program( + iced::application( "Solar System - Iced", SolarSystem::update, SolarSystem::view, diff --git a/examples/stopwatch/src/main.rs b/examples/stopwatch/src/main.rs index a814975371..bd56785a2b 100644 --- a/examples/stopwatch/src/main.rs +++ b/examples/stopwatch/src/main.rs @@ -7,7 +7,7 @@ use iced::{Alignment, Element, Subscription, Theme}; use std::time::{Duration, Instant}; pub fn main() -> iced::Result { - iced::program("Stopwatch - Iced", Stopwatch::update, Stopwatch::view) + iced::application("Stopwatch - Iced", Stopwatch::update, Stopwatch::view) .subscription(Stopwatch::subscription) .theme(Stopwatch::theme) .run() diff --git a/examples/styling/src/main.rs b/examples/styling/src/main.rs index 57e8f47e11..3124493be3 100644 --- a/examples/styling/src/main.rs +++ b/examples/styling/src/main.rs @@ -6,7 +6,7 @@ use iced::widget::{ use iced::{Alignment, Element, Length, Theme}; pub fn main() -> iced::Result { - iced::program("Styling - Iced", Styling::update, Styling::view) + iced::application("Styling - Iced", Styling::update, Styling::view) .theme(Styling::theme) .run() } diff --git a/examples/system_information/src/main.rs b/examples/system_information/src/main.rs index e2808edd17..363df59000 100644 --- a/examples/system_information/src/main.rs +++ b/examples/system_information/src/main.rs @@ -2,8 +2,12 @@ use iced::widget::{button, center, column, text}; use iced::{system, Element, Task}; pub fn main() -> iced::Result { - iced::program("System Information - Iced", Example::update, Example::view) - .run() + iced::application( + "System Information - Iced", + Example::update, + Example::view, + ) + .run() } #[derive(Default)] diff --git a/examples/the_matrix/src/main.rs b/examples/the_matrix/src/main.rs index f3a67ac840..2ae1cc3aea 100644 --- a/examples/the_matrix/src/main.rs +++ b/examples/the_matrix/src/main.rs @@ -11,7 +11,7 @@ use std::cell::RefCell; pub fn main() -> iced::Result { tracing_subscriber::fmt::init(); - iced::program("The Matrix - Iced", TheMatrix::update, TheMatrix::view) + iced::application("The Matrix - Iced", TheMatrix::update, TheMatrix::view) .subscription(TheMatrix::subscription) .antialiasing(true) .run() diff --git a/examples/toast/src/main.rs b/examples/toast/src/main.rs index aee2479e52..232133b135 100644 --- a/examples/toast/src/main.rs +++ b/examples/toast/src/main.rs @@ -9,7 +9,7 @@ use iced::{Alignment, Element, Length, Subscription, Task}; use toast::{Status, Toast}; pub fn main() -> iced::Result { - iced::program("Toast - Iced", App::update, App::view) + iced::application("Toast - Iced", App::update, App::view) .subscription(App::subscription) .run() } diff --git a/examples/todos/src/main.rs b/examples/todos/src/main.rs index c21e1a9674..a834c9462a 100644 --- a/examples/todos/src/main.rs +++ b/examples/todos/src/main.rs @@ -17,7 +17,7 @@ pub fn main() -> iced::Result { #[cfg(not(target_arch = "wasm32"))] tracing_subscriber::fmt::init(); - iced::program(Todos::title, Todos::update, Todos::view) + iced::application(Todos::title, Todos::update, Todos::view) .load(Todos::load) .subscription(Todos::subscription) .font(include_bytes!("../fonts/icons.ttf").as_slice()) diff --git a/examples/tour/src/main.rs b/examples/tour/src/main.rs index 78086ce9a7..94ba78eec4 100644 --- a/examples/tour/src/main.rs +++ b/examples/tour/src/main.rs @@ -16,7 +16,7 @@ pub fn main() -> iced::Result { #[cfg(not(target_arch = "wasm32"))] tracing_subscriber::fmt::init(); - iced::program(Tour::title, Tour::update, Tour::view) + iced::application(Tour::title, Tour::update, Tour::view) .centered() .run() } diff --git a/examples/url_handler/src/main.rs b/examples/url_handler/src/main.rs index 3ab19252dd..50a055f374 100644 --- a/examples/url_handler/src/main.rs +++ b/examples/url_handler/src/main.rs @@ -3,7 +3,7 @@ use iced::widget::{center, text}; use iced::{Element, Subscription}; pub fn main() -> iced::Result { - iced::program("URL Handler - Iced", App::update, App::view) + iced::application("URL Handler - Iced", App::update, App::view) .subscription(App::subscription) .run() } diff --git a/examples/vectorial_text/src/main.rs b/examples/vectorial_text/src/main.rs index 1ed7a2b1c5..6dd3273a12 100644 --- a/examples/vectorial_text/src/main.rs +++ b/examples/vectorial_text/src/main.rs @@ -6,7 +6,7 @@ use iced::widget::{ use iced::{Element, Length, Point, Rectangle, Renderer, Theme, Vector}; pub fn main() -> iced::Result { - iced::program( + iced::application( "Vectorial Text - Iced", VectorialText::update, VectorialText::view, diff --git a/examples/visible_bounds/src/main.rs b/examples/visible_bounds/src/main.rs index b43c0cca24..e46d1ff0d0 100644 --- a/examples/visible_bounds/src/main.rs +++ b/examples/visible_bounds/src/main.rs @@ -10,7 +10,7 @@ use iced::{ }; pub fn main() -> iced::Result { - iced::program("Visible Bounds - Iced", Example::update, Example::view) + iced::application("Visible Bounds - Iced", Example::update, Example::view) .subscription(Example::subscription) .theme(|_| Theme::Dark) .run() diff --git a/examples/websocket/src/main.rs b/examples/websocket/src/main.rs index 8c0fa1d075..8422ce162b 100644 --- a/examples/websocket/src/main.rs +++ b/examples/websocket/src/main.rs @@ -8,7 +8,7 @@ use iced::{color, Element, Length, Subscription, Task}; use once_cell::sync::Lazy; pub fn main() -> iced::Result { - iced::program("WebSocket - Iced", WebSocket::update, WebSocket::view) + iced::application("WebSocket - Iced", WebSocket::update, WebSocket::view) .load(WebSocket::load) .subscription(WebSocket::subscription) .run() diff --git a/runtime/src/lib.rs b/runtime/src/lib.rs index 5fde3039b8..b4a5e8199a 100644 --- a/runtime/src/lib.rs +++ b/runtime/src/lib.rs @@ -70,6 +70,12 @@ pub enum Action { /// Run a system action. System(system::Action), + + /// Exits the runtime. + /// + /// This will normally close any application windows and + /// terminate the runtime loop. + Exit, } impl Action { @@ -88,6 +94,7 @@ impl Action { Action::Clipboard(action) => Err(Action::Clipboard(action)), Action::Window(action) => Err(Action::Window(action)), Action::System(action) => Err(Action::System(action)), + Action::Exit => Err(Action::Exit), } } } @@ -110,6 +117,15 @@ where } Action::Window(_) => write!(f, "Action::Window"), Action::System(action) => write!(f, "Action::System({action:?})"), + Action::Exit => write!(f, "Action::Exit"), } } } + +/// Creates a [`Task`] that exits the iced runtime. +/// +/// This will normally close any application windows and +/// terminate the runtime loop. +pub fn exit() -> Task { + Task::effect(Action::Exit) +} diff --git a/src/advanced.rs b/src/advanced.rs index 5826ba0f57..8d06e805a8 100644 --- a/src/advanced.rs +++ b/src/advanced.rs @@ -1,5 +1,4 @@ //! Leverage advanced concepts like custom widgets. -pub use crate::application::Application; pub use crate::core::clipboard::{self, Clipboard}; pub use crate::core::image; pub use crate::core::layout::{self, Layout}; diff --git a/src/application.rs b/src/application.rs index 4cd4a87d07..edca6e79e6 100644 --- a/src/application.rs +++ b/src/application.rs @@ -1,278 +1,426 @@ -//! Build interactive cross-platform applications. -use crate::core::text; -use crate::graphics::compositor; -use crate::shell::application; -use crate::{Element, Executor, Settings, Subscription, Task}; +//! Create and run iced applications step by step. +//! +//! # Example +//! ```no_run +//! use iced::widget::{button, column, text, Column}; +//! use iced::Theme; +//! +//! pub fn main() -> iced::Result { +//! iced::application("A counter", update, view) +//! .theme(|_| Theme::Dark) +//! .centered() +//! .run() +//! } +//! +//! #[derive(Debug, Clone)] +//! enum Message { +//! Increment, +//! } +//! +//! fn update(value: &mut u64, message: Message) { +//! match message { +//! Message::Increment => *value += 1, +//! } +//! } +//! +//! fn view(value: &u64) -> Column { +//! column![ +//! text(value), +//! button("+").on_press(Message::Increment), +//! ] +//! } +//! ``` +use crate::program::{self, Program}; +use crate::window; +use crate::{Element, Font, Result, Settings, Size, Subscription, Task}; -pub use application::{Appearance, DefaultStyle}; +use std::borrow::Cow; -/// An interactive cross-platform application. -/// -/// This trait is the main entrypoint of Iced. Once implemented, you can run -/// your GUI application by simply calling [`run`](#method.run). -/// -/// - On native platforms, it will run in its own window. -/// - On the web, it will take control of the `` and the `<body>` of the -/// document. -/// -/// An [`Application`] can execute asynchronous actions by returning a -/// [`Task`] in some of its methods. -/// -/// When using an [`Application`] with the `debug` feature enabled, a debug view -/// can be toggled by pressing `F12`. -/// -/// # Examples -/// [The repository has a bunch of examples] that use the [`Application`] trait: -/// -/// - [`clock`], an application that uses the [`Canvas`] widget to draw a clock -/// and its hands to display the current time. -/// - [`download_progress`], a basic application that asynchronously downloads -/// a dummy file of 100 MB and tracks the download progress. -/// - [`events`], a log of native events displayed using a conditional -/// [`Subscription`]. -/// - [`game_of_life`], an interactive version of the [Game of Life], invented -/// by [John Horton Conway]. -/// - [`pokedex`], an application that displays a random Pokédex entry (sprite -/// included!) by using the [PokéAPI]. -/// - [`solar_system`], an animated solar system drawn using the [`Canvas`] widget -/// and showcasing how to compose different transforms. -/// - [`stopwatch`], a watch with start/stop and reset buttons showcasing how -/// to listen to time. -/// - [`todos`], a todos tracker inspired by [TodoMVC]. -/// -/// [The repository has a bunch of examples]: https://github.com/iced-rs/iced/tree/0.12/examples -/// [`clock`]: https://github.com/iced-rs/iced/tree/0.12/examples/clock -/// [`download_progress`]: https://github.com/iced-rs/iced/tree/0.12/examples/download_progress -/// [`events`]: https://github.com/iced-rs/iced/tree/0.12/examples/events -/// [`game_of_life`]: https://github.com/iced-rs/iced/tree/0.12/examples/game_of_life -/// [`pokedex`]: https://github.com/iced-rs/iced/tree/0.12/examples/pokedex -/// [`solar_system`]: https://github.com/iced-rs/iced/tree/0.12/examples/solar_system -/// [`stopwatch`]: https://github.com/iced-rs/iced/tree/0.12/examples/stopwatch -/// [`todos`]: https://github.com/iced-rs/iced/tree/0.12/examples/todos -/// [`Sandbox`]: crate::Sandbox -/// [`Canvas`]: crate::widget::Canvas -/// [PokéAPI]: https://pokeapi.co/ -/// [TodoMVC]: http://todomvc.com/ -/// -/// ## A simple "Hello, world!" -/// -/// If you just want to get started, here is a simple [`Application`] that -/// says "Hello, world!": +pub use crate::shell::program::{Appearance, DefaultStyle}; + +/// Creates an iced [`Application`] given its title, update, and view logic. /// +/// # Example /// ```no_run -/// use iced::advanced::Application; -/// use iced::executor; -/// use iced::{Task, Element, Settings, Theme, Renderer}; +/// use iced::widget::{button, column, text, Column}; /// /// pub fn main() -> iced::Result { -/// Hello::run(Settings::default()) +/// iced::application("A counter", update, view).run() /// } /// -/// struct Hello; -/// -/// impl Application for Hello { -/// type Executor = executor::Default; -/// type Flags = (); -/// type Message = (); -/// type Theme = Theme; -/// type Renderer = Renderer; -/// -/// fn new(_flags: ()) -> (Hello, Task<Self::Message>) { -/// (Hello, Task::none()) -/// } -/// -/// fn title(&self) -> String { -/// String::from("A cool application") -/// } +/// #[derive(Debug, Clone)] +/// enum Message { +/// Increment, +/// } /// -/// fn update(&mut self, _message: Self::Message) -> Task<Self::Message> { -/// Task::none() +/// fn update(value: &mut u64, message: Message) { +/// match message { +/// Message::Increment => *value += 1, /// } +/// } /// -/// fn view(&self) -> Element<Self::Message> { -/// "Hello, world!".into() -/// } +/// fn view(value: &u64) -> Column<Message> { +/// column![ +/// text(value), +/// button("+").on_press(Message::Increment), +/// ] /// } /// ``` -pub trait Application: Sized +pub fn application<State, Message, Theme, Renderer>( + title: impl Title<State>, + update: impl Update<State, Message>, + view: impl for<'a> self::View<'a, State, Message, Theme, Renderer>, +) -> Application<impl Program<State = State, Message = Message, Theme = Theme>> where - Self::Theme: DefaultStyle, + State: 'static, + Message: Send + std::fmt::Debug + 'static, + Theme: Default + DefaultStyle, + Renderer: program::Renderer, { - /// The [`Executor`] that will run commands and subscriptions. - /// - /// The [default executor] can be a good starting point! - /// - /// [`Executor`]: Self::Executor - /// [default executor]: crate::executor::Default - type Executor: Executor; + use std::marker::PhantomData; - /// The type of __messages__ your [`Application`] will produce. - type Message: std::fmt::Debug + Send; + struct Instance<State, Message, Theme, Renderer, Update, View> { + update: Update, + view: View, + _state: PhantomData<State>, + _message: PhantomData<Message>, + _theme: PhantomData<Theme>, + _renderer: PhantomData<Renderer>, + } - /// The theme of your [`Application`]. - type Theme: Default; + impl<State, Message, Theme, Renderer, Update, View> Program + for Instance<State, Message, Theme, Renderer, Update, View> + where + Message: Send + std::fmt::Debug + 'static, + Theme: Default + DefaultStyle, + Renderer: program::Renderer, + Update: self::Update<State, Message>, + View: for<'a> self::View<'a, State, Message, Theme, Renderer>, + { + type State = State; + type Message = Message; + type Theme = Theme; + type Renderer = Renderer; + type Executor = iced_futures::backend::default::Executor; - /// The renderer of your [`Application`]. - type Renderer: text::Renderer + compositor::Default; + fn load(&self) -> Task<Self::Message> { + Task::none() + } - /// The data needed to initialize your [`Application`]. - type Flags; + fn update( + &self, + state: &mut Self::State, + message: Self::Message, + ) -> Task<Self::Message> { + self.update.update(state, message).into() + } - /// Initializes the [`Application`] with the flags provided to - /// [`run`] as part of the [`Settings`]. - /// - /// Here is where you should return the initial state of your app. - /// - /// 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, Task<Self::Message>); + fn view<'a>( + &self, + state: &'a Self::State, + _window: window::Id, + ) -> Element<'a, Self::Message, Self::Theme, Self::Renderer> { + self.view.view(state).into() + } + } - /// Returns the current title of the [`Application`]. - /// - /// This title can be dynamic! The runtime will automatically update the - /// title of your application when necessary. - fn title(&self) -> String; + Application { + raw: Instance { + update, + view, + _state: PhantomData, + _message: PhantomData, + _theme: PhantomData, + _renderer: PhantomData, + }, + settings: Settings::default(), + window: window::Settings::default(), + } + .title(title) +} - /// Handles a __message__ and updates the state of the [`Application`]. +/// The underlying definition and configuration of an iced application. +/// +/// You can use this API to create and run iced applications +/// step by step—without coupling your logic to a trait +/// or a specific type. +/// +/// You can create an [`Application`] with the [`application`] helper. +#[derive(Debug)] +pub struct Application<P: Program> { + raw: P, + settings: Settings, + window: window::Settings, +} + +impl<P: Program> Application<P> { + /// Runs the [`Application`]. /// - /// This is where you define your __update logic__. All the __messages__, - /// produced by either user interactions or commands, will be handled by - /// this method. + /// The state of the [`Application`] must implement [`Default`]. + /// If your state does not implement [`Default`], use [`run_with`] + /// instead. /// - /// Any [`Task`] returned will be executed immediately in the background. - fn update(&mut self, message: Self::Message) -> Task<Self::Message>; + /// [`run_with`]: Self::run_with + pub fn run(self) -> Result + where + Self: 'static, + P::State: Default, + { + self.run_with(P::State::default) + } - /// Returns the widgets to display in the [`Application`]. - /// - /// These widgets can produce __messages__ based on user interaction. - fn view(&self) -> Element<'_, Self::Message, Self::Theme, Self::Renderer>; + /// Runs the [`Application`] with a closure that creates the initial state. + pub fn run_with<I>(self, initialize: I) -> Result + where + Self: 'static, + I: Fn() -> P::State + Clone + 'static, + { + self.raw + .run_with(self.settings, Some(self.window), initialize) + } - /// Returns the current [`Theme`] of the [`Application`]. - /// - /// [`Theme`]: Self::Theme - fn theme(&self) -> Self::Theme { - Self::Theme::default() + /// Sets the [`Settings`] that will be used to run the [`Application`]. + pub fn settings(self, settings: Settings) -> Self { + Self { settings, ..self } } - /// Returns the current [`Appearance`] of the [`Application`]. - fn style(&self, theme: &Self::Theme) -> Appearance { - theme.default_style() + /// Sets the [`Settings::antialiasing`] of the [`Application`]. + pub fn antialiasing(self, antialiasing: bool) -> Self { + Self { + settings: Settings { + antialiasing, + ..self.settings + }, + ..self + } } - /// Returns the event [`Subscription`] for the current state of the - /// application. - /// - /// A [`Subscription`] will be kept alive as long as you keep returning it, - /// and the __messages__ produced will be handled by - /// [`update`](#tymethod.update). - /// - /// By default, this method returns an empty [`Subscription`]. - fn subscription(&self) -> Subscription<Self::Message> { - Subscription::none() + /// Sets the default [`Font`] of the [`Application`]. + pub fn default_font(self, default_font: Font) -> Self { + Self { + settings: Settings { + default_font, + ..self.settings + }, + ..self + } } - /// Returns the scale factor of the [`Application`]. - /// - /// It can be used to dynamically control the size of the UI at runtime - /// (i.e. zooming). - /// - /// For instance, a scale factor of `2.0` will make widgets twice as big, - /// while a scale factor of `0.5` will shrink them to half their size. - /// - /// By default, it returns `1.0`. - fn scale_factor(&self) -> f64 { - 1.0 + /// Adds a font to the list of fonts that will be loaded at the start of the [`Application`]. + pub fn font(mut self, font: impl Into<Cow<'static, [u8]>>) -> Self { + self.settings.fonts.push(font.into()); + self } - /// Runs the [`Application`]. - /// - /// On native platforms, this method will take control of the current thread - /// until the [`Application`] exits. - /// - /// On the web platform, this method __will NOT return__ unless there is an - /// [`Error`] during startup. - /// - /// [`Error`]: crate::Error - fn run(settings: Settings<Self::Flags>) -> crate::Result - where - Self: 'static, - { - #[allow(clippy::needless_update)] - let renderer_settings = crate::graphics::Settings { - default_font: settings.default_font, - default_text_size: settings.default_text_size, - antialiasing: if settings.antialiasing { - Some(crate::graphics::Antialiasing::MSAAx4) - } else { - None + /// Sets the [`window::Settings::position`] to [`window::Position::Centered`] in the [`Application`]. + pub fn centered(self) -> Self { + Self { + window: window::Settings { + position: window::Position::Centered, + ..self.window }, - ..crate::graphics::Settings::default() - }; - - Ok(crate::shell::application::run::< - Instance<Self>, - Self::Executor, - <Self::Renderer as compositor::Default>::Compositor, - >(settings.into(), renderer_settings)?) + ..self + } } -} -struct Instance<A>(A) -where - A: Application, - A::Theme: DefaultStyle; + /// Sets the [`window::Settings::exit_on_close_request`] of the [`Application`]. + pub fn exit_on_close_request(self, exit_on_close_request: bool) -> Self { + Self { + window: window::Settings { + exit_on_close_request, + ..self.window + }, + ..self + } + } -impl<A> crate::runtime::Program for Instance<A> -where - A: Application, - A::Theme: DefaultStyle, -{ - type Message = A::Message; - type Theme = A::Theme; - type Renderer = A::Renderer; + /// Sets the [`window::Settings::size`] of the [`Application`]. + pub fn window_size(self, size: impl Into<Size>) -> Self { + Self { + window: window::Settings { + size: size.into(), + ..self.window + }, + ..self + } + } - fn update(&mut self, message: Self::Message) -> Task<Self::Message> { - self.0.update(message) + /// Sets the [`window::Settings::transparent`] of the [`Application`]. + pub fn transparent(self, transparent: bool) -> Self { + Self { + window: window::Settings { + transparent, + ..self.window + }, + ..self + } } - fn view(&self) -> Element<'_, Self::Message, Self::Theme, Self::Renderer> { - self.0.view() + /// Sets the [`Title`] of the [`Application`]. + pub(crate) fn title( + self, + title: impl Title<P::State>, + ) -> Application< + impl Program<State = P::State, Message = P::Message, Theme = P::Theme>, + > { + Application { + raw: program::with_title(self.raw, move |state, _window| { + title.title(state) + }), + settings: self.settings, + window: self.window, + } } -} -impl<A> application::Application for Instance<A> -where - A: Application, - A::Theme: DefaultStyle, -{ - type Flags = A::Flags; + /// Runs the [`Task`] produced by the closure at startup. + pub fn load( + self, + f: impl Fn() -> Task<P::Message>, + ) -> Application< + impl Program<State = P::State, Message = P::Message, Theme = P::Theme>, + > { + Application { + raw: program::with_load(self.raw, f), + settings: self.settings, + window: self.window, + } + } - fn new(flags: Self::Flags) -> (Self, Task<A::Message>) { - let (app, command) = A::new(flags); + /// Sets the subscription logic of the [`Application`]. + pub fn subscription( + self, + f: impl Fn(&P::State) -> Subscription<P::Message>, + ) -> Application< + impl Program<State = P::State, Message = P::Message, Theme = P::Theme>, + > { + Application { + raw: program::with_subscription(self.raw, f), + settings: self.settings, + window: self.window, + } + } - (Instance(app), command) + /// Sets the theme logic of the [`Application`]. + pub fn theme( + self, + f: impl Fn(&P::State) -> P::Theme, + ) -> Application< + impl Program<State = P::State, Message = P::Message, Theme = P::Theme>, + > { + Application { + raw: program::with_theme(self.raw, move |state, _window| f(state)), + settings: self.settings, + window: self.window, + } } - fn title(&self) -> String { - self.0.title() + /// Sets the style logic of the [`Application`]. + pub fn style( + self, + f: impl Fn(&P::State, &P::Theme) -> Appearance, + ) -> Application< + impl Program<State = P::State, Message = P::Message, Theme = P::Theme>, + > { + Application { + raw: program::with_style(self.raw, f), + settings: self.settings, + window: self.window, + } } - fn theme(&self) -> A::Theme { - self.0.theme() + /// Sets the scale factor of the [`Application`]. + pub fn scale_factor( + self, + f: impl Fn(&P::State) -> f64, + ) -> Application< + impl Program<State = P::State, Message = P::Message, Theme = P::Theme>, + > { + Application { + raw: program::with_scale_factor(self.raw, move |state, _window| { + f(state) + }), + settings: self.settings, + window: self.window, + } } +} + +/// The title logic of some [`Application`]. +/// +/// This trait is implemented both for `&static str` and +/// any closure `Fn(&State) -> String`. +/// +/// This trait allows the [`application`] builder to take any of them. +pub trait Title<State> { + /// Produces the title of the [`Application`]. + fn title(&self, state: &State) -> String; +} - fn style(&self, theme: &A::Theme) -> Appearance { - self.0.style(theme) +impl<State> Title<State> for &'static str { + fn title(&self, _state: &State) -> String { + self.to_string() } +} - fn subscription(&self) -> Subscription<Self::Message> { - self.0.subscription() +impl<T, State> Title<State> for T +where + T: Fn(&State) -> String, +{ + fn title(&self, state: &State) -> String { + self(state) } +} - fn scale_factor(&self) -> f64 { - self.0.scale_factor() +/// The update logic of some [`Application`]. +/// +/// This trait allows the [`application`] builder to take any closure that +/// returns any `Into<Task<Message>>`. +pub trait Update<State, Message> { + /// Processes the message and updates the state of the [`Application`]. + fn update( + &self, + state: &mut State, + message: Message, + ) -> impl Into<Task<Message>>; +} + +impl<T, State, Message, C> Update<State, Message> for T +where + T: Fn(&mut State, Message) -> C, + C: Into<Task<Message>>, +{ + fn update( + &self, + state: &mut State, + message: Message, + ) -> impl Into<Task<Message>> { + self(state, message) + } +} + +/// The view logic of some [`Application`]. +/// +/// This trait allows the [`application`] builder to take any closure that +/// returns any `Into<Element<'_, Message>>`. +pub trait View<'a, State, Message, Theme, Renderer> { + /// Produces the widget of the [`Application`]. + fn view( + &self, + state: &'a State, + ) -> impl Into<Element<'a, Message, Theme, Renderer>>; +} + +impl<'a, T, State, Message, Theme, Renderer, Widget> + View<'a, State, Message, Theme, Renderer> for T +where + T: Fn(&'a State) -> Widget, + State: 'static, + Widget: Into<Element<'a, Message, Theme, Renderer>>, +{ + fn view( + &self, + state: &'a State, + ) -> impl Into<Element<'a, Message, Theme, Renderer>> { + self(state) } } diff --git a/src/daemon.rs b/src/daemon.rs new file mode 100644 index 0000000000..58293949e7 --- /dev/null +++ b/src/daemon.rs @@ -0,0 +1,298 @@ +//! Create and run daemons that run in the background. +use crate::application; +use crate::program::{self, Program}; +use crate::window; +use crate::{Element, Font, Result, Settings, Subscription, Task}; + +use std::borrow::Cow; + +pub use crate::shell::program::{Appearance, DefaultStyle}; + +/// Creates an iced [`Daemon`] given its title, update, and view logic. +/// +/// A [`Daemon`] will not open a window by default, but will run silently +/// instead until a [`Task`] from [`window::open`] is returned by its update logic. +/// +/// Furthermore, a [`Daemon`] will not stop running when all its windows are closed. +/// In order to completely terminate a [`Daemon`], its process must be interrupted or +/// its update logic must produce a [`Task`] from [`exit`]. +/// +/// [`exit`]: crate::exit +pub fn daemon<State, Message, Theme, Renderer>( + title: impl Title<State>, + update: impl application::Update<State, Message>, + view: impl for<'a> self::View<'a, State, Message, Theme, Renderer>, +) -> Daemon<impl Program<State = State, Message = Message, Theme = Theme>> +where + State: 'static, + Message: Send + std::fmt::Debug + 'static, + Theme: Default + DefaultStyle, + Renderer: program::Renderer, +{ + use std::marker::PhantomData; + + struct Instance<State, Message, Theme, Renderer, Update, View> { + update: Update, + view: View, + _state: PhantomData<State>, + _message: PhantomData<Message>, + _theme: PhantomData<Theme>, + _renderer: PhantomData<Renderer>, + } + + impl<State, Message, Theme, Renderer, Update, View> Program + for Instance<State, Message, Theme, Renderer, Update, View> + where + Message: Send + std::fmt::Debug + 'static, + Theme: Default + DefaultStyle, + Renderer: program::Renderer, + Update: application::Update<State, Message>, + View: for<'a> self::View<'a, State, Message, Theme, Renderer>, + { + type State = State; + type Message = Message; + type Theme = Theme; + type Renderer = Renderer; + type Executor = iced_futures::backend::default::Executor; + + fn load(&self) -> Task<Self::Message> { + Task::none() + } + + fn update( + &self, + state: &mut Self::State, + message: Self::Message, + ) -> Task<Self::Message> { + self.update.update(state, message).into() + } + + fn view<'a>( + &self, + state: &'a Self::State, + window: window::Id, + ) -> Element<'a, Self::Message, Self::Theme, Self::Renderer> { + self.view.view(state, window).into() + } + } + + Daemon { + raw: Instance { + update, + view, + _state: PhantomData, + _message: PhantomData, + _theme: PhantomData, + _renderer: PhantomData, + }, + settings: Settings::default(), + } + .title(title) +} + +/// The underlying definition and configuration of an iced daemon. +/// +/// You can use this API to create and run iced applications +/// step by step—without coupling your logic to a trait +/// or a specific type. +/// +/// You can create a [`Daemon`] with the [`daemon`] helper. +#[derive(Debug)] +pub struct Daemon<P: Program> { + raw: P, + settings: Settings, +} + +impl<P: Program> Daemon<P> { + /// Runs the [`Daemon`]. + /// + /// The state of the [`Daemon`] must implement [`Default`]. + /// If your state does not implement [`Default`], use [`run_with`] + /// instead. + /// + /// [`run_with`]: Self::run_with + pub fn run(self) -> Result + where + Self: 'static, + P::State: Default, + { + self.run_with(P::State::default) + } + + /// Runs the [`Daemon`] with a closure that creates the initial state. + pub fn run_with<I>(self, initialize: I) -> Result + where + Self: 'static, + I: Fn() -> P::State + Clone + 'static, + { + self.raw.run_with(self.settings, None, initialize) + } + + /// Sets the [`Settings`] that will be used to run the [`Daemon`]. + pub fn settings(self, settings: Settings) -> Self { + Self { settings, ..self } + } + + /// Sets the [`Settings::antialiasing`] of the [`Daemon`]. + pub fn antialiasing(self, antialiasing: bool) -> Self { + Self { + settings: Settings { + antialiasing, + ..self.settings + }, + ..self + } + } + + /// Sets the default [`Font`] of the [`Daemon`]. + pub fn default_font(self, default_font: Font) -> Self { + Self { + settings: Settings { + default_font, + ..self.settings + }, + ..self + } + } + + /// Adds a font to the list of fonts that will be loaded at the start of the [`Daemon`]. + pub fn font(mut self, font: impl Into<Cow<'static, [u8]>>) -> Self { + self.settings.fonts.push(font.into()); + self + } + + /// Sets the [`Title`] of the [`Daemon`]. + pub(crate) fn title( + self, + title: impl Title<P::State>, + ) -> Daemon< + impl Program<State = P::State, Message = P::Message, Theme = P::Theme>, + > { + Daemon { + raw: program::with_title(self.raw, move |state, window| { + title.title(state, window) + }), + settings: self.settings, + } + } + + /// Runs the [`Task`] produced by the closure at startup. + pub fn load( + self, + f: impl Fn() -> Task<P::Message>, + ) -> Daemon< + impl Program<State = P::State, Message = P::Message, Theme = P::Theme>, + > { + Daemon { + raw: program::with_load(self.raw, f), + settings: self.settings, + } + } + + /// Sets the subscription logic of the [`Daemon`]. + pub fn subscription( + self, + f: impl Fn(&P::State) -> Subscription<P::Message>, + ) -> Daemon< + impl Program<State = P::State, Message = P::Message, Theme = P::Theme>, + > { + Daemon { + raw: program::with_subscription(self.raw, f), + settings: self.settings, + } + } + + /// Sets the theme logic of the [`Daemon`]. + pub fn theme( + self, + f: impl Fn(&P::State, window::Id) -> P::Theme, + ) -> Daemon< + impl Program<State = P::State, Message = P::Message, Theme = P::Theme>, + > { + Daemon { + raw: program::with_theme(self.raw, f), + settings: self.settings, + } + } + + /// Sets the style logic of the [`Daemon`]. + pub fn style( + self, + f: impl Fn(&P::State, &P::Theme) -> Appearance, + ) -> Daemon< + impl Program<State = P::State, Message = P::Message, Theme = P::Theme>, + > { + Daemon { + raw: program::with_style(self.raw, f), + settings: self.settings, + } + } + + /// Sets the scale factor of the [`Daemon`]. + pub fn scale_factor( + self, + f: impl Fn(&P::State, window::Id) -> f64, + ) -> Daemon< + impl Program<State = P::State, Message = P::Message, Theme = P::Theme>, + > { + Daemon { + raw: program::with_scale_factor(self.raw, f), + settings: self.settings, + } + } +} + +/// The title logic of some [`Daemon`]. +/// +/// This trait is implemented both for `&static str` and +/// any closure `Fn(&State, window::Id) -> String`. +/// +/// This trait allows the [`daemon`] builder to take any of them. +pub trait Title<State> { + /// Produces the title of the [`Daemon`]. + fn title(&self, state: &State, window: window::Id) -> String; +} + +impl<State> Title<State> for &'static str { + fn title(&self, _state: &State, _window: window::Id) -> String { + self.to_string() + } +} + +impl<T, State> Title<State> for T +where + T: Fn(&State, window::Id) -> String, +{ + fn title(&self, state: &State, window: window::Id) -> String { + self(state, window) + } +} + +/// The view logic of some [`Daemon`]. +/// +/// This trait allows the [`daemon`] builder to take any closure that +/// returns any `Into<Element<'_, Message>>`. +pub trait View<'a, State, Message, Theme, Renderer> { + /// Produces the widget of the [`Daemon`]. + fn view( + &self, + state: &'a State, + window: window::Id, + ) -> impl Into<Element<'a, Message, Theme, Renderer>>; +} + +impl<'a, T, State, Message, Theme, Renderer, Widget> + View<'a, State, Message, Theme, Renderer> for T +where + T: Fn(&'a State, window::Id) -> Widget, + State: 'static, + Widget: Into<Element<'a, Message, Theme, Renderer>>, +{ + fn view( + &self, + state: &'a State, + window: window::Id, + ) -> impl Into<Element<'a, Message, Theme, Renderer>> { + self(state, window) + } +} diff --git a/src/lib.rs b/src/lib.rs index cf0bc7d732..957c7ecc78 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -158,11 +158,11 @@ //! 1. Draw the resulting user interface. //! //! # Usage -//! Use [`run`] or the [`program`] builder. +//! Use [`run`] or the [`application`] builder. //! //! [Elm]: https://elm-lang.org/ //! [The Elm Architecture]: https://guide.elm-lang.org/architecture/ -//! [`program`]: program() +//! [`application`]: application() #![doc( html_logo_url = "https://raw.githubusercontent.com/iced-rs/iced/9ab6923e943f784985e9ef9ca28b10278297225d/docs/logo.svg" )] @@ -179,10 +179,11 @@ pub use iced_futures::futures; #[cfg(feature = "highlighter")] pub use iced_highlighter as highlighter; -mod application; mod error; +mod program; -pub mod program; +pub mod application; +pub mod daemon; pub mod settings; pub mod time; pub mod window; @@ -190,9 +191,6 @@ pub mod window; #[cfg(feature = "advanced")] pub mod advanced; -#[cfg(feature = "multi-window")] -pub mod multi_window; - pub use crate::core::alignment; pub use crate::core::border; pub use crate::core::color; @@ -203,7 +201,7 @@ pub use crate::core::{ Length, Padding, Pixels, Point, Radians, Rectangle, Rotation, Shadow, Size, Theme, Transformation, Vector, }; -pub use crate::runtime::Task; +pub use crate::runtime::{exit, Task}; pub mod clipboard { //! Access the clipboard. @@ -308,11 +306,12 @@ pub mod widget { mod runtime {} } +pub use application::{application, Application}; +pub use daemon::{daemon, Daemon}; pub use error::Error; pub use event::Event; pub use executor::Executor; pub use font::Font; -pub use program::Program; pub use renderer::Renderer; pub use settings::Settings; pub use subscription::Subscription; @@ -327,13 +326,13 @@ pub type Element< Renderer = crate::Renderer, > = crate::core::Element<'a, Message, Theme, Renderer>; -/// The result of running a [`Program`]. +/// The result of running an iced program. pub type Result = std::result::Result<(), Error>; /// Runs a basic iced application with default [`Settings`] given its title, /// update, and view logic. /// -/// This is equivalent to chaining [`program`] with [`Program::run`]. +/// This is equivalent to chaining [`application()`] with [`Application::run`]. /// /// [`program`]: program() /// @@ -364,9 +363,10 @@ pub type Result = std::result::Result<(), Error>; /// } /// ``` pub fn run<State, Message, Theme, Renderer>( - title: impl program::Title<State> + 'static, - update: impl program::Update<State, Message> + 'static, - view: impl for<'a> program::View<'a, State, Message, Theme, Renderer> + 'static, + title: impl application::Title<State> + 'static, + update: impl application::Update<State, Message> + 'static, + view: impl for<'a> application::View<'a, State, Message, Theme, Renderer> + + 'static, ) -> Result where State: Default + 'static, @@ -374,8 +374,5 @@ where Theme: Default + program::DefaultStyle + 'static, Renderer: program::Renderer + 'static, { - program(title, update, view).run() + application(title, update, view).run() } - -#[doc(inline)] -pub use program::program; diff --git a/src/multi_window.rs b/src/multi_window.rs deleted file mode 100644 index 4900bb85f5..0000000000 --- a/src/multi_window.rs +++ /dev/null @@ -1,254 +0,0 @@ -//! Leverage multi-window support in your application. -use crate::window; -use crate::{Element, Executor, Settings, Subscription, Task}; - -pub use crate::application::{Appearance, DefaultStyle}; - -/// An interactive cross-platform multi-window application. -/// -/// This trait is the main entrypoint of Iced. Once implemented, you can run -/// your GUI application by simply calling [`run`](#method.run). -/// -/// - On native platforms, it will run in its own windows. -/// - On the web, it will take control of the `<title>` and the `<body>` of the -/// document and display only the contents of the `window::Id::MAIN` window. -/// -/// An [`Application`] can execute asynchronous actions by returning a -/// [`Task`] in some of its methods. -/// -/// When using an [`Application`] with the `debug` feature enabled, a debug view -/// can be toggled by pressing `F12`. -/// -/// # Examples -/// See the `examples/multi-window` example to see this multi-window `Application` trait in action. -/// -/// ## A simple "Hello, world!" -/// -/// If you just want to get started, here is a simple [`Application`] that -/// says "Hello, world!": -/// -/// ```no_run -/// use iced::{executor, window}; -/// use iced::{Task, Element, Settings, Theme}; -/// use iced::multi_window::{self, Application}; -/// -/// pub fn main() -> iced::Result { -/// Hello::run(Settings::default()) -/// } -/// -/// struct Hello; -/// -/// impl multi_window::Application for Hello { -/// type Executor = executor::Default; -/// type Flags = (); -/// type Message = (); -/// type Theme = Theme; -/// -/// fn new(_flags: ()) -> (Hello, Task<Self::Message>) { -/// (Hello, Task::none()) -/// } -/// -/// fn title(&self, _window: window::Id) -> String { -/// String::from("A cool application") -/// } -/// -/// fn update(&mut self, _message: Self::Message) -> Task<Self::Message> { -/// Task::none() -/// } -/// -/// fn view(&self, _window: window::Id) -> Element<Self::Message> { -/// "Hello, world!".into() -/// } -/// } -/// ``` -/// -/// [`Sandbox`]: crate::Sandbox -pub trait Application: Sized -where - Self::Theme: DefaultStyle, -{ - /// The [`Executor`] that will run commands and subscriptions. - /// - /// The [default executor] can be a good starting point! - /// - /// [`Executor`]: Self::Executor - /// [default executor]: crate::executor::Default - type Executor: Executor; - - /// The type of __messages__ your [`Application`] will produce. - type Message: std::fmt::Debug + Send; - - /// The theme of your [`Application`]. - type Theme: Default; - - /// The data needed to initialize your [`Application`]. - type Flags; - - /// Initializes the [`Application`] with the flags provided to - /// [`run`] as part of the [`Settings`]. - /// - /// Here is where you should return the initial state of your app. - /// - /// 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, Task<Self::Message>); - - /// Returns the current title of the `window` of the [`Application`]. - /// - /// This title can be dynamic! The runtime will automatically update the - /// title of your window when necessary. - fn title(&self, window: window::Id) -> String; - - /// Handles a __message__ and updates the state of the [`Application`]. - /// - /// This is where you define your __update logic__. All the __messages__, - /// produced by either user interactions or commands, will be handled by - /// this method. - /// - /// Any [`Task`] returned will be executed immediately in the background. - fn update(&mut self, message: Self::Message) -> Task<Self::Message>; - - /// Returns the widgets to display in the `window` of the [`Application`]. - /// - /// These widgets can produce __messages__ based on user interaction. - fn view( - &self, - window: window::Id, - ) -> Element<'_, Self::Message, Self::Theme, crate::Renderer>; - - /// Returns the current [`Theme`] of the `window` of the [`Application`]. - /// - /// [`Theme`]: Self::Theme - #[allow(unused_variables)] - fn theme(&self, window: window::Id) -> Self::Theme { - Self::Theme::default() - } - - /// Returns the current `Style` of the [`Theme`]. - /// - /// [`Theme`]: Self::Theme - fn style(&self, theme: &Self::Theme) -> Appearance { - Self::Theme::default_style(theme) - } - - /// Returns the event [`Subscription`] for the current state of the - /// application. - /// - /// A [`Subscription`] will be kept alive as long as you keep returning it, - /// and the __messages__ produced will be handled by - /// [`update`](#tymethod.update). - /// - /// By default, this method returns an empty [`Subscription`]. - fn subscription(&self) -> Subscription<Self::Message> { - Subscription::none() - } - - /// Returns the scale factor of the `window` of the [`Application`]. - /// - /// It can be used to dynamically control the size of the UI at runtime - /// (i.e. zooming). - /// - /// For instance, a scale factor of `2.0` will make widgets twice as big, - /// while a scale factor of `0.5` will shrink them to half their size. - /// - /// By default, it returns `1.0`. - #[allow(unused_variables)] - fn scale_factor(&self, window: window::Id) -> f64 { - 1.0 - } - - /// Runs the multi-window [`Application`]. - /// - /// On native platforms, this method will take control of the current thread - /// until the [`Application`] exits. - /// - /// On the web platform, this method __will NOT return__ unless there is an - /// [`Error`] during startup. - /// - /// [`Error`]: crate::Error - fn run(settings: Settings<Self::Flags>) -> crate::Result - where - Self: 'static, - { - #[allow(clippy::needless_update)] - let renderer_settings = crate::graphics::Settings { - default_font: settings.default_font, - default_text_size: settings.default_text_size, - antialiasing: if settings.antialiasing { - Some(crate::graphics::Antialiasing::MSAAx4) - } else { - None - }, - ..crate::graphics::Settings::default() - }; - - Ok(crate::shell::multi_window::run::< - Instance<Self>, - Self::Executor, - crate::renderer::Compositor, - >(settings.into(), renderer_settings)?) - } -} - -struct Instance<A>(A) -where - A: Application, - A::Theme: DefaultStyle; - -impl<A> crate::runtime::multi_window::Program for Instance<A> -where - A: Application, - A::Theme: DefaultStyle, -{ - type Message = A::Message; - type Theme = A::Theme; - type Renderer = crate::Renderer; - - fn update(&mut self, message: Self::Message) -> Task<Self::Message> { - self.0.update(message) - } - - fn view( - &self, - window: window::Id, - ) -> Element<'_, Self::Message, Self::Theme, Self::Renderer> { - self.0.view(window) - } -} - -impl<A> crate::shell::multi_window::Application for Instance<A> -where - A: Application, - A::Theme: DefaultStyle, -{ - type Flags = A::Flags; - - fn new(flags: Self::Flags) -> (Self, Task<A::Message>) { - let (app, command) = A::new(flags); - - (Instance(app), command) - } - - fn title(&self, window: window::Id) -> String { - self.0.title(window) - } - - fn theme(&self, window: window::Id) -> A::Theme { - self.0.theme(window) - } - - fn style(&self, theme: &Self::Theme) -> Appearance { - self.0.style(theme) - } - - fn subscription(&self) -> Subscription<Self::Message> { - self.0.subscription() - } - - fn scale_factor(&self, window: window::Id) -> f64 { - self.0.scale_factor(window) - } -} diff --git a/src/program.rs b/src/program.rs index ea6b0e8e03..3f9d2d0c50 100644 --- a/src/program.rs +++ b/src/program.rs @@ -1,194 +1,108 @@ -//! Create and run iced applications step by step. -//! -//! # Example -//! ```no_run -//! use iced::widget::{button, column, text, Column}; -//! use iced::Theme; -//! -//! pub fn main() -> iced::Result { -//! iced::program("A counter", update, view) -//! .theme(|_| Theme::Dark) -//! .centered() -//! .run() -//! } -//! -//! #[derive(Debug, Clone)] -//! enum Message { -//! Increment, -//! } -//! -//! fn update(value: &mut u64, message: Message) { -//! match message { -//! Message::Increment => *value += 1, -//! } -//! } -//! -//! fn view(value: &u64) -> Column<Message> { -//! column![ -//! text(value), -//! button("+").on_press(Message::Increment), -//! ] -//! } -//! ``` -use crate::application::Application; use crate::core::text; -use crate::executor::{self, Executor}; use crate::graphics::compositor; +use crate::shell; use crate::window; -use crate::{Element, Font, Result, Settings, Size, Subscription, Task}; +use crate::{Element, Executor, Result, Settings, Subscription, Task}; -pub use crate::application::{Appearance, DefaultStyle}; +pub use crate::shell::program::{Appearance, DefaultStyle}; -use std::borrow::Cow; - -/// Creates an iced [`Program`] given its title, update, and view logic. -/// -/// # Example -/// ```no_run -/// use iced::widget::{button, column, text, Column}; -/// -/// pub fn main() -> iced::Result { -/// iced::program("A counter", update, view).run() -/// } -/// -/// #[derive(Debug, Clone)] -/// enum Message { -/// Increment, -/// } -/// -/// fn update(value: &mut u64, message: Message) { -/// match message { -/// Message::Increment => *value += 1, -/// } -/// } +/// The internal definition of a [`Program`]. /// -/// fn view(value: &u64) -> Column<Message> { -/// column![ -/// text(value), -/// button("+").on_press(Message::Increment), -/// ] -/// } -/// ``` -pub fn program<State, Message, Theme, Renderer>( - title: impl Title<State>, - update: impl Update<State, Message>, - view: impl for<'a> self::View<'a, State, Message, Theme, Renderer>, -) -> Program<impl Definition<State = State, Message = Message, Theme = Theme>> -where - State: 'static, - Message: Send + std::fmt::Debug + 'static, - Theme: Default + DefaultStyle, - Renderer: self::Renderer, -{ - use std::marker::PhantomData; - - struct Application<State, Message, Theme, Renderer, Update, View> { - update: Update, - view: View, - _state: PhantomData<State>, - _message: PhantomData<Message>, - _theme: PhantomData<Theme>, - _renderer: PhantomData<Renderer>, - } +/// You should not need to implement this trait directly. Instead, use the +/// methods available in the [`Program`] struct. +#[allow(missing_docs)] +pub trait Program: Sized { + /// The state of the program. + type State; - impl<State, Message, Theme, Renderer, Update, View> Definition - for Application<State, Message, Theme, Renderer, Update, View> - where - Message: Send + std::fmt::Debug + 'static, - Theme: Default + DefaultStyle, - Renderer: self::Renderer, - Update: self::Update<State, Message>, - View: for<'a> self::View<'a, State, Message, Theme, Renderer>, - { - type State = State; - type Message = Message; - type Theme = Theme; - type Renderer = Renderer; - type Executor = executor::Default; + /// The message of the program. + type Message: Send + std::fmt::Debug + 'static; - fn load(&self) -> Task<Self::Message> { - Task::none() - } + /// The theme of the program. + type Theme: Default + DefaultStyle; - fn update( - &self, - state: &mut Self::State, - message: Self::Message, - ) -> Task<Self::Message> { - self.update.update(state, message).into() - } + /// The renderer of the program. + type Renderer: Renderer; - fn view<'a>( - &self, - state: &'a Self::State, - ) -> Element<'a, Self::Message, Self::Theme, Self::Renderer> { - self.view.view(state).into() - } + /// The executor of the program. + type Executor: Executor; + + fn load(&self) -> Task<Self::Message>; + + fn update( + &self, + state: &mut Self::State, + message: Self::Message, + ) -> Task<Self::Message>; + + fn view<'a>( + &self, + state: &'a Self::State, + window: window::Id, + ) -> Element<'a, Self::Message, Self::Theme, Self::Renderer>; + + fn title(&self, _state: &Self::State, _window: window::Id) -> String { + String::from("A cool iced application!") } - Program { - raw: Application { - update, - view, - _state: PhantomData, - _message: PhantomData, - _theme: PhantomData, - _renderer: PhantomData, - }, - settings: Settings::default(), + fn subscription( + &self, + _state: &Self::State, + ) -> Subscription<Self::Message> { + Subscription::none() } - .title(title) -} -/// The underlying definition and configuration of an iced application. -/// -/// You can use this API to create and run iced applications -/// step by step—without coupling your logic to a trait -/// or a specific type. -/// -/// You can create a [`Program`] with the [`program`] helper. -/// -/// [`run`]: Program::run -#[derive(Debug)] -pub struct Program<P: Definition> { - raw: P, - settings: Settings, -} + fn theme(&self, _state: &Self::State, _window: window::Id) -> Self::Theme { + Self::Theme::default() + } + + fn style(&self, _state: &Self::State, theme: &Self::Theme) -> Appearance { + DefaultStyle::default_style(theme) + } -impl<P: Definition> Program<P> { - /// Runs the underlying [`Application`] of the [`Program`]. + fn scale_factor(&self, _state: &Self::State, _window: window::Id) -> f64 { + 1.0 + } + + /// Runs the [`Program`]. /// /// The state of the [`Program`] must implement [`Default`]. /// If your state does not implement [`Default`], use [`run_with`] /// instead. /// /// [`run_with`]: Self::run_with - pub fn run(self) -> Result + fn run( + self, + settings: Settings, + window_settings: Option<window::Settings>, + ) -> Result where Self: 'static, - P::State: Default, + Self::State: Default, { - self.run_with(P::State::default) + self.run_with(settings, window_settings, Self::State::default) } - /// Runs the underlying [`Application`] of the [`Program`] with a - /// closure that creates the initial state. - pub fn run_with( + /// Runs the [`Program`] with the given [`Settings`] and a closure that creates the initial state. + fn run_with<I>( self, - initialize: impl Fn() -> P::State + Clone + 'static, + settings: Settings, + window_settings: Option<window::Settings>, + initialize: I, ) -> Result where Self: 'static, + I: Fn() -> Self::State + Clone + 'static, { use std::marker::PhantomData; - struct Instance<P: Definition, I> { + struct Instance<P: Program, I> { program: P, state: P::State, _initialize: PhantomData<I>, } - impl<P: Definition, I: Fn() -> P::State> Application for Instance<P, I> { + impl<P: Program, I: Fn() -> P::State> shell::Program for Instance<P, I> { type Message = P::Message; type Theme = P::Theme; type Renderer = P::Renderer; @@ -211,8 +125,8 @@ impl<P: Definition> Program<P> { ) } - fn title(&self) -> String { - self.program.title(&self.state) + fn title(&self, window: window::Id) -> String { + self.program.title(&self.state, window) } fn update( @@ -224,259 +138,73 @@ impl<P: Definition> Program<P> { fn view( &self, + window: window::Id, ) -> crate::Element<'_, Self::Message, Self::Theme, Self::Renderer> { - self.program.view(&self.state) + self.program.view(&self.state, window) } fn subscription(&self) -> Subscription<Self::Message> { self.program.subscription(&self.state) } - fn theme(&self) -> Self::Theme { - self.program.theme(&self.state) + fn theme(&self, window: window::Id) -> Self::Theme { + self.program.theme(&self.state, window) } fn style(&self, theme: &Self::Theme) -> Appearance { self.program.style(&self.state, theme) } - } - let Self { raw, settings } = self; + fn scale_factor(&self, window: window::Id) -> f64 { + self.program.scale_factor(&self.state, window) + } + } - Instance::run(Settings { - flags: (raw, initialize), - id: settings.id, - window: settings.window, - fonts: settings.fonts, + #[allow(clippy::needless_update)] + let renderer_settings = crate::graphics::Settings { default_font: settings.default_font, default_text_size: settings.default_text_size, - antialiasing: settings.antialiasing, - }) - } - - /// Sets the [`Settings`] that will be used to run the [`Program`]. - pub fn settings(self, settings: Settings) -> Self { - Self { settings, ..self } - } - - /// Sets the [`Settings::antialiasing`] of the [`Program`]. - pub fn antialiasing(self, antialiasing: bool) -> Self { - Self { - settings: Settings { - antialiasing, - ..self.settings - }, - ..self - } - } - - /// Sets the default [`Font`] of the [`Program`]. - pub fn default_font(self, default_font: Font) -> Self { - Self { - settings: Settings { - default_font, - ..self.settings - }, - ..self - } - } - - /// Adds a font to the list of fonts that will be loaded at the start of the [`Program`]. - pub fn font(mut self, font: impl Into<Cow<'static, [u8]>>) -> Self { - self.settings.fonts.push(font.into()); - self - } - - /// Sets the [`window::Settings::position`] to [`window::Position::Centered`] in the [`Program`]. - pub fn centered(self) -> Self { - Self { - settings: Settings { - window: window::Settings { - position: window::Position::Centered, - ..self.settings.window - }, - ..self.settings - }, - ..self - } - } - - /// Sets the [`window::Settings::exit_on_close_request`] of the [`Program`]. - pub fn exit_on_close_request(self, exit_on_close_request: bool) -> Self { - Self { - settings: Settings { - window: window::Settings { - exit_on_close_request, - ..self.settings.window - }, - ..self.settings - }, - ..self - } - } - - /// Sets the [`window::Settings::size`] of the [`Program`]. - pub fn window_size(self, size: impl Into<Size>) -> Self { - Self { - settings: Settings { - window: window::Settings { - size: size.into(), - ..self.settings.window - }, - ..self.settings - }, - ..self - } - } - - /// Sets the [`window::Settings::transparent`] of the [`Program`]. - pub fn transparent(self, transparent: bool) -> Self { - Self { - settings: Settings { - window: window::Settings { - transparent, - ..self.settings.window - }, - ..self.settings + antialiasing: if settings.antialiasing { + Some(crate::graphics::Antialiasing::MSAAx4) + } else { + None }, - ..self - } - } - - /// Sets the [`Title`] of the [`Program`]. - pub(crate) fn title( - self, - title: impl Title<P::State>, - ) -> Program< - impl Definition<State = P::State, Message = P::Message, Theme = P::Theme>, - > { - Program { - raw: with_title(self.raw, title), - settings: self.settings, - } - } - - /// Runs the [`Task`] produced by the closure at startup. - pub fn load( - self, - f: impl Fn() -> Task<P::Message>, - ) -> Program< - impl Definition<State = P::State, Message = P::Message, Theme = P::Theme>, - > { - Program { - raw: with_load(self.raw, f), - settings: self.settings, - } - } - - /// Sets the subscription logic of the [`Program`]. - pub fn subscription( - self, - f: impl Fn(&P::State) -> Subscription<P::Message>, - ) -> Program< - impl Definition<State = P::State, Message = P::Message, Theme = P::Theme>, - > { - Program { - raw: with_subscription(self.raw, f), - settings: self.settings, - } - } - - /// Sets the theme logic of the [`Program`]. - pub fn theme( - self, - f: impl Fn(&P::State) -> P::Theme, - ) -> Program< - impl Definition<State = P::State, Message = P::Message, Theme = P::Theme>, - > { - Program { - raw: with_theme(self.raw, f), - settings: self.settings, - } - } - - /// Sets the style logic of the [`Program`]. - pub fn style( - self, - f: impl Fn(&P::State, &P::Theme) -> Appearance, - ) -> Program< - impl Definition<State = P::State, Message = P::Message, Theme = P::Theme>, - > { - Program { - raw: with_style(self.raw, f), - settings: self.settings, - } - } -} - -/// The internal definition of a [`Program`]. -/// -/// You should not need to implement this trait directly. Instead, use the -/// methods available in the [`Program`] struct. -#[allow(missing_docs)] -pub trait Definition: Sized { - /// The state of the program. - type State; - - /// The message of the program. - type Message: Send + std::fmt::Debug + 'static; - - /// The theme of the program. - type Theme: Default + DefaultStyle; - - /// The renderer of the program. - type Renderer: Renderer; - - /// The executor of the program. - type Executor: Executor; - - fn load(&self) -> Task<Self::Message>; - - fn update( - &self, - state: &mut Self::State, - message: Self::Message, - ) -> Task<Self::Message>; - - fn view<'a>( - &self, - state: &'a Self::State, - ) -> Element<'a, Self::Message, Self::Theme, Self::Renderer>; - - fn title(&self, _state: &Self::State) -> String { - String::from("A cool iced application!") - } - - fn subscription( - &self, - _state: &Self::State, - ) -> Subscription<Self::Message> { - Subscription::none() - } - - fn theme(&self, _state: &Self::State) -> Self::Theme { - Self::Theme::default() - } - - fn style(&self, _state: &Self::State, theme: &Self::Theme) -> Appearance { - DefaultStyle::default_style(theme) + ..crate::graphics::Settings::default() + }; + + Ok(shell::program::run::< + Instance<Self, I>, + <Self::Renderer as compositor::Default>::Compositor, + >( + Settings { + id: settings.id, + fonts: settings.fonts, + default_font: settings.default_font, + default_text_size: settings.default_text_size, + antialiasing: settings.antialiasing, + } + .into(), + renderer_settings, + window_settings, + (self, initialize), + )?) } } -fn with_title<P: Definition>( +pub fn with_title<P: Program>( program: P, - title: impl Title<P::State>, -) -> impl Definition<State = P::State, Message = P::Message, Theme = P::Theme> { + title: impl Fn(&P::State, window::Id) -> String, +) -> impl Program<State = P::State, Message = P::Message, Theme = P::Theme> { struct WithTitle<P, Title> { program: P, title: Title, } - impl<P, Title> Definition for WithTitle<P, Title> + impl<P, Title> Program for WithTitle<P, Title> where - P: Definition, - Title: self::Title<P::State>, + P: Program, + Title: Fn(&P::State, window::Id) -> String, { type State = P::State; type Message = P::Message; @@ -488,8 +216,8 @@ fn with_title<P: Definition>( self.program.load() } - fn title(&self, state: &Self::State) -> String { - self.title.title(state) + fn title(&self, state: &Self::State, window: window::Id) -> String { + (self.title)(state, window) } fn update( @@ -503,12 +231,17 @@ fn with_title<P: Definition>( fn view<'a>( &self, state: &'a Self::State, + window: window::Id, ) -> Element<'a, Self::Message, Self::Theme, Self::Renderer> { - self.program.view(state) + self.program.view(state, window) } - fn theme(&self, state: &Self::State) -> Self::Theme { - self.program.theme(state) + fn theme( + &self, + state: &Self::State, + window: window::Id, + ) -> Self::Theme { + self.program.theme(state, window) } fn subscription( @@ -525,21 +258,25 @@ fn with_title<P: Definition>( ) -> Appearance { self.program.style(state, theme) } + + fn scale_factor(&self, state: &Self::State, window: window::Id) -> f64 { + self.program.scale_factor(state, window) + } } WithTitle { program, title } } -fn with_load<P: Definition>( +pub fn with_load<P: Program>( program: P, f: impl Fn() -> Task<P::Message>, -) -> impl Definition<State = P::State, Message = P::Message, Theme = P::Theme> { +) -> impl Program<State = P::State, Message = P::Message, Theme = P::Theme> { struct WithLoad<P, F> { program: P, load: F, } - impl<P: Definition, F> Definition for WithLoad<P, F> + impl<P: Program, F> Program for WithLoad<P, F> where F: Fn() -> Task<P::Message>, { @@ -547,7 +284,7 @@ fn with_load<P: Definition>( type Message = P::Message; type Theme = P::Theme; type Renderer = P::Renderer; - type Executor = executor::Default; + type Executor = P::Executor; fn load(&self) -> Task<Self::Message> { Task::batch([self.program.load(), (self.load)()]) @@ -564,12 +301,13 @@ fn with_load<P: Definition>( fn view<'a>( &self, state: &'a Self::State, + window: window::Id, ) -> Element<'a, Self::Message, Self::Theme, Self::Renderer> { - self.program.view(state) + self.program.view(state, window) } - fn title(&self, state: &Self::State) -> String { - self.program.title(state) + fn title(&self, state: &Self::State, window: window::Id) -> String { + self.program.title(state, window) } fn subscription( @@ -579,8 +317,12 @@ fn with_load<P: Definition>( self.program.subscription(state) } - fn theme(&self, state: &Self::State) -> Self::Theme { - self.program.theme(state) + fn theme( + &self, + state: &Self::State, + window: window::Id, + ) -> Self::Theme { + self.program.theme(state, window) } fn style( @@ -590,21 +332,25 @@ fn with_load<P: Definition>( ) -> Appearance { self.program.style(state, theme) } + + fn scale_factor(&self, state: &Self::State, window: window::Id) -> f64 { + self.program.scale_factor(state, window) + } } WithLoad { program, load: f } } -fn with_subscription<P: Definition>( +pub fn with_subscription<P: Program>( program: P, f: impl Fn(&P::State) -> Subscription<P::Message>, -) -> impl Definition<State = P::State, Message = P::Message, Theme = P::Theme> { +) -> impl Program<State = P::State, Message = P::Message, Theme = P::Theme> { struct WithSubscription<P, F> { program: P, subscription: F, } - impl<P: Definition, F> Definition for WithSubscription<P, F> + impl<P: Program, F> Program for WithSubscription<P, F> where F: Fn(&P::State) -> Subscription<P::Message>, { @@ -612,7 +358,7 @@ fn with_subscription<P: Definition>( type Message = P::Message; type Theme = P::Theme; type Renderer = P::Renderer; - type Executor = executor::Default; + type Executor = P::Executor; fn subscription( &self, @@ -636,16 +382,21 @@ fn with_subscription<P: Definition>( fn view<'a>( &self, state: &'a Self::State, + window: window::Id, ) -> Element<'a, Self::Message, Self::Theme, Self::Renderer> { - self.program.view(state) + self.program.view(state, window) } - fn title(&self, state: &Self::State) -> String { - self.program.title(state) + fn title(&self, state: &Self::State, window: window::Id) -> String { + self.program.title(state, window) } - fn theme(&self, state: &Self::State) -> Self::Theme { - self.program.theme(state) + fn theme( + &self, + state: &Self::State, + window: window::Id, + ) -> Self::Theme { + self.program.theme(state, window) } fn style( @@ -655,6 +406,10 @@ fn with_subscription<P: Definition>( ) -> Appearance { self.program.style(state, theme) } + + fn scale_factor(&self, state: &Self::State, window: window::Id) -> f64 { + self.program.scale_factor(state, window) + } } WithSubscription { @@ -663,18 +418,18 @@ fn with_subscription<P: Definition>( } } -fn with_theme<P: Definition>( +pub fn with_theme<P: Program>( program: P, - f: impl Fn(&P::State) -> P::Theme, -) -> impl Definition<State = P::State, Message = P::Message, Theme = P::Theme> { + f: impl Fn(&P::State, window::Id) -> P::Theme, +) -> impl Program<State = P::State, Message = P::Message, Theme = P::Theme> { struct WithTheme<P, F> { program: P, theme: F, } - impl<P: Definition, F> Definition for WithTheme<P, F> + impl<P: Program, F> Program for WithTheme<P, F> where - F: Fn(&P::State) -> P::Theme, + F: Fn(&P::State, window::Id) -> P::Theme, { type State = P::State; type Message = P::Message; @@ -682,16 +437,20 @@ fn with_theme<P: Definition>( type Renderer = P::Renderer; type Executor = P::Executor; - fn theme(&self, state: &Self::State) -> Self::Theme { - (self.theme)(state) + fn theme( + &self, + state: &Self::State, + window: window::Id, + ) -> Self::Theme { + (self.theme)(state, window) } fn load(&self) -> Task<Self::Message> { self.program.load() } - fn title(&self, state: &Self::State) -> String { - self.program.title(state) + fn title(&self, state: &Self::State, window: window::Id) -> String { + self.program.title(state, window) } fn update( @@ -705,8 +464,9 @@ fn with_theme<P: Definition>( fn view<'a>( &self, state: &'a Self::State, + window: window::Id, ) -> Element<'a, Self::Message, Self::Theme, Self::Renderer> { - self.program.view(state) + self.program.view(state, window) } fn subscription( @@ -723,21 +483,25 @@ fn with_theme<P: Definition>( ) -> Appearance { self.program.style(state, theme) } + + fn scale_factor(&self, state: &Self::State, window: window::Id) -> f64 { + self.program.scale_factor(state, window) + } } WithTheme { program, theme: f } } -fn with_style<P: Definition>( +pub fn with_style<P: Program>( program: P, f: impl Fn(&P::State, &P::Theme) -> Appearance, -) -> impl Definition<State = P::State, Message = P::Message, Theme = P::Theme> { +) -> impl Program<State = P::State, Message = P::Message, Theme = P::Theme> { struct WithStyle<P, F> { program: P, style: F, } - impl<P: Definition, F> Definition for WithStyle<P, F> + impl<P: Program, F> Program for WithStyle<P, F> where F: Fn(&P::State, &P::Theme) -> Appearance, { @@ -759,8 +523,8 @@ fn with_style<P: Definition>( self.program.load() } - fn title(&self, state: &Self::State) -> String { - self.program.title(state) + fn title(&self, state: &Self::State, window: window::Id) -> String { + self.program.title(state, window) } fn update( @@ -774,8 +538,9 @@ fn with_style<P: Definition>( fn view<'a>( &self, state: &'a Self::State, + window: window::Id, ) -> Element<'a, Self::Message, Self::Theme, Self::Renderer> { - self.program.view(state) + self.program.view(state, window) } fn subscription( @@ -785,91 +550,96 @@ fn with_style<P: Definition>( self.program.subscription(state) } - fn theme(&self, state: &Self::State) -> Self::Theme { - self.program.theme(state) + fn theme( + &self, + state: &Self::State, + window: window::Id, + ) -> Self::Theme { + self.program.theme(state, window) + } + + fn scale_factor(&self, state: &Self::State, window: window::Id) -> f64 { + self.program.scale_factor(state, window) } } WithStyle { program, style: f } } -/// The title logic of some [`Program`]. -/// -/// This trait is implemented both for `&static str` and -/// any closure `Fn(&State) -> String`. -/// -/// This trait allows the [`program`] builder to take any of them. -pub trait Title<State> { - /// Produces the title of the [`Program`]. - fn title(&self, state: &State) -> String; -} - -impl<State> Title<State> for &'static str { - fn title(&self, _state: &State) -> String { - self.to_string() +pub fn with_scale_factor<P: Program>( + program: P, + f: impl Fn(&P::State, window::Id) -> f64, +) -> impl Program<State = P::State, Message = P::Message, Theme = P::Theme> { + struct WithScaleFactor<P, F> { + program: P, + scale_factor: F, } -} -impl<T, State> Title<State> for T -where - T: Fn(&State) -> String, -{ - fn title(&self, state: &State) -> String { - self(state) - } -} + impl<P: Program, F> Program for WithScaleFactor<P, F> + where + F: Fn(&P::State, window::Id) -> f64, + { + type State = P::State; + type Message = P::Message; + type Theme = P::Theme; + type Renderer = P::Renderer; + type Executor = P::Executor; -/// The update logic of some [`Program`]. -/// -/// This trait allows the [`program`] builder to take any closure that -/// returns any `Into<Task<Message>>`. -pub trait Update<State, Message> { - /// Processes the message and updates the state of the [`Program`]. - fn update( - &self, - state: &mut State, - message: Message, - ) -> impl Into<Task<Message>>; -} + fn load(&self) -> Task<Self::Message> { + self.program.load() + } -impl<T, State, Message, C> Update<State, Message> for T -where - T: Fn(&mut State, Message) -> C, - C: Into<Task<Message>>, -{ - fn update( - &self, - state: &mut State, - message: Message, - ) -> impl Into<Task<Message>> { - self(state, message) - } -} + fn title(&self, state: &Self::State, window: window::Id) -> String { + self.program.title(state, window) + } -/// The view logic of some [`Program`]. -/// -/// This trait allows the [`program`] builder to take any closure that -/// returns any `Into<Element<'_, Message>>`. -pub trait View<'a, State, Message, Theme, Renderer> { - /// Produces the widget of the [`Program`]. - fn view( - &self, - state: &'a State, - ) -> impl Into<Element<'a, Message, Theme, Renderer>>; -} + fn update( + &self, + state: &mut Self::State, + message: Self::Message, + ) -> Task<Self::Message> { + self.program.update(state, message) + } -impl<'a, T, State, Message, Theme, Renderer, Widget> - View<'a, State, Message, Theme, Renderer> for T -where - T: Fn(&'a State) -> Widget, - State: 'static, - Widget: Into<Element<'a, Message, Theme, Renderer>>, -{ - fn view( - &self, - state: &'a State, - ) -> impl Into<Element<'a, Message, Theme, Renderer>> { - self(state) + fn view<'a>( + &self, + state: &'a Self::State, + window: window::Id, + ) -> Element<'a, Self::Message, Self::Theme, Self::Renderer> { + self.program.view(state, window) + } + + fn subscription( + &self, + state: &Self::State, + ) -> Subscription<Self::Message> { + self.program.subscription(state) + } + + fn theme( + &self, + state: &Self::State, + window: window::Id, + ) -> Self::Theme { + self.program.theme(state, window) + } + + fn style( + &self, + state: &Self::State, + theme: &Self::Theme, + ) -> Appearance { + self.program.style(state, theme) + } + + fn scale_factor(&self, state: &Self::State, window: window::Id) -> f64 { + (self.scale_factor)(state, window) + } + } + + WithScaleFactor { + program, + scale_factor: f, } } diff --git a/src/settings.rs b/src/settings.rs index f794784119..ebac7a86de 100644 --- a/src/settings.rs +++ b/src/settings.rs @@ -1,30 +1,17 @@ //! Configure your application. -use crate::window; use crate::{Font, Pixels}; use std::borrow::Cow; -/// The settings of an iced [`Program`]. -/// -/// [`Program`]: crate::Program +/// The settings of an iced program. #[derive(Debug, Clone)] -pub struct Settings<Flags = ()> { +pub struct Settings { /// The identifier of the application. /// /// If provided, this identifier may be used to identify the application or /// communicate with it through the windowing system. pub id: Option<String>, - /// The window settings. - /// - /// They will be ignored on the Web. - pub window: window::Settings, - - /// The data needed to initialize the [`Program`]. - /// - /// [`Program`]: crate::Program - pub flags: Flags, - /// The fonts to load on boot. pub fonts: Vec<Cow<'static, [u8]>>, @@ -50,34 +37,10 @@ pub struct Settings<Flags = ()> { pub antialiasing: bool, } -impl<Flags> Settings<Flags> { - /// Initialize [`Program`] settings using the given data. - /// - /// [`Program`]: crate::Program - pub fn with_flags(flags: Flags) -> Self { - let default_settings = Settings::<()>::default(); - - Self { - flags, - id: default_settings.id, - window: default_settings.window, - fonts: default_settings.fonts, - default_font: default_settings.default_font, - default_text_size: default_settings.default_text_size, - antialiasing: default_settings.antialiasing, - } - } -} - -impl<Flags> Default for Settings<Flags> -where - Flags: Default, -{ +impl Default for Settings { fn default() -> Self { Self { id: None, - window: window::Settings::default(), - flags: Default::default(), fonts: Vec::new(), default_font: Font::default(), default_text_size: Pixels(16.0), @@ -86,12 +49,10 @@ where } } -impl<Flags> From<Settings<Flags>> for iced_winit::Settings<Flags> { - fn from(settings: Settings<Flags>) -> iced_winit::Settings<Flags> { +impl From<Settings> for iced_winit::Settings { + fn from(settings: Settings) -> iced_winit::Settings { iced_winit::Settings { id: settings.id, - window: settings.window, - flags: settings.flags, fonts: settings.fonts, } } diff --git a/winit/Cargo.toml b/winit/Cargo.toml index 6d3dddde8e..68368aa14e 100644 --- a/winit/Cargo.toml +++ b/winit/Cargo.toml @@ -17,7 +17,7 @@ workspace = true default = ["x11", "wayland", "wayland-dlopen", "wayland-csd-adwaita"] debug = ["iced_runtime/debug"] system = ["sysinfo"] -application = [] +program = [] x11 = ["winit/x11"] wayland = ["winit/wayland"] wayland-dlopen = ["winit/wayland-dlopen"] diff --git a/winit/src/application.rs b/winit/src/application.rs deleted file mode 100644 index a93878ead9..0000000000 --- a/winit/src/application.rs +++ /dev/null @@ -1,1082 +0,0 @@ -//! Create interactive, native cross-platform applications. -mod state; - -pub use state::State; - -use crate::conversion; -use crate::core; -use crate::core::mouse; -use crate::core::renderer; -use crate::core::time::Instant; -use crate::core::widget::operation; -use crate::core::window; -use crate::core::{Color, Event, Point, Size, Theme}; -use crate::futures::futures; -use crate::futures::subscription::{self, Subscription}; -use crate::futures::{Executor, Runtime}; -use crate::graphics; -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::{Action, Debug, Task}; -use crate::{Clipboard, Error, Proxy, Settings}; - -use futures::channel::mpsc; -use futures::channel::oneshot; - -use std::borrow::Cow; -use std::mem::ManuallyDrop; -use std::sync::Arc; - -/// An interactive, native cross-platform application. -/// -/// This trait is the main entrypoint of Iced. Once implemented, you can run -/// your GUI application by simply calling [`run`]. It will run in -/// its own window. -/// -/// An [`Application`] can execute asynchronous actions by returning a -/// [`Task`] in some of its methods. -/// -/// When using an [`Application`] with the `debug` feature enabled, a debug view -/// can be toggled by pressing `F12`. -pub trait Application: Program -where - Self::Theme: DefaultStyle, -{ - /// The data needed to initialize your [`Application`]. - type Flags; - - /// Initializes the [`Application`] with the flags provided to - /// [`run`] as part of the [`Settings`]. - /// - /// Here is where you should return the initial state of your app. - /// - /// 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, Task<Self::Message>); - - /// Returns the current title of the [`Application`]. - /// - /// This title can be dynamic! The runtime will automatically update the - /// title of your application when necessary. - fn title(&self) -> String; - - /// Returns the current `Theme` of the [`Application`]. - fn theme(&self) -> Self::Theme; - - /// Returns the `Style` variation of the `Theme`. - fn style(&self, theme: &Self::Theme) -> Appearance { - theme.default_style() - } - - /// Returns the event `Subscription` for the current state of the - /// application. - /// - /// The messages produced by the `Subscription` will be handled by - /// [`update`](#tymethod.update). - /// - /// A `Subscription` will be kept alive as long as you keep returning it! - /// - /// By default, it returns an empty subscription. - fn subscription(&self) -> Subscription<Self::Message> { - Subscription::none() - } - - /// Returns the scale factor of the [`Application`]. - /// - /// It can be used to dynamically control the size of the UI at runtime - /// (i.e. zooming). - /// - /// For instance, a scale factor of `2.0` will make widgets twice as big, - /// while a scale factor of `0.5` will shrink them to half their size. - /// - /// By default, it returns `1.0`. - fn scale_factor(&self) -> f64 { - 1.0 - } -} - -/// The appearance of an application. -#[derive(Debug, Clone, Copy, PartialEq)] -pub struct Appearance { - /// The background [`Color`] of the application. - pub background_color: Color, - - /// The default text [`Color`] of the application. - pub text_color: Color, -} - -/// The default style of an [`Application`]. -pub trait DefaultStyle { - /// Returns the default style of an [`Application`]. - fn default_style(&self) -> Appearance; -} - -impl DefaultStyle for Theme { - fn default_style(&self) -> Appearance { - default(self) - } -} - -/// The default [`Appearance`] of an [`Application`] with the built-in [`Theme`]. -pub fn default(theme: &Theme) -> Appearance { - let palette = theme.extended_palette(); - - Appearance { - background_color: palette.background.base.color, - text_color: palette.background.base.text, - } -} - -/// Runs an [`Application`] with an executor, compositor, and the provided -/// settings. -pub fn run<A, E, C>( - settings: Settings<A::Flags>, - graphics_settings: graphics::Settings, -) -> Result<(), Error> -where - A: Application + 'static, - E: Executor + 'static, - C: Compositor<Renderer = A::Renderer> + 'static, - A::Theme: DefaultStyle, -{ - use futures::task; - use futures::Future; - use winit::event_loop::EventLoop; - - let mut debug = Debug::new(); - debug.startup_started(); - - let event_loop = EventLoop::with_user_event() - .build() - .expect("Create event loop"); - - let (proxy, worker) = Proxy::new(event_loop.create_proxy()); - - let mut runtime = { - let executor = E::new().map_err(Error::ExecutorCreationFailed)?; - executor.spawn(worker); - - Runtime::new(executor, proxy.clone()) - }; - - 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(); - - let (boot_sender, boot_receiver) = oneshot::channel(); - let (event_sender, event_receiver) = mpsc::unbounded(); - let (control_sender, control_receiver) = mpsc::unbounded(); - - let instance = Box::pin(run_instance::<A, E, C>( - application, - runtime, - proxy, - debug, - boot_receiver, - event_receiver, - control_sender, - settings.fonts, - )); - - let context = task::Context::from_waker(task::noop_waker_ref()); - - struct Runner<Message: 'static, F, C> { - instance: std::pin::Pin<Box<F>>, - context: task::Context<'static>, - boot: Option<BootConfig<C>>, - sender: mpsc::UnboundedSender<winit::event::Event<Action<Message>>>, - receiver: mpsc::UnboundedReceiver<winit::event_loop::ControlFlow>, - error: Option<Error>, - #[cfg(target_arch = "wasm32")] - is_booted: std::rc::Rc<std::cell::RefCell<bool>>, - #[cfg(target_arch = "wasm32")] - queued_events: Vec<winit::event::Event<Action<Message>>>, - } - - struct BootConfig<C> { - sender: oneshot::Sender<Boot<C>>, - id: Option<String>, - title: String, - window_settings: window::Settings, - graphics_settings: graphics::Settings, - } - - let runner = Runner { - instance, - context, - boot: Some(BootConfig { - sender: boot_sender, - id, - title, - window_settings: settings.window, - graphics_settings, - }), - sender: event_sender, - receiver: control_receiver, - error: None, - #[cfg(target_arch = "wasm32")] - is_booted: std::rc::Rc::new(std::cell::RefCell::new(false)), - #[cfg(target_arch = "wasm32")] - queued_events: Vec::new(), - }; - - impl<Message, F, C> winit::application::ApplicationHandler<Action<Message>> - for Runner<Message, F, C> - where - F: Future<Output = ()>, - C: Compositor + 'static, - { - fn resumed(&mut self, event_loop: &winit::event_loop::ActiveEventLoop) { - let Some(BootConfig { - sender, - id, - title, - window_settings, - graphics_settings, - }) = self.boot.take() - else { - return; - }; - - let should_be_visible = window_settings.visible; - let exit_on_close_request = window_settings.exit_on_close_request; - - #[cfg(target_arch = "wasm32")] - let target = window_settings.platform_specific.target.clone(); - - let window_attributes = conversion::window_attributes( - window_settings, - &title, - event_loop.primary_monitor(), - id, - ) - .with_visible(false); - - log::debug!("Window attributes: {window_attributes:#?}"); - - let window = match event_loop.create_window(window_attributes) { - Ok(window) => Arc::new(window), - Err(error) => { - self.error = Some(Error::WindowCreationFailed(error)); - event_loop.exit(); - return; - } - }; - - let finish_boot = { - let window = window.clone(); - - async move { - let compositor = - C::new(graphics_settings, window.clone()).await?; - - sender - .send(Boot { - window, - compositor, - should_be_visible, - exit_on_close_request, - }) - .ok() - .expect("Send boot event"); - - Ok::<_, graphics::Error>(()) - } - }; - - #[cfg(not(target_arch = "wasm32"))] - if let Err(error) = futures::executor::block_on(finish_boot) { - self.error = Some(Error::GraphicsCreationFailed(error)); - event_loop.exit(); - } - - #[cfg(target_arch = "wasm32")] - { - use winit::platform::web::WindowExtWebSys; - - let canvas = window.canvas().expect("Get window canvas"); - let _ = canvas.set_attribute( - "style", - "display: block; width: 100%; height: 100%", - ); - - let window = web_sys::window().unwrap(); - let document = window.document().unwrap(); - let body = document.body().unwrap(); - - let target = target.and_then(|target| { - body.query_selector(&format!("#{target}")) - .ok() - .unwrap_or(None) - }); - - match target { - Some(node) => { - let _ = node.replace_with_with_node_1(&canvas).expect( - &format!("Could not replace #{}", node.id()), - ); - } - None => { - let _ = body - .append_child(&canvas) - .expect("Append canvas to HTML body"); - } - }; - - let is_booted = self.is_booted.clone(); - - wasm_bindgen_futures::spawn_local(async move { - finish_boot.await.expect("Finish boot!"); - - *is_booted.borrow_mut() = true; - }); - } - } - - fn new_events( - &mut self, - event_loop: &winit::event_loop::ActiveEventLoop, - cause: winit::event::StartCause, - ) { - if self.boot.is_some() { - return; - } - - self.process_event( - event_loop, - winit::event::Event::NewEvents(cause), - ); - } - - fn window_event( - &mut self, - event_loop: &winit::event_loop::ActiveEventLoop, - window_id: winit::window::WindowId, - event: winit::event::WindowEvent, - ) { - #[cfg(target_os = "windows")] - let is_move_or_resize = matches!( - event, - winit::event::WindowEvent::Resized(_) - | winit::event::WindowEvent::Moved(_) - ); - - self.process_event( - event_loop, - winit::event::Event::WindowEvent { window_id, event }, - ); - - // TODO: Remove when unnecessary - // On Windows, we emulate an `AboutToWait` event after every `Resized` event - // since the event loop does not resume during resize interaction. - // More details: https://github.com/rust-windowing/winit/issues/3272 - #[cfg(target_os = "windows")] - { - if is_move_or_resize { - self.process_event( - event_loop, - winit::event::Event::AboutToWait, - ); - } - } - } - - fn user_event( - &mut self, - event_loop: &winit::event_loop::ActiveEventLoop, - action: Action<Message>, - ) { - self.process_event( - event_loop, - winit::event::Event::UserEvent(action), - ); - } - - fn about_to_wait( - &mut self, - event_loop: &winit::event_loop::ActiveEventLoop, - ) { - self.process_event(event_loop, winit::event::Event::AboutToWait); - } - } - - impl<Message, F, C> Runner<Message, F, C> - where - F: Future<Output = ()>, - { - fn process_event( - &mut self, - event_loop: &winit::event_loop::ActiveEventLoop, - event: winit::event::Event<Action<Message>>, - ) { - // On Wasm, events may start being processed before the compositor - // boots up. We simply queue them and process them once ready. - #[cfg(target_arch = "wasm32")] - if !*self.is_booted.borrow() { - self.queued_events.push(event); - return; - } else if !self.queued_events.is_empty() { - let queued_events = std::mem::take(&mut self.queued_events); - - // This won't infinitely recurse, since we `mem::take` - for event in queued_events { - self.process_event(event_loop, event); - } - } - - if event_loop.exiting() { - return; - } - - self.sender.start_send(event).expect("Send event"); - - let poll = self.instance.as_mut().poll(&mut self.context); - - match poll { - task::Poll::Pending => { - if let Ok(Some(flow)) = self.receiver.try_next() { - event_loop.set_control_flow(flow); - } - } - task::Poll::Ready(_) => { - event_loop.exit(); - } - } - } - } - - #[cfg(not(target_arch = "wasm32"))] - { - let mut runner = runner; - let _ = event_loop.run_app(&mut runner); - - runner.error.map(Err).unwrap_or(Ok(())) - } - - #[cfg(target_arch = "wasm32")] - { - use winit::platform::web::EventLoopExtWebSys; - let _ = event_loop.spawn_app(runner); - - Ok(()) - } -} - -struct Boot<C> { - window: Arc<winit::window::Window>, - compositor: C, - should_be_visible: bool, - exit_on_close_request: bool, -} - -async fn run_instance<A, E, C>( - mut application: A, - mut runtime: Runtime<E, Proxy<A::Message>, Action<A::Message>>, - mut proxy: Proxy<A::Message>, - mut debug: Debug, - mut boot: oneshot::Receiver<Boot<C>>, - mut event_receiver: mpsc::UnboundedReceiver< - winit::event::Event<Action<A::Message>>, - >, - mut control_sender: mpsc::UnboundedSender<winit::event_loop::ControlFlow>, - fonts: Vec<Cow<'static, [u8]>>, -) where - A: Application + 'static, - E: Executor + 'static, - C: Compositor<Renderer = A::Renderer> + 'static, - A::Theme: DefaultStyle, -{ - use futures::stream::StreamExt; - use winit::event; - use winit::event_loop::ControlFlow; - - let Boot { - window, - mut compositor, - should_be_visible, - exit_on_close_request, - } = boot.try_recv().ok().flatten().expect("Receive boot"); - - let mut renderer = compositor.create_renderer(); - - for font in fonts { - compositor.load_font(font); - } - - let mut state = State::new(&application, &window); - let mut viewport_version = state.viewport_version(); - let physical_size = state.physical_size(); - - let mut clipboard = Clipboard::connect(&window); - let cache = user_interface::Cache::default(); - let mut surface = compositor.create_surface( - window.clone(), - physical_size.width, - physical_size.height, - ); - let mut should_exit = false; - - if should_be_visible { - window.set_visible(true); - } - - runtime.track( - application - .subscription() - .map(Action::Output) - .into_recipes(), - ); - - let mut user_interface = ManuallyDrop::new(build_user_interface( - &application, - cache, - &mut renderer, - state.logical_size(), - &mut debug, - )); - - let mut mouse_interaction = mouse::Interaction::default(); - let mut events = Vec::new(); - let mut messages = Vec::new(); - let mut user_events = 0; - let mut redraw_pending = false; - - debug.startup_finished(); - - while let Some(event) = event_receiver.next().await { - match event { - event::Event::NewEvents( - event::StartCause::Init - | event::StartCause::ResumeTimeReached { .. }, - ) if !redraw_pending => { - window.request_redraw(); - redraw_pending = true; - } - event::Event::PlatformSpecific(event::PlatformSpecific::MacOS( - event::MacOS::ReceivedUrl(url), - )) => { - runtime.broadcast(subscription::Event::PlatformSpecific( - subscription::PlatformSpecific::MacOS( - subscription::MacOS::ReceivedUrl(url), - ), - )); - } - 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 { - event: event::WindowEvent::RedrawRequested { .. }, - .. - } => { - let physical_size = state.physical_size(); - - if physical_size.width == 0 || physical_size.height == 0 { - continue; - } - - let current_viewport_version = state.viewport_version(); - - if viewport_version != current_viewport_version { - let logical_size = state.logical_size(); - - debug.layout_started(); - user_interface = ManuallyDrop::new( - ManuallyDrop::into_inner(user_interface) - .relayout(logical_size, &mut renderer), - ); - debug.layout_finished(); - - compositor.configure_surface( - &mut surface, - physical_size.width, - physical_size.height, - ); - - viewport_version = current_viewport_version; - } - - // TODO: Avoid redrawing all the time by forcing widgets to - // request redraws on state changes - // - // Then, we can use the `interface_state` here to decide if a redraw - // is needed right away, or simply wait until a specific time. - let redraw_event = Event::Window( - window::Event::RedrawRequested(Instant::now()), - ); - - let (interface_state, _) = user_interface.update( - &[redraw_event.clone()], - state.cursor(), - &mut renderer, - &mut clipboard, - &mut messages, - ); - - let _ = control_sender.start_send(match interface_state { - user_interface::State::Updated { - redraw_request: Some(redraw_request), - } => match redraw_request { - window::RedrawRequest::NextFrame => { - window.request_redraw(); - - ControlFlow::Wait - } - window::RedrawRequest::At(at) => { - ControlFlow::WaitUntil(at) - } - }, - _ => ControlFlow::Wait, - }); - - runtime.broadcast(subscription::Event::Interaction { - window: window::Id::MAIN, - event: redraw_event, - status: core::event::Status::Ignored, - }); - - debug.draw_started(); - let new_mouse_interaction = user_interface.draw( - &mut renderer, - state.theme(), - &renderer::Style { - text_color: state.text_color(), - }, - state.cursor(), - ); - redraw_pending = false; - debug.draw_finished(); - - if new_mouse_interaction != mouse_interaction { - window.set_cursor(conversion::mouse_interaction( - new_mouse_interaction, - )); - - mouse_interaction = new_mouse_interaction; - } - - debug.render_started(); - match compositor.present( - &mut renderer, - &mut surface, - state.viewport(), - state.background_color(), - &debug.overlay(), - ) { - Ok(()) => { - debug.render_finished(); - - // TODO: Handle animations! - // Maybe we can use `ControlFlow::WaitUntil` for this. - } - Err(error) => match error { - // This is an unrecoverable error. - compositor::SurfaceError::OutOfMemory => { - panic!("{error:?}"); - } - _ => { - debug.render_finished(); - - // Try rendering again next frame. - window.request_redraw(); - } - }, - } - } - event::Event::WindowEvent { - event: window_event, - .. - } => { - if requests_exit(&window_event, state.modifiers()) - && exit_on_close_request - { - break; - } - - state.update(&window, &window_event, &mut debug); - - if let Some(event) = conversion::window_event( - window_event, - state.scale_factor(), - state.modifiers(), - ) { - events.push(event); - } - } - event::Event::AboutToWait => { - if events.is_empty() && messages.is_empty() { - continue; - } - - debug.event_processing_started(); - - let (interface_state, statuses) = user_interface.update( - &events, - state.cursor(), - &mut renderer, - &mut clipboard, - &mut messages, - ); - - debug.event_processing_finished(); - - for (event, status) in - events.drain(..).zip(statuses.into_iter()) - { - runtime.broadcast(subscription::Event::Interaction { - window: window::Id::MAIN, - event, - status, - }); - } - - if !messages.is_empty() - || matches!( - interface_state, - user_interface::State::Outdated - ) - { - let cache = - ManuallyDrop::into_inner(user_interface).into_cache(); - - // Update application - update( - &mut application, - &mut state, - &mut runtime, - &mut debug, - &mut messages, - &window, - ); - - user_interface = ManuallyDrop::new(build_user_interface( - &application, - cache, - &mut renderer, - state.logical_size(), - &mut debug, - )); - - if should_exit { - break; - } - - if user_events > 0 { - proxy.free_slots(user_events); - user_events = 0; - } - } - - if !redraw_pending { - window.request_redraw(); - redraw_pending = true; - } - } - _ => {} - } - } - - // Manually drop the user interface - drop(ManuallyDrop::into_inner(user_interface)); -} - -/// Returns true if the provided event should cause an [`Application`] to -/// exit. -pub fn requests_exit( - event: &winit::event::WindowEvent, - _modifiers: winit::keyboard::ModifiersState, -) -> bool { - use winit::event::WindowEvent; - - match event { - WindowEvent::CloseRequested => true, - #[cfg(target_os = "macos")] - WindowEvent::KeyboardInput { - event: - winit::event::KeyEvent { - logical_key: winit::keyboard::Key::Character(c), - state: winit::event::ElementState::Pressed, - .. - }, - .. - } if c == "q" && _modifiers.super_key() => true, - _ => false, - } -} - -/// Builds a [`UserInterface`] for the provided [`Application`], logging -/// [`struct@Debug`] information accordingly. -pub fn build_user_interface<'a, A: Application>( - application: &'a A, - cache: user_interface::Cache, - renderer: &mut A::Renderer, - size: Size, - debug: &mut Debug, -) -> UserInterface<'a, A::Message, A::Theme, A::Renderer> -where - A::Theme: DefaultStyle, -{ - debug.view_started(); - let view = application.view(); - debug.view_finished(); - - debug.layout_started(); - let user_interface = UserInterface::build(view, size, cache, renderer); - debug.layout_finished(); - - user_interface -} - -/// Updates an [`Application`] by feeding it the provided messages, spawning any -/// resulting [`Task`], and tracking its [`Subscription`]. -pub fn update<A: Application, E: Executor>( - application: &mut A, - state: &mut State<A>, - runtime: &mut Runtime<E, Proxy<A::Message>, Action<A::Message>>, - debug: &mut Debug, - messages: &mut Vec<A::Message>, - window: &winit::window::Window, -) where - A::Theme: DefaultStyle, -{ - for message in messages.drain(..) { - debug.log_message(&message); - - debug.update_started(); - let task = runtime.enter(|| application.update(message)); - debug.update_finished(); - - if let Some(stream) = task.into_stream() { - runtime.run(stream); - } - } - - state.synchronize(application, window); - - let subscription = application.subscription(); - runtime.track(subscription.map(Action::Output).into_recipes()); -} - -/// Runs the actions of a [`Task`]. -pub fn run_action<A, C>( - action: Action<A::Message>, - user_interface: &mut UserInterface<'_, A::Message, A::Theme, C::Renderer>, - compositor: &mut C, - surface: &mut C::Surface, - state: &State<A>, - renderer: &mut A::Renderer, - messages: &mut Vec<A::Message>, - clipboard: &mut Clipboard, - should_exit: &mut bool, - debug: &mut Debug, - window: &winit::window::Window, -) where - A: Application, - C: Compositor<Renderer = A::Renderer> + 'static, - A::Theme: DefaultStyle, -{ - use crate::runtime::system; - use crate::runtime::window; - - match action { - Action::Clipboard(action) => match action { - clipboard::Action::Read { target, channel } => { - let _ = channel.send(clipboard.read(target)); - } - clipboard::Action::Write { target, contents } => { - clipboard.write(target, contents); - } - }, - 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, channel) => { - let size = - window.inner_size().to_logical(window.scale_factor()); - - 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::<f32>(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 - }; - - 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::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() { - f(handle); - } - } - - window::Action::Screenshot(_id, channel) => { - let bytes = compositor.screenshot( - renderer, - surface, - state.viewport(), - state.background_color(), - &debug.overlay(), - ); - - 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(); - - 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); - } - } - } - } - Action::LoadFont { bytes, channel } => { - // TODO: Error handling (?) - compositor.load_font(bytes); - - let _ = channel.send(Ok(())); - } - Action::Output(message) => { - messages.push(message); - } - } -} diff --git a/winit/src/application/state.rs b/winit/src/application/state.rs deleted file mode 100644 index a0a0693310..0000000000 --- a/winit/src/application/state.rs +++ /dev/null @@ -1,221 +0,0 @@ -use crate::application; -use crate::conversion; -use crate::core::mouse; -use crate::core::{Color, Size}; -use crate::graphics::Viewport; -use crate::runtime::Debug; -use crate::Application; - -use std::marker::PhantomData; -use winit::event::{Touch, WindowEvent}; -use winit::window::Window; - -/// The state of a windowed [`Application`]. -#[allow(missing_debug_implementations)] -pub struct State<A: Application> -where - A::Theme: application::DefaultStyle, -{ - title: String, - scale_factor: f64, - viewport: Viewport, - viewport_version: usize, - cursor_position: Option<winit::dpi::PhysicalPosition<f64>>, - modifiers: winit::keyboard::ModifiersState, - theme: A::Theme, - appearance: application::Appearance, - application: PhantomData<A>, -} - -impl<A: Application> State<A> -where - A::Theme: application::DefaultStyle, -{ - /// Creates a new [`State`] for the provided [`Application`] and window. - pub fn new(application: &A, window: &Window) -> Self { - let title = application.title(); - let scale_factor = application.scale_factor(); - let theme = application.theme(); - let appearance = application.style(&theme); - - let viewport = { - let physical_size = window.inner_size(); - - Viewport::with_physical_size( - Size::new(physical_size.width, physical_size.height), - window.scale_factor() * scale_factor, - ) - }; - - Self { - title, - scale_factor, - viewport, - viewport_version: 0, - cursor_position: None, - modifiers: winit::keyboard::ModifiersState::default(), - theme, - appearance, - application: PhantomData, - } - } - - /// Returns the current [`Viewport`] of the [`State`]. - pub fn viewport(&self) -> &Viewport { - &self.viewport - } - - /// Returns the version of the [`Viewport`] of the [`State`]. - /// - /// The version is incremented every time the [`Viewport`] changes. - pub fn viewport_version(&self) -> usize { - self.viewport_version - } - - /// Returns the physical [`Size`] of the [`Viewport`] of the [`State`]. - pub fn physical_size(&self) -> Size<u32> { - self.viewport.physical_size() - } - - /// Returns the logical [`Size`] of the [`Viewport`] of the [`State`]. - pub fn logical_size(&self) -> Size<f32> { - self.viewport.logical_size() - } - - /// Returns the current scale factor of the [`Viewport`] of the [`State`]. - pub fn scale_factor(&self) -> f64 { - self.viewport.scale_factor() - } - - /// Returns the current cursor position of the [`State`]. - pub fn cursor(&self) -> mouse::Cursor { - self.cursor_position - .map(|cursor_position| { - conversion::cursor_position( - cursor_position, - self.viewport.scale_factor(), - ) - }) - .map(mouse::Cursor::Available) - .unwrap_or(mouse::Cursor::Unavailable) - } - - /// Returns the current keyboard modifiers of the [`State`]. - pub fn modifiers(&self) -> winit::keyboard::ModifiersState { - self.modifiers - } - - /// Returns the current theme of the [`State`]. - pub fn theme(&self) -> &A::Theme { - &self.theme - } - - /// Returns the current background [`Color`] of the [`State`]. - pub fn background_color(&self) -> Color { - self.appearance.background_color - } - - /// Returns the current text [`Color`] of the [`State`]. - pub fn text_color(&self) -> Color { - self.appearance.text_color - } - - /// Processes the provided window event and updates the [`State`] - /// accordingly. - pub fn update( - &mut self, - window: &Window, - event: &WindowEvent, - _debug: &mut Debug, - ) { - match event { - WindowEvent::Resized(new_size) => { - let size = Size::new(new_size.width, new_size.height); - - self.viewport = Viewport::with_physical_size( - size, - window.scale_factor() * self.scale_factor, - ); - - self.viewport_version = self.viewport_version.wrapping_add(1); - } - WindowEvent::ScaleFactorChanged { - scale_factor: new_scale_factor, - .. - } => { - let size = self.viewport.physical_size(); - - self.viewport = Viewport::with_physical_size( - size, - new_scale_factor * self.scale_factor, - ); - - self.viewport_version = self.viewport_version.wrapping_add(1); - } - WindowEvent::CursorMoved { position, .. } - | WindowEvent::Touch(Touch { - location: position, .. - }) => { - self.cursor_position = Some(*position); - } - WindowEvent::CursorLeft { .. } => { - self.cursor_position = None; - } - WindowEvent::ModifiersChanged(new_modifiers) => { - self.modifiers = new_modifiers.state(); - } - #[cfg(feature = "debug")] - WindowEvent::KeyboardInput { - event: - winit::event::KeyEvent { - logical_key: - winit::keyboard::Key::Named( - winit::keyboard::NamedKey::F12, - ), - state: winit::event::ElementState::Pressed, - .. - }, - .. - } => _debug.toggle(), - _ => {} - } - } - - /// Synchronizes the [`State`] with its [`Application`] and its respective - /// window. - /// - /// Normally an [`Application`] should be synchronized with its [`State`] - /// and window after calling [`crate::application::update`]. - pub fn synchronize(&mut self, application: &A, window: &Window) { - // Update window title - let new_title = application.title(); - - if self.title != new_title { - window.set_title(&new_title); - - self.title = new_title; - } - - // Update scale factor and size - let new_scale_factor = application.scale_factor(); - let new_size = window.inner_size(); - let current_size = self.viewport.physical_size(); - - if self.scale_factor != new_scale_factor - || (current_size.width, current_size.height) - != (new_size.width, new_size.height) - { - self.viewport = Viewport::with_physical_size( - Size::new(new_size.width, new_size.height), - window.scale_factor() * new_scale_factor, - ); - self.viewport_version = self.viewport_version.wrapping_add(1); - - self.scale_factor = new_scale_factor; - } - - // Update theme and appearance - self.theme = application.theme(); - self.appearance = application.style(&self.theme); - } -} diff --git a/winit/src/lib.rs b/winit/src/lib.rs index 3619cde8a5..3c11b72a8a 100644 --- a/winit/src/lib.rs +++ b/winit/src/lib.rs @@ -5,7 +5,7 @@ //! `iced_winit` offers some convenient abstractions on top of [`iced_runtime`] //! to quickstart development when using [`winit`]. //! -//! It exposes a renderer-agnostic [`Application`] trait that can be implemented +//! It exposes a renderer-agnostic [`Program`] trait that can be implemented //! and then run with a simple call. The use of this trait is optional. //! //! Additionally, a [`conversion`] module is available for users that decide to @@ -24,24 +24,23 @@ pub use iced_runtime::core; pub use iced_runtime::futures; pub use winit; -#[cfg(feature = "multi-window")] -pub mod multi_window; - -#[cfg(feature = "application")] -pub mod application; pub mod clipboard; pub mod conversion; pub mod settings; +#[cfg(feature = "program")] +pub mod program; + #[cfg(feature = "system")] pub mod system; mod error; mod proxy; -#[cfg(feature = "application")] -pub use application::Application; pub use clipboard::Clipboard; pub use error::Error; pub use proxy::Proxy; pub use settings::Settings; + +#[cfg(feature = "program")] +pub use program::Program; diff --git a/winit/src/multi_window.rs b/winit/src/program.rs similarity index 85% rename from winit/src/multi_window.rs rename to winit/src/program.rs index 8bd8a64d40..28cd8e52ae 100644 --- a/winit/src/multi_window.rs +++ b/winit/src/program.rs @@ -10,7 +10,7 @@ use crate::core::mouse; use crate::core::renderer; use crate::core::widget::operation; use crate::core::window; -use crate::core::{Point, Size}; +use crate::core::{Color, Element, Point, Size, Theme}; use crate::futures::futures::channel::mpsc; use crate::futures::futures::channel::oneshot; use crate::futures::futures::executor; @@ -20,14 +20,12 @@ use crate::futures::subscription::{self, Subscription}; use crate::futures::{Executor, Runtime}; use crate::graphics; use crate::graphics::{compositor, Compositor}; -use crate::multi_window::window_manager::WindowManager; -use crate::runtime::multi_window::Program; use crate::runtime::user_interface::{self, UserInterface}; use crate::runtime::Debug; -use crate::runtime::{Action, Task}; +use crate::runtime::{self, Action, Task}; use crate::{Clipboard, Error, Proxy, Settings}; -pub use crate::application::{default, Appearance, DefaultStyle}; +use window_manager::WindowManager; use rustc_hash::FxHashMap; use std::mem::ManuallyDrop; @@ -40,19 +38,37 @@ use std::time::Instant; /// your GUI application by simply calling [`run`]. It will run in /// its own window. /// -/// An [`Application`] can execute asynchronous actions by returning a +/// A [`Program`] can execute asynchronous actions by returning a /// [`Task`] in some of its methods. /// -/// When using an [`Application`] with the `debug` feature enabled, a debug view +/// When using a [`Program`] with the `debug` feature enabled, a debug view /// can be toggled by pressing `F12`. -pub trait Application: Program +pub trait Program where + Self: Sized, Self::Theme: DefaultStyle, { - /// The data needed to initialize your [`Application`]. + /// The type of __messages__ your [`Program`] will produce. + type Message: std::fmt::Debug + Send; + + /// The theme used to draw the [`Program`]. + type Theme; + + /// The [`Executor`] that will run commands and subscriptions. + /// + /// The [default executor] can be a good starting point! + /// + /// [`Executor`]: Self::Executor + /// [default executor]: crate::futures::backend::default::Executor + type Executor: Executor; + + /// The graphics backend to use to draw the [`Program`]. + type Renderer: core::Renderer + core::text::Renderer; + + /// The data needed to initialize your [`Program`]. type Flags; - /// Initializes the [`Application`] with the flags provided to + /// Initializes the [`Program`] with the flags provided to /// [`run`] as part of the [`Settings`]. /// /// Here is where you should return the initial state of your app. @@ -62,13 +78,31 @@ where /// load state from a file, perform an initial HTTP request, etc. fn new(flags: Self::Flags) -> (Self, Task<Self::Message>); - /// Returns the current title of the [`Application`]. + /// Returns the current title of the [`Program`]. /// /// This title can be dynamic! The runtime will automatically update the /// title of your application when necessary. fn title(&self, window: window::Id) -> String; - /// Returns the current `Theme` of the [`Application`]. + /// Handles a __message__ and updates the state of the [`Program`]. + /// + /// This is where you define your __update logic__. All the __messages__, + /// produced by either user interactions or commands, will be handled by + /// this method. + /// + /// Any [`Task`] returned will be executed immediately in the background by the + /// runtime. + fn update(&mut self, message: Self::Message) -> Task<Self::Message>; + + /// Returns the widgets to display in the [`Program`] for the `window`. + /// + /// These widgets can produce __messages__ based on user interaction. + fn view( + &self, + window: window::Id, + ) -> Element<'_, Self::Message, Self::Theme, Self::Renderer>; + + /// Returns the current `Theme` of the [`Program`]. fn theme(&self, window: window::Id) -> Self::Theme; /// Returns the `Style` variation of the `Theme`. @@ -89,7 +123,7 @@ where Subscription::none() } - /// Returns the scale factor of the window of the [`Application`]. + /// Returns the scale factor of the window of the [`Program`]. /// /// It can be used to dynamically control the size of the UI at runtime /// (i.e. zooming). @@ -104,17 +138,49 @@ where } } -/// Runs an [`Application`] with an executor, compositor, and the provided +/// The appearance of a program. +#[derive(Debug, Clone, Copy, PartialEq)] +pub struct Appearance { + /// The background [`Color`] of the application. + pub background_color: Color, + + /// The default text [`Color`] of the application. + pub text_color: Color, +} + +/// The default style of a [`Program`]. +pub trait DefaultStyle { + /// Returns the default style of a [`Program`]. + fn default_style(&self) -> Appearance; +} + +impl DefaultStyle for Theme { + fn default_style(&self) -> Appearance { + default(self) + } +} + +/// The default [`Appearance`] of a [`Program`] with the built-in [`Theme`]. +pub fn default(theme: &Theme) -> Appearance { + let palette = theme.extended_palette(); + + Appearance { + background_color: palette.background.base.color, + text_color: palette.background.base.text, + } +} +/// Runs a [`Program`] with an executor, compositor, and the provided /// settings. -pub fn run<A, E, C>( - settings: Settings<A::Flags>, +pub fn run<P, C>( + settings: Settings, graphics_settings: graphics::Settings, + window_settings: Option<window::Settings>, + flags: P::Flags, ) -> Result<(), Error> where - A: Application + 'static, - E: Executor + 'static, - C: Compositor<Renderer = A::Renderer> + 'static, - A::Theme: DefaultStyle, + P: Program + 'static, + C: Compositor<Renderer = P::Renderer> + 'static, + P::Theme: DefaultStyle, { use winit::event_loop::EventLoop; @@ -128,30 +194,24 @@ where let (proxy, worker) = Proxy::new(event_loop.create_proxy()); let mut runtime = { - let executor = E::new().map_err(Error::ExecutorCreationFailed)?; + let executor = + P::Executor::new().map_err(Error::ExecutorCreationFailed)?; executor.spawn(worker); Runtime::new(executor, proxy.clone()) }; - let (application, task) = { - let flags = settings.flags; - - runtime.enter(|| A::new(flags)) - }; + let (application, task) = runtime.enter(|| P::new(flags)); if let Some(stream) = task.into_stream() { runtime.run(stream); } - let id = settings.id; - let title = application.title(window::Id::MAIN); - let (boot_sender, boot_receiver) = oneshot::channel(); let (event_sender, event_receiver) = mpsc::unbounded(); let (control_sender, control_receiver) = mpsc::unbounded(); - let instance = Box::pin(run_instance::<A, E, C>( + let instance = Box::pin(run_instance::<P, C>( application, runtime, proxy, @@ -166,6 +226,7 @@ where struct Runner<Message: 'static, F, C> { instance: std::pin::Pin<Box<F>>, context: task::Context<'static>, + id: Option<String>, boot: Option<BootConfig<C>>, sender: mpsc::UnboundedSender<Event<Message>>, receiver: mpsc::UnboundedReceiver<Control>, @@ -174,20 +235,17 @@ where struct BootConfig<C> { sender: oneshot::Sender<Boot<C>>, - id: Option<String>, - title: String, - window_settings: window::Settings, + window_settings: Option<window::Settings>, graphics_settings: graphics::Settings, } let mut runner = Runner { instance, context, + id: settings.id, boot: Some(BootConfig { sender: boot_sender, - id, - title, - window_settings: settings.window, + window_settings, graphics_settings, }), sender: event_sender, @@ -204,8 +262,6 @@ where fn resumed(&mut self, event_loop: &winit::event_loop::ActiveEventLoop) { let Some(BootConfig { sender, - id, - title, window_settings, graphics_settings, }) = self.boot.take() @@ -213,20 +269,9 @@ where return; }; - let should_be_visible = window_settings.visible; - let exit_on_close_request = window_settings.exit_on_close_request; - - let window_attributes = conversion::window_attributes( - window_settings, - &title, - event_loop.primary_monitor(), - id, - ) - .with_visible(false); - - log::debug!("Window attributes: {window_attributes:#?}"); - - let window = match event_loop.create_window(window_attributes) { + let window = match event_loop.create_window( + winit::window::WindowAttributes::default().with_visible(false), + ) { Ok(window) => Arc::new(window), Err(error) => { self.error = Some(Error::WindowCreationFailed(error)); @@ -235,16 +280,17 @@ where } }; + let clipboard = Clipboard::connect(&window); + let finish_boot = async move { let compositor = C::new(graphics_settings, window.clone()).await?; sender .send(Boot { - window, compositor, - should_be_visible, - exit_on_close_request, + clipboard, + window_settings, }) .ok() .expect("Send boot event"); @@ -386,7 +432,12 @@ where let window = event_loop .create_window( conversion::window_attributes( - settings, &title, monitor, None, + settings, + &title, + monitor + .or(event_loop + .primary_monitor()), + self.id.clone(), ), ) .expect("Create window"); @@ -423,10 +474,9 @@ where } struct Boot<C> { - window: Arc<winit::window::Window>, compositor: C, - should_be_visible: bool, - exit_on_close_request: bool, + clipboard: Clipboard, + window_settings: Option<window::Settings>, } enum Event<Message: 'static> { @@ -449,62 +499,37 @@ enum Control { }, } -async fn run_instance<A, E, C>( - mut application: A, - mut runtime: Runtime<E, Proxy<A::Message>, Action<A::Message>>, - mut proxy: Proxy<A::Message>, +async fn run_instance<P, C>( + mut program: P, + mut runtime: Runtime<P::Executor, Proxy<P::Message>, Action<P::Message>>, + mut proxy: Proxy<P::Message>, mut debug: Debug, mut boot: oneshot::Receiver<Boot<C>>, - mut event_receiver: mpsc::UnboundedReceiver<Event<Action<A::Message>>>, + mut event_receiver: mpsc::UnboundedReceiver<Event<Action<P::Message>>>, mut control_sender: mpsc::UnboundedSender<Control>, ) where - A: Application + 'static, - E: Executor + 'static, - C: Compositor<Renderer = A::Renderer> + 'static, - A::Theme: DefaultStyle, + P: Program + 'static, + C: Compositor<Renderer = P::Renderer> + 'static, + P::Theme: DefaultStyle, { use winit::event; use winit::event_loop::ControlFlow; let Boot { - window: main_window, mut compositor, - should_be_visible, - exit_on_close_request, + mut clipboard, + window_settings, } = boot.try_recv().ok().flatten().expect("Receive boot"); let mut window_manager = WindowManager::new(); - let _ = window_manager.insert( - window::Id::MAIN, - main_window, - &application, - &mut compositor, - exit_on_close_request, - ); - - let main_window = window_manager - .get_mut(window::Id::MAIN) - .expect("Get main window"); - - if should_be_visible { - main_window.raw.set_visible(true); - } - - let mut clipboard = Clipboard::connect(&main_window.raw); - let mut events = { - vec![( - window::Id::MAIN, - core::Event::Window(window::Event::Opened { - position: main_window.position(), - size: main_window.size(), - }), - )] - }; + let mut events = Vec::new(); + let mut messages = Vec::new(); + let mut actions = 0; let mut ui_caches = FxHashMap::default(); let mut user_interfaces = ManuallyDrop::new(build_user_interfaces( - &application, + &program, &mut debug, &mut window_manager, FxHashMap::from_iter([( @@ -513,15 +538,19 @@ async fn run_instance<A, E, C>( )]), )); - runtime.track( - application - .subscription() - .map(Action::Output) - .into_recipes(), - ); + runtime.track(program.subscription().map(Action::Output).into_recipes()); - let mut messages = Vec::new(); - let mut user_events = 0; + let is_daemon = window_settings.is_none(); + + if let Some(window_settings) = window_settings { + let (sender, _receiver) = oneshot::channel(); + + proxy.send_action(Action::Window(runtime::window::Action::Open( + window::Id::unique(), + window_settings, + sender, + ))); + } debug.startup_finished(); @@ -535,7 +564,7 @@ async fn run_instance<A, E, C>( let window = window_manager.insert( id, Arc::new(window), - &application, + &program, &mut compositor, exit_on_close_request, ); @@ -545,7 +574,7 @@ async fn run_instance<A, E, C>( let _ = user_interfaces.insert( id, build_user_interface( - &application, + &program, user_interface::Cache::default(), &mut window.renderer, logical_size, @@ -591,7 +620,7 @@ async fn run_instance<A, E, C>( event::Event::UserEvent(action) => { run_action( action, - &application, + &program, &mut compositor, &mut messages, &mut clipboard, @@ -601,7 +630,7 @@ async fn run_instance<A, E, C>( &mut window_manager, &mut ui_caches, ); - user_events += 1; + actions += 1; } event::Event::WindowEvent { window_id: id, @@ -782,6 +811,16 @@ async fn run_instance<A, E, C>( event: window_event, window_id, } => { + if !is_daemon + && matches!( + window_event, + winit::event::WindowEvent::Destroyed + ) + && window_manager.is_empty() + { + break 'main; + } + let Some((id, window)) = window_manager.get_mut_alias(window_id) else { @@ -801,10 +840,6 @@ async fn run_instance<A, E, C>( id, core::Event::Window(window::Event::Closed), )); - - if window_manager.is_empty() { - break 'main; - } } else { window.state.update( &window.raw, @@ -903,7 +938,7 @@ async fn run_instance<A, E, C>( // Update application update( - &mut application, + &mut program, &mut runtime, &mut debug, &mut messages, @@ -913,7 +948,7 @@ async fn run_instance<A, E, C>( // application update since we don't know what changed for (id, window) in window_manager.iter_mut() { window.state.synchronize( - &application, + &program, id, &window.raw, ); @@ -926,15 +961,15 @@ async fn run_instance<A, E, C>( // rebuild UIs with the synchronized states user_interfaces = ManuallyDrop::new(build_user_interfaces( - &application, + &program, &mut debug, &mut window_manager, cached_interfaces, )); - if user_events > 0 { - proxy.free_slots(user_events); - user_events = 0; + if actions > 0 { + proxy.free_slots(actions); + actions = 0; } } } @@ -947,17 +982,17 @@ async fn run_instance<A, E, C>( let _ = ManuallyDrop::into_inner(user_interfaces); } -/// Builds a window's [`UserInterface`] for the [`Application`]. -fn build_user_interface<'a, A: Application>( - application: &'a A, +/// Builds a window's [`UserInterface`] for the [`Program`]. +fn build_user_interface<'a, P: Program>( + application: &'a P, cache: user_interface::Cache, - renderer: &mut A::Renderer, + renderer: &mut P::Renderer, size: Size, debug: &mut Debug, id: window::Id, -) -> UserInterface<'a, A::Message, A::Theme, A::Renderer> +) -> UserInterface<'a, P::Message, P::Theme, P::Renderer> where - A::Theme: DefaultStyle, + P::Theme: DefaultStyle, { debug.view_started(); let view = application.view(id); @@ -970,13 +1005,13 @@ where user_interface } -fn update<A: Application, E: Executor>( - application: &mut A, - runtime: &mut Runtime<E, Proxy<A::Message>, Action<A::Message>>, +fn update<P: Program, E: Executor>( + application: &mut P, + runtime: &mut Runtime<E, Proxy<P::Message>, Action<P::Message>>, debug: &mut Debug, - messages: &mut Vec<A::Message>, + messages: &mut Vec<P::Message>, ) where - A::Theme: DefaultStyle, + P::Theme: DefaultStyle, { for message in messages.drain(..) { debug.log_message(&message); @@ -994,24 +1029,24 @@ fn update<A: Application, E: Executor>( runtime.track(subscription.map(Action::Output).into_recipes()); } -fn run_action<A, C>( - action: Action<A::Message>, - application: &A, +fn run_action<P, C>( + action: Action<P::Message>, + application: &P, compositor: &mut C, - messages: &mut Vec<A::Message>, + messages: &mut Vec<P::Message>, clipboard: &mut Clipboard, control_sender: &mut mpsc::UnboundedSender<Control>, debug: &mut Debug, interfaces: &mut FxHashMap< window::Id, - UserInterface<'_, A::Message, A::Theme, A::Renderer>, + UserInterface<'_, P::Message, P::Theme, P::Renderer>, >, - window_manager: &mut WindowManager<A, C>, + window_manager: &mut WindowManager<P, C>, ui_caches: &mut FxHashMap<window::Id, user_interface::Cache>, ) where - A: Application, - C: Compositor<Renderer = A::Renderer> + 'static, - A::Theme: DefaultStyle, + P: Program, + C: Compositor<Renderer = P::Renderer> + 'static, + P::Theme: DefaultStyle, { use crate::runtime::clipboard; use crate::runtime::system; @@ -1047,12 +1082,6 @@ fn run_action<A, C>( 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) { @@ -1266,19 +1295,24 @@ fn run_action<A, C>( let _ = channel.send(Ok(())); } + Action::Exit => { + control_sender + .start_send(Control::Exit) + .expect("Send control action"); + } } } /// Build the user interface for every window. -pub fn build_user_interfaces<'a, A: Application, C>( - application: &'a A, +pub fn build_user_interfaces<'a, P: Program, C>( + application: &'a P, debug: &mut Debug, - window_manager: &mut WindowManager<A, C>, + window_manager: &mut WindowManager<P, C>, mut cached_user_interfaces: FxHashMap<window::Id, user_interface::Cache>, -) -> FxHashMap<window::Id, UserInterface<'a, A::Message, A::Theme, A::Renderer>> +) -> FxHashMap<window::Id, UserInterface<'a, P::Message, P::Theme, P::Renderer>> where - C: Compositor<Renderer = A::Renderer>, - A::Theme: DefaultStyle, + C: Compositor<Renderer = P::Renderer>, + P::Theme: DefaultStyle, { cached_user_interfaces .drain() @@ -1300,7 +1334,7 @@ where .collect() } -/// Returns true if the provided event should cause an [`Application`] to +/// Returns true if the provided event should cause a [`Program`] to /// exit. pub fn user_force_quit( event: &winit::event::WindowEvent, diff --git a/winit/src/multi_window/state.rs b/winit/src/program/state.rs similarity index 90% rename from winit/src/multi_window/state.rs rename to winit/src/program/state.rs index dfd8e69683..a7fa2788d9 100644 --- a/winit/src/multi_window/state.rs +++ b/winit/src/program/state.rs @@ -2,16 +2,16 @@ use crate::conversion; use crate::core::{mouse, window}; use crate::core::{Color, Size}; use crate::graphics::Viewport; -use crate::multi_window::{self, Application}; +use crate::program::{self, Program}; use std::fmt::{Debug, Formatter}; use winit::event::{Touch, WindowEvent}; use winit::window::Window; -/// The state of a multi-windowed [`Application`]. -pub struct State<A: Application> +/// The state of a multi-windowed [`Program`]. +pub struct State<P: Program> where - A::Theme: multi_window::DefaultStyle, + P::Theme: program::DefaultStyle, { title: String, scale_factor: f64, @@ -19,13 +19,13 @@ where viewport_version: u64, cursor_position: Option<winit::dpi::PhysicalPosition<f64>>, modifiers: winit::keyboard::ModifiersState, - theme: A::Theme, - appearance: multi_window::Appearance, + theme: P::Theme, + appearance: program::Appearance, } -impl<A: Application> Debug for State<A> +impl<P: Program> Debug for State<P> where - A::Theme: multi_window::DefaultStyle, + P::Theme: program::DefaultStyle, { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { f.debug_struct("multi_window::State") @@ -39,13 +39,13 @@ where } } -impl<A: Application> State<A> +impl<P: Program> State<P> where - A::Theme: multi_window::DefaultStyle, + P::Theme: program::DefaultStyle, { - /// Creates a new [`State`] for the provided [`Application`]'s `window`. + /// Creates a new [`State`] for the provided [`Program`]'s `window`. pub fn new( - application: &A, + application: &P, window_id: window::Id, window: &Window, ) -> Self { @@ -121,7 +121,7 @@ where } /// Returns the current theme of the [`State`]. - pub fn theme(&self) -> &A::Theme { + pub fn theme(&self) -> &P::Theme { &self.theme } @@ -195,14 +195,14 @@ where } } - /// Synchronizes the [`State`] with its [`Application`] and its respective + /// Synchronizes the [`State`] with its [`Program`] and its respective /// window. /// - /// Normally, an [`Application`] should be synchronized with its [`State`] + /// Normally, a [`Program`] should be synchronized with its [`State`] /// and window after calling [`State::update`]. pub fn synchronize( &mut self, - application: &A, + application: &P, window_id: window::Id, window: &Window, ) { diff --git a/winit/src/multi_window/window_manager.rs b/winit/src/program/window_manager.rs similarity index 73% rename from winit/src/multi_window/window_manager.rs rename to winit/src/program/window_manager.rs index 57a7dc7e38..fcbf79f6e7 100644 --- a/winit/src/multi_window/window_manager.rs +++ b/winit/src/program/window_manager.rs @@ -2,28 +2,28 @@ use crate::core::mouse; use crate::core::window::Id; use crate::core::{Point, Size}; use crate::graphics::Compositor; -use crate::multi_window::{Application, DefaultStyle, State}; +use crate::program::{DefaultStyle, Program, State}; use std::collections::BTreeMap; use std::sync::Arc; use winit::monitor::MonitorHandle; #[allow(missing_debug_implementations)] -pub struct WindowManager<A, C> +pub struct WindowManager<P, C> where - A: Application, - C: Compositor<Renderer = A::Renderer>, - A::Theme: DefaultStyle, + P: Program, + C: Compositor<Renderer = P::Renderer>, + P::Theme: DefaultStyle, { aliases: BTreeMap<winit::window::WindowId, Id>, - entries: BTreeMap<Id, Window<A, C>>, + entries: BTreeMap<Id, Window<P, C>>, } -impl<A, C> WindowManager<A, C> +impl<P, C> WindowManager<P, C> where - A: Application, - C: Compositor<Renderer = A::Renderer>, - A::Theme: DefaultStyle, + P: Program, + C: Compositor<Renderer = P::Renderer>, + P::Theme: DefaultStyle, { pub fn new() -> Self { Self { @@ -36,10 +36,10 @@ where &mut self, id: Id, window: Arc<winit::window::Window>, - application: &A, + application: &P, compositor: &mut C, exit_on_close_request: bool, - ) -> &mut Window<A, C> { + ) -> &mut Window<P, C> { let state = State::new(application, id, &window); let viewport_version = state.viewport_version(); let physical_size = state.physical_size(); @@ -76,18 +76,18 @@ where pub fn iter_mut( &mut self, - ) -> impl Iterator<Item = (Id, &mut Window<A, C>)> { + ) -> impl Iterator<Item = (Id, &mut Window<P, C>)> { self.entries.iter_mut().map(|(k, v)| (*k, v)) } - pub fn get_mut(&mut self, id: Id) -> Option<&mut Window<A, C>> { + pub fn get_mut(&mut self, id: Id) -> Option<&mut Window<P, C>> { self.entries.get_mut(&id) } pub fn get_mut_alias( &mut self, id: winit::window::WindowId, - ) -> Option<(Id, &mut Window<A, C>)> { + ) -> Option<(Id, &mut Window<P, C>)> { let id = self.aliases.get(&id).copied()?; Some((id, self.get_mut(id)?)) @@ -97,7 +97,7 @@ where self.entries.values().last()?.raw.current_monitor() } - pub fn remove(&mut self, id: Id) -> Option<Window<A, C>> { + pub fn remove(&mut self, id: Id) -> Option<Window<P, C>> { let window = self.entries.remove(&id)?; let _ = self.aliases.remove(&window.raw.id()); @@ -105,11 +105,11 @@ where } } -impl<A, C> Default for WindowManager<A, C> +impl<P, C> Default for WindowManager<P, C> where - A: Application, - C: Compositor<Renderer = A::Renderer>, - A::Theme: DefaultStyle, + P: Program, + C: Compositor<Renderer = P::Renderer>, + P::Theme: DefaultStyle, { fn default() -> Self { Self::new() @@ -117,26 +117,26 @@ where } #[allow(missing_debug_implementations)] -pub struct Window<A, C> +pub struct Window<P, C> where - A: Application, - C: Compositor<Renderer = A::Renderer>, - A::Theme: DefaultStyle, + P: Program, + C: Compositor<Renderer = P::Renderer>, + P::Theme: DefaultStyle, { pub raw: Arc<winit::window::Window>, - pub state: State<A>, + pub state: State<P>, pub viewport_version: u64, pub exit_on_close_request: bool, pub mouse_interaction: mouse::Interaction, pub surface: C::Surface, - pub renderer: A::Renderer, + pub renderer: P::Renderer, } -impl<A, C> Window<A, C> +impl<P, C> Window<P, C> where - A: Application, - C: Compositor<Renderer = A::Renderer>, - A::Theme: DefaultStyle, + P: Program, + C: Compositor<Renderer = P::Renderer>, + P::Theme: DefaultStyle, { pub fn position(&self) -> Option<Point> { self.raw diff --git a/winit/src/proxy.rs b/winit/src/proxy.rs index 0ab61375db..d8ad8b3f01 100644 --- a/winit/src/proxy.rs +++ b/winit/src/proxy.rs @@ -78,11 +78,22 @@ impl<T: 'static> Proxy<T> { /// Note: This skips the backpressure mechanism with an unbounded /// channel. Use sparingly! pub fn send(&mut self, value: T) + where + T: std::fmt::Debug, + { + self.send_action(Action::Output(value)); + } + + /// Sends an action to the event loop. + /// + /// Note: This skips the backpressure mechanism with an unbounded + /// channel. Use sparingly! + pub fn send_action(&mut self, action: Action<T>) where T: std::fmt::Debug, { self.raw - .send_event(Action::Output(value)) + .send_event(action) .expect("Send message to event loop"); } diff --git a/winit/src/settings.rs b/winit/src/settings.rs index 2e541128b5..78368a04aa 100644 --- a/winit/src/settings.rs +++ b/winit/src/settings.rs @@ -1,25 +1,15 @@ //! Configure your application. -use crate::core::window; - use std::borrow::Cow; /// The settings of an application. #[derive(Debug, Clone, Default)] -pub struct Settings<Flags> { +pub struct Settings { /// The identifier of the application. /// /// If provided, this identifier may be used to identify the application or /// communicate with it through the windowing system. pub id: Option<String>, - /// The [`window::Settings`]. - pub window: window::Settings, - - /// The data needed to initialize an [`Application`]. - /// - /// [`Application`]: crate::Application - pub flags: Flags, - /// The fonts to load on boot. pub fonts: Vec<Cow<'static, [u8]>>, }