diff --git a/Cargo.toml b/Cargo.toml index e042c89f..0ae1a955 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -28,7 +28,7 @@ wgpu-bare = ["iced_renderer/wgpu-bare", "iced_widget/wgpu"] # Enables the `tiny-skia` software renderer -default = ["tiny-skia", "x11", "wayland", "tokio"] +default = ["tiny-skia", "tokio"] # Enable the `tiny-skia` software renderer backend tiny-skia = ["iced_renderer/tiny-skia"] # Enables the `image` widget @@ -116,7 +116,11 @@ a11y = [ "iced_winit?/a11y", ] # Enables the winit shell. Conflicts with `wayland` and `glutin`. -winit = ["iced_winit", "iced_accessibility?/accesskit_winit"] +winit = [ + "iced_winit", + "iced_accessibility?/accesskit_winit", + "iced_program/winit", +] # Enables the sctk shell. wayland = ["iced_widget/wayland", "iced_core/wayland", "iced_winit/wayland"] [dependencies] diff --git a/core/src/overlay.rs b/core/src/overlay.rs index b29934ec..67628d98 100644 --- a/core/src/overlay.rs +++ b/core/src/overlay.rs @@ -10,7 +10,6 @@ pub use nested::Nested; use crate::layout; use crate::mouse; use crate::renderer; -use crate::widget::Operation; use crate::widget::Tree; use crate::{Clipboard, Event, Layout, Rectangle, Shell, Size, Vector}; diff --git a/program/Cargo.toml b/program/Cargo.toml index b86421fb..f3fd1c53 100644 --- a/program/Cargo.toml +++ b/program/Cargo.toml @@ -15,6 +15,7 @@ rust-version.workspace = true workspace = true [features] +winit = [] debug = [] time-travel = [] diff --git a/program/src/lib.rs b/program/src/lib.rs index 0a88c375..bb710e73 100644 --- a/program/src/lib.rs +++ b/program/src/lib.rs @@ -1,6 +1,7 @@ //! The definition of an iced program. pub use iced_graphics as graphics; pub use iced_runtime as runtime; +pub use iced_runtime::Task; pub use iced_runtime::core; pub use iced_runtime::futures; @@ -17,14 +18,6 @@ use crate::core::window; use crate::core::{Element, Font, Settings}; use crate::futures::{Executor, Subscription}; use crate::graphics::compositor; -use crate::runtime::task::Task; -#[cfg(any(feature = "winit", feature = "wayland"))] -use crate::shell; -#[cfg(any(feature = "winit", feature = "wayland"))] -pub use crate::shell::program::{Appearance, DefaultStyle}; - -#[cfg(not(any(feature = "winit", feature = "wayland")))] -pub use crate::runtime::{Appearance, DefaultStyle}; /// The internal definition of a [`Program`]. /// @@ -124,138 +117,138 @@ pub trait Program: Sized { &[] } - #[cfg(any(feature = "winit", feature = "wayland"))] - /// 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()) - }) - } + // #[cfg(feature = "winit")] + // /// 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: crate::Settings, + // window_settings: Option, + // ) -> crate::Result + // where + // Self: 'static, + // Self::State: Default, + // { + // self.run_with(settings, window_settings, || { + // (Self::State::default(), Task::none()) + // }) + // } - #[cfg(any(feature = "winit", feature = "wayland"))] - /// 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; + // #[cfg(feature = "winit")] + // /// Runs the [`Program`] with the given [`Settings`] and a closure that creates the initial state. + // fn run_with( + // self, + // settings: crate::Settings, + // window_settings: Option, + // initialize: I, + // ) -> crate::Result + // where + // Self: 'static, + // I: FnOnce() -> (Self::State, Task) + 'static, + // { + // use std::marker::PhantomData; - struct Instance { - program: P, - state: P::State, - _initialize: 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; + // 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 new( - (program, initialize): Self::Flags, - ) -> (Self, Task) { - let (state, task) = initialize(); + // fn new( + // (program, initialize): Self::Flags, + // ) -> (Self, Task) { + // let (state, task) = initialize(); - ( - Self { - program, - state, - _initialize: PhantomData, - }, - task, - ) - } + // ( + // Self { + // program, + // state, + // _initialize: PhantomData, + // }, + // task, + // ) + // } - fn title(&self, window: window::Id) -> String { - self.program.title(&self.state, window) - } + // 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 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 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 subscription(&self) -> Subscription { + // self.program.subscription(&self.state) + // } - fn theme(&self, window: window::Id) -> Self::Theme { - self.program.theme(&self.state, window) - } + // fn theme(&self, window: window::Id) -> Self::Theme { + // self.program.theme(&self.state, window) + // } - fn style(&self, theme: &Self::Theme) -> Appearance { - self.program.style(&self.state, theme) - } + // fn style(&self, theme: &Self::Theme) -> Appearance { + // self.program.style(&self.state, theme) + // } - fn scale_factor(&self, window: window::Id) -> f64 { - self.program.scale_factor(&self.state, window) - } - } + // 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() - }; + // #[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, - exit_on_close_request: settings.exit_on_close_request, - } - .into(), - renderer_settings, - window_settings, - (self, initialize), - )?) - } + // Ok(shell::program::run::< + // Instance, + // ::Compositor, + // >( + // crate::Settings { + // id: settings.id, + // fonts: settings.fonts, + // default_font: settings.default_font, + // default_text_size: settings.default_text_size, + // antialiasing: settings.antialiasing, + // exit_on_close_request: settings.exit_on_close_request, + // } + // .into(), + // renderer_settings, + // window_settings, + // (self, initialize), + // )?) + // } } /// Decorates a [`Program`] with the given title function. diff --git a/runtime/src/dnd.rs b/runtime/src/dnd.rs index 2fd39244..0461bece 100644 --- a/runtime/src/dnd.rs +++ b/runtime/src/dnd.rs @@ -2,10 +2,8 @@ use std::any::Any; -use bytes::buf::Take; use dnd::{DndDestinationRectangle, DndSurface}; -use iced_core::{clipboard::DndSource, Vector}; -use iced_futures::MaybeSend; +use iced_core::clipboard::DndSource; use window_clipboard::mime::{AllowedMimeTypes, AsMimeTypes}; use crate::{oneshot, task, Action, Task}; diff --git a/runtime/src/lib.rs b/runtime/src/lib.rs index 11328920..68c0d455 100644 --- a/runtime/src/lib.rs +++ b/runtime/src/lib.rs @@ -29,7 +29,6 @@ pub use user_interface::UserInterface; pub use window::Window; use crate::futures::futures::channel::oneshot; -use dnd::DndAction; use std::borrow::Cow; use std::fmt; @@ -70,6 +69,7 @@ pub enum Action { /// This will normally close any application windows and /// terminate the runtime loop. Exit, + /// Run a Dnd action. Dnd(crate::dnd::DndAction), @@ -126,7 +126,7 @@ where Action::PlatformSpecific(action) => { write!(f, "Action::PlatformSpecific({:?})", action) } - Action::Dnd(action) => write!(f, "Action::Dnd({:?})", action), + Action::Dnd(_) => write!(f, "Action::Dnd"), } } } diff --git a/runtime/src/platform_specific/mod.rs b/runtime/src/platform_specific/mod.rs index a8f761b7..572288b3 100644 --- a/runtime/src/platform_specific/mod.rs +++ b/runtime/src/platform_specific/mod.rs @@ -1,8 +1,6 @@ //! Platform specific actions defined for wayland -use std::{fmt, marker::PhantomData}; - -use iced_futures::MaybeSend; +use std::fmt; #[cfg(feature = "wayland")] /// Platform specific actions defined for wayland @@ -20,6 +18,7 @@ impl fmt::Debug for Action { match self { #[cfg(feature = "wayland")] Action::Wayland(action) => action.fmt(_f), + #[cfg(not(feature = "wayland"))] _ => Ok(()), } } diff --git a/runtime/src/program/state.rs b/runtime/src/program/state.rs index c0befaa2..1eb2d770 100644 --- a/runtime/src/program/state.rs +++ b/runtime/src/program/state.rs @@ -1,5 +1,3 @@ -use iced_core::widget::operation::Outcome; - use crate::core::event::{self, Event}; use crate::core::mouse; use crate::core::renderer; @@ -211,7 +209,6 @@ where operation::Outcome::Chain(next) => { current_operation = Some(next); } - _ => {} }; } } diff --git a/runtime/src/user_interface.rs b/runtime/src/user_interface.rs index 3c705f13..32430e36 100644 --- a/runtime/src/user_interface.rs +++ b/runtime/src/user_interface.rs @@ -1,7 +1,6 @@ //! Implement your own event loop to drive a user interface. use iced_core::clipboard::DndDestinationRectangles; -use iced_core::widget::Operation; use iced_core::widget::tree::NAMED; use crate::core::event::{self, Event}; diff --git a/src/application.rs b/src/application.rs index 4ec326d7..cefea7b3 100644 --- a/src/application.rs +++ b/src/application.rs @@ -30,8 +30,6 @@ //! ] //! } //! ``` -use crate::message; -use crate::program::{self, Appearance, DefaultStyle, Program}; use crate::theme; @@ -40,7 +38,9 @@ use crate::shell; use crate::window; use crate::{ Element, Executor, Font, Never, Preset, Result, Settings, Size, - Subscription, Theme, task::Task, + Subscription, Theme, message, + program::{self, Program}, + task::Task, }; use iced_debug as debug; diff --git a/src/daemon.rs b/src/daemon.rs index 35213273..597e52d3 100644 --- a/src/daemon.rs +++ b/src/daemon.rs @@ -13,7 +13,7 @@ use crate::{ use iced_debug as debug; -#[cfg(not(any(feature = "winit", feature = "wayland")))] +#[cfg(not(feature = "winit"))] use crate::runtime::{Appearance, DefaultStyle}; use std::borrow::Cow; diff --git a/src/error.rs b/src/error.rs index 507cd3ab..9db46fe1 100644 --- a/src/error.rs +++ b/src/error.rs @@ -34,10 +34,6 @@ impl From for Error { shell::Error::WindowCreationFailed(error) => { Error::WindowCreationFailed(Box::new(error)) } - #[cfg(feature = "wayland")] - shell::Error::WindowCreationFailed(error) => { - Error::WindowCreationFailed(Box::new(error)) - } shell::Error::GraphicsCreationFailed(error) => { Error::GraphicsCreationFailed(error) } diff --git a/src/lib.rs b/src/lib.rs index bcadf12f..0964ffa4 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -504,17 +504,17 @@ compile_error!( Available options: thread-pool, tokio, or smol." ); -#[cfg(all( - target_family = "unix", - not(target_os = "macos"), - not(feature = "wayland"), - not(feature = "x11"), -))] -compile_error!( - "No Unix display server backend has been enabled. You must enable a \ - display server feature.\n\ - Available options: x11, wayland." -); +// #[cfg(all( +// target_family = "unix", +// not(target_os = "macos"), +// not(feature = "wayland"), +// not(feature = "x11"), +// ))] +// compile_error!( +// "No Unix display server backend has been enabled. You must enable a \ +// display server feature.\n\ +// Available options: x11, wayland." +// ); #[cfg(feature = "highlighter")] pub use iced_highlighter as highlighter; @@ -544,19 +544,6 @@ pub use application::Application; #[cfg(feature = "winit")] pub use program::Program; -// wayland application -// #[cfg(feature = "wayland")] -// pub mod wayland; -// #[cfg(feature = "wayland")] -// pub use wayland::application; -// #[cfg(feature = "wayland")] -// pub use wayland::application::Application; -// #[cfg(feature = "wayland")] -// pub use wayland::program; -// #[doc(inline)] -// #[cfg(feature = "wayland")] -// pub use wayland::program::Program; - #[cfg(feature = "advanced")] pub mod advanced; diff --git a/winit/src/program.rs b/winit/src/program.rs deleted file mode 100644 index 01916650..00000000 --- a/winit/src/program.rs +++ /dev/null @@ -1,2348 +0,0 @@ -//! Create interactive, native cross-platform applications for WGPU. -#[path = "application/drag_resize.rs"] -mod drag_resize; -mod state; -mod window_manager; - -pub use runtime::{Appearance, DefaultStyle, default}; -use winit::event_loop::OwnedDisplayHandle; - -use crate::conversion; -use crate::core; -use crate::core::Clipboard as CoreClipboard; -use crate::core::Length; -use crate::core::mouse; -use crate::core::renderer; -use crate::core::time::Instant; -use crate::core::widget::Operation; -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::platform_specific; -use crate::runtime::Debug; -use crate::runtime::user_interface::{self, UserInterface}; -use crate::runtime::{self, Action, Task}; -use crate::{Clipboard, Error, Proxy, Settings}; -use dnd::DndSurface; -use dnd::Icon; -use iced_futures::core::widget::operation::search_id; -use iced_graphics::Viewport; -pub use state::State; -use window_clipboard::mime::ClipboardStoreData; -use winit::raw_window_handle::HasWindowHandle; - -pub(crate) use window_manager::WindowManager; - -use rustc_hash::FxHashMap; -use std::any::Any; -use std::borrow::Cow; -use std::collections::HashMap; -use std::mem::ManuallyDrop; -use std::sync::Arc; -use std::time::Duration; - -/// 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: DefaultStyle, -{ - /// The type of __messages__ your [`Program`] will produce. - type Message: std::fmt::Debug + Send; - - /// The theme used to draw the [`Program`]. - type Theme; - - /// The [`Executor`] that will run commands and subscriptions. - /// - /// The [default executor] can be a good starting point! - /// - /// [`Executor`]: Self::Executor - /// [default executor]: crate::futures::backend::default::Executor - type Executor: Executor; - - /// The graphics backend to use to draw the [`Program`]. - type Renderer: core::Renderer + core::text::Renderer; - - /// The data needed to initialize your [`Program`]. - type Flags; - - /// Initializes the [`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) -> Appearance { - theme.default_style() - } - - /// Returns the event `Subscription` for the current state of the - /// application. - /// - /// The messages produced by the `Subscription` will be handled by - /// [`update`](#tymethod.update). - /// - /// A `Subscription` will be kept alive as long as you keep returning it! - /// - /// By default, it returns an empty subscription. - fn subscription(&self) -> Subscription { - 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: DefaultStyle, -{ - use winit::event_loop::EventLoop; - - let mut debug = Debug::new(); - debug.startup_started(); - - let event_loop = EventLoop::new().expect("Create event loop"); - #[cfg(feature = "wayland")] - let is_wayland = - winit::platform::wayland::EventLoopExtWayland::is_wayland(&event_loop); - #[cfg(not(feature = "wayland"))] - let is_wayland = false; - - let (event_sender, event_receiver) = mpsc::unbounded(); - let (proxy, worker): (Proxy<

::Message>, _) = - Proxy::new(event_loop.create_proxy(), event_sender.clone()); - - 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 open = iced_runtime::task::oneshot(|channel| { - iced_runtime::Action::Window(iced_runtime::window::Action::Open( - iced_runtime::core::window::Id::RESERVED, - window_settings, - channel, - )) - }); - - 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 (boot_sender, boot_receiver) = oneshot::channel(); - let (control_sender, control_receiver) = mpsc::unbounded(); - - let instance = Box::pin(run_instance::( - program, - runtime, - proxy.clone(), - debug, - boot_receiver, - event_receiver, - control_sender.clone(), - event_loop.owned_display_handle(), - is_daemon, - )); - - let context = task::Context::from_waker(task::noop_waker_ref()); - - struct Runner { - instance: std::pin::Pin>, - context: task::Context<'static>, - id: Option, - boot: Option>, - sender: mpsc::UnboundedSender>, - receiver: mpsc::UnboundedReceiver, - error: Option, - - #[cfg(target_arch = "wasm32")] - is_booted: std::rc::Rc>, - #[cfg(target_arch = "wasm32")] - canvas: Option, - } - - struct BootConfig { - sender: oneshot::Sender>, - fonts: Vec>, - graphics_settings: graphics::Settings, - control_sender: mpsc::UnboundedSender, - is_wayland: bool, - } - - let runner = Runner { - instance, - context, - id: settings.id, - boot: Some(BootConfig { - sender: boot_sender, - fonts: settings.fonts, - graphics_settings, - control_sender, - is_wayland, - }), - sender: event_sender, - receiver: control_receiver, - error: None, - - #[cfg(target_arch = "wasm32")] - is_booted: std::rc::Rc::new(std::cell::RefCell::new(false)), - #[cfg(target_arch = "wasm32")] - canvas: None, - }; - - impl winit::application::ApplicationHandler - for Runner - where - Message: std::fmt::Debug, - F: Future, - C: Compositor + 'static, - { - fn proxy_wake_up( - &mut self, - event_loop: &dyn winit::event_loop::ActiveEventLoop, - ) { - self.process_event(event_loop, None); - } - - fn new_events( - &mut self, - event_loop: &dyn winit::event_loop::ActiveEventLoop, - cause: winit::event::StartCause, - ) { - if self.boot.is_some() { - return; - } - self.process_event(event_loop, Some(Event::NewEvents(cause))); - } - - fn window_event( - &mut self, - event_loop: &dyn 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, - Some(Event::Winit(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 about_to_wait( - &mut self, - event_loop: &dyn winit::event_loop::ActiveEventLoop, - ) { - self.process_event(event_loop, Some(Event::AboutToWait)); - } - - fn can_create_surfaces( - &mut self, - event_loop: &dyn winit::event_loop::ActiveEventLoop, - ) { - // create initial window - let Some(BootConfig { - sender, - fonts, - graphics_settings, - control_sender, - is_wayland, - }) = self.boot.take() - else { - return; - }; - - let window: Arc = match event_loop - .create_window( - winit::window::WindowAttributes::default() - .with_visible(false), - ) { - Ok(window) => Arc::from(window), - Err(error) => { - self.error = Some(Error::WindowCreationFailed(error)); - event_loop.exit(); - return; - } - }; - - #[cfg(target_arch = "wasm32")] - { - use winit::platform::web::WindowExtWebSys; - self.canvas = window.canvas(); - } - - let finish_boot = async move { - let mut compositor = - C::new(graphics_settings, window.clone()).await?; - - for font in fonts { - compositor.load_font(font); - } - - sender - .send(Boot { - compositor, - is_wayland, - }) - .ok() - .expect("Send boot event"); - - Ok::<_, graphics::Error>(()) - }; - - #[cfg(not(target_arch = "wasm32"))] - if let Err(error) = - crate::futures::futures::executor::block_on(finish_boot) - { - self.error = Some(Error::GraphicsCreationFailed(error)); - event_loop.exit(); - } - - #[cfg(target_arch = "wasm32")] - { - let is_booted = self.is_booted.clone(); - - wasm_bindgen_futures::spawn_local(async move { - finish_boot.await.expect("Finish boot!"); - - *is_booted.borrow_mut() = true; - }); - - event_loop - .set_control_flow(winit::event_loop::ControlFlow::Poll); - } - } - } - - impl Runner - where - F: Future, - C: Compositor, - { - fn process_event( - &mut self, - event_loop: &dyn winit::event_loop::ActiveEventLoop, - event: Option>, - ) { - if event_loop.exiting() { - return; - } - - if let Some(event) = event { - 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 new < current => {} - ( - 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 resize_border = settings.resize_border; - - 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:#?}" - ); - - let window = Arc::from( - event_loop - .create_window(window_attributes) - .expect("Create window"), - ); - - #[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, - Some(Event::WindowCreated { - id, - window, - exit_on_close_request, - make_visible: visible, - on_open, - resize_border, - }), - ); - } - Control::Exit => { - event_loop.exit(); - } - Control::Dnd(e) => { - self.sender.start_send(Event::Dnd(e)).unwrap(); - } - #[cfg(feature = "a11y")] - Control::Accessibility(id, event) => { - self.process_event( - event_loop, - Some(Event::Accessibility(id, event)), - ); - } - #[cfg(feature = "a11y")] - Control::AccessibilityEnabled(event) => { - self.process_event( - event_loop, - Some(Event::AccessibilityEnabled(event)), - ); - } - Control::PlatformSpecific(e) => { - self.sender - .start_send(Event::PlatformSpecific(e)) - .unwrap(); - } - Control::AboutToWait => { - self.sender - .start_send(Event::AboutToWait) - .expect("Send event"); - } - Control::Winit(id, e) => { - self.sender - .start_send(Event::Winit(id, e)) - .expect("Send event"); - } - Control::StartDnd => { - self.sender - .start_send(Event::StartDnd) - .expect("Send event"); - } - }, - _ => { - 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(()) - } -} - -struct Boot { - compositor: C, - is_wayland: bool, -} - -pub(crate) enum Event { - WindowCreated { - id: window::Id, - window: Arc, - exit_on_close_request: bool, - make_visible: bool, - on_open: oneshot::Sender, - resize_border: u32, - }, - Dnd(dnd::DndEvent), - #[cfg(feature = "a11y")] - Accessibility(window::Id, iced_accessibility::accesskit::ActionRequest), - #[cfg(feature = "a11y")] - AccessibilityEnabled(bool), - Winit(winit::window::WindowId, winit::event::WindowEvent), - AboutToWait, - UserEvent(Action), - NewEvents(winit::event::StartCause), - PlatformSpecific(crate::platform_specific::Event), - StartDnd, -} - -pub(crate) enum Control { - ChangeFlow(winit::event_loop::ControlFlow), - Exit, - CreateWindow { - id: window::Id, - settings: window::Settings, - title: String, - monitor: Option, - on_open: oneshot::Sender, - }, - Dnd(dnd::DndEvent), - #[cfg(feature = "a11y")] - Accessibility(window::Id, iced_accessibility::accesskit::ActionRequest), - #[cfg(feature = "a11y")] - AccessibilityEnabled(bool), - PlatformSpecific(crate::platform_specific::Event), - AboutToWait, - Winit(winit::window::WindowId, winit::event::WindowEvent), - StartDnd, -} - -async fn run_instance<'a, P, C>( - mut program: P, - mut runtime: Runtime, Action>, - mut proxy: Proxy, - mut debug: Debug, - boot: oneshot::Receiver>, - mut event_receiver: mpsc::UnboundedReceiver>, - mut control_sender: mpsc::UnboundedSender, - display_handle: OwnedDisplayHandle, - is_daemon: bool, - is_wayland: bool, -) where - P: Program + 'static, - C: Compositor + 'static, - P::Theme: DefaultStyle, -{ - use winit::event; - use winit::event_loop::ControlFlow; - - let Boot { - mut compositor, - is_wayland, - } = boot.await.expect("Receive boot"); - - let mut platform_specific_handler = - crate::platform_specific::PlatformSpecific::default(); - #[cfg(all(feature = "wayland", target_os = "linux"))] - if is_wayland { - platform_specific_handler = platform_specific_handler.with_wayland( - control_sender.clone(), - proxy.raw.clone(), - display_handle, - ); - } - - let mut window_manager = WindowManager::new(); - let mut is_window_opening = !is_daemon; - - let mut events = Vec::new(); - let mut messages = Vec::new(); - let mut actions = 0; - - #[cfg(feature = "a11y")] - let (mut adapters, mut a11y_enabled) = if let Some((main_id, title, raw)) = - window_manager.ids().next().and_then(|id| { - window_manager - .get(id) - .map(|w| (id, w.state.title.clone(), w.raw.clone())) - }) { - let node_id = core::id::window_node_id(); - use crate::a11y::*; - use iced_accessibility::accesskit::{ - ActivationHandler, Node, NodeId, Role, Tree, TreeUpdate, - }; - use iced_accessibility::accesskit_winit::Adapter; - - let activation_handler = WinitActivationHandler { - proxy: control_sender.clone(), - title: title.clone(), - }; - - let action_handler = WinitActionHandler { - id: main_id, - proxy: control_sender.clone(), - }; - - let deactivation_handler = WinitDeactivationHandler { - proxy: control_sender.clone(), - }; - ( - HashMap::from([( - main_id, - ( - node_id, - Adapter::with_direct_handlers( - raw.as_ref(), - activation_handler, - action_handler, - deactivation_handler, - ), - ), - )]), - false, - ) - } else { - (Default::default(), false) - }; - - let mut ui_caches = FxHashMap::default(); - let mut user_interfaces: ManuallyDrop< - HashMap< - window::Id, - UserInterface< - '_, -

::Message, -

::Theme, -

::Renderer, - >, - rustc_hash::FxBuildHasher, - >, - > = ManuallyDrop::new(FxHashMap::default()); - let mut clipboard = Clipboard::unconnected(); - - let mut cur_dnd_surface: Option = None; - - debug.startup_finished(); - loop { - // Empty the queue if possible - let event = if let Ok(event) = event_receiver.try_next() { - event - } else { - platform_specific_handler.send_ready(); - event_receiver.next().await - }; - - let Some(event) = event else { - break; - }; - - match event { - Event::StartDnd => { - let queued = clipboard.get_queued(); - for crate::clipboard::StartDnd { - internal, - source_surface, - icon_surface, - content, - actions, - } in queued - { - let Some(window_id) = source_surface.and_then(|source| { - match source { - core::clipboard::DndSource::Surface(s) => Some(s), - core::clipboard::DndSource::Widget(w) => { - // search windows for widget with operation - user_interfaces.iter_mut().find_map( - |(ui_id, ui)| { - let Some(ui_renderer) = window_manager - .get_mut(ui_id.clone()) - .map(|w| &w.renderer) - else { - return None; - }; - - let operation: Box> = - Box::new(operation::map( - Box::new(search_id::search_id( - w.clone(), - )), - |_| {}, - )); - let mut current_operation = - Some(operation); - - while let Some(mut operation) = - current_operation.take() - { - ui.operate( - ui_renderer, - operation.as_mut(), - ); - - match operation.finish() { - operation::Outcome::None => {} - operation::Outcome::Some( - (), - ) => { - return Some(ui_id.clone()); - } - operation::Outcome::Chain( - next, - ) => { - current_operation = - Some(next); - } - } - } - None - }, - ) - } - } - }) else { - eprintln!("No source surface"); - continue; - }; - - let Some(window) = window_manager.get_mut(window_id) else { - eprintln!("No window"); - continue; - }; - - let state = &window.state; - let icon_surface = icon_surface - .map(|i| { - let i: Box = i; - i - }) - .map(|i| { - i.downcast::, - core::widget::tree::State, - )>>() - .unwrap() - }) - .map( - |e: Box< - Arc<( - core::Element< - 'static, - (), - P::Theme, - P::Renderer, - >, - core::widget::tree::State, - )>, - >| { - let mut renderer = compositor.create_renderer(); - - let e = Arc::into_inner(*e).unwrap(); - let (mut e, widget_state) = e; - let lim = core::layout::Limits::new( - Size::new(1., 1.), - Size::new( - state.viewport().physical_width() - as f32, - state.viewport().physical_height() - as f32, - ), - ); - - let mut tree = core::widget::Tree { - id: e.as_widget().id(), - tag: e.as_widget().tag(), - state: widget_state, - children: e.as_widget().children(), - }; - - let size = e - .as_widget() - .layout(&mut tree, &renderer, &lim); - e.as_widget_mut().diff(&mut tree); - - let size = lim.resolve( - Length::Shrink, - Length::Shrink, - size.size(), - ); - let viewport = Viewport::with_logical_size( - size, - state.viewport().scale_factor(), - ); - let mut surface = compositor.create_surface( - window.raw.clone(), - viewport.physical_width(), - viewport.physical_height(), - ); - - let mut ui = UserInterface::build( - e, - size, - user_interface::Cache::default(), - &mut renderer, - ); - _ = ui.draw( - &mut renderer, - state.theme(), - &renderer::Style { - icon_color: state.icon_color(), - text_color: state.text_color(), - scale_factor: state.scale_factor(), - }, - Default::default(), - ); - let mut bytes = compositor.screenshot( - &mut renderer, - &mut surface, - &viewport, - core::Color::TRANSPARENT, - &debug.overlay(), - ); - for pix in bytes.chunks_exact_mut(4) { - // rgba -> argb little endian - pix.swap(0, 2); - } - Icon::Buffer { - data: Arc::new(bytes), - width: viewport.physical_width(), - height: viewport.physical_height(), - transparent: true, - } - }, - ); - - clipboard.start_dnd_winit( - internal, - DndSurface(Arc::new(Box::new(window.raw.clone()))), - icon_surface, - content, - actions, - ); - } - } - Event::WindowCreated { - id, - window, - exit_on_close_request, - make_visible, - on_open, - resize_border, - } => { - let window = window_manager.insert( - id, - window, - &program, - &mut compositor, - exit_on_close_request, - resize_border, - ); - #[cfg(feature = "wayland")] - platform_specific_handler.send_wayland( - platform_specific::Action::TrackWindow( - window.raw.clone(), - id, - ), - ); - #[cfg(feature = "a11y")] - { - use crate::a11y::*; - use iced_accessibility::accesskit::{ - ActivationHandler, Node, NodeId, Role, Tree, TreeUpdate, - }; - use iced_accessibility::accesskit_winit::Adapter; - - let node_id = core::id::window_node_id(); - - let activation_handler = WinitActivationHandler { - proxy: control_sender.clone(), - title: window.state.title.clone(), - }; - - let action_handler = WinitActionHandler { - id, - proxy: control_sender.clone(), - }; - - let deactivation_handler = WinitDeactivationHandler { - proxy: control_sender.clone(), - }; - _ = adapters.insert( - id, - ( - node_id, - Adapter::with_direct_handlers( - window.raw.as_ref(), - activation_handler, - action_handler, - deactivation_handler, - ), - ), - ); - } - - 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, - &mut debug, - id, - window.raw.clone(), - window.prev_dnd_destination_rectangles_count, - &mut clipboard, - ), - ); - let _ = ui_caches.insert(id, user_interface::Cache::default()); - - if make_visible { - window.raw.set_visible(true); - } - - events.push(( - Some(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(), - crate::clipboard::ControlSender { - sender: control_sender.clone(), - proxy: proxy.raw.clone(), - }, - ); - } - - let _ = on_open.send(id); - is_window_opening = false; - } - Event::UserEvent(action) => { - run_action( - action, - &program, - &mut compositor, - &mut events, - &mut messages, - &mut clipboard, - &mut control_sender, - &mut debug, - &mut user_interfaces, - &mut window_manager, - &mut ui_caches, - &mut is_window_opening, - &mut platform_specific_handler, - ); - actions += 1; - } - Event::NewEvents( - event::StartCause::Init - | event::StartCause::ResumeTimeReached { .. }, - ) => { - if window_manager.ids().next().is_none() { - _ = control_sender - .start_send(Control::ChangeFlow(ControlFlow::Wait)); - } - for (_id, window) in window_manager.iter_mut() { - window.request_redraw(); - } - } - Event::Winit(window_id, event) => { - match event { - event::WindowEvent::RedrawRequested => { - let Some((id, window)) = - window_manager.get_mut_alias(window_id) - else { - continue; - }; - - // TODO: Avoid redrawing all the time by forcing widgets to - // request redraws on state changes - // - // Then, we can use the `interface_state` here to decide if a redraw - // is needed right away, or simply wait until a specific time. - let redraw_event = 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, - ); - - debug.draw_started(); - let new_mouse_interaction = ui.draw( - &mut window.renderer, - window.state.theme(), - &renderer::Style { - icon_color: window.state.icon_color(), - text_color: window.state.text_color(), - scale_factor: window.state.scale_factor(), - }, - cursor, - ); - platform_specific_handler - .update_subsurfaces(id, window.raw.as_ref()); - debug.draw_finished(); - - if new_mouse_interaction != window.mouse_interaction { - if let Some(interaction) = - conversion::mouse_interaction( - new_mouse_interaction, - ) - { - if matches!( - window.mouse_interaction, - mouse::Interaction::Hide - ) { - window.raw.set_cursor_visible(true); - } - window.raw.set_cursor(interaction.into()) - } else { - window.raw.set_cursor_visible(false); - } - - window.mouse_interaction = new_mouse_interaction; - } - - runtime.broadcast(subscription::Event::Interaction { - window: id, - event: redraw_event, - status: core::event::Status::Ignored, - }); - - if control_sender - .start_send(Control::ChangeFlow(match ui_state { - user_interface::State::Updated { - redraw_request: Some(redraw_request), - } => match redraw_request { - window::RedrawRequest::NextFrame => { - window.request_redraw(); - - ControlFlow::Wait - } - window::RedrawRequest::At(at) => { - ControlFlow::WaitUntil(at) - } - }, - _ => ControlFlow::Wait, - })) - .is_err() - { - panic!("send error"); - } - - 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(); - debug.layout_started(); - let mut ui = user_interfaces - .remove(&id) - .expect("Remove user interface") - .relayout(logical_size, &mut window.renderer); - - #[cfg(feature = "a11y")] - { - use iced_accessibility::{ - A11yId, A11yNode, A11yTree, - accesskit::{ - Node, NodeId, Role, Tree, TreeUpdate, - }, - }; - if let Some(Some((a11y_id, adapter))) = - a11y_enabled.then(|| adapters.get_mut(&id)) - { - // TODO cleanup duplication - let child_tree = - ui.a11y_nodes(window.state.cursor()); - let mut root = Node::new(Role::Window); - root.set_name( - window.state.title.to_string(), - ); - let window_tree = - A11yTree::node_with_child_tree( - A11yNode::new(root, *a11y_id), - child_tree, - ); - let tree = Tree::new(NodeId(*a11y_id)); - - let focus = - Arc::new(std::sync::Mutex::new(None)); - let focus_clone = focus.clone(); - let operation: Box> = - Box::new(operation::map( - Box::new( - operation::focusable::find_focused( - ), - ), - move |id| { - let mut guard = focus.lock().unwrap(); - _ = guard.replace(id); - }, - )); - let mut current_operation = Some(operation); - - while let Some(mut operation) = - current_operation.take() - { - ui.operate( - &window.renderer, - operation.as_mut(), - ); - - match operation.finish() { - operation::Outcome::None => {} - operation::Outcome::Some(()) => { - break; - } - operation::Outcome::Chain(next) => { - current_operation = Some(next); - } - } - } - let mut guard = focus_clone.lock().unwrap(); - let focus = guard - .take() - .map(|id| A11yId::Widget(id)); - tracing::debug!( - "focus: {:?}\ntree root: {:?}\n children: {:?}", - &focus, - window_tree - .root() - .iter() - .map(|n| (n.node().role(), n.id())) - .collect::>(), - window_tree - .children() - .iter() - .map(|n| (n.node().role(), n.id())) - .collect::>() - ); - let focus = focus - .filter(|f_id| { - window_tree.contains(f_id) - }) - .map(|id| id.into()) - .unwrap_or_else(|| tree.root); - adapter.update_if_active(|| TreeUpdate { - nodes: window_tree.into(), - tree: Some(tree), - focus, - }); - } - } - - let _ = user_interfaces.insert(id, ui); - debug.layout_finished(); - - debug.draw_started(); - let new_mouse_interaction = user_interfaces - .get_mut(&id) - .expect("Get user interface") - .draw( - &mut window.renderer, - window.state.theme(), - &renderer::Style { - icon_color: window.state.icon_color(), - text_color: window.state.text_color(), - scale_factor: window - .state - .scale_factor(), - }, - window.state.cursor(), - ); - platform_specific_handler - .update_subsurfaces(id, window.raw.as_ref()); - debug.draw_finished(); - - if new_mouse_interaction != window.mouse_interaction - { - if let Some(interaction) = - conversion::mouse_interaction( - new_mouse_interaction, - ) - { - if matches!( - window.mouse_interaction, - mouse::Interaction::Hide - ) { - window.raw.set_cursor_visible(true); - } - window.raw.set_cursor(interaction.into()) - } else { - window.raw.set_cursor_visible(false); - } - - window.mouse_interaction = - new_mouse_interaction; - } - compositor.configure_surface( - &mut window.surface, - physical_size.width, - physical_size.height, - ); - - window.viewport_version = - window.state.viewport_version(); - } - - window.raw.pre_present_notify(); - debug.render_started(); - match compositor.present( - &mut window.renderer, - &mut window.surface, - window.state.viewport(), - window.state.background_color(), - &debug.overlay(), - ) { - Ok(()) => { - debug.render_finished(); - } - Err(error) => { - match error { - // This is an unrecoverable error. - compositor::SurfaceError::OutOfMemory => { - panic!("{:?}", error); - } - compositor::SurfaceError::NoDamage => { - debug.render_finished(); - - // TODO Ideally there would be a way to know if some widget wants to animate? - let _ = control_sender.start_send( - Control::ChangeFlow( - ControlFlow::WaitUntil( - Instant::now().checked_add( - Duration::from_millis(100), - ).unwrap_or(Instant::now()), - ), - ), - ); - } - _ => { - debug.render_finished(); - log::error!( - "Error {error:?} when \ - presenting surface." - ); - - // Try rendering all windows again next frame. - for (_id, window) in - window_manager.iter_mut() - { - window.request_redraw(); - } - } - } - } - } - } - window_event => { - 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; - }; - - // Initiates a drag resize window state when found. - if let Some(func) = - window.drag_resize_window_func.as_mut() - { - if func(window.raw.as_ref(), &window_event) { - continue; - } - } - - 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 debug, - &mut user_interfaces, - &mut window_manager, - &mut ui_caches, - &mut is_window_opening, - &mut platform_specific_handler, - ); - } else { - window.state.update( - window.raw.as_ref(), - &window_event, - &mut debug, - ); - if let Some(event) = conversion::window_event( - window_event, - window.state.scale_factor(), - window.state.modifiers(), - ) { - events.push((Some(id), event)); - } - } - } - _ => {} - } - } - Event::AboutToWait => { - let skip = events.is_empty() && messages.is_empty(); - if skip - && window_manager.iter_mut().all(|(_, w)| !w.resize_enabled) - { - continue; - } - - debug.event_processing_started(); - let mut uis_stale = false; - let mut resized = false; - for (id, window) in window_manager.iter_mut() { - if skip && !window.resize_enabled { - continue; - } - let mut window_events = vec![]; - - events.retain(|(window_id, event)| { - if *window_id == Some(id) { - window_events.push(event.clone()); - false - } else { - true - } - }); - let no_window_events = window_events.is_empty(); - #[cfg(feature = "wayland")] - window_events.push(core::Event::PlatformSpecific( - core::event::PlatformSpecific::Wayland( - core::event::wayland::Event::RequestResize, - ), - )); - 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, - ); - - let mut needs_redraw = - !no_window_events || !messages.is_empty(); - - if let Some(requested_size) = - clipboard.requested_logical_size.lock().unwrap().take() - { - let requested_physical_size = - winit::dpi::PhysicalSize::new( - (requested_size.width as f64 - * window.state.scale_factor()) - .ceil() as u32, - (requested_size.height as f64 - * window.state.scale_factor()) - .ceil() as u32, - ); - let physical_size = window.state.physical_size(); - if requested_physical_size.width != physical_size.width - || requested_physical_size.height - != physical_size.height - { - // FIXME what to do when we are stuck in a configure event/resize request loop - // We don't have control over how winit handles this. - window.resize_enabled = true; - resized = true; - needs_redraw = true; - let s = winit::dpi::Size::Physical( - requested_physical_size, - ); - _ = window.raw.request_surface_size(s); - window.raw.set_min_surface_size(Some(s)); - window.raw.set_max_surface_size(Some(s)); - window.state.synchronize( - &program, - id, - window.raw.as_ref(), - ); - } - } - if needs_redraw { - window.request_redraw(); - } else { - continue; - } - - if !uis_stale { - uis_stale = - matches!(ui_state, user_interface::State::Outdated); - } - - for (event, status) in - window_events.into_iter().zip(statuses.into_iter()) - { - runtime.broadcast(subscription::Event::Interaction { - window: id, - event, - status, - }); - } - } - - if !resized && skip { - continue; - } - - for (id, event) in events.drain(..) { - runtime.broadcast(subscription::Event::Interaction { - window: id.unwrap_or(window::Id::NONE), - event, - status: core::event::Status::Ignored, - }); - } - - debug.event_processing_finished(); - - 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 debug, - &mut messages, - ); - - for (id, window) in window_manager.iter_mut() { - window.state.synchronize( - &program, - id, - window.raw.as_ref(), - ); - - window.request_redraw(); - } - - user_interfaces = ManuallyDrop::new(build_user_interfaces( - &program, - &mut debug, - &mut window_manager, - cached_interfaces, - &mut clipboard, - )); - - if actions > 0 { - proxy.free_slots(actions); - actions = 0; - } - } - - debug.draw_started(); - - for (id, window) in window_manager.iter_mut() { - // TODO: Avoid redrawing all the time by forcing widgets to - // request redraws on state changes - // - // Then, we can use the `interface_state` here to decide if a redraw - // is needed right away, or simply wait until a specific time. - let redraw_event = 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 new_mouse_interaction = { - let state = &window.state; - - ui.draw( - &mut window.renderer, - state.theme(), - &renderer::Style { - icon_color: state.icon_color(), - text_color: state.text_color(), - scale_factor: state.scale_factor(), - }, - cursor, - ) - }; - - if new_mouse_interaction != window.mouse_interaction { - if let Some(interaction) = - conversion::mouse_interaction(new_mouse_interaction) - { - if matches!( - window.mouse_interaction, - mouse::Interaction::Hide - ) { - window.raw.set_cursor_visible(true); - } - window.raw.set_cursor(interaction.into()) - } else { - window.raw.set_cursor_visible(false); - } - - window.mouse_interaction = new_mouse_interaction; - } - - // TODO once widgets can request to be redrawn, we can avoid always requesting a - // redraw - window.request_redraw(); - runtime.broadcast(subscription::Event::Interaction { - window: id, - event: redraw_event, - status: core::event::Status::Ignored, - }); - - let _ = control_sender.start_send(Control::ChangeFlow( - match ui_state { - user_interface::State::Updated { - redraw_request: Some(redraw_request), - } => match redraw_request { - window::RedrawRequest::NextFrame => { - window.request_redraw(); - - ControlFlow::Wait - } - window::RedrawRequest::At(at) => { - ControlFlow::WaitUntil(at) - } - }, - _ => ControlFlow::Wait, - }, - )); - } - - debug.draw_finished(); - } - - Event::Dnd(e) => { - match &e { - dnd::DndEvent::Offer(_, dnd::OfferEvent::Leave) => { - events.push((cur_dnd_surface, core::Event::Dnd(e))); - // XXX can't clear the dnd surface on leave because - // the data event comes after - // cur_dnd_surface = None; - } - dnd::DndEvent::Offer( - _, - dnd::OfferEvent::Enter { surface, .. }, - ) => { - let window_handle = surface.0.window_handle().ok(); - let window_id = window_manager.iter_mut().find_map( - |(id, window)| { - if window - .raw - .window_handle() - .ok() - .zip(window_handle) - .map(|(a, b)| a == b) - .unwrap_or_default() - { - Some(id) - } else { - None - } - }, - ); - cur_dnd_surface = window_id; - events.push((cur_dnd_surface, core::Event::Dnd(e))); - } - dnd::DndEvent::Offer(..) => { - events.push((cur_dnd_surface, core::Event::Dnd(e))); - } - dnd::DndEvent::Source(_) => { - for w in window_manager.ids() { - events.push((Some(w), core::Event::Dnd(e.clone()))); - } - } - }; - } - #[cfg(feature = "a11y")] - Event::Accessibility(id, e) => { - match e.action { - iced_accessibility::accesskit::Action::Focus => { - // TODO send a command for this - } - _ => {} - } - events.push((Some(id), conversion::a11y(e))); - } - #[cfg(feature = "a11y")] - Event::AccessibilityEnabled(enabled) => { - a11y_enabled = enabled; - } - Event::PlatformSpecific(e) => { - crate::platform_specific::handle_event( - e, - &mut events, - &mut platform_specific_handler, - &program, - &mut compositor, - &mut window_manager, - &mut debug, - &mut user_interfaces, - &mut clipboard, - #[cfg(feature = "a11y")] - &mut adapters, - ); - } - _ => { - // log ignored events? - } - } - } - - let _ = ManuallyDrop::into_inner(user_interfaces); -} - -/// Builds a window's [`UserInterface`] for the [`Program`]. -pub(crate) fn build_user_interface<'a, P: Program>( - program: &'a P, - cache: user_interface::Cache, - renderer: &mut P::Renderer, - size: Size, - debug: &mut Debug, - id: window::Id, - raw: Arc, - prev_dnd_destination_rectangles_count: usize, - clipboard: &mut Clipboard, -) -> UserInterface<'a, P::Message, P::Theme, P::Renderer> -where - P::Theme: DefaultStyle, -{ - debug.view_started(); - let view = program.view(id); - debug.view_finished(); - - debug.layout_started(); - let user_interface = UserInterface::build(view, size, cache, renderer); - debug.layout_finished(); - - let dnd_rectangles = user_interface - .dnd_rectangles(prev_dnd_destination_rectangles_count, renderer); - let new_dnd_rectangles_count = dnd_rectangles.as_ref().len(); - if new_dnd_rectangles_count > 0 || prev_dnd_destination_rectangles_count > 0 - { - clipboard.register_dnd_destination( - DndSurface(Arc::new(Box::new(raw.clone()))), - dnd_rectangles.into_rectangles(), - ); - } - - user_interface -} - -fn update( - program: &mut P, - runtime: &mut Runtime, Action>, - debug: &mut Debug, - messages: &mut Vec, -) where - P::Theme: DefaultStyle, -{ - for message in messages.drain(..) { - debug.log_message(&message); - debug.update_started(); - - let task = runtime.enter(|| program.update(message)); - debug.update_finished(); - - 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 C, - events: &mut Vec<(Option, core::Event)>, - messages: &mut Vec, - clipboard: &mut Clipboard, - control_sender: &mut mpsc::UnboundedSender, - debug: &mut Debug, - 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, - platform_specific: &mut crate::platform_specific::PlatformSpecific, -) where - P: Program, - C: Compositor + 'static, - P::Theme: DefaultStyle, -{ - 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); - } - clipboard::Action::WriteData(contents, kind) => { - clipboard.write_data(kind, ClipboardStoreData(contents)) - } - clipboard::Action::ReadData(allowed, tx, kind) => { - let contents = clipboard.read_data(kind, allowed); - _ = tx.send(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); - #[cfg(feature = "wayland")] - platform_specific - .send_wayland(platform_specific::Action::RemoveWindow(id)); - - if let Some(window) = window_manager.remove(id) { - clipboard.register_dnd_destination( - DndSurface(Arc::new(Box::new(window.raw.clone()))), - Vec::new(), - ); - let proxy = clipboard.proxy(); - if clipboard.window_id() == Some(window.raw.id()) { - *clipboard = window_manager - .first() - .map(|window| window.raw.clone()) - .zip(proxy) - .map(|(w, proxy)| { - Clipboard::connect( - w, - crate::clipboard::ControlSender { - sender: control_sender.clone(), - proxy, - }, - ) - }) - .unwrap_or_else(Clipboard::unconnected); - } - - events.push(( - Some(id), - core::Event::Window(core::window::Event::Closed), - )); - } - } - 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::Resize(id, size) => { - if let Some(window) = window_manager.get_mut(id) { - let _ = window.raw.request_surface_size( - winit::dpi::LogicalSize { - width: size.width, - height: size.height, - } - .into(), - ); - } - } - window::Action::GetSize(id, channel) => { - if let Some(window) = window_manager.get_mut(id) { - let size = window - .raw - .surface_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 - .inner_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, - } - .into(), - ); - } - } - window::Action::ChangeMode(id, mode) => { - if let Some(window) = window_manager.get_mut(id) { - window.raw.set_visible(conversion::visible(mode)); - window.raw.set_fullscreen(conversion::fullscreen( - window.raw.current_monitor(), - mode, - )); - } - } - window::Action::ChangeIcon(id, icon) => { - if let Some(window) = window_manager.get_mut(id) { - window.raw.set_window_icon(conversion::icon(icon)); - } - } - window::Action::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::ChangeLevel(id, level) => { - if let Some(window) = window_manager.get_mut(id) { - window - .raw - .set_window_level(conversion::window_level(level)); - } - } - window::Action::ShowSystemMenu(id) => { - if let Some(window) = window_manager.get_mut(id) { - if let mouse::Cursor::Available(point) = - window.state.cursor() - { - window.raw.show_window_menu( - winit::dpi::LogicalPosition { - x: point.x, - y: point.y, - } - .into(), - ); - } - } - } - 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) { - let bytes = compositor.screenshot( - &mut window.renderer, - &mut window.surface, - window.state.viewport(), - window.state.background_color(), - &debug.overlay(), - ); - - let _ = channel.send(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")] - { - 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 } => { - // TODO: Error handling (?) - compositor.load_font(bytes.clone()); - - let _ = channel.send(Ok(())); - } - Action::Exit => { - control_sender - .start_send(Control::Exit) - .expect("Send control action"); - } - Action::Dnd(a) => match a { - iced_runtime::dnd::DndAction::RegisterDndDestination { - surface, - rectangles, - } => { - clipboard.register_dnd_destination(surface, rectangles); - } - iced_runtime::dnd::DndAction::StartDnd { - internal, - source_surface, - icon_surface, - content, - actions, - } => { - clipboard.start_dnd( - internal, - source_surface, - icon_surface.map(|d| d as Box), - content, - actions, - ); - } - iced_runtime::dnd::DndAction::EndDnd => { - clipboard.end_dnd(); - } - iced_runtime::dnd::DndAction::PeekDnd(m, channel) => { - let data = clipboard.peek_dnd(m); - _ = channel.send(data); - } - iced_runtime::dnd::DndAction::SetAction(a) => { - clipboard.set_action(a); - } - }, - Action::PlatformSpecific(a) => { - platform_specific.send_action(a); - } - } -} - -/// Build the user interface for every window. -pub fn build_user_interfaces<'a, P: Program, C>( - program: &'a P, - debug: &mut Debug, - window_manager: &mut WindowManager, - mut cached_user_interfaces: FxHashMap, - clipboard: &mut Clipboard, -) -> FxHashMap> -where - C: Compositor, - P::Theme: DefaultStyle, -{ - cached_user_interfaces - .drain() - .filter_map(|(id, cache)| { - let window = window_manager.get_mut(id)?; - let interface = build_user_interface( - program, - cache, - &mut window.renderer, - window.state.logical_size(), - debug, - id, - window.raw.clone(), - window.prev_dnd_destination_rectangles_count, - clipboard, - ); - - let dnd_rectangles = interface.dnd_rectangles( - window.prev_dnd_destination_rectangles_count, - &window.renderer, - ); - let new_dnd_rectangles_count = dnd_rectangles.as_ref().len(); - if new_dnd_rectangles_count > 0 - || window.prev_dnd_destination_rectangles_count > 0 - { - clipboard.register_dnd_destination( - DndSurface(Arc::new(Box::new(window.raw.clone()))), - dnd_rectangles.into_rectangles(), - ); - } - - window.prev_dnd_destination_rectangles_count = - new_dnd_rectangles_count; - - Some((id, interface)) - }) - .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, - } -}