diff --git a/Cargo.lock b/Cargo.lock index e2aa6a34..da8fc3e2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2602,6 +2602,14 @@ dependencies = [ "syntect", ] +[[package]] +name = "iced_program" +version = "0.14.0-dev" +dependencies = [ + "iced_graphics", + "iced_runtime", +] + [[package]] name = "iced_renderer" version = "0.14.0-dev" @@ -2694,9 +2702,7 @@ name = "iced_winit" version = "0.14.0-dev" dependencies = [ "iced_debug", - "iced_futures", - "iced_graphics", - "iced_runtime", + "iced_program", "log", "rustc-hash 2.1.1", "sysinfo", diff --git a/Cargo.toml b/Cargo.toml index 44832890..2a1594ca 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -112,6 +112,7 @@ members = [ "futures", "graphics", "highlighter", + "program", "renderer", "runtime", "test", @@ -141,6 +142,7 @@ iced_debug = { version = "0.14.0-dev", path = "debug" } iced_futures = { version = "0.14.0-dev", path = "futures" } iced_graphics = { version = "0.14.0-dev", path = "graphics" } iced_highlighter = { version = "0.14.0-dev", path = "highlighter" } +iced_program = { version = "0.14.0-dev", path = "program" } iced_renderer = { version = "0.14.0-dev", path = "renderer" } iced_runtime = { version = "0.14.0-dev", path = "runtime" } iced_test = { version = "0.14.0-dev", path = "test" } diff --git a/examples/arc/src/main.rs b/examples/arc/src/main.rs index f63b82d0..c9cad1b6 100644 --- a/examples/arc/src/main.rs +++ b/examples/arc/src/main.rs @@ -8,7 +8,7 @@ use iced::window; use iced::{Element, Fill, Point, Rectangle, Renderer, Subscription, Theme}; pub fn main() -> iced::Result { - iced::application("Arc - Iced", Arc::update, Arc::view) + iced::application(Arc::default, 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 95ad299d..02d0f9e9 100644 --- a/examples/bezier_tool/src/main.rs +++ b/examples/bezier_tool/src/main.rs @@ -3,7 +3,7 @@ use iced::widget::{button, container, horizontal_space, hover, right}; use iced::{Element, Theme}; pub fn main() -> iced::Result { - iced::application("Bezier Tool - Iced", Example::update, Example::view) + iced::application(Example::default, Example::update, Example::view) .theme(|_| Theme::CatppuccinMocha) .antialiasing(true) .run() diff --git a/examples/changelog/src/main.rs b/examples/changelog/src/main.rs index a1d0d799..fbb0cfbf 100644 --- a/examples/changelog/src/main.rs +++ b/examples/changelog/src/main.rs @@ -12,9 +12,9 @@ use iced::{Center, Element, Fill, FillPortion, Font, Task, Theme}; pub fn main() -> iced::Result { tracing_subscriber::fmt::init(); - iced::application("Changelog Generator", Generator::update, Generator::view) + iced::application(Generator::new, Generator::update, Generator::view) .theme(Generator::theme) - .run_with(Generator::new) + .run() } enum Generator { diff --git a/examples/checkbox/src/main.rs b/examples/checkbox/src/main.rs index f06557f8..563b721d 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::application("Checkbox - Iced", Example::update, Example::view) + iced::application(Example::default, 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 1c90708f..533cafd8 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::application("Clock - Iced", Clock::update, Clock::view) + iced::application(Clock::default, 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 b11c8a2b..b16121da 100644 --- a/examples/color_palette/src/main.rs +++ b/examples/color_palette/src/main.rs @@ -12,7 +12,7 @@ use std::ops::RangeInclusive; pub fn main() -> iced::Result { iced::application( - "Color Palette - Iced", + ColorPalette::default, ColorPalette::update, ColorPalette::view, ) diff --git a/examples/combo_box/src/main.rs b/examples/combo_box/src/main.rs index af53b17a..5124c8ef 100644 --- a/examples/combo_box/src/main.rs +++ b/examples/combo_box/src/main.rs @@ -4,7 +4,7 @@ use iced::widget::{ use iced::{Center, Element, Fill}; pub fn main() -> iced::Result { - iced::run("Combo Box - Iced", Example::update, Example::view) + iced::run(Example::update, Example::view) } struct Example { diff --git a/examples/counter/src/main.rs b/examples/counter/src/main.rs index 8f5f9754..d097e052 100644 --- a/examples/counter/src/main.rs +++ b/examples/counter/src/main.rs @@ -2,7 +2,7 @@ use iced::Center; use iced::widget::{Column, button, column, text}; pub fn main() -> iced::Result { - iced::run("A cool counter", Counter::update, Counter::view) + iced::run(Counter::update, Counter::view) } #[derive(Default)] diff --git a/examples/custom_quad/src/main.rs b/examples/custom_quad/src/main.rs index f9c07da9..f0d9c28c 100644 --- a/examples/custom_quad/src/main.rs +++ b/examples/custom_quad/src/main.rs @@ -87,7 +87,7 @@ use iced::widget::{center, column, slider, text}; use iced::{Center, Color, Element, Shadow, Vector}; pub fn main() -> iced::Result { - iced::run("Custom Quad - Iced", Example::update, Example::view) + iced::run(Example::update, Example::view) } struct Example { diff --git a/examples/custom_shader/src/main.rs b/examples/custom_shader/src/main.rs index 8c187d3c..54a1c709 100644 --- a/examples/custom_shader/src/main.rs +++ b/examples/custom_shader/src/main.rs @@ -9,13 +9,9 @@ use iced::window; use iced::{Center, Color, Element, Fill, Subscription}; fn main() -> iced::Result { - iced::application( - "Custom Shader - Iced", - IcedCubes::update, - IcedCubes::view, - ) - .subscription(IcedCubes::subscription) - .run() + iced::application(IcedCubes::default, IcedCubes::update, IcedCubes::view) + .subscription(IcedCubes::subscription) + .run() } struct IcedCubes { diff --git a/examples/custom_widget/src/main.rs b/examples/custom_widget/src/main.rs index d561c2e0..db4255be 100644 --- a/examples/custom_widget/src/main.rs +++ b/examples/custom_widget/src/main.rs @@ -78,7 +78,7 @@ use iced::widget::{center, column, slider, text}; use iced::{Center, Element}; pub fn main() -> iced::Result { - iced::run("Custom Widget - Iced", Example::update, Example::view) + iced::run(Example::update, Example::view) } struct Example { diff --git a/examples/download_progress/src/main.rs b/examples/download_progress/src/main.rs index 0ba8a297..9945fcaa 100644 --- a/examples/download_progress/src/main.rs +++ b/examples/download_progress/src/main.rs @@ -7,12 +7,7 @@ use iced::widget::{Column, button, center, column, progress_bar, text}; use iced::{Center, Element, Function, Right, Task}; pub fn main() -> iced::Result { - iced::application( - "Download Progress - Iced", - Example::update, - Example::view, - ) - .run() + iced::application(Example::default, Example::update, Example::view).run() } #[derive(Debug)] diff --git a/examples/editor/src/main.rs b/examples/editor/src/main.rs index c039672e..af75f581 100644 --- a/examples/editor/src/main.rs +++ b/examples/editor/src/main.rs @@ -12,11 +12,11 @@ use std::path::{Path, PathBuf}; use std::sync::Arc; pub fn main() -> iced::Result { - iced::application("Editor - Iced", Editor::update, Editor::view) + iced::application(Editor::new, Editor::update, Editor::view) .theme(Editor::theme) .font(include_bytes!("../fonts/icons.ttf").as_slice()) .default_font(Font::MONOSPACE) - .run_with(Editor::new) + .run() } struct Editor { diff --git a/examples/events/src/main.rs b/examples/events/src/main.rs index a7d98912..c38b22b9 100644 --- a/examples/events/src/main.rs +++ b/examples/events/src/main.rs @@ -4,7 +4,7 @@ use iced::window; use iced::{Center, Element, Fill, Subscription, Task}; pub fn main() -> iced::Result { - iced::application("Events - Iced", Events::update, Events::view) + iced::application(Events::default, 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 48b0864c..6d1b5c52 100644 --- a/examples/exit/src/main.rs +++ b/examples/exit/src/main.rs @@ -3,7 +3,7 @@ use iced::window; use iced::{Center, Element, Task}; pub fn main() -> iced::Result { - iced::application("Exit - Iced", Exit::update, Exit::view).run() + iced::run(Exit::update, Exit::view) } #[derive(Default)] diff --git a/examples/ferris/src/main.rs b/examples/ferris/src/main.rs index eaf51354..c28b04c6 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::application("Ferris - Iced", Image::update, Image::view) + iced::application(Image::default, Image::update, Image::view) .subscription(Image::subscription) .theme(|_| Theme::TokyoNight) .run() diff --git a/examples/gallery/src/main.rs b/examples/gallery/src/main.rs index caa11016..01e6aab4 100644 --- a/examples/gallery/src/main.rs +++ b/examples/gallery/src/main.rs @@ -21,10 +21,10 @@ use iced::{ use std::collections::HashMap; fn main() -> iced::Result { - iced::application("Gallery - Iced", Gallery::update, Gallery::view) + iced::application(Gallery::new, Gallery::update, Gallery::view) .subscription(Gallery::subscription) .theme(Gallery::theme) - .run_with(Gallery::new) + .run() } struct Gallery { diff --git a/examples/game_of_life/src/main.rs b/examples/game_of_life/src/main.rs index 7793ba77..c18b240e 100644 --- a/examples/game_of_life/src/main.rs +++ b/examples/game_of_life/src/main.rs @@ -14,16 +14,12 @@ use iced::{Center, Element, Fill, Function, Subscription, Task, Theme}; pub fn main() -> iced::Result { tracing_subscriber::fmt::init(); - iced::application( - "Game of Life - Iced", - GameOfLife::update, - GameOfLife::view, - ) - .subscription(GameOfLife::subscription) - .theme(|_| Theme::Dark) - .antialiasing(true) - .centered() - .run() + iced::application(GameOfLife::default, GameOfLife::update, GameOfLife::view) + .subscription(GameOfLife::subscription) + .theme(|_| Theme::Dark) + .antialiasing(true) + .centered() + .run() } struct GameOfLife { diff --git a/examples/geometry/src/main.rs b/examples/geometry/src/main.rs index 0255cdfb..f1025675 100644 --- a/examples/geometry/src/main.rs +++ b/examples/geometry/src/main.rs @@ -157,7 +157,7 @@ use iced::widget::{center_x, center_y, column, scrollable}; use rainbow::rainbow; pub fn main() -> iced::Result { - iced::run("Custom 2D Geometry - Iced", |_: &mut _, _| {}, view) + iced::run((), view) } fn view(_state: &()) -> Element<'_, ()> { diff --git a/examples/gradient/src/main.rs b/examples/gradient/src/main.rs index c103389a..93c40e05 100644 --- a/examples/gradient/src/main.rs +++ b/examples/gradient/src/main.rs @@ -8,7 +8,7 @@ use iced::{Center, Color, Element, Fill, Radians, Theme, color}; pub fn main() -> iced::Result { tracing_subscriber::fmt::init(); - iced::application("Gradient - Iced", Gradient::update, Gradient::view) + iced::application(Gradient::default, Gradient::update, Gradient::view) .style(Gradient::style) .transparent(true) .run() diff --git a/examples/layout/src/main.rs b/examples/layout/src/main.rs index 979a8c30..768180a8 100644 --- a/examples/layout/src/main.rs +++ b/examples/layout/src/main.rs @@ -12,9 +12,10 @@ use iced::{ }; pub fn main() -> iced::Result { - iced::application(Layout::title, Layout::update, Layout::view) + iced::application(Layout::default, Layout::update, Layout::view) .subscription(Layout::subscription) .theme(Layout::theme) + .title(Layout::title) .run() } diff --git a/examples/lazy/src/main.rs b/examples/lazy/src/main.rs index 8f756210..3d2eb61c 100644 --- a/examples/lazy/src/main.rs +++ b/examples/lazy/src/main.rs @@ -8,7 +8,7 @@ use std::collections::HashSet; use std::hash::Hash; pub fn main() -> iced::Result { - iced::run("Lazy - Iced", App::update, App::view) + iced::run(App::update, App::view) } struct App { diff --git a/examples/loading_spinners/src/main.rs b/examples/loading_spinners/src/main.rs index 3b178148..59aab315 100644 --- a/examples/loading_spinners/src/main.rs +++ b/examples/loading_spinners/src/main.rs @@ -12,7 +12,7 @@ use linear::Linear; pub fn main() -> iced::Result { iced::application( - "Loading Spinners - Iced", + LoadingSpinners::default, LoadingSpinners::update, LoadingSpinners::view, ) diff --git a/examples/loupe/src/main.rs b/examples/loupe/src/main.rs index 3dde6872..114f481d 100644 --- a/examples/loupe/src/main.rs +++ b/examples/loupe/src/main.rs @@ -4,7 +4,7 @@ use iced::{Center, Element}; use loupe::loupe; pub fn main() -> iced::Result { - iced::run("Loupe - Iced", Loupe::update, Loupe::view) + iced::run(Loupe::update, Loupe::view) } #[derive(Default)] diff --git a/examples/markdown/src/main.rs b/examples/markdown/src/main.rs index 38a56c6b..99eb9ef8 100644 --- a/examples/markdown/src/main.rs +++ b/examples/markdown/src/main.rs @@ -18,11 +18,11 @@ use std::io; use std::sync::Arc; pub fn main() -> iced::Result { - iced::application("Markdown - Iced", Markdown::update, Markdown::view) + iced::application(Markdown::new, Markdown::update, Markdown::view) .font(icon::FONT) .subscription(Markdown::subscription) .theme(Markdown::theme) - .run_with(Markdown::new) + .run() } struct Markdown { diff --git a/examples/modal/src/main.rs b/examples/modal/src/main.rs index 067ca24d..236f32f8 100644 --- a/examples/modal/src/main.rs +++ b/examples/modal/src/main.rs @@ -10,7 +10,7 @@ use iced::{Bottom, Color, Element, Fill, Subscription, Task}; use std::fmt; pub fn main() -> iced::Result { - iced::application("Modal - Iced", App::update, App::view) + iced::application(App::default, 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 8cec9d4c..3f9efc31 100644 --- a/examples/multi_window/src/main.rs +++ b/examples/multi_window/src/main.rs @@ -10,11 +10,12 @@ use iced::{ use std::collections::BTreeMap; fn main() -> iced::Result { - iced::daemon(Example::title, Example::update, Example::view) + iced::daemon(Example::new, Example::update, Example::view) .subscription(Example::subscription) + .title(Example::title) .theme(Example::theme) .scale_factor(Example::scale_factor) - .run_with(Example::new) + .run() } struct Example { diff --git a/examples/multitouch/src/main.rs b/examples/multitouch/src/main.rs index bda3b8f7..3a23fd83 100644 --- a/examples/multitouch/src/main.rs +++ b/examples/multitouch/src/main.rs @@ -12,7 +12,7 @@ use std::collections::HashMap; pub fn main() -> iced::Result { tracing_subscriber::fmt::init(); - iced::application("Multitouch - Iced", Multitouch::update, Multitouch::view) + iced::application(Multitouch::default, 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 17ba5804..aad55122 100644 --- a/examples/pane_grid/src/main.rs +++ b/examples/pane_grid/src/main.rs @@ -6,7 +6,7 @@ use iced::widget::{ use iced::{Center, Color, Element, Fill, Size, Subscription}; pub fn main() -> iced::Result { - iced::application("Pane Grid - Iced", Example::update, Example::view) + iced::application(Example::default, Example::update, Example::view) .subscription(Example::subscription) .run() } diff --git a/examples/pick_list/src/main.rs b/examples/pick_list/src/main.rs index d8b2b389..33aa6cda 100644 --- a/examples/pick_list/src/main.rs +++ b/examples/pick_list/src/main.rs @@ -2,7 +2,7 @@ use iced::widget::{column, pick_list, scrollable, vertical_space}; use iced::{Center, Element, Fill}; pub fn main() -> iced::Result { - iced::run("Pick List - Iced", Example::update, Example::view) + iced::run(Example::update, Example::view) } #[derive(Default)] diff --git a/examples/pokedex/src/main.rs b/examples/pokedex/src/main.rs index 2e972f6b..cfbef4af 100644 --- a/examples/pokedex/src/main.rs +++ b/examples/pokedex/src/main.rs @@ -3,8 +3,9 @@ use iced::widget::{self, center, column, image, row, text}; use iced::{Center, Element, Fill, Right, Task}; pub fn main() -> iced::Result { - iced::application(Pokedex::title, Pokedex::update, Pokedex::view) - .run_with(Pokedex::new) + iced::application(Pokedex::new, Pokedex::update, Pokedex::view) + .title(Pokedex::title) + .run() } #[derive(Debug)] diff --git a/examples/progress_bar/src/main.rs b/examples/progress_bar/src/main.rs index 07e07f14..df1c4dc2 100644 --- a/examples/progress_bar/src/main.rs +++ b/examples/progress_bar/src/main.rs @@ -5,7 +5,7 @@ use iced::widget::{ }; pub fn main() -> iced::Result { - iced::run("Progress Bar - Iced", Progress::update, Progress::view) + iced::run(Progress::update, Progress::view) } #[derive(Default)] diff --git a/examples/qr_code/src/main.rs b/examples/qr_code/src/main.rs index 2c892e3f..91625c95 100644 --- a/examples/qr_code/src/main.rs +++ b/examples/qr_code/src/main.rs @@ -7,7 +7,7 @@ use std::ops::RangeInclusive; pub fn main() -> iced::Result { iced::application( - "QR Code Generator - Iced", + QRGenerator::default, QRGenerator::update, QRGenerator::view, ) diff --git a/examples/screenshot/src/main.rs b/examples/screenshot/src/main.rs index 7766542d..e4d0fc68 100644 --- a/examples/screenshot/src/main.rs +++ b/examples/screenshot/src/main.rs @@ -15,7 +15,7 @@ use ::image::ColorType; fn main() -> iced::Result { tracing_subscriber::fmt::init(); - iced::application("Screenshot - Iced", Example::update, Example::view) + iced::application(Example::default, Example::update, Example::view) .subscription(Example::subscription) .run() } diff --git a/examples/scrollable/src/main.rs b/examples/scrollable/src/main.rs index fec4e1b4..793aae2a 100644 --- a/examples/scrollable/src/main.rs +++ b/examples/scrollable/src/main.rs @@ -11,7 +11,7 @@ static SCROLLABLE_ID: LazyLock = pub fn main() -> iced::Result { iced::application( - "Scrollable - Iced", + ScrollableDemo::default, ScrollableDemo::update, ScrollableDemo::view, ) diff --git a/examples/sierpinski_triangle/src/main.rs b/examples/sierpinski_triangle/src/main.rs index a4a89455..60fc1d29 100644 --- a/examples/sierpinski_triangle/src/main.rs +++ b/examples/sierpinski_triangle/src/main.rs @@ -8,7 +8,7 @@ use std::fmt::Debug; fn main() -> iced::Result { iced::application( - "Sierpinski Triangle - Iced", + SierpinskiEmulator::default, SierpinskiEmulator::update, SierpinskiEmulator::view, ) diff --git a/examples/slider/src/main.rs b/examples/slider/src/main.rs index ffb5475f..ea11aad9 100644 --- a/examples/slider/src/main.rs +++ b/examples/slider/src/main.rs @@ -2,7 +2,7 @@ use iced::widget::{column, container, iced, slider, text, vertical_slider}; use iced::{Center, Element, Fill}; pub fn main() -> iced::Result { - iced::run("Slider - Iced", Slider::update, Slider::view) + iced::run(Slider::update, Slider::view) } #[derive(Debug, Clone)] diff --git a/examples/solar_system/src/main.rs b/examples/solar_system/src/main.rs index 1e74f2bd..07450309 100644 --- a/examples/solar_system/src/main.rs +++ b/examples/solar_system/src/main.rs @@ -22,7 +22,7 @@ pub fn main() -> iced::Result { tracing_subscriber::fmt::init(); iced::application( - "Solar System - Iced", + SolarSystem::default, SolarSystem::update, SolarSystem::view, ) diff --git a/examples/stopwatch/src/main.rs b/examples/stopwatch/src/main.rs index 5055216f..e4d5d7d0 100644 --- a/examples/stopwatch/src/main.rs +++ b/examples/stopwatch/src/main.rs @@ -4,7 +4,7 @@ use iced::widget::{button, center, column, row, text}; use iced::{Center, Element, Subscription, Theme}; pub fn main() -> iced::Result { - iced::application("Stopwatch - Iced", Stopwatch::update, Stopwatch::view) + iced::application(Stopwatch::default, 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 fce2b162..3f2f50db 100644 --- a/examples/styling/src/main.rs +++ b/examples/styling/src/main.rs @@ -7,7 +7,7 @@ use iced::widget::{ use iced::{Center, Element, Fill, Subscription, Theme}; pub fn main() -> iced::Result { - iced::application("Styling - Iced", Styling::update, Styling::view) + iced::application(Styling::default, Styling::update, Styling::view) .subscription(Styling::subscription) .theme(Styling::theme) .run() diff --git a/examples/svg/src/main.rs b/examples/svg/src/main.rs index 14d8f164..ce580ca1 100644 --- a/examples/svg/src/main.rs +++ b/examples/svg/src/main.rs @@ -2,7 +2,7 @@ use iced::widget::{center, center_x, checkbox, column, svg}; use iced::{Element, Fill, color}; pub fn main() -> iced::Result { - iced::run("SVG - Iced", Tiger::update, Tiger::view) + iced::run(Tiger::update, Tiger::view) } #[derive(Debug, Default)] diff --git a/examples/system_information/src/main.rs b/examples/system_information/src/main.rs index 27980c9b..f0bdb32d 100644 --- a/examples/system_information/src/main.rs +++ b/examples/system_information/src/main.rs @@ -2,12 +2,7 @@ use iced::widget::{button, center, column, text}; use iced::{Element, Task, system}; pub fn main() -> iced::Result { - iced::application( - "System Information - Iced", - Example::update, - Example::view, - ) - .run_with(Example::new) + iced::application(Example::new, Example::update, Example::view).run() } #[derive(Default)] diff --git a/examples/the_matrix/src/main.rs b/examples/the_matrix/src/main.rs index 53e268c1..7f9383d2 100644 --- a/examples/the_matrix/src/main.rs +++ b/examples/the_matrix/src/main.rs @@ -10,7 +10,7 @@ use std::cell::RefCell; pub fn main() -> iced::Result { tracing_subscriber::fmt::init(); - iced::application("The Matrix - Iced", TheMatrix::update, TheMatrix::view) + iced::application(TheMatrix::default, 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 dc314df8..42ca62fb 100644 --- a/examples/toast/src/main.rs +++ b/examples/toast/src/main.rs @@ -9,7 +9,7 @@ use iced::{Center, Element, Fill, Subscription, Task}; use toast::{Status, Toast}; pub fn main() -> iced::Result { - iced::application("Toast - Iced", App::update, App::view) + iced::application(App::default, App::update, App::view) .subscription(App::subscription) .run() } diff --git a/examples/todos/src/main.rs b/examples/todos/src/main.rs index 65a34c64..5425c986 100644 --- a/examples/todos/src/main.rs +++ b/examples/todos/src/main.rs @@ -15,11 +15,12 @@ pub fn main() -> iced::Result { #[cfg(not(target_arch = "wasm32"))] tracing_subscriber::fmt::init(); - iced::application(Todos::title, Todos::update, Todos::view) + iced::application(Todos::new, Todos::update, Todos::view) .subscription(Todos::subscription) + .title(Todos::title) .font(Todos::ICON_FONT) .window_size((500.0, 800.0)) - .run_with(Todos::new) + .run() } #[derive(Debug)] diff --git a/examples/tooltip/src/main.rs b/examples/tooltip/src/main.rs index 9e4e7cbe..599fcd4e 100644 --- a/examples/tooltip/src/main.rs +++ b/examples/tooltip/src/main.rs @@ -3,7 +3,7 @@ use iced::widget::tooltip::Position; use iced::widget::{button, center, container, tooltip}; pub fn main() -> iced::Result { - iced::run("Tooltip - Iced", Tooltip::update, Tooltip::view) + iced::run(Tooltip::update, Tooltip::view) } #[derive(Default)] diff --git a/examples/tour/src/main.rs b/examples/tour/src/main.rs index 060cd6d0..f34c9da0 100644 --- a/examples/tour/src/main.rs +++ b/examples/tour/src/main.rs @@ -17,7 +17,8 @@ pub fn main() -> iced::Result { #[cfg(not(target_arch = "wasm32"))] tracing_subscriber::fmt::init(); - iced::application(Tour::title, Tour::update, Tour::view) + iced::application(Tour::default, Tour::update, Tour::view) + .title(Tour::title) .centered() .run() } diff --git a/examples/url_handler/src/main.rs b/examples/url_handler/src/main.rs index 50a055f3..7c3e3f35 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::application("URL Handler - Iced", App::update, App::view) + iced::application(App::default, 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 c4ce7072..6bdd92bc 100644 --- a/examples/vectorial_text/src/main.rs +++ b/examples/vectorial_text/src/main.rs @@ -7,7 +7,7 @@ use iced::{Center, Element, Fill, Point, Rectangle, Renderer, Theme, Vector}; pub fn main() -> iced::Result { iced::application( - "Vectorial Text - Iced", + VectorialText::default, VectorialText::update, VectorialText::view, ) diff --git a/examples/visible_bounds/src/main.rs b/examples/visible_bounds/src/main.rs index f8f9f420..0b152e44 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::application("Visible Bounds - Iced", Example::update, Example::view) + iced::application(Example::default, 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 b918c479..069ca391 100644 --- a/examples/websocket/src/main.rs +++ b/examples/websocket/src/main.rs @@ -7,9 +7,9 @@ use iced::{Center, Element, Fill, Subscription, Task, color}; use std::sync::LazyLock; pub fn main() -> iced::Result { - iced::application("WebSocket - Iced", WebSocket::update, WebSocket::view) + iced::application(WebSocket::new, WebSocket::update, WebSocket::view) .subscription(WebSocket::subscription) - .run_with(WebSocket::new) + .run() } struct WebSocket { diff --git a/graphics/src/settings.rs b/graphics/src/settings.rs index 118ed73b..6773937a 100644 --- a/graphics/src/settings.rs +++ b/graphics/src/settings.rs @@ -1,5 +1,5 @@ use crate::Antialiasing; -use crate::core::{Font, Pixels}; +use crate::core::{self, Font, Pixels}; /// The settings of a renderer. #[derive(Debug, Clone, Copy, PartialEq)] @@ -27,3 +27,13 @@ impl Default for Settings { } } } + +impl From for Settings { + fn from(settings: core::Settings) -> Self { + Self { + default_font: settings.default_font, + default_text_size: settings.default_text_size, + antialiasing: settings.antialiasing.then_some(Antialiasing::MSAAx4), + } + } +} diff --git a/program/Cargo.toml b/program/Cargo.toml new file mode 100644 index 00000000..07880705 --- /dev/null +++ b/program/Cargo.toml @@ -0,0 +1,18 @@ +[package] +name = "iced_program" +version.workspace = true +authors.workspace = true +edition.workspace = true +license.workspace = true +repository.workspace = true +homepage.workspace = true +categories.workspace = true +keywords.workspace = true +rust-version.workspace = true + +[dependencies] +iced_graphics.workspace = true +iced_runtime.workspace = true + +[lints] +workspace = true diff --git a/src/program.rs b/program/src/lib.rs similarity index 74% rename from src/program.rs rename to program/src/lib.rs index 602e8227..7e5757de 100644 --- a/src/program.rs +++ b/program/src/lib.rs @@ -1,14 +1,21 @@ -use crate::core::text; -use crate::graphics::compositor; -use crate::shell; -use crate::theme; -use crate::window; -use crate::{Element, Executor, Result, Settings, Subscription, Task}; +//! The definition of an iced program. +pub use iced_graphics as graphics; +pub use iced_runtime as runtime; +pub use iced_runtime::core; +pub use iced_runtime::futures; -/// The internal definition of a [`Program`]. +use crate::core::Element; +use crate::core::text; +use crate::core::theme; +use crate::core::window; +use crate::futures::{Executor, Subscription}; +use crate::graphics::compositor; +use crate::runtime::Task; + +/// An interactive, native, cross-platform, multi-windowed application. /// -/// You should not need to implement this trait directly. Instead, use the -/// methods available in the [`Program`] struct. +/// A [`Program`] can execute asynchronous actions by returning a +/// [`Task`] in some of its methods. #[allow(missing_docs)] pub trait Program: Sized { /// The state of the program. @@ -26,6 +33,11 @@ pub trait Program: Sized { /// The executor of the program. type Executor: Executor; + /// Returns the unique name of the [`Program`]. + fn name() -> &'static str; + + fn boot(&self) -> (Self::State, Task); + fn update( &self, state: &mut Self::State, @@ -39,7 +51,32 @@ pub trait Program: Sized { ) -> Element<'a, Self::Message, Self::Theme, Self::Renderer>; fn title(&self, _state: &Self::State, _window: window::Id) -> String { - String::from("A cool iced application!") + let mut title = String::new(); + + for (i, part) in Self::name().split("_").enumerate() { + use std::borrow::Cow; + + let part = match part { + "a" | "an" | "of" | "in" | "and" => Cow::Borrowed(part), + _ => { + let mut part = part.to_owned(); + + if let Some(first_letter) = part.get_mut(0..1) { + first_letter.make_ascii_uppercase(); + } + + Cow::Owned(part) + } + }; + + if i > 0 { + title.push(' '); + } + + title.push_str(&part); + } + + format!("{title} - Iced") } fn subscription( @@ -60,142 +97,9 @@ pub trait Program: Sized { 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 - fn run( - self, - settings: Settings, - window_settings: Option, - ) -> Result - where - Self: 'static, - Self::State: Default, - { - self.run_with(settings, window_settings, || { - (Self::State::default(), Task::none()) - }) - } - - /// Runs the [`Program`] with the given [`Settings`] and a closure that creates the initial state. - fn run_with( - self, - settings: Settings, - window_settings: Option, - initialize: I, - ) -> Result - where - Self: 'static, - I: FnOnce() -> (Self::State, Task) + 'static, - { - use std::marker::PhantomData; - - struct Instance { - program: P, - state: P::State, - _initialize: PhantomData, - } - - impl (P::State, Task)> - shell::Program for Instance - { - type Message = P::Message; - type Theme = P::Theme; - type Renderer = P::Renderer; - type Flags = (P, I); - type Executor = P::Executor; - - fn name() -> &'static str { - std::any::type_name::() - } - - fn new( - (program, initialize): Self::Flags, - ) -> (Self, Task) { - let (state, task) = initialize(); - - ( - Self { - program, - state, - _initialize: PhantomData, - }, - task, - ) - } - - fn title(&self, window: window::Id) -> String { - self.program.title(&self.state, window) - } - - fn update( - &mut self, - message: Self::Message, - ) -> Task { - self.program.update(&mut self.state, message) - } - - fn view( - &self, - window: window::Id, - ) -> crate::Element<'_, Self::Message, Self::Theme, Self::Renderer> - { - self.program.view(&self.state, window) - } - - fn subscription(&self) -> Subscription { - self.program.subscription(&self.state) - } - - fn theme(&self, window: window::Id) -> Self::Theme { - self.program.theme(&self.state, window) - } - - fn style(&self, theme: &Self::Theme) -> theme::Style { - self.program.style(&self.state, theme) - } - - fn scale_factor(&self, window: window::Id) -> f64 { - self.program.scale_factor(&self.state, window) - } - } - - #[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(shell::program::run::< - Instance, - ::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), - )?) - } } +/// Decorates a [`Program`] with the given title function. pub fn with_title( program: P, title: impl Fn(&P::State, window::Id) -> String, @@ -220,6 +124,14 @@ pub fn with_title( (self.title)(state, window) } + fn name() -> &'static str { + P::name() + } + + fn boot(&self) -> (Self::State, Task) { + self.program.boot() + } + fn update( &self, state: &mut Self::State, @@ -267,6 +179,7 @@ pub fn with_title( WithTitle { program, title } } +/// Decorates a [`Program`] with the given subscription function. pub fn with_subscription( program: P, f: impl Fn(&P::State) -> Subscription, @@ -293,6 +206,14 @@ pub fn with_subscription( (self.subscription)(state) } + fn name() -> &'static str { + P::name() + } + + fn boot(&self) -> (Self::State, Task) { + self.program.boot() + } + fn update( &self, state: &mut Self::State, @@ -340,6 +261,7 @@ pub fn with_subscription( } } +/// Decorates a [`Program`] with the given theme function. pub fn with_theme( program: P, f: impl Fn(&P::State, window::Id) -> P::Theme, @@ -367,6 +289,14 @@ pub fn with_theme( (self.theme)(state, window) } + fn name() -> &'static str { + P::name() + } + + fn boot(&self) -> (Self::State, Task) { + self.program.boot() + } + fn title(&self, state: &Self::State, window: window::Id) -> String { self.program.title(state, window) } @@ -410,6 +340,7 @@ pub fn with_theme( WithTheme { program, theme: f } } +/// Decorates a [`Program`] with the given style function. pub fn with_style( program: P, f: impl Fn(&P::State, &P::Theme) -> theme::Style, @@ -437,6 +368,14 @@ pub fn with_style( (self.style)(state, theme) } + fn name() -> &'static str { + P::name() + } + + fn boot(&self) -> (Self::State, Task) { + self.program.boot() + } + fn title(&self, state: &Self::State, window: window::Id) -> String { self.program.title(state, window) } @@ -480,6 +419,7 @@ pub fn with_style( WithStyle { program, style: f } } +/// Decorates a [`Program`] with the given scale factor function. pub fn with_scale_factor( program: P, f: impl Fn(&P::State, window::Id) -> f64, @@ -503,6 +443,14 @@ pub fn with_scale_factor( self.program.title(state, window) } + fn name() -> &'static str { + P::name() + } + + fn boot(&self) -> (Self::State, Task) { + self.program.boot() + } + fn update( &self, state: &mut Self::State, @@ -553,6 +501,7 @@ pub fn with_scale_factor( } } +/// Decorates a [`Program`] with the given executor function. pub fn with_executor( program: P, ) -> impl Program { @@ -577,6 +526,14 @@ pub fn with_executor( self.program.title(state, window) } + fn name() -> &'static str { + P::name() + } + + fn boot(&self) -> (Self::State, Task) { + self.program.boot() + } + fn update( &self, state: &mut Self::State, @@ -631,3 +588,57 @@ pub fn with_executor( pub trait Renderer: text::Renderer + compositor::Default {} impl Renderer for T where T: text::Renderer + compositor::Default {} + +/// A particular instance of a running [`Program`]. +#[allow(missing_debug_implementations)] +pub struct Instance { + program: P, + state: P::State, +} + +impl Instance

{ + /// Creates a new [`Instance`] of the given [`Program`]. + pub fn new(program: P) -> (Self, Task) { + let (state, task) = program.boot(); + + (Self { program, state }, task) + } + + /// Returns the current title of the [`Instance`]. + pub fn title(&self, window: window::Id) -> String { + self.program.title(&self.state, window) + } + + /// Processes the given message and updates the [`Instance`]. + pub fn update(&mut self, message: P::Message) -> Task { + self.program.update(&mut self.state, message) + } + + /// Produces the current widget tree of the [`Instance`]. + pub fn view( + &self, + window: window::Id, + ) -> Element<'_, P::Message, P::Theme, P::Renderer> { + self.program.view(&self.state, window) + } + + /// Returns the current [`Subscription`] of the [`Instance`]. + pub fn subscription(&self) -> Subscription { + self.program.subscription(&self.state) + } + + /// Returns the current theme of the [`Instance`]. + pub fn theme(&self, window: window::Id) -> P::Theme { + self.program.theme(&self.state, window) + } + + /// Returns the current [`theme::Style`] of the [`Instance`]. + pub fn style(&self, theme: &P::Theme) -> theme::Style { + self.program.style(&self.state, theme) + } + + /// Returns the current scale factor of the [`Instance`]. + pub fn scale_factor(&self, window: window::Id) -> f64 { + self.program.scale_factor(&self.state, window) + } +} diff --git a/src/application.rs b/src/application.rs index c79ed62b..da0fbd37 100644 --- a/src/application.rs +++ b/src/application.rs @@ -6,7 +6,7 @@ //! use iced::Theme; //! //! pub fn main() -> iced::Result { -//! iced::application("A counter", update, view) +//! iced::application(u64::default, update, view) //! .theme(|_| Theme::Dark) //! .centered() //! .run() @@ -31,6 +31,7 @@ //! } //! ``` use crate::program::{self, Program}; +use crate::shell; use crate::theme; use crate::window; use crate::{ @@ -39,14 +40,14 @@ use crate::{ use std::borrow::Cow; -/// Creates an iced [`Application`] given its title, update, and view logic. +/// Creates an iced [`Application`] given its update and view logic. /// /// # Example /// ```no_run /// use iced::widget::{button, column, text, Column}; /// /// pub fn main() -> iced::Result { -/// iced::application("A counter", update, view).run() +/// iced::application(u64::default, update, view).run() /// } /// /// #[derive(Debug, Clone)] @@ -68,7 +69,7 @@ use std::borrow::Cow; /// } /// ``` pub fn application( - title: impl Title, + new: impl New, update: impl Update, view: impl for<'a> self::View<'a, State, Message, Theme, Renderer>, ) -> Application> @@ -80,7 +81,8 @@ where { use std::marker::PhantomData; - struct Instance { + struct Instance { + new: New, update: Update, view: View, _state: PhantomData, @@ -89,12 +91,13 @@ where _renderer: PhantomData, } - impl Program - for Instance + impl Program + for Instance where Message: Send + std::fmt::Debug + 'static, Theme: Default + theme::Base, Renderer: program::Renderer, + New: self::New, Update: self::Update, View: for<'a> self::View<'a, State, Message, Theme, Renderer>, { @@ -104,6 +107,16 @@ where type Renderer = Renderer; type Executor = iced_futures::backend::default::Executor; + fn name() -> &'static str { + let name = std::any::type_name::(); + + name.split("::").next().unwrap_or("a_cool_application") + } + + fn boot(&self) -> (State, Task) { + self.new.new() + } + fn update( &self, state: &mut Self::State, @@ -123,6 +136,7 @@ where Application { raw: Instance { + new, update, view, _state: PhantomData, @@ -133,7 +147,6 @@ where settings: Settings::default(), window: window::Settings::default(), } - .title(title) } /// The underlying definition and configuration of an iced application. @@ -161,19 +174,8 @@ impl Application

{ pub fn run(self) -> Result where Self: 'static, - P::State: Default, { - self.raw.run(self.settings, Some(self.window)) - } - - /// Runs the [`Application`] with a closure that creates the initial state. - pub fn run_with(self, initialize: I) -> Result - where - Self: 'static, - I: FnOnce() -> (P::State, Task) + 'static, - { - self.raw - .run_with(self.settings, Some(self.window), initialize) + Ok(shell::run(self.raw, self.settings, Some(self.window))?) } /// Sets the [`Settings`] that will be used to run the [`Application`]. @@ -305,7 +307,7 @@ impl Application

{ } /// Sets the [`Title`] of the [`Application`]. - pub(crate) fn title( + pub fn title( self, title: impl Title, ) -> Application< @@ -395,6 +397,42 @@ impl Application

{ } } +/// The logic to initialize the `State` of some [`Application`]. +pub trait New { + /// Initializes the [`Application`] state. + #[allow(clippy::new_ret_no_self)] + #[allow(clippy::wrong_self_convention)] + fn new(&self) -> (State, Task); +} + +impl New for T +where + T: Fn() -> C, + C: IntoState, +{ + fn new(&self) -> (State, Task) { + self().into_state() + } +} + +/// TODO +pub trait IntoState { + /// TODO + fn into_state(self) -> (State, Task); +} + +impl IntoState for State { + fn into_state(self) -> (State, Task) { + (self, Task::none()) + } +} + +impl IntoState for (State, Task) { + fn into_state(self) -> (State, Task) { + self + } +} + /// The title logic of some [`Application`]. /// /// This trait is implemented both for `&static str` and diff --git a/src/daemon.rs b/src/daemon.rs index fd6d0278..322cf23e 100644 --- a/src/daemon.rs +++ b/src/daemon.rs @@ -1,6 +1,7 @@ //! Create and run daemons that run in the background. use crate::application; use crate::program::{self, Program}; +use crate::shell; use crate::theme; use crate::window; use crate::{Element, Executor, Font, Result, Settings, Subscription, Task}; @@ -18,7 +19,7 @@ use std::borrow::Cow; /// /// [`exit`]: crate::exit pub fn daemon( - title: impl Title, + boot: impl application::New, update: impl application::Update, view: impl for<'a> self::View<'a, State, Message, Theme, Renderer>, ) -> Daemon> @@ -30,7 +31,8 @@ where { use std::marker::PhantomData; - struct Instance { + struct Instance { + boot: Boot, update: Update, view: View, _state: PhantomData, @@ -39,12 +41,13 @@ where _renderer: PhantomData, } - impl Program - for Instance + impl Program + for Instance where Message: Send + std::fmt::Debug + 'static, Theme: Default + theme::Base, Renderer: program::Renderer, + Boot: application::New, Update: application::Update, View: for<'a> self::View<'a, State, Message, Theme, Renderer>, { @@ -54,6 +57,16 @@ where type Renderer = Renderer; type Executor = iced_futures::backend::default::Executor; + fn name() -> &'static str { + let name = std::any::type_name::(); + + name.split("::").next().unwrap_or("a_cool_daemon") + } + + fn boot(&self) -> (Self::State, Task) { + self.boot.new() + } + fn update( &self, state: &mut Self::State, @@ -73,6 +86,7 @@ where Daemon { raw: Instance { + boot, update, view, _state: PhantomData, @@ -82,7 +96,6 @@ where }, settings: Settings::default(), } - .title(title) } /// The underlying definition and configuration of an iced daemon. @@ -109,18 +122,8 @@ impl Daemon

{ pub fn run(self) -> Result where Self: 'static, - P::State: Default, { - self.raw.run(self.settings, None) - } - - /// Runs the [`Daemon`] with a closure that creates the initial state. - pub fn run_with(self, initialize: I) -> Result - where - Self: 'static, - I: FnOnce() -> (P::State, Task) + 'static, - { - self.raw.run_with(self.settings, None, initialize) + Ok(shell::run(self.raw, self.settings, None)?) } /// Sets the [`Settings`] that will be used to run the [`Daemon`]. @@ -157,7 +160,7 @@ impl Daemon

{ } /// Sets the [`Title`] of the [`Daemon`]. - pub(crate) fn title( + pub fn title( self, title: impl Title, ) -> Daemon< diff --git a/src/lib.rs b/src/lib.rs index 95820ed7..ed224d0c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -31,7 +31,7 @@ //! //! ```rust,no_run //! pub fn main() -> iced::Result { -//! iced::run("A cool counter", update, view) +//! iced::run(update, view) //! } //! # fn update(state: &mut (), message: ()) {} //! # fn view(state: &()) -> iced::Element<()> { iced::widget::text("").into() } @@ -198,16 +198,20 @@ //! calling [`run`]: //! //! ```rust,no_run -//! # #[derive(Default)] //! # struct State; //! use iced::Theme; //! //! pub fn main() -> iced::Result { -//! iced::application("A cool application", update, view) +//! iced::application(new, update, view) //! .theme(theme) //! .run() //! } //! +//! fn new() -> State { +//! // ... +//! # State +//! } +//! //! fn theme(state: &State) -> Theme { //! Theme::TokyoNight //! } @@ -335,7 +339,6 @@ //! You will need to define a `subscription` function and use the [`Application`] builder: //! //! ```rust,no_run -//! # #[derive(Default)] //! # struct State; //! use iced::window; //! use iced::{Size, Subscription}; @@ -346,7 +349,7 @@ //! } //! //! pub fn main() -> iced::Result { -//! iced::application("A cool application", update, view) +//! iced::application(new, update, view) //! .subscription(subscription) //! .run() //! } @@ -354,6 +357,7 @@ //! fn subscription(state: &State) -> Subscription { //! window::resize_events().map(|(_id, size)| Message::WindowResized(size)) //! } +//! # fn new() -> State { State } //! # fn update(state: &mut State, message: Message) {} //! # fn view(state: &State) -> iced::Element { iced::widget::text("").into() } //! ``` @@ -475,6 +479,7 @@ use iced_widget::graphics; use iced_widget::renderer; use iced_winit as shell; use iced_winit::core; +use iced_winit::program; use iced_winit::runtime; pub use iced_futures::futures; @@ -487,7 +492,6 @@ pub use iced_highlighter as highlighter; pub use iced_renderer::wgpu::wgpu; mod error; -mod program; pub mod application; pub mod daemon; @@ -658,7 +662,7 @@ pub type Result = std::result::Result<(), Error>; /// use iced::widget::{button, column, text, Column}; /// /// pub fn main() -> iced::Result { -/// iced::run("A counter", update, view) +/// iced::run(update, view) /// } /// /// #[derive(Debug, Clone)] @@ -680,7 +684,6 @@ pub type Result = std::result::Result<(), Error>; /// } /// ``` pub fn run( - title: impl application::Title + 'static, update: impl application::Update + 'static, view: impl for<'a> application::View<'a, State, Message, Theme, Renderer> + 'static, @@ -691,5 +694,5 @@ where Theme: Default + theme::Base + 'static, Renderer: program::Renderer + 'static, { - application(title, update, view).run() + application(State::default, update, view).run() } diff --git a/winit/Cargo.toml b/winit/Cargo.toml index d5eb9563..f2157978 100644 --- a/winit/Cargo.toml +++ b/winit/Cargo.toml @@ -26,9 +26,7 @@ unconditional-rendering = [] [dependencies] iced_debug.workspace = true -iced_futures.workspace = true -iced_graphics.workspace = true -iced_runtime.workspace = true +iced_program.workspace = true log.workspace = true rustc-hash.workspace = true diff --git a/winit/src/error.rs b/winit/src/error.rs index 7687fb17..2b7f4344 100644 --- a/winit/src/error.rs +++ b/winit/src/error.rs @@ -18,7 +18,7 @@ pub enum Error { } impl From for Error { - fn from(error: iced_graphics::Error) -> Error { + fn from(error: graphics::Error) -> Error { Error::GraphicsCreationFailed(error) } } diff --git a/winit/src/lib.rs b/winit/src/lib.rs index d1b2ae99..ff5e814a 100644 --- a/winit/src/lib.rs +++ b/winit/src/lib.rs @@ -18,30 +18,1501 @@ html_logo_url = "https://raw.githubusercontent.com/iced-rs/iced/9ab6923e943f784985e9ef9ca28b10278297225d/docs/logo.svg" )] #![cfg_attr(docsrs, feature(doc_auto_cfg))] -pub use iced_graphics as graphics; -pub use iced_runtime as runtime; -pub use iced_runtime::core; -pub use iced_runtime::debug; -pub use iced_runtime::futures; +pub use iced_program as program; +pub use program::core; +pub use program::graphics; +pub use program::runtime; +pub use runtime::debug; +pub use runtime::futures; pub use winit; 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; +mod window; pub use clipboard::Clipboard; pub use error::Error; pub use proxy::Proxy; -pub use settings::Settings; -#[cfg(feature = "program")] -pub use program::Program; +use crate::core::keyboard; +use crate::core::mouse; +use crate::core::renderer; +use crate::core::theme; +use crate::core::time::Instant; +use crate::core::widget::operation; +use crate::core::{Point, Settings, Size}; +use crate::futures::futures::channel::mpsc; +use crate::futures::futures::channel::oneshot; +use crate::futures::futures::task; +use crate::futures::futures::{Future, StreamExt}; +use crate::futures::subscription; +use crate::futures::{Executor, Runtime}; +use crate::graphics::{Compositor, compositor}; +use crate::runtime::user_interface::{self, UserInterface}; +use crate::runtime::{Action, Task}; + +use program::Program; +use window::WindowManager; + +use rustc_hash::FxHashMap; +use std::borrow::Cow; +use std::mem::ManuallyDrop; +use std::sync::Arc; + +/// Runs a [`Program`] with the provided settings. +pub fn run

( + program: P, + settings: Settings, + window_settings: Option, +) -> Result<(), Error> +where + P: Program + 'static, + P::Theme: theme::Base, +{ + use winit::event_loop::EventLoop; + + debug::init(P::name()); + + let boot_span = debug::boot(); + + let graphics_settings = settings.clone().into(); + 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 = + P::Executor::new().map_err(Error::ExecutorCreationFailed)?; + executor.spawn(worker); + + Runtime::new(executor, proxy.clone()) + }; + + let (program, task) = runtime.enter(|| program::Instance::new(program)); + let is_daemon = window_settings.is_none(); + + let task = if let Some(window_settings) = window_settings { + let mut task = Some(task); + + let (_id, open) = runtime::window::open(window_settings); + + open.then(move |_| task.take().unwrap_or(Task::none())) + } else { + task + }; + + if let Some(stream) = runtime::task::into_stream(task) { + runtime.run(stream); + } + + runtime.track(subscription::into_recipes( + runtime.enter(|| program.subscription().map(Action::Output)), + )); + + let (event_sender, event_receiver) = mpsc::unbounded(); + let (control_sender, control_receiver) = mpsc::unbounded(); + + let instance = Box::pin(run_instance::

( + program, + runtime, + proxy.clone(), + event_receiver, + control_sender, + is_daemon, + graphics_settings, + settings.fonts, + )); + + let context = task::Context::from_waker(task::noop_waker_ref()); + + struct Runner { + instance: std::pin::Pin>, + context: task::Context<'static>, + id: Option, + sender: mpsc::UnboundedSender>>, + receiver: mpsc::UnboundedReceiver, + error: Option, + + #[cfg(target_arch = "wasm32")] + canvas: Option, + } + + let runner = Runner { + instance, + context, + id: settings.id, + sender: event_sender, + receiver: control_receiver, + error: None, + + #[cfg(target_arch = "wasm32")] + canvas: None, + }; + + boot_span.finish(); + + impl winit::application::ApplicationHandler> + for Runner + where + Message: std::fmt::Debug, + F: Future, + { + fn resumed( + &mut self, + _event_loop: &winit::event_loop::ActiveEventLoop, + ) { + } + + fn new_events( + &mut self, + event_loop: &winit::event_loop::ActiveEventLoop, + cause: winit::event::StartCause, + ) { + self.process_event( + event_loop, + Event::EventLoopAwakened(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, + Event::EventLoopAwakened(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, + Event::EventLoopAwakened( + winit::event::Event::AboutToWait, + ), + ); + } + } + } + + fn user_event( + &mut self, + event_loop: &winit::event_loop::ActiveEventLoop, + action: Action, + ) { + self.process_event( + event_loop, + Event::EventLoopAwakened(winit::event::Event::UserEvent( + action, + )), + ); + } + + fn received_url( + &mut self, + event_loop: &winit::event_loop::ActiveEventLoop, + url: String, + ) { + self.process_event( + event_loop, + Event::EventLoopAwakened( + winit::event::Event::PlatformSpecific( + winit::event::PlatformSpecific::MacOS( + winit::event::MacOS::ReceivedUrl(url), + ), + ), + ), + ); + } + + fn about_to_wait( + &mut self, + event_loop: &winit::event_loop::ActiveEventLoop, + ) { + self.process_event( + event_loop, + Event::EventLoopAwakened(winit::event::Event::AboutToWait), + ); + } + } + + impl Runner + where + F: Future, + { + fn process_event( + &mut self, + event_loop: &winit::event_loop::ActiveEventLoop, + event: Event>, + ) { + if event_loop.exiting() { + return; + } + + self.sender.start_send(event).expect("Send event"); + + loop { + let poll = self.instance.as_mut().poll(&mut self.context); + + match poll { + task::Poll::Pending => match self.receiver.try_next() { + Ok(Some(control)) => match control { + Control::ChangeFlow(flow) => { + use winit::event_loop::ControlFlow; + + match (event_loop.control_flow(), flow) { + ( + ControlFlow::WaitUntil(current), + ControlFlow::WaitUntil(new), + ) if current < new => {} + ( + ControlFlow::WaitUntil(target), + ControlFlow::Wait, + ) if target > Instant::now() => {} + _ => { + event_loop.set_control_flow(flow); + } + } + } + Control::CreateWindow { + id, + settings, + title, + monitor, + on_open, + } => { + let exit_on_close_request = + settings.exit_on_close_request; + + let visible = settings.visible; + + #[cfg(target_arch = "wasm32")] + let target = + settings.platform_specific.target.clone(); + + let window_attributes = + conversion::window_attributes( + settings, + &title, + monitor + .or(event_loop.primary_monitor()), + self.id.clone(), + ) + .with_visible(false); + + #[cfg(target_arch = "wasm32")] + let window_attributes = { + use winit::platform::web::WindowAttributesExtWebSys; + window_attributes + .with_canvas(self.canvas.take()) + }; + + log::info!( + "Window attributes for id `{id:#?}`: {window_attributes:#?}" + ); + + // On macOS, the `position` in `WindowAttributes` represents the "inner" + // position of the window; while on other platforms it's the "outer" position. + // We fix the inconsistency on macOS by positioning the window after creation. + #[cfg(target_os = "macos")] + let mut window_attributes = window_attributes; + + #[cfg(target_os = "macos")] + let position = + window_attributes.position.take(); + + let window = event_loop + .create_window(window_attributes) + .expect("Create window"); + + #[cfg(target_os = "macos")] + if let Some(position) = position { + window.set_outer_position(position); + } + + #[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", + ); + } + }; + } + + self.process_event( + event_loop, + Event::WindowCreated { + id, + window: Arc::new(window), + exit_on_close_request, + make_visible: visible, + on_open, + }, + ); + } + Control::Exit => { + event_loop.exit(); + } + Control::Crash(error) => { + self.error = Some(error); + event_loop.exit(); + } + }, + _ => { + break; + } + }, + task::Poll::Ready(_) => { + event_loop.exit(); + break; + } + }; + } + } + } + + #[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(()) + } +} + +#[derive(Debug)] +enum Event { + WindowCreated { + id: window::Id, + window: Arc, + exit_on_close_request: bool, + make_visible: bool, + on_open: oneshot::Sender, + }, + EventLoopAwakened(winit::event::Event), +} + +#[derive(Debug)] +enum Control { + ChangeFlow(winit::event_loop::ControlFlow), + Exit, + Crash(Error), + CreateWindow { + id: window::Id, + settings: window::Settings, + title: String, + monitor: Option, + on_open: oneshot::Sender, + }, +} + +async fn run_instance

( + mut program: program::Instance

, + mut runtime: Runtime, Action>, + mut proxy: Proxy, + mut event_receiver: mpsc::UnboundedReceiver>>, + mut control_sender: mpsc::UnboundedSender, + is_daemon: bool, + graphics_settings: graphics::Settings, + default_fonts: Vec>, +) where + P: Program + 'static, + P::Theme: theme::Base, +{ + use winit::event; + use winit::event_loop::ControlFlow; + + let mut window_manager = WindowManager::new(); + let mut is_window_opening = !is_daemon; + + let mut compositor = None; + 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(FxHashMap::default()); + let mut clipboard = Clipboard::unconnected(); + let mut compositor_receiver: Option> = None; + + loop { + let event = if compositor_receiver.is_some() { + let compositor_receiver = + compositor_receiver.take().expect("Waiting for compositor"); + + match compositor_receiver.await { + Ok(Ok((new_compositor, event))) => { + compositor = Some(new_compositor); + + Some(event) + } + Ok(Err(error)) => { + control_sender + .start_send(Control::Crash( + Error::GraphicsCreationFailed(error), + )) + .expect("Send control action"); + break; + } + Err(error) => { + panic!("Compositor initialization failed: {error}") + } + } + // Empty the queue if possible + } else if let Ok(event) = event_receiver.try_next() { + event + } else { + event_receiver.next().await + }; + + let Some(event) = event else { + break; + }; + + match event { + Event::WindowCreated { + id, + window, + exit_on_close_request, + make_visible, + on_open, + } => { + if compositor.is_none() { + let (compositor_sender, new_compositor_receiver) = + oneshot::channel(); + + compositor_receiver = Some(new_compositor_receiver); + + let create_compositor = { + let default_fonts = default_fonts.clone(); + + async move { + let mut compositor = + ::Compositor::new(graphics_settings, window.clone()).await; + + if let Ok(compositor) = &mut compositor { + for font in default_fonts { + compositor.load_font(font.clone()); + } + } + + compositor_sender + .send(compositor.map(|compositor| { + ( + compositor, + Event::WindowCreated { + id, + window, + exit_on_close_request, + make_visible, + on_open, + }, + ) + })) + .ok() + .expect("Send compositor"); + } + }; + + #[cfg(not(target_arch = "wasm32"))] + crate::futures::futures::executor::block_on( + create_compositor, + ); + + #[cfg(target_arch = "wasm32")] + { + wasm_bindgen_futures::spawn_local(create_compositor); + } + + continue; + } + + let window = window_manager.insert( + id, + window, + &program, + compositor + .as_mut() + .expect("Compositor must be initialized"), + exit_on_close_request, + ); + + let logical_size = window.state.logical_size(); + + let _ = user_interfaces.insert( + id, + build_user_interface( + &program, + user_interface::Cache::default(), + &mut window.renderer, + logical_size, + id, + ), + ); + let _ = ui_caches.insert(id, user_interface::Cache::default()); + + if make_visible { + window.raw.set_visible(true); + } + + events.push(( + id, + core::Event::Window(window::Event::Opened { + position: window.position(), + size: window.size(), + }), + )); + + if clipboard.window_id().is_none() { + clipboard = Clipboard::connect(window.raw.clone()); + } + + let _ = on_open.send(id); + is_window_opening = false; + } + Event::EventLoopAwakened(event) => { + match event { + event::Event::NewEvents(event::StartCause::Init) => { + for (_id, window) in window_manager.iter_mut() { + window.raw.request_redraw(); + } + } + event::Event::NewEvents( + event::StartCause::ResumeTimeReached { .. }, + ) => { + let now = Instant::now(); + + for (_id, window) in window_manager.iter_mut() { + if let Some(redraw_at) = window.redraw_at { + if redraw_at <= now { + window.raw.request_redraw(); + window.redraw_at = None; + } + } + } + + if let Some(redraw_at) = window_manager.redraw_at() { + let _ = + control_sender.start_send(Control::ChangeFlow( + ControlFlow::WaitUntil(redraw_at), + )); + } else { + let _ = control_sender.start_send( + Control::ChangeFlow(ControlFlow::Wait), + ); + } + } + 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, + &program, + &mut compositor, + &mut events, + &mut messages, + &mut clipboard, + &mut control_sender, + &mut user_interfaces, + &mut window_manager, + &mut ui_caches, + &mut is_window_opening, + ); + actions += 1; + } + event::Event::WindowEvent { + window_id: id, + event: event::WindowEvent::RedrawRequested, + .. + } => { + let Some(compositor) = &mut compositor else { + continue; + }; + + let Some((id, window)) = + window_manager.get_mut_alias(id) + else { + continue; + }; + + let physical_size = window.state.physical_size(); + + if physical_size.width == 0 || physical_size.height == 0 + { + continue; + } + + if window.viewport_version + != window.state.viewport_version() + { + let logical_size = window.state.logical_size(); + + let layout_span = debug::layout(id); + let ui = user_interfaces + .remove(&id) + .expect("Remove user interface"); + + let _ = user_interfaces.insert( + id, + ui.relayout(logical_size, &mut window.renderer), + ); + layout_span.finish(); + + compositor.configure_surface( + &mut window.surface, + physical_size.width, + physical_size.height, + ); + + window.viewport_version = + window.state.viewport_version(); + } + + let redraw_event = core::Event::Window( + window::Event::RedrawRequested(Instant::now()), + ); + + let cursor = window.state.cursor(); + + let ui = user_interfaces + .get_mut(&id) + .expect("Get user interface"); + + let (ui_state, _) = ui.update( + &[redraw_event.clone()], + cursor, + &mut window.renderer, + &mut clipboard, + &mut messages, + ); + + let draw_span = debug::draw(id); + let new_mouse_interaction = ui.draw( + &mut window.renderer, + window.state.theme(), + &renderer::Style { + text_color: window.state.text_color(), + }, + cursor, + ); + draw_span.finish(); + + if new_mouse_interaction != window.mouse_interaction { + window.raw.set_cursor( + conversion::mouse_interaction( + new_mouse_interaction, + ), + ); + + window.mouse_interaction = new_mouse_interaction; + } + + runtime.broadcast(subscription::Event::Interaction { + window: id, + event: redraw_event, + status: core::event::Status::Ignored, + }); + + if let user_interface::State::Updated { + redraw_request, + input_method, + } = ui_state + { + window.request_redraw(redraw_request); + window.request_input_method(input_method); + } + + window.draw_preedit(); + + let present_span = debug::present(id); + match compositor.present( + &mut window.renderer, + &mut window.surface, + window.state.viewport(), + window.state.background_color(), + ) { + Ok(()) => { + present_span.finish(); + } + Err(error) => match error { + // This is an unrecoverable error. + compositor::SurfaceError::OutOfMemory => { + panic!("{:?}", error); + } + _ => { + present_span.finish(); + + log::error!( + "Error {error:?} when \ + presenting surface." + ); + + // Try rendering all windows again next frame. + for (_id, window) in + window_manager.iter_mut() + { + window.raw.request_redraw(); + } + } + }, + } + } + event::Event::WindowEvent { + event: window_event, + window_id, + } => { + if !is_daemon + && matches!( + window_event, + winit::event::WindowEvent::Destroyed + ) + && !is_window_opening + && window_manager.is_empty() + { + control_sender + .start_send(Control::Exit) + .expect("Send control action"); + + continue; + } + + let Some((id, window)) = + window_manager.get_mut_alias(window_id) + else { + continue; + }; + + if matches!( + window_event, + winit::event::WindowEvent::Resized(_) + ) { + window.raw.request_redraw(); + } + + if matches!( + window_event, + winit::event::WindowEvent::CloseRequested + ) && window.exit_on_close_request + { + run_action( + Action::Window(runtime::window::Action::Close( + id, + )), + &program, + &mut compositor, + &mut events, + &mut messages, + &mut clipboard, + &mut control_sender, + &mut user_interfaces, + &mut window_manager, + &mut ui_caches, + &mut is_window_opening, + ); + } else { + window.state.update(&window.raw, &window_event); + + if let Some(event) = conversion::window_event( + window_event, + window.state.scale_factor(), + window.state.modifiers(), + ) { + if matches!( + event, + core::Event::Keyboard( + keyboard::Event::KeyPressed { + modified_key: keyboard::Key::Named( + keyboard::key::Named::F12 + ), + .. + } + ) + ) { + debug::toggle_comet(); + } + + events.push((id, event)); + } + } + } + event::Event::AboutToWait => { + if actions > 0 { + proxy.free_slots(actions); + actions = 0; + } + + if events.is_empty() + && messages.is_empty() + && window_manager.is_idle() + { + continue; + } + + let mut uis_stale = false; + + for (id, window) in window_manager.iter_mut() { + let interact_span = debug::interact(id); + let mut window_events = vec![]; + + events.retain(|(window_id, event)| { + if *window_id == id { + window_events.push(event.clone()); + false + } else { + true + } + }); + + if window_events.is_empty() && messages.is_empty() { + continue; + } + + let (ui_state, statuses) = user_interfaces + .get_mut(&id) + .expect("Get user interface") + .update( + &window_events, + window.state.cursor(), + &mut window.renderer, + &mut clipboard, + &mut messages, + ); + + #[cfg(feature = "unconditional-rendering")] + window.request_redraw( + window::RedrawRequest::NextFrame, + ); + + match ui_state { + user_interface::State::Updated { + redraw_request: _redraw_request, + .. + } => { + #[cfg(not( + feature = "unconditional-rendering" + ))] + window.request_redraw(_redraw_request); + } + user_interface::State::Outdated => { + uis_stale = true; + } + } + + for (event, status) in window_events + .into_iter() + .zip(statuses.into_iter()) + { + runtime.broadcast( + subscription::Event::Interaction { + window: id, + event, + status, + }, + ); + } + + interact_span.finish(); + } + + for (id, event) in events.drain(..) { + runtime.broadcast( + subscription::Event::Interaction { + window: id, + event, + status: core::event::Status::Ignored, + }, + ); + } + + if !messages.is_empty() || uis_stale { + let cached_interfaces: FxHashMap< + window::Id, + user_interface::Cache, + > = ManuallyDrop::into_inner(user_interfaces) + .drain() + .map(|(id, ui)| (id, ui.into_cache())) + .collect(); + + update(&mut program, &mut runtime, &mut messages); + + for (id, window) in window_manager.iter_mut() { + window.state.synchronize( + &program, + id, + &window.raw, + ); + + window.raw.request_redraw(); + } + + user_interfaces = + ManuallyDrop::new(build_user_interfaces( + &program, + &mut window_manager, + cached_interfaces, + )); + } + + if let Some(redraw_at) = window_manager.redraw_at() { + let _ = + control_sender.start_send(Control::ChangeFlow( + ControlFlow::WaitUntil(redraw_at), + )); + } else { + let _ = control_sender.start_send( + Control::ChangeFlow(ControlFlow::Wait), + ); + } + } + _ => {} + } + } + } + } + + let _ = ManuallyDrop::into_inner(user_interfaces); +} + +/// Builds a window's [`UserInterface`] for the [`Program`]. +fn build_user_interface<'a, P: Program>( + program: &'a program::Instance

, + cache: user_interface::Cache, + renderer: &mut P::Renderer, + size: Size, + id: window::Id, +) -> UserInterface<'a, P::Message, P::Theme, P::Renderer> +where + P::Theme: theme::Base, +{ + let view_span = debug::view(id); + let view = program.view(id); + view_span.finish(); + + let layout_span = debug::layout(id); + let user_interface = UserInterface::build(view, size, cache, renderer); + layout_span.finish(); + + user_interface +} + +fn update( + program: &mut program::Instance

, + runtime: &mut Runtime, Action>, + messages: &mut Vec, +) where + P::Theme: theme::Base, +{ + for message in messages.drain(..) { + let update_span = debug::update(&message); + let task = runtime.enter(|| program.update(message)); + update_span.finish(); + + if let Some(stream) = runtime::task::into_stream(task) { + runtime.run(stream); + } + } + + let subscription = runtime.enter(|| program.subscription()); + runtime.track(subscription::into_recipes(subscription.map(Action::Output))); +} + +fn run_action( + action: Action, + program: &program::Instance

, + compositor: &mut Option, + events: &mut Vec<(window::Id, core::Event)>, + messages: &mut Vec, + clipboard: &mut Clipboard, + control_sender: &mut mpsc::UnboundedSender, + interfaces: &mut FxHashMap< + window::Id, + UserInterface<'_, P::Message, P::Theme, P::Renderer>, + >, + window_manager: &mut WindowManager, + ui_caches: &mut FxHashMap, + is_window_opening: &mut bool, +) where + P: Program, + C: Compositor + 'static, + P::Theme: theme::Base, +{ + use crate::runtime::clipboard; + use crate::runtime::system; + use crate::runtime::window; + + match action { + Action::Output(message) => { + messages.push(message); + } + 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::Open(id, settings, channel) => { + let monitor = window_manager.last_monitor(); + + control_sender + .start_send(Control::CreateWindow { + id, + settings, + title: program.title(id), + monitor, + on_open: channel, + }) + .expect("Send control action"); + + *is_window_opening = true; + } + window::Action::Close(id) => { + let _ = ui_caches.remove(&id); + let _ = interfaces.remove(&id); + + if let Some(window) = window_manager.remove(id) { + if clipboard.window_id() == Some(window.raw.id()) { + *clipboard = window_manager + .first() + .map(|window| window.raw.clone()) + .map(Clipboard::connect) + .unwrap_or_else(Clipboard::unconnected); + } + + events.push(( + id, + core::Event::Window(core::window::Event::Closed), + )); + } + + if window_manager.is_empty() { + *compositor = None; + } + } + window::Action::GetOldest(channel) => { + let id = + window_manager.iter_mut().next().map(|(id, _window)| id); + + let _ = channel.send(id); + } + window::Action::GetLatest(channel) => { + let id = + window_manager.iter_mut().last().map(|(id, _window)| id); + + let _ = channel.send(id); + } + window::Action::Drag(id) => { + if let Some(window) = window_manager.get_mut(id) { + let _ = window.raw.drag_window(); + } + } + window::Action::DragResize(id, direction) => { + if let Some(window) = window_manager.get_mut(id) { + let _ = window.raw.drag_resize_window( + conversion::resize_direction(direction), + ); + } + } + window::Action::Resize(id, size) => { + if let Some(window) = window_manager.get_mut(id) { + let _ = window.raw.request_inner_size( + winit::dpi::LogicalSize { + width: size.width, + height: size.height, + }, + ); + } + } + window::Action::SetMinSize(id, size) => { + if let Some(window) = window_manager.get_mut(id) { + window.raw.set_min_inner_size(size.map(|size| { + winit::dpi::LogicalSize { + width: size.width, + height: size.height, + } + })); + } + } + window::Action::SetMaxSize(id, size) => { + if let Some(window) = window_manager.get_mut(id) { + window.raw.set_max_inner_size(size.map(|size| { + winit::dpi::LogicalSize { + width: size.width, + height: size.height, + } + })); + } + } + window::Action::SetResizeIncrements(id, increments) => { + if let Some(window) = window_manager.get_mut(id) { + window.raw.set_resize_increments(increments.map(|size| { + winit::dpi::LogicalSize { + width: size.width, + height: size.height, + } + })); + } + } + window::Action::SetResizable(id, resizable) => { + if let Some(window) = window_manager.get_mut(id) { + window.raw.set_resizable(resizable); + } + } + window::Action::GetSize(id, channel) => { + if let Some(window) = window_manager.get_mut(id) { + let size = window + .raw + .inner_size() + .to_logical(window.raw.scale_factor()); + + let _ = channel.send(Size::new(size.width, size.height)); + } + } + window::Action::GetMaximized(id, channel) => { + if let Some(window) = window_manager.get_mut(id) { + let _ = channel.send(window.raw.is_maximized()); + } + } + window::Action::Maximize(id, maximized) => { + if let Some(window) = window_manager.get_mut(id) { + window.raw.set_maximized(maximized); + } + } + window::Action::GetMinimized(id, channel) => { + if let Some(window) = window_manager.get_mut(id) { + let _ = channel.send(window.raw.is_minimized()); + } + } + window::Action::Minimize(id, minimized) => { + if let Some(window) = window_manager.get_mut(id) { + window.raw.set_minimized(minimized); + } + } + window::Action::GetPosition(id, channel) => { + if let Some(window) = window_manager.get(id) { + let position = window + .raw + .outer_position() + .map(|position| { + let position = position + .to_logical::(window.raw.scale_factor()); + + Point::new(position.x, position.y) + }) + .ok(); + + let _ = channel.send(position); + } + } + window::Action::GetScaleFactor(id, channel) => { + if let Some(window) = window_manager.get_mut(id) { + let scale_factor = window.raw.scale_factor(); + + let _ = channel.send(scale_factor as f32); + } + } + window::Action::Move(id, position) => { + if let Some(window) = window_manager.get_mut(id) { + window.raw.set_outer_position( + winit::dpi::LogicalPosition { + x: position.x, + y: position.y, + }, + ); + } + } + window::Action::SetMode(id, mode) => { + if let Some(window) = window_manager.get_mut(id) { + window.raw.set_visible(conversion::visible(mode)); + window.raw.set_fullscreen(conversion::fullscreen( + window.raw.current_monitor(), + mode, + )); + } + } + window::Action::SetIcon(id, icon) => { + if let Some(window) = window_manager.get_mut(id) { + window.raw.set_window_icon(conversion::icon(icon)); + } + } + window::Action::GetMode(id, channel) => { + if let Some(window) = window_manager.get_mut(id) { + let mode = if window.raw.is_visible().unwrap_or(true) { + conversion::mode(window.raw.fullscreen()) + } else { + core::window::Mode::Hidden + }; + + let _ = channel.send(mode); + } + } + window::Action::ToggleMaximize(id) => { + if let Some(window) = window_manager.get_mut(id) { + window.raw.set_maximized(!window.raw.is_maximized()); + } + } + window::Action::ToggleDecorations(id) => { + if let Some(window) = window_manager.get_mut(id) { + window.raw.set_decorations(!window.raw.is_decorated()); + } + } + window::Action::RequestUserAttention(id, attention_type) => { + if let Some(window) = window_manager.get_mut(id) { + window.raw.request_user_attention( + attention_type.map(conversion::user_attention), + ); + } + } + window::Action::GainFocus(id) => { + if let Some(window) = window_manager.get_mut(id) { + window.raw.focus_window(); + } + } + window::Action::SetLevel(id, level) => { + if let Some(window) = window_manager.get_mut(id) { + window + .raw + .set_window_level(conversion::window_level(level)); + } + } + window::Action::ShowSystemMenu(id) => { + if let Some(window) = window_manager.get_mut(id) { + if let mouse::Cursor::Available(point) = + window.state.cursor() + { + window.raw.show_window_menu( + winit::dpi::LogicalPosition { + x: point.x, + y: point.y, + }, + ); + } + } + } + window::Action::GetRawId(id, channel) => { + if let Some(window) = window_manager.get_mut(id) { + let _ = channel.send(window.raw.id().into()); + } + } + window::Action::RunWithHandle(id, f) => { + use window::raw_window_handle::HasWindowHandle; + + if let Some(handle) = window_manager + .get_mut(id) + .and_then(|window| window.raw.window_handle().ok()) + { + f(handle); + } + } + window::Action::Screenshot(id, channel) => { + if let Some(window) = window_manager.get_mut(id) { + if let Some(compositor) = compositor { + let bytes = compositor.screenshot( + &mut window.renderer, + window.state.viewport(), + window.state.background_color(), + ); + + let _ = channel.send(core::window::Screenshot::new( + bytes, + window.state.physical_size(), + window.state.viewport().scale_factor(), + )); + } + } + } + window::Action::EnableMousePassthrough(id) => { + if let Some(window) = window_manager.get_mut(id) { + let _ = window.raw.set_cursor_hittest(false); + } + } + window::Action::DisableMousePassthrough(id) => { + if let Some(window) = window_manager.get_mut(id) { + let _ = window.raw.set_cursor_hittest(true); + } + } + }, + Action::System(action) => match action { + system::Action::QueryInformation(_channel) => { + #[cfg(feature = "system")] + { + if let Some(compositor) = compositor { + 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() { + for (id, ui) in interfaces.iter_mut() { + if let Some(window) = window_manager.get_mut(*id) { + ui.operate(&window.renderer, operation.as_mut()); + } + } + + match operation.finish() { + operation::Outcome::None => {} + operation::Outcome::Some(()) => {} + operation::Outcome::Chain(next) => { + current_operation = Some(next); + } + } + } + } + Action::LoadFont { bytes, channel } => { + if let Some(compositor) = compositor { + // TODO: Error handling (?) + compositor.load_font(bytes.clone()); + + 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, P: Program, C>( + program: &'a program::Instance

, + window_manager: &mut WindowManager, + mut cached_user_interfaces: FxHashMap, +) -> FxHashMap> +where + C: Compositor, + P::Theme: theme::Base, +{ + cached_user_interfaces + .drain() + .filter_map(|(id, cache)| { + let window = window_manager.get_mut(id)?; + + Some(( + id, + build_user_interface( + program, + cache, + &mut window.renderer, + window.state.logical_size(), + id, + ), + )) + }) + .collect() +} + +/// Returns true if the provided event should cause a [`Program`] to +/// exit. +pub fn user_force_quit( + event: &winit::event::WindowEvent, + _modifiers: winit::keyboard::ModifiersState, +) -> bool { + match event { + #[cfg(target_os = "macos")] + winit::event::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, + } +} diff --git a/winit/src/program.rs b/winit/src/program.rs deleted file mode 100644 index b0344050..00000000 --- a/winit/src/program.rs +++ /dev/null @@ -1,1599 +0,0 @@ -//! Create interactive, native cross-platform applications for WGPU. -mod state; -mod window_manager; - -pub use state::State; - -use crate::conversion; -use crate::core; -use crate::core::keyboard; -use crate::core::mouse; -use crate::core::renderer; -use crate::core::theme; -use crate::core::time::Instant; -use crate::core::widget::operation; -use crate::core::window; -use crate::core::{Element, Point, Size}; -use crate::futures::futures::channel::mpsc; -use crate::futures::futures::channel::oneshot; -use crate::futures::futures::task; -use crate::futures::futures::{Future, StreamExt}; -use crate::futures::subscription::{self, Subscription}; -use crate::futures::{Executor, Runtime}; -use crate::graphics; -use crate::graphics::{Compositor, compositor}; -use crate::runtime::debug; -use crate::runtime::user_interface::{self, UserInterface}; -use crate::runtime::{self, Action, Task}; -use crate::{Clipboard, Error, Proxy, Settings}; - -use window_manager::WindowManager; - -use rustc_hash::FxHashMap; -use std::borrow::Cow; -use std::mem::ManuallyDrop; -use std::sync::Arc; - -/// An interactive, native, cross-platform, multi-windowed application. -/// -/// This trait is the main entrypoint of multi-window Iced. Once implemented, you can run -/// your GUI application by simply calling [`run`]. It will run in -/// its own window. -/// -/// A [`Program`] can execute asynchronous actions by returning a -/// [`Task`] in some of its methods. -/// -/// When using a [`Program`] with the `debug` feature enabled, a debug view -/// can be toggled by pressing `F12`. -pub trait Program -where - Self: Sized, - Self::Theme: theme::Base, -{ - /// 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; - - /// Returns the unique name of the [`Program`]. - fn name() -> &'static str; - - /// 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. - /// - /// 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); - - /// 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; - - /// 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; - - /// 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`. - fn style(&self, theme: &Self::Theme) -> theme::Style { - theme::Base::base(theme) - } - - /// 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 { - Subscription::none() - } - - /// 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). - /// - /// 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 a [`Program`] with an executor, compositor, and the provided -/// settings. -pub fn run( - settings: Settings, - graphics_settings: graphics::Settings, - window_settings: Option, - flags: P::Flags, -) -> Result<(), Error> -where - P: Program + 'static, - C: Compositor + 'static, - P::Theme: theme::Base, -{ - use winit::event_loop::EventLoop; - - debug::init(P::name()); - - let boot_span = debug::boot(); - - 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 = - P::Executor::new().map_err(Error::ExecutorCreationFailed)?; - executor.spawn(worker); - - Runtime::new(executor, proxy.clone()) - }; - - let (program, task) = runtime.enter(|| P::new(flags)); - let is_daemon = window_settings.is_none(); - - let task = if let Some(window_settings) = window_settings { - let mut task = Some(task); - - let (_id, open) = runtime::window::open(window_settings); - - open.then(move |_| task.take().unwrap_or(Task::none())) - } else { - task - }; - - if let Some(stream) = runtime::task::into_stream(task) { - runtime.run(stream); - } - - runtime.track(subscription::into_recipes( - runtime.enter(|| program.subscription().map(Action::Output)), - )); - - let (event_sender, event_receiver) = mpsc::unbounded(); - let (control_sender, control_receiver) = mpsc::unbounded(); - - let instance = Box::pin(run_instance::( - program, - runtime, - proxy.clone(), - event_receiver, - control_sender, - is_daemon, - graphics_settings, - settings.fonts, - )); - - let context = task::Context::from_waker(task::noop_waker_ref()); - - struct Runner { - instance: std::pin::Pin>, - context: task::Context<'static>, - id: Option, - sender: mpsc::UnboundedSender>>, - receiver: mpsc::UnboundedReceiver, - error: Option, - - #[cfg(target_arch = "wasm32")] - canvas: Option, - } - - let runner = Runner { - instance, - context, - id: settings.id, - sender: event_sender, - receiver: control_receiver, - error: None, - - #[cfg(target_arch = "wasm32")] - canvas: None, - }; - - boot_span.finish(); - - impl winit::application::ApplicationHandler> - for Runner - where - Message: std::fmt::Debug, - F: Future, - { - fn resumed( - &mut self, - _event_loop: &winit::event_loop::ActiveEventLoop, - ) { - } - - fn new_events( - &mut self, - event_loop: &winit::event_loop::ActiveEventLoop, - cause: winit::event::StartCause, - ) { - self.process_event( - event_loop, - Event::EventLoopAwakened(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, - Event::EventLoopAwakened(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, - Event::EventLoopAwakened( - winit::event::Event::AboutToWait, - ), - ); - } - } - } - - fn user_event( - &mut self, - event_loop: &winit::event_loop::ActiveEventLoop, - action: Action, - ) { - self.process_event( - event_loop, - Event::EventLoopAwakened(winit::event::Event::UserEvent( - action, - )), - ); - } - - fn received_url( - &mut self, - event_loop: &winit::event_loop::ActiveEventLoop, - url: String, - ) { - self.process_event( - event_loop, - Event::EventLoopAwakened( - winit::event::Event::PlatformSpecific( - winit::event::PlatformSpecific::MacOS( - winit::event::MacOS::ReceivedUrl(url), - ), - ), - ), - ); - } - - fn about_to_wait( - &mut self, - event_loop: &winit::event_loop::ActiveEventLoop, - ) { - self.process_event( - event_loop, - Event::EventLoopAwakened(winit::event::Event::AboutToWait), - ); - } - } - - impl Runner - where - F: Future, - { - fn process_event( - &mut self, - event_loop: &winit::event_loop::ActiveEventLoop, - event: Event>, - ) { - if event_loop.exiting() { - return; - } - - self.sender.start_send(event).expect("Send event"); - - loop { - let poll = self.instance.as_mut().poll(&mut self.context); - - match poll { - task::Poll::Pending => match self.receiver.try_next() { - Ok(Some(control)) => match control { - Control::ChangeFlow(flow) => { - use winit::event_loop::ControlFlow; - - match (event_loop.control_flow(), flow) { - ( - ControlFlow::WaitUntil(current), - ControlFlow::WaitUntil(new), - ) if current < new => {} - ( - ControlFlow::WaitUntil(target), - ControlFlow::Wait, - ) if target > Instant::now() => {} - _ => { - event_loop.set_control_flow(flow); - } - } - } - Control::CreateWindow { - id, - settings, - title, - monitor, - on_open, - } => { - let exit_on_close_request = - settings.exit_on_close_request; - - let visible = settings.visible; - - #[cfg(target_arch = "wasm32")] - let target = - settings.platform_specific.target.clone(); - - let window_attributes = - conversion::window_attributes( - settings, - &title, - monitor - .or(event_loop.primary_monitor()), - self.id.clone(), - ) - .with_visible(false); - - #[cfg(target_arch = "wasm32")] - let window_attributes = { - use winit::platform::web::WindowAttributesExtWebSys; - window_attributes - .with_canvas(self.canvas.take()) - }; - - log::info!( - "Window attributes for id `{id:#?}`: {window_attributes:#?}" - ); - - // On macOS, the `position` in `WindowAttributes` represents the "inner" - // position of the window; while on other platforms it's the "outer" position. - // We fix the inconsistency on macOS by positioning the window after creation. - #[cfg(target_os = "macos")] - let mut window_attributes = window_attributes; - - #[cfg(target_os = "macos")] - let position = - window_attributes.position.take(); - - let window = event_loop - .create_window(window_attributes) - .expect("Create window"); - - #[cfg(target_os = "macos")] - if let Some(position) = position { - window.set_outer_position(position); - } - - #[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", - ); - } - }; - } - - self.process_event( - event_loop, - Event::WindowCreated { - id, - window: Arc::new(window), - exit_on_close_request, - make_visible: visible, - on_open, - }, - ); - } - Control::Exit => { - event_loop.exit(); - } - Control::Crash(error) => { - self.error = Some(error); - event_loop.exit(); - } - }, - _ => { - break; - } - }, - task::Poll::Ready(_) => { - event_loop.exit(); - break; - } - }; - } - } - } - - #[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(()) - } -} - -#[derive(Debug)] -enum Event { - WindowCreated { - id: window::Id, - window: Arc, - exit_on_close_request: bool, - make_visible: bool, - on_open: oneshot::Sender, - }, - EventLoopAwakened(winit::event::Event), -} - -#[derive(Debug)] -enum Control { - ChangeFlow(winit::event_loop::ControlFlow), - Exit, - Crash(Error), - CreateWindow { - id: window::Id, - settings: window::Settings, - title: String, - monitor: Option, - on_open: oneshot::Sender, - }, -} - -async fn run_instance( - mut program: P, - mut runtime: Runtime, Action>, - mut proxy: Proxy, - mut event_receiver: mpsc::UnboundedReceiver>>, - mut control_sender: mpsc::UnboundedSender, - is_daemon: bool, - graphics_settings: graphics::Settings, - default_fonts: Vec>, -) where - P: Program + 'static, - C: Compositor + 'static, - P::Theme: theme::Base, -{ - use winit::event; - use winit::event_loop::ControlFlow; - - let mut window_manager = WindowManager::new(); - let mut is_window_opening = !is_daemon; - - let mut compositor = None; - 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(FxHashMap::default()); - let mut clipboard = Clipboard::unconnected(); - let mut compositor_receiver: Option> = None; - - loop { - let event = if compositor_receiver.is_some() { - let compositor_receiver = - compositor_receiver.take().expect("Waiting for compositor"); - - match compositor_receiver.await { - Ok(Ok((new_compositor, event))) => { - compositor = Some(new_compositor); - - Some(event) - } - Ok(Err(error)) => { - control_sender - .start_send(Control::Crash( - Error::GraphicsCreationFailed(error), - )) - .expect("Send control action"); - break; - } - Err(error) => { - panic!("Compositor initialization failed: {error}") - } - } - // Empty the queue if possible - } else if let Ok(event) = event_receiver.try_next() { - event - } else { - event_receiver.next().await - }; - - let Some(event) = event else { - break; - }; - - match event { - Event::WindowCreated { - id, - window, - exit_on_close_request, - make_visible, - on_open, - } => { - if compositor.is_none() { - let (compositor_sender, new_compositor_receiver) = - oneshot::channel(); - - compositor_receiver = Some(new_compositor_receiver); - - let create_compositor = { - let default_fonts = default_fonts.clone(); - - async move { - let mut compositor = - C::new(graphics_settings, window.clone()).await; - - if let Ok(compositor) = &mut compositor { - for font in default_fonts { - compositor.load_font(font.clone()); - } - } - - compositor_sender - .send(compositor.map(|compositor| { - ( - compositor, - Event::WindowCreated { - id, - window, - exit_on_close_request, - make_visible, - on_open, - }, - ) - })) - .ok() - .expect("Send compositor"); - } - }; - - #[cfg(not(target_arch = "wasm32"))] - crate::futures::futures::executor::block_on( - create_compositor, - ); - - #[cfg(target_arch = "wasm32")] - { - wasm_bindgen_futures::spawn_local(create_compositor); - } - - continue; - } - - let window = window_manager.insert( - id, - window, - &program, - compositor - .as_mut() - .expect("Compositor must be initialized"), - exit_on_close_request, - ); - - let logical_size = window.state.logical_size(); - - let _ = user_interfaces.insert( - id, - build_user_interface( - &program, - user_interface::Cache::default(), - &mut window.renderer, - logical_size, - id, - ), - ); - let _ = ui_caches.insert(id, user_interface::Cache::default()); - - if make_visible { - window.raw.set_visible(true); - } - - events.push(( - id, - core::Event::Window(window::Event::Opened { - position: window.position(), - size: window.size(), - }), - )); - - if clipboard.window_id().is_none() { - clipboard = Clipboard::connect(window.raw.clone()); - } - - let _ = on_open.send(id); - is_window_opening = false; - } - Event::EventLoopAwakened(event) => { - match event { - event::Event::NewEvents(event::StartCause::Init) => { - for (_id, window) in window_manager.iter_mut() { - window.raw.request_redraw(); - } - } - event::Event::NewEvents( - event::StartCause::ResumeTimeReached { .. }, - ) => { - let now = Instant::now(); - - for (_id, window) in window_manager.iter_mut() { - if let Some(redraw_at) = window.redraw_at { - if redraw_at <= now { - window.raw.request_redraw(); - window.redraw_at = None; - } - } - } - - if let Some(redraw_at) = window_manager.redraw_at() { - let _ = - control_sender.start_send(Control::ChangeFlow( - ControlFlow::WaitUntil(redraw_at), - )); - } else { - let _ = control_sender.start_send( - Control::ChangeFlow(ControlFlow::Wait), - ); - } - } - 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, - &program, - &mut compositor, - &mut events, - &mut messages, - &mut clipboard, - &mut control_sender, - &mut user_interfaces, - &mut window_manager, - &mut ui_caches, - &mut is_window_opening, - ); - actions += 1; - } - event::Event::WindowEvent { - window_id: id, - event: event::WindowEvent::RedrawRequested, - .. - } => { - let Some(compositor) = &mut compositor else { - continue; - }; - - let Some((id, window)) = - window_manager.get_mut_alias(id) - else { - continue; - }; - - let physical_size = window.state.physical_size(); - - if physical_size.width == 0 || physical_size.height == 0 - { - continue; - } - - if window.viewport_version - != window.state.viewport_version() - { - let logical_size = window.state.logical_size(); - - let layout_span = debug::layout(id); - let ui = user_interfaces - .remove(&id) - .expect("Remove user interface"); - - let _ = user_interfaces.insert( - id, - ui.relayout(logical_size, &mut window.renderer), - ); - layout_span.finish(); - - compositor.configure_surface( - &mut window.surface, - physical_size.width, - physical_size.height, - ); - - window.viewport_version = - window.state.viewport_version(); - } - - let redraw_event = core::Event::Window( - window::Event::RedrawRequested(Instant::now()), - ); - - let cursor = window.state.cursor(); - - let ui = user_interfaces - .get_mut(&id) - .expect("Get user interface"); - - let (ui_state, _) = ui.update( - &[redraw_event.clone()], - cursor, - &mut window.renderer, - &mut clipboard, - &mut messages, - ); - - let draw_span = debug::draw(id); - let new_mouse_interaction = ui.draw( - &mut window.renderer, - window.state.theme(), - &renderer::Style { - text_color: window.state.text_color(), - }, - cursor, - ); - draw_span.finish(); - - if new_mouse_interaction != window.mouse_interaction { - window.raw.set_cursor( - conversion::mouse_interaction( - new_mouse_interaction, - ), - ); - - window.mouse_interaction = new_mouse_interaction; - } - - runtime.broadcast(subscription::Event::Interaction { - window: id, - event: redraw_event, - status: core::event::Status::Ignored, - }); - - if let user_interface::State::Updated { - redraw_request, - input_method, - } = ui_state - { - window.request_redraw(redraw_request); - window.request_input_method(input_method); - } - - window.draw_preedit(); - - let present_span = debug::present(id); - match compositor.present( - &mut window.renderer, - &mut window.surface, - window.state.viewport(), - window.state.background_color(), - ) { - Ok(()) => { - present_span.finish(); - } - Err(error) => match error { - // This is an unrecoverable error. - compositor::SurfaceError::OutOfMemory => { - panic!("{:?}", error); - } - _ => { - present_span.finish(); - - log::error!( - "Error {error:?} when \ - presenting surface." - ); - - // Try rendering all windows again next frame. - for (_id, window) in - window_manager.iter_mut() - { - window.raw.request_redraw(); - } - } - }, - } - } - event::Event::WindowEvent { - event: window_event, - window_id, - } => { - if !is_daemon - && matches!( - window_event, - winit::event::WindowEvent::Destroyed - ) - && !is_window_opening - && window_manager.is_empty() - { - control_sender - .start_send(Control::Exit) - .expect("Send control action"); - - continue; - } - - let Some((id, window)) = - window_manager.get_mut_alias(window_id) - else { - continue; - }; - - if matches!( - window_event, - winit::event::WindowEvent::Resized(_) - ) { - window.raw.request_redraw(); - } - - if matches!( - window_event, - winit::event::WindowEvent::CloseRequested - ) && window.exit_on_close_request - { - run_action( - Action::Window(runtime::window::Action::Close( - id, - )), - &program, - &mut compositor, - &mut events, - &mut messages, - &mut clipboard, - &mut control_sender, - &mut user_interfaces, - &mut window_manager, - &mut ui_caches, - &mut is_window_opening, - ); - } else { - window.state.update(&window.raw, &window_event); - - if let Some(event) = conversion::window_event( - window_event, - window.state.scale_factor(), - window.state.modifiers(), - ) { - if matches!( - event, - core::Event::Keyboard( - keyboard::Event::KeyPressed { - modified_key: keyboard::Key::Named( - keyboard::key::Named::F12 - ), - .. - } - ) - ) { - debug::toggle_comet(); - } - - events.push((id, event)); - } - } - } - event::Event::AboutToWait => { - if actions > 0 { - proxy.free_slots(actions); - actions = 0; - } - - if events.is_empty() - && messages.is_empty() - && window_manager.is_idle() - { - continue; - } - - let mut uis_stale = false; - - for (id, window) in window_manager.iter_mut() { - let interact_span = debug::interact(id); - let mut window_events = vec![]; - - events.retain(|(window_id, event)| { - if *window_id == id { - window_events.push(event.clone()); - false - } else { - true - } - }); - - if window_events.is_empty() && messages.is_empty() { - continue; - } - - let (ui_state, statuses) = user_interfaces - .get_mut(&id) - .expect("Get user interface") - .update( - &window_events, - window.state.cursor(), - &mut window.renderer, - &mut clipboard, - &mut messages, - ); - - #[cfg(feature = "unconditional-rendering")] - window.request_redraw( - window::RedrawRequest::NextFrame, - ); - - match ui_state { - user_interface::State::Updated { - redraw_request: _redraw_request, - .. - } => { - #[cfg(not( - feature = "unconditional-rendering" - ))] - window.request_redraw(_redraw_request); - } - user_interface::State::Outdated => { - uis_stale = true; - } - } - - for (event, status) in window_events - .into_iter() - .zip(statuses.into_iter()) - { - runtime.broadcast( - subscription::Event::Interaction { - window: id, - event, - status, - }, - ); - } - - interact_span.finish(); - } - - for (id, event) in events.drain(..) { - runtime.broadcast( - subscription::Event::Interaction { - window: id, - event, - status: core::event::Status::Ignored, - }, - ); - } - - if !messages.is_empty() || uis_stale { - let cached_interfaces: FxHashMap< - window::Id, - user_interface::Cache, - > = ManuallyDrop::into_inner(user_interfaces) - .drain() - .map(|(id, ui)| (id, ui.into_cache())) - .collect(); - - update(&mut program, &mut runtime, &mut messages); - - for (id, window) in window_manager.iter_mut() { - window.state.synchronize( - &program, - id, - &window.raw, - ); - - window.raw.request_redraw(); - } - - user_interfaces = - ManuallyDrop::new(build_user_interfaces( - &program, - &mut window_manager, - cached_interfaces, - )); - } - - if let Some(redraw_at) = window_manager.redraw_at() { - let _ = - control_sender.start_send(Control::ChangeFlow( - ControlFlow::WaitUntil(redraw_at), - )); - } else { - let _ = control_sender.start_send( - Control::ChangeFlow(ControlFlow::Wait), - ); - } - } - _ => {} - } - } - } - } - - let _ = ManuallyDrop::into_inner(user_interfaces); -} - -/// Builds a window's [`UserInterface`] for the [`Program`]. -fn build_user_interface<'a, P: Program>( - program: &'a P, - cache: user_interface::Cache, - renderer: &mut P::Renderer, - size: Size, - id: window::Id, -) -> UserInterface<'a, P::Message, P::Theme, P::Renderer> -where - P::Theme: theme::Base, -{ - let view_span = debug::view(id); - let view = program.view(id); - view_span.finish(); - - let layout_span = debug::layout(id); - let user_interface = UserInterface::build(view, size, cache, renderer); - layout_span.finish(); - - user_interface -} - -fn update( - program: &mut P, - runtime: &mut Runtime, Action>, - messages: &mut Vec, -) where - P::Theme: theme::Base, -{ - for message in messages.drain(..) { - let update_span = debug::update(&message); - let task = runtime.enter(|| program.update(message)); - update_span.finish(); - - if let Some(stream) = runtime::task::into_stream(task) { - runtime.run(stream); - } - } - - let subscription = runtime.enter(|| program.subscription()); - runtime.track(subscription::into_recipes(subscription.map(Action::Output))); -} - -fn run_action( - action: Action, - program: &P, - compositor: &mut Option, - events: &mut Vec<(window::Id, core::Event)>, - messages: &mut Vec, - clipboard: &mut Clipboard, - control_sender: &mut mpsc::UnboundedSender, - interfaces: &mut FxHashMap< - window::Id, - UserInterface<'_, P::Message, P::Theme, P::Renderer>, - >, - window_manager: &mut WindowManager, - ui_caches: &mut FxHashMap, - is_window_opening: &mut bool, -) where - P: Program, - C: Compositor + 'static, - P::Theme: theme::Base, -{ - use crate::runtime::clipboard; - use crate::runtime::system; - use crate::runtime::window; - - match action { - Action::Output(message) => { - messages.push(message); - } - 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::Open(id, settings, channel) => { - let monitor = window_manager.last_monitor(); - - control_sender - .start_send(Control::CreateWindow { - id, - settings, - title: program.title(id), - monitor, - on_open: channel, - }) - .expect("Send control action"); - - *is_window_opening = true; - } - window::Action::Close(id) => { - let _ = ui_caches.remove(&id); - let _ = interfaces.remove(&id); - - if let Some(window) = window_manager.remove(id) { - if clipboard.window_id() == Some(window.raw.id()) { - *clipboard = window_manager - .first() - .map(|window| window.raw.clone()) - .map(Clipboard::connect) - .unwrap_or_else(Clipboard::unconnected); - } - - events.push(( - id, - core::Event::Window(core::window::Event::Closed), - )); - } - - if window_manager.is_empty() { - *compositor = None; - } - } - window::Action::GetOldest(channel) => { - let id = - window_manager.iter_mut().next().map(|(id, _window)| id); - - let _ = channel.send(id); - } - window::Action::GetLatest(channel) => { - let id = - window_manager.iter_mut().last().map(|(id, _window)| id); - - let _ = channel.send(id); - } - window::Action::Drag(id) => { - if let Some(window) = window_manager.get_mut(id) { - let _ = window.raw.drag_window(); - } - } - window::Action::DragResize(id, direction) => { - if let Some(window) = window_manager.get_mut(id) { - let _ = window.raw.drag_resize_window( - conversion::resize_direction(direction), - ); - } - } - window::Action::Resize(id, size) => { - if let Some(window) = window_manager.get_mut(id) { - let _ = window.raw.request_inner_size( - winit::dpi::LogicalSize { - width: size.width, - height: size.height, - }, - ); - } - } - window::Action::SetMinSize(id, size) => { - if let Some(window) = window_manager.get_mut(id) { - window.raw.set_min_inner_size(size.map(|size| { - winit::dpi::LogicalSize { - width: size.width, - height: size.height, - } - })); - } - } - window::Action::SetMaxSize(id, size) => { - if let Some(window) = window_manager.get_mut(id) { - window.raw.set_max_inner_size(size.map(|size| { - winit::dpi::LogicalSize { - width: size.width, - height: size.height, - } - })); - } - } - window::Action::SetResizeIncrements(id, increments) => { - if let Some(window) = window_manager.get_mut(id) { - window.raw.set_resize_increments(increments.map(|size| { - winit::dpi::LogicalSize { - width: size.width, - height: size.height, - } - })); - } - } - window::Action::SetResizable(id, resizable) => { - if let Some(window) = window_manager.get_mut(id) { - window.raw.set_resizable(resizable); - } - } - window::Action::GetSize(id, channel) => { - if let Some(window) = window_manager.get_mut(id) { - let size = window - .raw - .inner_size() - .to_logical(window.raw.scale_factor()); - - let _ = channel.send(Size::new(size.width, size.height)); - } - } - window::Action::GetMaximized(id, channel) => { - if let Some(window) = window_manager.get_mut(id) { - let _ = channel.send(window.raw.is_maximized()); - } - } - window::Action::Maximize(id, maximized) => { - if let Some(window) = window_manager.get_mut(id) { - window.raw.set_maximized(maximized); - } - } - window::Action::GetMinimized(id, channel) => { - if let Some(window) = window_manager.get_mut(id) { - let _ = channel.send(window.raw.is_minimized()); - } - } - window::Action::Minimize(id, minimized) => { - if let Some(window) = window_manager.get_mut(id) { - window.raw.set_minimized(minimized); - } - } - window::Action::GetPosition(id, channel) => { - if let Some(window) = window_manager.get(id) { - let position = window - .raw - .outer_position() - .map(|position| { - let position = position - .to_logical::(window.raw.scale_factor()); - - Point::new(position.x, position.y) - }) - .ok(); - - let _ = channel.send(position); - } - } - window::Action::GetScaleFactor(id, channel) => { - if let Some(window) = window_manager.get_mut(id) { - let scale_factor = window.raw.scale_factor(); - - let _ = channel.send(scale_factor as f32); - } - } - window::Action::Move(id, position) => { - if let Some(window) = window_manager.get_mut(id) { - window.raw.set_outer_position( - winit::dpi::LogicalPosition { - x: position.x, - y: position.y, - }, - ); - } - } - window::Action::SetMode(id, mode) => { - if let Some(window) = window_manager.get_mut(id) { - window.raw.set_visible(conversion::visible(mode)); - window.raw.set_fullscreen(conversion::fullscreen( - window.raw.current_monitor(), - mode, - )); - } - } - window::Action::SetIcon(id, icon) => { - if let Some(window) = window_manager.get_mut(id) { - window.raw.set_window_icon(conversion::icon(icon)); - } - } - window::Action::GetMode(id, channel) => { - if let Some(window) = window_manager.get_mut(id) { - let mode = if window.raw.is_visible().unwrap_or(true) { - conversion::mode(window.raw.fullscreen()) - } else { - core::window::Mode::Hidden - }; - - let _ = channel.send(mode); - } - } - window::Action::ToggleMaximize(id) => { - if let Some(window) = window_manager.get_mut(id) { - window.raw.set_maximized(!window.raw.is_maximized()); - } - } - window::Action::ToggleDecorations(id) => { - if let Some(window) = window_manager.get_mut(id) { - window.raw.set_decorations(!window.raw.is_decorated()); - } - } - window::Action::RequestUserAttention(id, attention_type) => { - if let Some(window) = window_manager.get_mut(id) { - window.raw.request_user_attention( - attention_type.map(conversion::user_attention), - ); - } - } - window::Action::GainFocus(id) => { - if let Some(window) = window_manager.get_mut(id) { - window.raw.focus_window(); - } - } - window::Action::SetLevel(id, level) => { - if let Some(window) = window_manager.get_mut(id) { - window - .raw - .set_window_level(conversion::window_level(level)); - } - } - window::Action::ShowSystemMenu(id) => { - if let Some(window) = window_manager.get_mut(id) { - if let mouse::Cursor::Available(point) = - window.state.cursor() - { - window.raw.show_window_menu( - winit::dpi::LogicalPosition { - x: point.x, - y: point.y, - }, - ); - } - } - } - window::Action::GetRawId(id, channel) => { - if let Some(window) = window_manager.get_mut(id) { - let _ = channel.send(window.raw.id().into()); - } - } - window::Action::RunWithHandle(id, f) => { - use window::raw_window_handle::HasWindowHandle; - - if let Some(handle) = window_manager - .get_mut(id) - .and_then(|window| window.raw.window_handle().ok()) - { - f(handle); - } - } - window::Action::Screenshot(id, channel) => { - if let Some(window) = window_manager.get_mut(id) { - if let Some(compositor) = compositor { - let bytes = compositor.screenshot( - &mut window.renderer, - window.state.viewport(), - window.state.background_color(), - ); - - let _ = channel.send(core::window::Screenshot::new( - bytes, - window.state.physical_size(), - window.state.viewport().scale_factor(), - )); - } - } - } - window::Action::EnableMousePassthrough(id) => { - if let Some(window) = window_manager.get_mut(id) { - let _ = window.raw.set_cursor_hittest(false); - } - } - window::Action::DisableMousePassthrough(id) => { - if let Some(window) = window_manager.get_mut(id) { - let _ = window.raw.set_cursor_hittest(true); - } - } - }, - Action::System(action) => match action { - system::Action::QueryInformation(_channel) => { - #[cfg(feature = "system")] - { - if let Some(compositor) = compositor { - 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() { - for (id, ui) in interfaces.iter_mut() { - if let Some(window) = window_manager.get_mut(*id) { - ui.operate(&window.renderer, operation.as_mut()); - } - } - - match operation.finish() { - operation::Outcome::None => {} - operation::Outcome::Some(()) => {} - operation::Outcome::Chain(next) => { - current_operation = Some(next); - } - } - } - } - Action::LoadFont { bytes, channel } => { - if let Some(compositor) = compositor { - // TODO: Error handling (?) - compositor.load_font(bytes.clone()); - - 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, P: Program, C>( - program: &'a P, - window_manager: &mut WindowManager, - mut cached_user_interfaces: FxHashMap, -) -> FxHashMap> -where - C: Compositor, - P::Theme: theme::Base, -{ - cached_user_interfaces - .drain() - .filter_map(|(id, cache)| { - let window = window_manager.get_mut(id)?; - - Some(( - id, - build_user_interface( - program, - cache, - &mut window.renderer, - window.state.logical_size(), - id, - ), - )) - }) - .collect() -} - -/// Returns true if the provided event should cause a [`Program`] to -/// exit. -pub fn user_force_quit( - event: &winit::event::WindowEvent, - _modifiers: winit::keyboard::ModifiersState, -) -> bool { - match event { - #[cfg(target_os = "macos")] - winit::event::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, - } -} diff --git a/winit/src/settings.rs b/winit/src/settings.rs deleted file mode 100644 index e2bf8abf..00000000 --- a/winit/src/settings.rs +++ /dev/null @@ -1,26 +0,0 @@ -//! Configure your application. -use crate::core; - -use std::borrow::Cow; - -/// The settings of an application. -#[derive(Debug, Clone, Default)] -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, - - /// The fonts to load on boot. - pub fonts: Vec>, -} - -impl From for Settings { - fn from(settings: core::Settings) -> Self { - Self { - id: settings.id, - fonts: settings.fonts, - } - } -} diff --git a/winit/src/program/window_manager.rs b/winit/src/window.rs similarity index 98% rename from winit/src/program/window_manager.rs rename to winit/src/window.rs index 3ad574ca..801fc086 100644 --- a/winit/src/program/window_manager.rs +++ b/winit/src/window.rs @@ -1,3 +1,9 @@ +mod state; + +use state::State; + +pub use crate::core::window::{Event, Id, RedrawRequest, Settings}; + use crate::conversion; use crate::core::alignment; use crate::core::input_method; @@ -6,12 +12,11 @@ use crate::core::renderer; use crate::core::text; use crate::core::theme; use crate::core::time::Instant; -use crate::core::window::{Id, RedrawRequest}; use crate::core::{ Color, InputMethod, Padding, Point, Rectangle, Size, Text, Vector, }; use crate::graphics::Compositor; -use crate::program::{Program, State}; +use crate::program::{self, Program}; use winit::dpi::{LogicalPosition, LogicalSize}; use winit::monitor::MonitorHandle; @@ -47,11 +52,11 @@ where &mut self, id: Id, window: Arc, - application: &P, + program: &program::Instance

, compositor: &mut C, exit_on_close_request: bool, ) -> &mut Window { - let state = State::new(application, id, &window); + let state = State::new(program, id, &window); let viewport_version = state.viewport_version(); let physical_size = state.physical_size(); let surface = compositor.create_surface( diff --git a/winit/src/program/state.rs b/winit/src/window/state.rs similarity index 92% rename from winit/src/program/state.rs rename to winit/src/window/state.rs index 1b844b82..e17b32a3 100644 --- a/winit/src/program/state.rs +++ b/winit/src/window/state.rs @@ -2,7 +2,7 @@ use crate::conversion; use crate::core::{Color, Size}; use crate::core::{mouse, theme, window}; use crate::graphics::Viewport; -use crate::program::Program; +use crate::program::{self, Program}; use winit::event::{Touch, WindowEvent}; use winit::window::Window; @@ -46,14 +46,14 @@ where { /// Creates a new [`State`] for the provided [`Program`]'s `window`. pub fn new( - application: &P, + program: &program::Instance

, window_id: window::Id, window: &Window, ) -> Self { - let title = application.title(window_id); - let scale_factor = application.scale_factor(window_id); - let theme = application.theme(window_id); - let style = application.style(&theme); + let title = program.title(window_id); + let scale_factor = program.scale_factor(window_id); + let theme = program.theme(window_id); + let style = program.style(&theme); let viewport = { let physical_size = window.inner_size(); @@ -185,12 +185,12 @@ where /// and window after calling [`State::update`]. pub fn synchronize( &mut self, - application: &P, + program: &program::Instance

, window_id: window::Id, window: &Window, ) { // Update window title - let new_title = application.title(window_id); + let new_title = program.title(window_id); if self.title != new_title { window.set_title(&new_title); @@ -198,7 +198,7 @@ where } // Update scale factor and size - let new_scale_factor = application.scale_factor(window_id); + let new_scale_factor = program.scale_factor(window_id); let new_size = window.inner_size(); let current_size = self.viewport.physical_size(); @@ -216,7 +216,7 @@ where } // Update theme and appearance - self.theme = application.theme(window_id); - self.style = application.style(&self.theme); + self.theme = program.theme(window_id); + self.style = program.style(&self.theme); } }