diff options
Diffstat (limited to 'winit/src/program.rs')
| -rw-r--r-- | winit/src/program.rs | 1356 | 
1 files changed, 1356 insertions, 0 deletions
| diff --git a/winit/src/program.rs b/winit/src/program.rs new file mode 100644 index 00000000..28cd8e52 --- /dev/null +++ b/winit/src/program.rs @@ -0,0 +1,1356 @@ +//! 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::mouse; +use crate::core::renderer; +use crate::core::widget::operation; +use crate::core::window; +use crate::core::{Color, Element, Point, Size, Theme}; +use crate::futures::futures::channel::mpsc; +use crate::futures::futures::channel::oneshot; +use crate::futures::futures::executor; +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::user_interface::{self, UserInterface}; +use crate::runtime::Debug; +use crate::runtime::{self, Action, Task}; +use crate::{Clipboard, Error, Proxy, Settings}; + +use window_manager::WindowManager; + +use rustc_hash::FxHashMap; +use std::mem::ManuallyDrop; +use std::sync::Arc; +use std::time::Instant; + +/// 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<Self::Message>); + +    /// 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<Self::Message>; + +    /// Returns the widgets to display in the [`Program`] for the `window`. +    /// +    /// These widgets can produce __messages__ based on user interaction. +    fn view( +        &self, +        window: window::Id, +    ) -> Element<'_, Self::Message, Self::Theme, Self::Renderer>; + +    /// Returns the current `Theme` of the [`Program`]. +    fn theme(&self, window: window::Id) -> Self::Theme; + +    /// Returns the `Style` variation of the `Theme`. +    fn style(&self, theme: &Self::Theme) -> Appearance { +        theme.default_style() +    } + +    /// Returns the event `Subscription` for the current state of the +    /// application. +    /// +    /// The messages produced by the `Subscription` will be handled by +    /// [`update`](#tymethod.update). +    /// +    /// A `Subscription` will be kept alive as long as you keep returning it! +    /// +    /// By default, it returns an empty subscription. +    fn subscription(&self) -> Subscription<Self::Message> { +        Subscription::none() +    } + +    /// Returns the scale factor of the 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 +    } +} + +/// The appearance of a program. +#[derive(Debug, Clone, Copy, PartialEq)] +pub struct Appearance { +    /// The background [`Color`] of the application. +    pub background_color: Color, + +    /// The default text [`Color`] of the application. +    pub text_color: Color, +} + +/// The default style of a [`Program`]. +pub trait DefaultStyle { +    /// Returns the default style of a [`Program`]. +    fn default_style(&self) -> Appearance; +} + +impl DefaultStyle for Theme { +    fn default_style(&self) -> Appearance { +        default(self) +    } +} + +/// The default [`Appearance`] of a [`Program`] with the built-in [`Theme`]. +pub fn default(theme: &Theme) -> Appearance { +    let palette = theme.extended_palette(); + +    Appearance { +        background_color: palette.background.base.color, +        text_color: palette.background.base.text, +    } +} +/// Runs a [`Program`] with an executor, compositor, and the provided +/// settings. +pub fn run<P, C>( +    settings: Settings, +    graphics_settings: graphics::Settings, +    window_settings: Option<window::Settings>, +    flags: P::Flags, +) -> Result<(), Error> +where +    P: Program + 'static, +    C: Compositor<Renderer = P::Renderer> + 'static, +    P::Theme: DefaultStyle, +{ +    use winit::event_loop::EventLoop; + +    let mut debug = Debug::new(); +    debug.startup_started(); + +    let event_loop = EventLoop::with_user_event() +        .build() +        .expect("Create event loop"); + +    let (proxy, worker) = Proxy::new(event_loop.create_proxy()); + +    let mut runtime = { +        let executor = +            P::Executor::new().map_err(Error::ExecutorCreationFailed)?; +        executor.spawn(worker); + +        Runtime::new(executor, proxy.clone()) +    }; + +    let (application, task) = runtime.enter(|| P::new(flags)); + +    if let Some(stream) = task.into_stream() { +        runtime.run(stream); +    } + +    let (boot_sender, boot_receiver) = oneshot::channel(); +    let (event_sender, event_receiver) = mpsc::unbounded(); +    let (control_sender, control_receiver) = mpsc::unbounded(); + +    let instance = Box::pin(run_instance::<P, C>( +        application, +        runtime, +        proxy, +        debug, +        boot_receiver, +        event_receiver, +        control_sender, +    )); + +    let context = task::Context::from_waker(task::noop_waker_ref()); + +    struct Runner<Message: 'static, F, C> { +        instance: std::pin::Pin<Box<F>>, +        context: task::Context<'static>, +        id: Option<String>, +        boot: Option<BootConfig<C>>, +        sender: mpsc::UnboundedSender<Event<Message>>, +        receiver: mpsc::UnboundedReceiver<Control>, +        error: Option<Error>, +    } + +    struct BootConfig<C> { +        sender: oneshot::Sender<Boot<C>>, +        window_settings: Option<window::Settings>, +        graphics_settings: graphics::Settings, +    } + +    let mut runner = Runner { +        instance, +        context, +        id: settings.id, +        boot: Some(BootConfig { +            sender: boot_sender, +            window_settings, +            graphics_settings, +        }), +        sender: event_sender, +        receiver: control_receiver, +        error: None, +    }; + +    impl<Message, F, C> winit::application::ApplicationHandler<Message> +        for Runner<Message, F, C> +    where +        F: Future<Output = ()>, +        C: Compositor, +    { +        fn resumed(&mut self, event_loop: &winit::event_loop::ActiveEventLoop) { +            let Some(BootConfig { +                sender, +                window_settings, +                graphics_settings, +            }) = self.boot.take() +            else { +                return; +            }; + +            let window = match event_loop.create_window( +                winit::window::WindowAttributes::default().with_visible(false), +            ) { +                Ok(window) => Arc::new(window), +                Err(error) => { +                    self.error = Some(Error::WindowCreationFailed(error)); +                    event_loop.exit(); +                    return; +                } +            }; + +            let clipboard = Clipboard::connect(&window); + +            let finish_boot = async move { +                let compositor = +                    C::new(graphics_settings, window.clone()).await?; + +                sender +                    .send(Boot { +                        compositor, +                        clipboard, +                        window_settings, +                    }) +                    .ok() +                    .expect("Send boot event"); + +                Ok::<_, graphics::Error>(()) +            }; + +            if let Err(error) = executor::block_on(finish_boot) { +                self.error = Some(Error::GraphicsCreationFailed(error)); +                event_loop.exit(); +            } +        } + +        fn new_events( +            &mut self, +            event_loop: &winit::event_loop::ActiveEventLoop, +            cause: winit::event::StartCause, +        ) { +            if self.boot.is_some() { +                return; +            } + +            self.process_event( +                event_loop, +                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, +            message: Message, +        ) { +            self.process_event( +                event_loop, +                Event::EventLoopAwakened(winit::event::Event::UserEvent( +                    message, +                )), +            ); +        } + +        fn about_to_wait( +            &mut self, +            event_loop: &winit::event_loop::ActiveEventLoop, +        ) { +            self.process_event( +                event_loop, +                Event::EventLoopAwakened(winit::event::Event::AboutToWait), +            ); +        } +    } + +    impl<Message, F, C> Runner<Message, F, C> +    where +        F: Future<Output = ()>, +        C: Compositor, +    { +        fn process_event( +            &mut self, +            event_loop: &winit::event_loop::ActiveEventLoop, +            event: Event<Message>, +        ) { +            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 new < current => {} +                                    ( +                                        ControlFlow::WaitUntil(target), +                                        ControlFlow::Wait, +                                    ) if target > Instant::now() => {} +                                    _ => { +                                        event_loop.set_control_flow(flow); +                                    } +                                } +                            } +                            Control::CreateWindow { +                                id, +                                settings, +                                title, +                                monitor, +                            } => { +                                let exit_on_close_request = +                                    settings.exit_on_close_request; + +                                let window = event_loop +                                    .create_window( +                                        conversion::window_attributes( +                                            settings, +                                            &title, +                                            monitor +                                                .or(event_loop +                                                    .primary_monitor()), +                                            self.id.clone(), +                                        ), +                                    ) +                                    .expect("Create window"); + +                                self.process_event( +                                    event_loop, +                                    Event::WindowCreated { +                                        id, +                                        window, +                                        exit_on_close_request, +                                    }, +                                ); +                            } +                            Control::Exit => { +                                event_loop.exit(); +                            } +                        }, +                        _ => { +                            break; +                        } +                    }, +                    task::Poll::Ready(_) => { +                        event_loop.exit(); +                        break; +                    } +                }; +            } +        } +    } + +    let _ = event_loop.run_app(&mut runner); + +    Ok(()) +} + +struct Boot<C> { +    compositor: C, +    clipboard: Clipboard, +    window_settings: Option<window::Settings>, +} + +enum Event<Message: 'static> { +    WindowCreated { +        id: window::Id, +        window: winit::window::Window, +        exit_on_close_request: bool, +    }, +    EventLoopAwakened(winit::event::Event<Message>), +} + +enum Control { +    ChangeFlow(winit::event_loop::ControlFlow), +    Exit, +    CreateWindow { +        id: window::Id, +        settings: window::Settings, +        title: String, +        monitor: Option<winit::monitor::MonitorHandle>, +    }, +} + +async fn run_instance<P, C>( +    mut program: P, +    mut runtime: Runtime<P::Executor, Proxy<P::Message>, Action<P::Message>>, +    mut proxy: Proxy<P::Message>, +    mut debug: Debug, +    mut boot: oneshot::Receiver<Boot<C>>, +    mut event_receiver: mpsc::UnboundedReceiver<Event<Action<P::Message>>>, +    mut control_sender: mpsc::UnboundedSender<Control>, +) where +    P: Program + 'static, +    C: Compositor<Renderer = P::Renderer> + 'static, +    P::Theme: DefaultStyle, +{ +    use winit::event; +    use winit::event_loop::ControlFlow; + +    let Boot { +        mut compositor, +        mut clipboard, +        window_settings, +    } = boot.try_recv().ok().flatten().expect("Receive boot"); + +    let mut window_manager = WindowManager::new(); + +    let mut events = Vec::new(); +    let mut messages = Vec::new(); +    let mut actions = 0; + +    let mut ui_caches = FxHashMap::default(); +    let mut user_interfaces = ManuallyDrop::new(build_user_interfaces( +        &program, +        &mut debug, +        &mut window_manager, +        FxHashMap::from_iter([( +            window::Id::MAIN, +            user_interface::Cache::default(), +        )]), +    )); + +    runtime.track(program.subscription().map(Action::Output).into_recipes()); + +    let is_daemon = window_settings.is_none(); + +    if let Some(window_settings) = window_settings { +        let (sender, _receiver) = oneshot::channel(); + +        proxy.send_action(Action::Window(runtime::window::Action::Open( +            window::Id::unique(), +            window_settings, +            sender, +        ))); +    } + +    debug.startup_finished(); + +    'main: while let Some(event) = event_receiver.next().await { +        match event { +            Event::WindowCreated { +                id, +                window, +                exit_on_close_request, +            } => { +                let window = window_manager.insert( +                    id, +                    Arc::new(window), +                    &program, +                    &mut compositor, +                    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, +                        &mut debug, +                        id, +                    ), +                ); +                let _ = ui_caches.insert(id, user_interface::Cache::default()); + +                events.push(( +                    id, +                    core::Event::Window(window::Event::Opened { +                        position: window.position(), +                        size: window.size(), +                    }), +                )); +            } +            Event::EventLoopAwakened(event) => { +                match event { +                    event::Event::NewEvents( +                        event::StartCause::Init +                        | event::StartCause::ResumeTimeReached { .. }, +                    ) => { +                        for (_id, window) in window_manager.iter_mut() { +                            // TODO once widgets can request to be redrawn, we can avoid always requesting a +                            // redraw +                            window.raw.request_redraw(); +                        } +                    } +                    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 messages, +                            &mut clipboard, +                            &mut control_sender, +                            &mut debug, +                            &mut user_interfaces, +                            &mut window_manager, +                            &mut ui_caches, +                        ); +                        actions += 1; +                    } +                    event::Event::WindowEvent { +                        window_id: id, +                        event: event::WindowEvent::RedrawRequested, +                        .. +                    } => { +                        let Some((id, window)) = +                            window_manager.get_mut_alias(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 { +                                text_color: window.state.text_color(), +                            }, +                            cursor, +                        ); +                        debug.draw_finished(); + +                        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, +                        }); + +                        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.raw.request_redraw(); + +                                        ControlFlow::Wait +                                    } +                                    window::RedrawRequest::At(at) => { +                                        ControlFlow::WaitUntil(at) +                                    } +                                }, +                                _ => ControlFlow::Wait, +                            }, +                        )); + +                        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 ui = user_interfaces +                                .remove(&id) +                                .expect("Remove user interface"); + +                            let _ = user_interfaces.insert( +                                id, +                                ui.relayout(logical_size, &mut window.renderer), +                            ); +                            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 { +                                        text_color: window.state.text_color(), +                                    }, +                                    window.state.cursor(), +                                ); +                            debug.draw_finished(); + +                            if new_mouse_interaction != window.mouse_interaction +                            { +                                window.raw.set_cursor( +                                    conversion::mouse_interaction( +                                        new_mouse_interaction, +                                    ), +                                ); + +                                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(); +                        } + +                        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(); + +                                // TODO: Handle animations! +                                // Maybe we can use `ControlFlow::WaitUntil` for this. +                            } +                            Err(error) => match error { +                                // This is an unrecoverable error. +                                compositor::SurfaceError::OutOfMemory => { +                                    panic!("{:?}", error); +                                } +                                _ => { +                                    debug.render_finished(); + +                                    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 +                            ) +                            && window_manager.is_empty() +                        { +                            break 'main; +                        } + +                        let Some((id, window)) = +                            window_manager.get_mut_alias(window_id) +                        else { +                            continue; +                        }; + +                        if matches!( +                            window_event, +                            winit::event::WindowEvent::CloseRequested +                        ) && window.exit_on_close_request +                        { +                            let _ = window_manager.remove(id); +                            let _ = user_interfaces.remove(&id); +                            let _ = ui_caches.remove(&id); + +                            events.push(( +                                id, +                                core::Event::Window(window::Event::Closed), +                            )); +                        } else { +                            window.state.update( +                                &window.raw, +                                &window_event, +                                &mut debug, +                            ); + +                            if let Some(event) = conversion::window_event( +                                window_event, +                                window.state.scale_factor(), +                                window.state.modifiers(), +                            ) { +                                events.push((id, event)); +                            } +                        } +                    } +                    event::Event::AboutToWait => { +                        if events.is_empty() && messages.is_empty() { +                            continue; +                        } + +                        debug.event_processing_started(); +                        let mut uis_stale = false; + +                        for (id, window) in window_manager.iter_mut() { +                            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, +                                ); + +                            window.raw.request_redraw(); + +                            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, +                                    }, +                                ); +                            } +                        } + +                        for (id, event) in events.drain(..) { +                            runtime.broadcast( +                                subscription::Event::Interaction { +                                    window: id, +                                    event, +                                    status: core::event::Status::Ignored, +                                }, +                            ); +                        } + +                        debug.event_processing_finished(); + +                        // TODO mw application update returns which window IDs to update +                        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 application +                            update( +                                &mut program, +                                &mut runtime, +                                &mut debug, +                                &mut messages, +                            ); + +                            // we must synchronize all window states with application state after an +                            // application update since we don't know what changed +                            for (id, window) in window_manager.iter_mut() { +                                window.state.synchronize( +                                    &program, +                                    id, +                                    &window.raw, +                                ); + +                                // TODO once widgets can request to be redrawn, we can avoid always requesting a +                                // redraw +                                window.raw.request_redraw(); +                            } + +                            // rebuild UIs with the synchronized states +                            user_interfaces = +                                ManuallyDrop::new(build_user_interfaces( +                                    &program, +                                    &mut debug, +                                    &mut window_manager, +                                    cached_interfaces, +                                )); + +                            if actions > 0 { +                                proxy.free_slots(actions); +                                actions = 0; +                            } +                        } +                    } +                    _ => {} +                } +            } +        } +    } + +    let _ = ManuallyDrop::into_inner(user_interfaces); +} + +/// Builds a window's [`UserInterface`] for the [`Program`]. +fn build_user_interface<'a, P: Program>( +    application: &'a P, +    cache: user_interface::Cache, +    renderer: &mut P::Renderer, +    size: Size, +    debug: &mut Debug, +    id: window::Id, +) -> UserInterface<'a, P::Message, P::Theme, P::Renderer> +where +    P::Theme: DefaultStyle, +{ +    debug.view_started(); +    let view = application.view(id); +    debug.view_finished(); + +    debug.layout_started(); +    let user_interface = UserInterface::build(view, size, cache, renderer); +    debug.layout_finished(); + +    user_interface +} + +fn update<P: Program, E: Executor>( +    application: &mut P, +    runtime: &mut Runtime<E, Proxy<P::Message>, Action<P::Message>>, +    debug: &mut Debug, +    messages: &mut Vec<P::Message>, +) where +    P::Theme: DefaultStyle, +{ +    for message in messages.drain(..) { +        debug.log_message(&message); +        debug.update_started(); + +        let task = runtime.enter(|| application.update(message)); +        debug.update_finished(); + +        if let Some(stream) = task.into_stream() { +            runtime.run(stream); +        } +    } + +    let subscription = application.subscription(); +    runtime.track(subscription.map(Action::Output).into_recipes()); +} + +fn run_action<P, C>( +    action: Action<P::Message>, +    application: &P, +    compositor: &mut C, +    messages: &mut Vec<P::Message>, +    clipboard: &mut Clipboard, +    control_sender: &mut mpsc::UnboundedSender<Control>, +    debug: &mut Debug, +    interfaces: &mut FxHashMap< +        window::Id, +        UserInterface<'_, P::Message, P::Theme, P::Renderer>, +    >, +    window_manager: &mut WindowManager<P, C>, +    ui_caches: &mut FxHashMap<window::Id, user_interface::Cache>, +) where +    P: Program, +    C: Compositor<Renderer = P::Renderer> + '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); +            } +        }, +        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: application.title(id), +                        monitor, +                    }) +                    .expect("Send control action"); + +                let _ = channel.send(id); +            } +            window::Action::Close(id) => { +                let _ = window_manager.remove(id); +                let _ = ui_caches.remove(&id); +            } +            window::Action::Drag(id) => { +                if let Some(window) = window_manager.get_mut(id) { +                    let _ = window.raw.drag_window(); +                } +            } +            window::Action::Resize(id, size) => { +                if let Some(window) = window_manager.get_mut(id) { +                    let _ = window.raw.request_inner_size( +                        winit::dpi::LogicalSize { +                            width: size.width, +                            height: size.height, +                        }, +                    ); +                } +            } +            window::Action::FetchSize(id, channel) => { +                if let Some(window) = window_manager.get_mut(id) { +                    let size = window +                        .raw +                        .inner_size() +                        .to_logical(window.raw.scale_factor()); + +                    let _ = channel.send(Size::new(size.width, size.height)); +                } +            } +            window::Action::FetchMaximized(id, 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::FetchMinimized(id, channel) => { +                if let Some(window) = window_manager.get_mut(id) { +                    let _ = channel.send(window.raw.is_minimized()); +                } +            } +            window::Action::Minimize(id, minimized) => { +                if let Some(window) = window_manager.get_mut(id) { +                    window.raw.set_minimized(minimized); +                } +            } +            window::Action::FetchPosition(id, channel) => { +                if let Some(window) = window_manager.get_mut(id) { +                    let position = window +                        .raw +                        .inner_position() +                        .map(|position| { +                            let position = position +                                .to_logical::<f32>(window.raw.scale_factor()); + +                            Point::new(position.x, position.y) +                        }) +                        .ok(); + +                    let _ = channel.send(position); +                } +            } +            window::Action::Move(id, position) => { +                if let Some(window) = window_manager.get_mut(id) { +                    window.raw.set_outer_position( +                        winit::dpi::LogicalPosition { +                            x: position.x, +                            y: position.y, +                        }, +                    ); +                } +            } +            window::Action::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::FetchMode(id, channel) => { +                if let Some(window) = window_manager.get_mut(id) { +                    let mode = if window.raw.is_visible().unwrap_or(true) { +                        conversion::mode(window.raw.fullscreen()) +                    } else { +                        core::window::Mode::Hidden +                    }; + +                    let _ = channel.send(mode); +                } +            } +            window::Action::ToggleMaximize(id) => { +                if let Some(window) = window_manager.get_mut(id) { +                    window.raw.set_maximized(!window.raw.is_maximized()); +                } +            } +            window::Action::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, +                            }, +                        ); +                    } +                } +            } +            window::Action::FetchRawId(id, channel) => { +                if let Some(window) = window_manager.get_mut(id) { +                    let _ = channel.send(window.raw.id().into()); +                } +            } +            window::Action::RunWithHandle(id, 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(), +                    )); +                } +            } +        }, +        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"); +        } +    } +} + +/// Build the user interface for every window. +pub fn build_user_interfaces<'a, P: Program, C>( +    application: &'a P, +    debug: &mut Debug, +    window_manager: &mut WindowManager<P, C>, +    mut cached_user_interfaces: FxHashMap<window::Id, user_interface::Cache>, +) -> FxHashMap<window::Id, UserInterface<'a, P::Message, P::Theme, P::Renderer>> +where +    C: Compositor<Renderer = P::Renderer>, +    P::Theme: DefaultStyle, +{ +    cached_user_interfaces +        .drain() +        .filter_map(|(id, cache)| { +            let window = window_manager.get_mut(id)?; + +            Some(( +                id, +                build_user_interface( +                    application, +                    cache, +                    &mut window.renderer, +                    window.state.logical_size(), +                    debug, +                    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, +    } +} | 
