iced_winit/
lib.rs

1//! A windowing shell for Iced, on top of [`winit`].
2//!
3//! ![The native path of the Iced ecosystem](https://github.com/iced-rs/iced/blob/0525d76ff94e828b7b21634fa94a747022001c83/docs/graphs/native.png?raw=true)
4//!
5//! `iced_winit` offers some convenient abstractions on top of [`iced_runtime`]
6//! to quickstart development when using [`winit`].
7//!
8//! It exposes a renderer-agnostic [`Program`] trait that can be implemented
9//! and then run with a simple call. The use of this trait is optional.
10//!
11//! Additionally, a [`conversion`] module is available for users that decide to
12//! implement a custom event loop.
13//!
14//! [`iced_runtime`]: https://github.com/iced-rs/iced/tree/0.14/runtime
15//! [`winit`]: https://github.com/rust-windowing/winit
16//! [`conversion`]: crate::conversion
17#![doc(
18    html_logo_url = "https://raw.githubusercontent.com/iced-rs/iced/9ab6923e943f784985e9ef9ca28b10278297225d/docs/logo.svg"
19)]
20#![cfg_attr(docsrs, feature(doc_cfg))]
21pub use iced_debug as debug;
22pub use iced_program as program;
23pub use program::core;
24pub use program::graphics;
25pub use program::runtime;
26pub use runtime::futures;
27pub use winit;
28
29pub mod clipboard;
30pub mod conversion;
31
32mod error;
33mod proxy;
34mod window;
35
36pub use clipboard::Clipboard;
37pub use error::Error;
38pub use proxy::Proxy;
39
40use crate::core::mouse;
41use crate::core::renderer;
42use crate::core::theme;
43use crate::core::time::Instant;
44use crate::core::widget::operation;
45use crate::core::{Point, Size};
46use crate::futures::futures::channel::mpsc;
47use crate::futures::futures::channel::oneshot;
48use crate::futures::futures::task;
49use crate::futures::futures::{Future, StreamExt};
50use crate::futures::subscription;
51use crate::futures::{Executor, Runtime};
52use crate::graphics::{Compositor, Shell, compositor};
53use crate::runtime::image;
54use crate::runtime::system;
55use crate::runtime::user_interface::{self, UserInterface};
56use crate::runtime::{Action, Task};
57
58use program::Program;
59use window::WindowManager;
60
61use rustc_hash::FxHashMap;
62use std::borrow::Cow;
63use std::mem::ManuallyDrop;
64use std::slice;
65use std::sync::Arc;
66
67/// Runs a [`Program`] with the provided settings.
68pub fn run<P>(program: P) -> Result<(), Error>
69where
70    P: Program + 'static,
71    P::Theme: theme::Base,
72{
73    use winit::event_loop::EventLoop;
74
75    let boot_span = debug::boot();
76    let settings = program.settings();
77    let window_settings = program.window();
78
79    let event_loop = EventLoop::with_user_event()
80        .build()
81        .expect("Create event loop");
82
83    let graphics_settings = settings.clone().into();
84    let display_handle = event_loop.owned_display_handle();
85
86    let (proxy, worker) = Proxy::new(event_loop.create_proxy());
87
88    #[cfg(feature = "debug")]
89    {
90        let proxy = proxy.clone();
91
92        debug::on_hotpatch(move || {
93            proxy.send_action(Action::Reload);
94        });
95    }
96
97    let mut runtime = {
98        let executor =
99            P::Executor::new().map_err(Error::ExecutorCreationFailed)?;
100        executor.spawn(worker);
101
102        Runtime::new(executor, proxy.clone())
103    };
104
105    let (program, task) = runtime.enter(|| program::Instance::new(program));
106    let is_daemon = window_settings.is_none();
107
108    let task = if let Some(window_settings) = window_settings {
109        let mut task = Some(task);
110
111        let (_id, open) = runtime::window::open(window_settings);
112
113        open.then(move |_| task.take().unwrap_or_else(Task::none))
114    } else {
115        task
116    };
117
118    if let Some(stream) = runtime::task::into_stream(task) {
119        runtime.run(stream);
120    }
121
122    runtime.track(subscription::into_recipes(
123        runtime.enter(|| program.subscription().map(Action::Output)),
124    ));
125
126    let (event_sender, event_receiver) = mpsc::unbounded();
127    let (control_sender, control_receiver) = mpsc::unbounded();
128    let (system_theme_sender, system_theme_receiver) = oneshot::channel();
129
130    let instance = Box::pin(run_instance::<P>(
131        program,
132        runtime,
133        proxy.clone(),
134        event_receiver,
135        control_sender,
136        display_handle,
137        is_daemon,
138        graphics_settings,
139        settings.fonts,
140        system_theme_receiver,
141    ));
142
143    let context = task::Context::from_waker(task::noop_waker_ref());
144
145    struct Runner<Message: 'static, F> {
146        instance: std::pin::Pin<Box<F>>,
147        context: task::Context<'static>,
148        id: Option<String>,
149        sender: mpsc::UnboundedSender<Event<Action<Message>>>,
150        receiver: mpsc::UnboundedReceiver<Control>,
151        error: Option<Error>,
152        system_theme: Option<oneshot::Sender<theme::Mode>>,
153
154        #[cfg(target_arch = "wasm32")]
155        canvas: Option<web_sys::HtmlCanvasElement>,
156    }
157
158    let runner = Runner {
159        instance,
160        context,
161        id: settings.id,
162        sender: event_sender,
163        receiver: control_receiver,
164        error: None,
165        system_theme: Some(system_theme_sender),
166
167        #[cfg(target_arch = "wasm32")]
168        canvas: None,
169    };
170
171    boot_span.finish();
172
173    impl<Message, F> winit::application::ApplicationHandler<Action<Message>>
174        for Runner<Message, F>
175    where
176        F: Future<Output = ()>,
177    {
178        fn resumed(&mut self, event_loop: &winit::event_loop::ActiveEventLoop) {
179            if let Some(sender) = self.system_theme.take() {
180                let _ = sender.send(
181                    event_loop
182                        .system_theme()
183                        .map(conversion::theme_mode)
184                        .unwrap_or_default(),
185                );
186            }
187        }
188
189        fn new_events(
190            &mut self,
191            event_loop: &winit::event_loop::ActiveEventLoop,
192            cause: winit::event::StartCause,
193        ) {
194            self.process_event(
195                event_loop,
196                Event::EventLoopAwakened(winit::event::Event::NewEvents(cause)),
197            );
198        }
199
200        fn window_event(
201            &mut self,
202            event_loop: &winit::event_loop::ActiveEventLoop,
203            window_id: winit::window::WindowId,
204            event: winit::event::WindowEvent,
205        ) {
206            #[cfg(target_os = "windows")]
207            let is_move_or_resize = matches!(
208                event,
209                winit::event::WindowEvent::Resized(_)
210                    | winit::event::WindowEvent::Moved(_)
211            );
212
213            self.process_event(
214                event_loop,
215                Event::EventLoopAwakened(winit::event::Event::WindowEvent {
216                    window_id,
217                    event,
218                }),
219            );
220
221            // TODO: Remove when unnecessary
222            // On Windows, we emulate an `AboutToWait` event after every `Resized` event
223            // since the event loop does not resume during resize interaction.
224            // More details: https://github.com/rust-windowing/winit/issues/3272
225            #[cfg(target_os = "windows")]
226            {
227                if is_move_or_resize {
228                    self.process_event(
229                        event_loop,
230                        Event::EventLoopAwakened(
231                            winit::event::Event::AboutToWait,
232                        ),
233                    );
234                }
235            }
236        }
237
238        fn user_event(
239            &mut self,
240            event_loop: &winit::event_loop::ActiveEventLoop,
241            action: Action<Message>,
242        ) {
243            self.process_event(
244                event_loop,
245                Event::EventLoopAwakened(winit::event::Event::UserEvent(
246                    action,
247                )),
248            );
249        }
250
251        fn about_to_wait(
252            &mut self,
253            event_loop: &winit::event_loop::ActiveEventLoop,
254        ) {
255            self.process_event(
256                event_loop,
257                Event::EventLoopAwakened(winit::event::Event::AboutToWait),
258            );
259        }
260    }
261
262    impl<Message, F> Runner<Message, F>
263    where
264        F: Future<Output = ()>,
265    {
266        fn process_event(
267            &mut self,
268            event_loop: &winit::event_loop::ActiveEventLoop,
269            event: Event<Action<Message>>,
270        ) {
271            if event_loop.exiting() {
272                return;
273            }
274
275            self.sender.start_send(event).expect("Send event");
276
277            loop {
278                let poll = self.instance.as_mut().poll(&mut self.context);
279
280                match poll {
281                    task::Poll::Pending => match self.receiver.try_next() {
282                        Ok(Some(control)) => match control {
283                            Control::ChangeFlow(flow) => {
284                                use winit::event_loop::ControlFlow;
285
286                                match (event_loop.control_flow(), flow) {
287                                    (
288                                        ControlFlow::WaitUntil(current),
289                                        ControlFlow::WaitUntil(new),
290                                    ) if current < new => {}
291                                    (
292                                        ControlFlow::WaitUntil(target),
293                                        ControlFlow::Wait,
294                                    ) if target > Instant::now() => {}
295                                    _ => {
296                                        event_loop.set_control_flow(flow);
297                                    }
298                                }
299                            }
300                            Control::CreateWindow {
301                                id,
302                                settings,
303                                title,
304                                scale_factor,
305                                monitor,
306                                on_open,
307                            } => {
308                                let exit_on_close_request =
309                                    settings.exit_on_close_request;
310
311                                let visible = settings.visible;
312
313                                #[cfg(target_arch = "wasm32")]
314                                let target =
315                                    settings.platform_specific.target.clone();
316
317                                let window_attributes =
318                                    conversion::window_attributes(
319                                        settings,
320                                        &title,
321                                        scale_factor,
322                                        monitor
323                                            .or(event_loop.primary_monitor()),
324                                        self.id.clone(),
325                                    )
326                                    .with_visible(false);
327
328                                #[cfg(target_arch = "wasm32")]
329                                let window_attributes = {
330                                    use winit::platform::web::WindowAttributesExtWebSys;
331                                    window_attributes
332                                        .with_canvas(self.canvas.take())
333                                };
334
335                                log::info!(
336                                    "Window attributes for id `{id:#?}`: {window_attributes:#?}"
337                                );
338
339                                // On macOS, the `position` in `WindowAttributes` represents the "inner"
340                                // position of the window; while on other platforms it's the "outer" position.
341                                // We fix the inconsistency on macOS by positioning the window after creation.
342                                #[cfg(target_os = "macos")]
343                                let mut window_attributes = window_attributes;
344
345                                #[cfg(target_os = "macos")]
346                                let position =
347                                    window_attributes.position.take();
348
349                                let window = event_loop
350                                    .create_window(window_attributes)
351                                    .expect("Create window");
352
353                                #[cfg(target_os = "macos")]
354                                if let Some(position) = position {
355                                    window.set_outer_position(position);
356                                }
357
358                                #[cfg(target_arch = "wasm32")]
359                                {
360                                    use winit::platform::web::WindowExtWebSys;
361
362                                    let canvas = window
363                                        .canvas()
364                                        .expect("Get window canvas");
365
366                                    let _ = canvas.set_attribute(
367                                        "style",
368                                        "display: block; width: 100%; height: 100%",
369                                    );
370
371                                    let window = web_sys::window().unwrap();
372                                    let document = window.document().unwrap();
373                                    let body = document.body().unwrap();
374
375                                    let target = target.and_then(|target| {
376                                        body.query_selector(&format!(
377                                            "#{target}"
378                                        ))
379                                        .ok()
380                                        .unwrap_or(None)
381                                    });
382
383                                    match target {
384                                        Some(node) => {
385                                            let _ = node
386                                                .replace_with_with_node_1(
387                                                    &canvas,
388                                                )
389                                                .expect(&format!(
390                                                    "Could not replace #{}",
391                                                    node.id()
392                                                ));
393                                        }
394                                        None => {
395                                            let _ = body
396                                                .append_child(&canvas)
397                                                .expect(
398                                                "Append canvas to HTML body",
399                                            );
400                                        }
401                                    };
402                                }
403
404                                self.process_event(
405                                    event_loop,
406                                    Event::WindowCreated {
407                                        id,
408                                        window: Arc::new(window),
409                                        exit_on_close_request,
410                                        make_visible: visible,
411                                        on_open,
412                                    },
413                                );
414                            }
415                            Control::Exit => {
416                                self.process_event(event_loop, Event::Exit);
417                                event_loop.exit();
418                                break;
419                            }
420                            Control::Crash(error) => {
421                                self.error = Some(error);
422                                event_loop.exit();
423                            }
424                            Control::SetAutomaticWindowTabbing(_enabled) => {
425                                #[cfg(target_os = "macos")]
426                                {
427                                    use winit::platform::macos::ActiveEventLoopExtMacOS;
428                                    event_loop
429                                        .set_allows_automatic_window_tabbing(
430                                            _enabled,
431                                        );
432                                }
433                            }
434                        },
435                        _ => {
436                            break;
437                        }
438                    },
439                    task::Poll::Ready(_) => {
440                        event_loop.exit();
441                        break;
442                    }
443                };
444            }
445        }
446    }
447
448    #[cfg(not(target_arch = "wasm32"))]
449    {
450        let mut runner = runner;
451        let _ = event_loop.run_app(&mut runner);
452
453        runner.error.map(Err).unwrap_or(Ok(()))
454    }
455
456    #[cfg(target_arch = "wasm32")]
457    {
458        use winit::platform::web::EventLoopExtWebSys;
459        let _ = event_loop.spawn_app(runner);
460
461        Ok(())
462    }
463}
464
465#[derive(Debug)]
466enum Event<Message: 'static> {
467    WindowCreated {
468        id: window::Id,
469        window: Arc<winit::window::Window>,
470        exit_on_close_request: bool,
471        make_visible: bool,
472        on_open: oneshot::Sender<window::Id>,
473    },
474    EventLoopAwakened(winit::event::Event<Message>),
475    Exit,
476}
477
478#[derive(Debug)]
479enum Control {
480    ChangeFlow(winit::event_loop::ControlFlow),
481    Exit,
482    Crash(Error),
483    CreateWindow {
484        id: window::Id,
485        settings: window::Settings,
486        title: String,
487        monitor: Option<winit::monitor::MonitorHandle>,
488        on_open: oneshot::Sender<window::Id>,
489        scale_factor: f32,
490    },
491    SetAutomaticWindowTabbing(bool),
492}
493
494async fn run_instance<P>(
495    mut program: program::Instance<P>,
496    mut runtime: Runtime<P::Executor, Proxy<P::Message>, Action<P::Message>>,
497    mut proxy: Proxy<P::Message>,
498    mut event_receiver: mpsc::UnboundedReceiver<Event<Action<P::Message>>>,
499    mut control_sender: mpsc::UnboundedSender<Control>,
500    display_handle: winit::event_loop::OwnedDisplayHandle,
501    is_daemon: bool,
502    graphics_settings: graphics::Settings,
503    default_fonts: Vec<Cow<'static, [u8]>>,
504    mut _system_theme: oneshot::Receiver<theme::Mode>,
505) where
506    P: Program + 'static,
507    P::Theme: theme::Base,
508{
509    use winit::event;
510    use winit::event_loop::ControlFlow;
511
512    let mut window_manager = WindowManager::new();
513    let mut is_window_opening = !is_daemon;
514
515    let mut compositor = None;
516    let mut events = Vec::new();
517    let mut messages = Vec::new();
518    let mut actions = 0;
519
520    let mut ui_caches = FxHashMap::default();
521    let mut user_interfaces = ManuallyDrop::new(FxHashMap::default());
522    let mut clipboard = Clipboard::unconnected();
523
524    #[cfg(all(feature = "linux-theme-detection", target_os = "linux"))]
525    let mut system_theme = {
526        let to_mode = |color_scheme| match color_scheme {
527            mundy::ColorScheme::NoPreference => theme::Mode::None,
528            mundy::ColorScheme::Light => theme::Mode::Light,
529            mundy::ColorScheme::Dark => theme::Mode::Dark,
530        };
531
532        runtime.run(
533            mundy::Preferences::stream(mundy::Interest::ColorScheme)
534                .map(move |preferences| {
535                    Action::System(system::Action::NotifyTheme(to_mode(
536                        preferences.color_scheme,
537                    )))
538                })
539                .boxed(),
540        );
541
542        runtime
543            .enter(|| {
544                mundy::Preferences::once_blocking(
545                    mundy::Interest::ColorScheme,
546                    core::time::Duration::from_millis(200),
547                )
548            })
549            .map(|preferences| to_mode(preferences.color_scheme))
550            .unwrap_or_default()
551    };
552
553    #[cfg(not(all(feature = "linux-theme-detection", target_os = "linux")))]
554    let mut system_theme =
555        _system_theme.try_recv().ok().flatten().unwrap_or_default();
556
557    log::info!("System theme: {system_theme:?}");
558
559    'next_event: loop {
560        // Empty the queue if possible
561        let event = if let Ok(event) = event_receiver.try_next() {
562            event
563        } else {
564            event_receiver.next().await
565        };
566
567        let Some(event) = event else {
568            break;
569        };
570
571        match event {
572            Event::WindowCreated {
573                id,
574                window,
575                exit_on_close_request,
576                make_visible,
577                on_open,
578            } => {
579                if compositor.is_none() {
580                    let (compositor_sender, compositor_receiver) =
581                        oneshot::channel();
582
583                    let create_compositor = {
584                        let window = window.clone();
585                        let display_handle = display_handle.clone();
586                        let proxy = proxy.clone();
587                        let default_fonts = default_fonts.clone();
588
589                        async move {
590                            let shell = Shell::new(proxy.clone());
591
592                            let mut compositor =
593                                <P::Renderer as compositor::Default>::Compositor::new(
594                                    graphics_settings,
595                                    display_handle,
596                                    window,
597                                    shell,
598                                ).await;
599
600                            if let Ok(compositor) = &mut compositor {
601                                for font in default_fonts {
602                                    compositor.load_font(font.clone());
603                                }
604                            }
605
606                            compositor_sender
607                                .send(compositor)
608                                .ok()
609                                .expect("Send compositor");
610
611                            // HACK! Send a proxy event on completion to trigger
612                            // a runtime re-poll
613                            // TODO: Send compositor through proxy (?)
614                            {
615                                let (sender, _receiver) = oneshot::channel();
616
617                                proxy.send_action(Action::Window(
618                                    runtime::window::Action::GetLatest(sender),
619                                ));
620                            }
621                        }
622                    };
623
624                    #[cfg(target_arch = "wasm32")]
625                    wasm_bindgen_futures::spawn_local(create_compositor);
626
627                    #[cfg(not(target_arch = "wasm32"))]
628                    runtime.block_on(create_compositor);
629
630                    match compositor_receiver
631                        .await
632                        .expect("Wait for compositor")
633                    {
634                        Ok(new_compositor) => {
635                            compositor = Some(new_compositor);
636                        }
637                        Err(error) => {
638                            let _ = control_sender
639                                .start_send(Control::Crash(error.into()));
640                            continue;
641                        }
642                    }
643                }
644
645                let window_theme = window
646                    .theme()
647                    .map(conversion::theme_mode)
648                    .unwrap_or_default();
649
650                if system_theme != window_theme {
651                    system_theme = window_theme;
652
653                    runtime.broadcast(subscription::Event::SystemThemeChanged(
654                        window_theme,
655                    ));
656                }
657
658                let is_first = window_manager.is_empty();
659                let window = window_manager.insert(
660                    id,
661                    window,
662                    &program,
663                    compositor
664                        .as_mut()
665                        .expect("Compositor must be initialized"),
666                    exit_on_close_request,
667                    system_theme,
668                );
669
670                window.raw.set_theme(conversion::window_theme(
671                    window.state.theme_mode(),
672                ));
673
674                debug::theme_changed(|| {
675                    if is_first {
676                        theme::Base::palette(window.state.theme())
677                    } else {
678                        None
679                    }
680                });
681
682                let logical_size = window.state.logical_size();
683
684                let _ = user_interfaces.insert(
685                    id,
686                    build_user_interface(
687                        &program,
688                        user_interface::Cache::default(),
689                        &mut window.renderer,
690                        logical_size,
691                        id,
692                    ),
693                );
694                let _ = ui_caches.insert(id, user_interface::Cache::default());
695
696                if make_visible {
697                    window.raw.set_visible(true);
698                }
699
700                events.push((
701                    id,
702                    core::Event::Window(window::Event::Opened {
703                        position: window.position(),
704                        size: window.logical_size(),
705                    }),
706                ));
707
708                if clipboard.window_id().is_none() {
709                    clipboard = Clipboard::connect(window.raw.clone());
710                }
711
712                let _ = on_open.send(id);
713                is_window_opening = false;
714            }
715            Event::EventLoopAwakened(event) => {
716                match event {
717                    event::Event::NewEvents(event::StartCause::Init) => {
718                        for (_id, window) in window_manager.iter_mut() {
719                            window.raw.request_redraw();
720                        }
721                    }
722                    event::Event::NewEvents(
723                        event::StartCause::ResumeTimeReached { .. },
724                    ) => {
725                        let now = Instant::now();
726
727                        for (_id, window) in window_manager.iter_mut() {
728                            if let Some(redraw_at) = window.redraw_at
729                                && redraw_at <= now
730                            {
731                                window.raw.request_redraw();
732                                window.redraw_at = None;
733                            }
734                        }
735
736                        if let Some(redraw_at) = window_manager.redraw_at() {
737                            let _ =
738                                control_sender.start_send(Control::ChangeFlow(
739                                    ControlFlow::WaitUntil(redraw_at),
740                                ));
741                        } else {
742                            let _ = control_sender.start_send(
743                                Control::ChangeFlow(ControlFlow::Wait),
744                            );
745                        }
746                    }
747                    event::Event::UserEvent(action) => {
748                        run_action(
749                            action,
750                            &program,
751                            &mut runtime,
752                            &mut compositor,
753                            &mut events,
754                            &mut messages,
755                            &mut clipboard,
756                            &mut control_sender,
757                            &mut user_interfaces,
758                            &mut window_manager,
759                            &mut ui_caches,
760                            &mut is_window_opening,
761                            &mut system_theme,
762                        );
763                        actions += 1;
764                    }
765                    event::Event::WindowEvent {
766                        window_id: id,
767                        event: event::WindowEvent::RedrawRequested,
768                        ..
769                    } => {
770                        let Some(mut current_compositor) = compositor.as_mut()
771                        else {
772                            continue;
773                        };
774
775                        let Some((id, mut window)) =
776                            window_manager.get_mut_alias(id)
777                        else {
778                            continue;
779                        };
780
781                        let physical_size = window.state.physical_size();
782                        let mut logical_size = window.state.logical_size();
783
784                        if physical_size.width == 0 || physical_size.height == 0
785                        {
786                            continue;
787                        }
788
789                        // Window was resized between redraws
790                        if window.surface_version
791                            != window.state.surface_version()
792                        {
793                            let ui = user_interfaces
794                                .remove(&id)
795                                .expect("Remove user interface");
796
797                            let layout_span = debug::layout(id);
798                            let _ = user_interfaces.insert(
799                                id,
800                                ui.relayout(logical_size, &mut window.renderer),
801                            );
802                            layout_span.finish();
803
804                            current_compositor.configure_surface(
805                                &mut window.surface,
806                                physical_size.width,
807                                physical_size.height,
808                            );
809
810                            window.surface_version =
811                                window.state.surface_version();
812                        }
813
814                        let redraw_event = core::Event::Window(
815                            window::Event::RedrawRequested(Instant::now()),
816                        );
817
818                        let cursor = window.state.cursor();
819
820                        let mut interface = user_interfaces
821                            .get_mut(&id)
822                            .expect("Get user interface");
823
824                        let interact_span = debug::interact(id);
825                        let mut redraw_count = 0;
826
827                        let state = loop {
828                            let message_count = messages.len();
829                            let (state, _) = interface.update(
830                                slice::from_ref(&redraw_event),
831                                cursor,
832                                &mut window.renderer,
833                                &mut clipboard,
834                                &mut messages,
835                            );
836
837                            if message_count == messages.len()
838                                && !state.has_layout_changed()
839                            {
840                                break state;
841                            }
842
843                            if redraw_count >= 2 {
844                                log::warn!(
845                                    "More than 3 consecutive RedrawRequested events \
846                                    produced layout invalidation"
847                                );
848
849                                break state;
850                            }
851
852                            redraw_count += 1;
853
854                            if !messages.is_empty() {
855                                let caches: FxHashMap<_, _> =
856                                    ManuallyDrop::into_inner(user_interfaces)
857                                        .into_iter()
858                                        .map(|(id, interface)| {
859                                            (id, interface.into_cache())
860                                        })
861                                        .collect();
862
863                                let actions = update(
864                                    &mut program,
865                                    &mut runtime,
866                                    &mut messages,
867                                );
868
869                                user_interfaces =
870                                    ManuallyDrop::new(build_user_interfaces(
871                                        &program,
872                                        &mut window_manager,
873                                        caches,
874                                    ));
875
876                                for action in actions {
877                                    // Defer all window actions to avoid compositor
878                                    // race conditions while redrawing
879                                    if let Action::Window(_) = action {
880                                        proxy.send_action(action);
881                                        continue;
882                                    }
883
884                                    run_action(
885                                        action,
886                                        &program,
887                                        &mut runtime,
888                                        &mut compositor,
889                                        &mut events,
890                                        &mut messages,
891                                        &mut clipboard,
892                                        &mut control_sender,
893                                        &mut user_interfaces,
894                                        &mut window_manager,
895                                        &mut ui_caches,
896                                        &mut is_window_opening,
897                                        &mut system_theme,
898                                    );
899                                }
900
901                                for (window_id, window) in
902                                    window_manager.iter_mut()
903                                {
904                                    // We are already redrawing this window
905                                    if window_id == id {
906                                        continue;
907                                    }
908
909                                    window.raw.request_redraw();
910                                }
911
912                                let Some(next_compositor) = compositor.as_mut()
913                                else {
914                                    continue 'next_event;
915                                };
916
917                                current_compositor = next_compositor;
918                                window = window_manager.get_mut(id).unwrap();
919
920                                // Window scale factor changed during a redraw request
921                                if logical_size != window.state.logical_size() {
922                                    logical_size = window.state.logical_size();
923
924                                    log::debug!(
925                                        "Window scale factor changed during a redraw request"
926                                    );
927
928                                    let ui = user_interfaces
929                                        .remove(&id)
930                                        .expect("Remove user interface");
931
932                                    let layout_span = debug::layout(id);
933                                    let _ = user_interfaces.insert(
934                                        id,
935                                        ui.relayout(
936                                            logical_size,
937                                            &mut window.renderer,
938                                        ),
939                                    );
940                                    layout_span.finish();
941                                }
942
943                                interface =
944                                    user_interfaces.get_mut(&id).unwrap();
945                            }
946                        };
947                        interact_span.finish();
948
949                        let draw_span = debug::draw(id);
950                        interface.draw(
951                            &mut window.renderer,
952                            window.state.theme(),
953                            &renderer::Style {
954                                text_color: window.state.text_color(),
955                            },
956                            cursor,
957                        );
958                        draw_span.finish();
959
960                        if let user_interface::State::Updated {
961                            redraw_request,
962                            input_method,
963                            mouse_interaction,
964                            ..
965                        } = state
966                        {
967                            window.request_redraw(redraw_request);
968                            window.request_input_method(input_method);
969                            window.update_mouse(mouse_interaction);
970                        }
971
972                        runtime.broadcast(subscription::Event::Interaction {
973                            window: id,
974                            event: redraw_event,
975                            status: core::event::Status::Ignored,
976                        });
977
978                        window.draw_preedit();
979
980                        let present_span = debug::present(id);
981                        match current_compositor.present(
982                            &mut window.renderer,
983                            &mut window.surface,
984                            window.state.viewport(),
985                            window.state.background_color(),
986                            || window.raw.pre_present_notify(),
987                        ) {
988                            Ok(()) => {
989                                present_span.finish();
990                            }
991                            Err(error) => match error {
992                                compositor::SurfaceError::OutOfMemory => {
993                                    // This is an unrecoverable error.
994                                    panic!("{error:?}");
995                                }
996                                compositor::SurfaceError::Outdated
997                                | compositor::SurfaceError::Lost => {
998                                    present_span.finish();
999
1000                                    // Reconfigure surface and try redrawing
1001                                    let physical_size =
1002                                        window.state.physical_size();
1003
1004                                    if error == compositor::SurfaceError::Lost {
1005                                        window.surface = current_compositor
1006                                            .create_surface(
1007                                                window.raw.clone(),
1008                                                physical_size.width,
1009                                                physical_size.height,
1010                                            );
1011                                    } else {
1012                                        current_compositor.configure_surface(
1013                                            &mut window.surface,
1014                                            physical_size.width,
1015                                            physical_size.height,
1016                                        );
1017                                    }
1018
1019                                    window.raw.request_redraw();
1020                                }
1021                                _ => {
1022                                    present_span.finish();
1023
1024                                    log::error!(
1025                                        "Error {error:?} when \
1026                                        presenting surface."
1027                                    );
1028
1029                                    // Try rendering all windows again next frame.
1030                                    for (_id, window) in
1031                                        window_manager.iter_mut()
1032                                    {
1033                                        window.raw.request_redraw();
1034                                    }
1035                                }
1036                            },
1037                        }
1038                    }
1039                    event::Event::WindowEvent {
1040                        event: window_event,
1041                        window_id,
1042                    } => {
1043                        if !is_daemon
1044                            && matches!(
1045                                window_event,
1046                                winit::event::WindowEvent::Destroyed
1047                            )
1048                            && !is_window_opening
1049                            && window_manager.is_empty()
1050                        {
1051                            control_sender
1052                                .start_send(Control::Exit)
1053                                .expect("Send control action");
1054
1055                            continue;
1056                        }
1057
1058                        let Some((id, window)) =
1059                            window_manager.get_mut_alias(window_id)
1060                        else {
1061                            continue;
1062                        };
1063
1064                        match window_event {
1065                            winit::event::WindowEvent::Resized(_) => {
1066                                window.raw.request_redraw();
1067                            }
1068                            winit::event::WindowEvent::ThemeChanged(theme) => {
1069                                let mode = conversion::theme_mode(theme);
1070
1071                                if mode != system_theme {
1072                                    system_theme = mode;
1073
1074                                    runtime.broadcast(
1075                                        subscription::Event::SystemThemeChanged(
1076                                            mode,
1077                                        ),
1078                                    );
1079                                }
1080                            }
1081                            _ => {}
1082                        }
1083
1084                        if matches!(
1085                            window_event,
1086                            winit::event::WindowEvent::CloseRequested
1087                        ) && window.exit_on_close_request
1088                        {
1089                            run_action(
1090                                Action::Window(runtime::window::Action::Close(
1091                                    id,
1092                                )),
1093                                &program,
1094                                &mut runtime,
1095                                &mut compositor,
1096                                &mut events,
1097                                &mut messages,
1098                                &mut clipboard,
1099                                &mut control_sender,
1100                                &mut user_interfaces,
1101                                &mut window_manager,
1102                                &mut ui_caches,
1103                                &mut is_window_opening,
1104                                &mut system_theme,
1105                            );
1106                        } else {
1107                            window.state.update(
1108                                &program,
1109                                &window.raw,
1110                                &window_event,
1111                            );
1112
1113                            if let Some(event) = conversion::window_event(
1114                                window_event,
1115                                window.state.scale_factor(),
1116                                window.state.modifiers(),
1117                            ) {
1118                                events.push((id, event));
1119                            }
1120                        }
1121                    }
1122                    event::Event::AboutToWait => {
1123                        if actions > 0 {
1124                            proxy.free_slots(actions);
1125                            actions = 0;
1126                        }
1127
1128                        if events.is_empty()
1129                            && messages.is_empty()
1130                            && window_manager.is_idle()
1131                        {
1132                            continue;
1133                        }
1134
1135                        let mut uis_stale = false;
1136
1137                        for (id, window) in window_manager.iter_mut() {
1138                            let interact_span = debug::interact(id);
1139                            let mut window_events = vec![];
1140
1141                            events.retain(|(window_id, event)| {
1142                                if *window_id == id {
1143                                    window_events.push(event.clone());
1144                                    false
1145                                } else {
1146                                    true
1147                                }
1148                            });
1149
1150                            if window_events.is_empty() && messages.is_empty() {
1151                                continue;
1152                            }
1153
1154                            let (ui_state, statuses) = user_interfaces
1155                                .get_mut(&id)
1156                                .expect("Get user interface")
1157                                .update(
1158                                    &window_events,
1159                                    window.state.cursor(),
1160                                    &mut window.renderer,
1161                                    &mut clipboard,
1162                                    &mut messages,
1163                                );
1164
1165                            #[cfg(feature = "unconditional-rendering")]
1166                            window.request_redraw(
1167                                window::RedrawRequest::NextFrame,
1168                            );
1169
1170                            match ui_state {
1171                                user_interface::State::Updated {
1172                                    redraw_request: _redraw_request,
1173                                    mouse_interaction,
1174                                    ..
1175                                } => {
1176                                    window.update_mouse(mouse_interaction);
1177
1178                                    #[cfg(not(
1179                                        feature = "unconditional-rendering"
1180                                    ))]
1181                                    window.request_redraw(_redraw_request);
1182                                }
1183                                user_interface::State::Outdated => {
1184                                    uis_stale = true;
1185                                }
1186                            }
1187
1188                            for (event, status) in window_events
1189                                .into_iter()
1190                                .zip(statuses.into_iter())
1191                            {
1192                                runtime.broadcast(
1193                                    subscription::Event::Interaction {
1194                                        window: id,
1195                                        event,
1196                                        status,
1197                                    },
1198                                );
1199                            }
1200
1201                            interact_span.finish();
1202                        }
1203
1204                        for (id, event) in events.drain(..) {
1205                            runtime.broadcast(
1206                                subscription::Event::Interaction {
1207                                    window: id,
1208                                    event,
1209                                    status: core::event::Status::Ignored,
1210                                },
1211                            );
1212                        }
1213
1214                        if !messages.is_empty() || uis_stale {
1215                            let cached_interfaces: FxHashMap<_, _> =
1216                                ManuallyDrop::into_inner(user_interfaces)
1217                                    .into_iter()
1218                                    .map(|(id, ui)| (id, ui.into_cache()))
1219                                    .collect();
1220
1221                            let actions = update(
1222                                &mut program,
1223                                &mut runtime,
1224                                &mut messages,
1225                            );
1226
1227                            user_interfaces =
1228                                ManuallyDrop::new(build_user_interfaces(
1229                                    &program,
1230                                    &mut window_manager,
1231                                    cached_interfaces,
1232                                ));
1233
1234                            for action in actions {
1235                                run_action(
1236                                    action,
1237                                    &program,
1238                                    &mut runtime,
1239                                    &mut compositor,
1240                                    &mut events,
1241                                    &mut messages,
1242                                    &mut clipboard,
1243                                    &mut control_sender,
1244                                    &mut user_interfaces,
1245                                    &mut window_manager,
1246                                    &mut ui_caches,
1247                                    &mut is_window_opening,
1248                                    &mut system_theme,
1249                                );
1250                            }
1251
1252                            for (_id, window) in window_manager.iter_mut() {
1253                                window.raw.request_redraw();
1254                            }
1255                        }
1256
1257                        if let Some(redraw_at) = window_manager.redraw_at() {
1258                            let _ =
1259                                control_sender.start_send(Control::ChangeFlow(
1260                                    ControlFlow::WaitUntil(redraw_at),
1261                                ));
1262                        } else {
1263                            let _ = control_sender.start_send(
1264                                Control::ChangeFlow(ControlFlow::Wait),
1265                            );
1266                        }
1267                    }
1268                    _ => {}
1269                }
1270            }
1271            Event::Exit => break,
1272        }
1273    }
1274
1275    let _ = ManuallyDrop::into_inner(user_interfaces);
1276}
1277
1278/// Builds a window's [`UserInterface`] for the [`Program`].
1279fn build_user_interface<'a, P: Program>(
1280    program: &'a program::Instance<P>,
1281    cache: user_interface::Cache,
1282    renderer: &mut P::Renderer,
1283    size: Size,
1284    id: window::Id,
1285) -> UserInterface<'a, P::Message, P::Theme, P::Renderer>
1286where
1287    P::Theme: theme::Base,
1288{
1289    let view_span = debug::view(id);
1290    let view = program.view(id);
1291    view_span.finish();
1292
1293    let layout_span = debug::layout(id);
1294    let user_interface = UserInterface::build(view, size, cache, renderer);
1295    layout_span.finish();
1296
1297    user_interface
1298}
1299
1300fn update<P: Program, E: Executor>(
1301    program: &mut program::Instance<P>,
1302    runtime: &mut Runtime<E, Proxy<P::Message>, Action<P::Message>>,
1303    messages: &mut Vec<P::Message>,
1304) -> Vec<Action<P::Message>>
1305where
1306    P::Theme: theme::Base,
1307{
1308    use futures::futures;
1309
1310    let mut actions = Vec::new();
1311
1312    for message in messages.drain(..) {
1313        let task = runtime.enter(|| program.update(message));
1314
1315        if let Some(mut stream) = runtime::task::into_stream(task) {
1316            let waker = futures::task::noop_waker_ref();
1317            let mut context = futures::task::Context::from_waker(waker);
1318
1319            // Run immediately available actions synchronously (e.g. widget operations)
1320            loop {
1321                match runtime.enter(|| stream.poll_next_unpin(&mut context)) {
1322                    futures::task::Poll::Ready(Some(action)) => {
1323                        actions.push(action);
1324                    }
1325                    futures::task::Poll::Ready(None) => {
1326                        break;
1327                    }
1328                    futures::task::Poll::Pending => {
1329                        runtime.run(stream);
1330                        break;
1331                    }
1332                }
1333            }
1334        }
1335    }
1336
1337    let subscription = runtime.enter(|| program.subscription());
1338    let recipes = subscription::into_recipes(subscription.map(Action::Output));
1339
1340    runtime.track(recipes);
1341
1342    actions
1343}
1344
1345fn run_action<'a, P, C>(
1346    action: Action<P::Message>,
1347    program: &'a program::Instance<P>,
1348    runtime: &mut Runtime<P::Executor, Proxy<P::Message>, Action<P::Message>>,
1349    compositor: &mut Option<C>,
1350    events: &mut Vec<(window::Id, core::Event)>,
1351    messages: &mut Vec<P::Message>,
1352    clipboard: &mut Clipboard,
1353    control_sender: &mut mpsc::UnboundedSender<Control>,
1354    interfaces: &mut FxHashMap<
1355        window::Id,
1356        UserInterface<'a, P::Message, P::Theme, P::Renderer>,
1357    >,
1358    window_manager: &mut WindowManager<P, C>,
1359    ui_caches: &mut FxHashMap<window::Id, user_interface::Cache>,
1360    is_window_opening: &mut bool,
1361    system_theme: &mut theme::Mode,
1362) where
1363    P: Program,
1364    C: Compositor<Renderer = P::Renderer> + 'static,
1365    P::Theme: theme::Base,
1366{
1367    use crate::runtime::clipboard;
1368    use crate::runtime::window;
1369
1370    match action {
1371        Action::Output(message) => {
1372            messages.push(message);
1373        }
1374        Action::Clipboard(action) => match action {
1375            clipboard::Action::Read { target, channel } => {
1376                let _ = channel.send(clipboard.read(target));
1377            }
1378            clipboard::Action::Write { target, contents } => {
1379                clipboard.write(target, contents);
1380            }
1381        },
1382        Action::Window(action) => match action {
1383            window::Action::Open(id, settings, channel) => {
1384                let monitor = window_manager.last_monitor();
1385
1386                control_sender
1387                    .start_send(Control::CreateWindow {
1388                        id,
1389                        settings,
1390                        title: program.title(id),
1391                        scale_factor: program.scale_factor(id),
1392                        monitor,
1393                        on_open: channel,
1394                    })
1395                    .expect("Send control action");
1396
1397                *is_window_opening = true;
1398            }
1399            window::Action::Close(id) => {
1400                let _ = ui_caches.remove(&id);
1401                let _ = interfaces.remove(&id);
1402
1403                if let Some(window) = window_manager.remove(id) {
1404                    if clipboard.window_id() == Some(window.raw.id()) {
1405                        *clipboard = window_manager
1406                            .first()
1407                            .map(|window| window.raw.clone())
1408                            .map(Clipboard::connect)
1409                            .unwrap_or_else(Clipboard::unconnected);
1410                    }
1411
1412                    events.push((
1413                        id,
1414                        core::Event::Window(core::window::Event::Closed),
1415                    ));
1416                }
1417
1418                if window_manager.is_empty() {
1419                    *compositor = None;
1420                }
1421            }
1422            window::Action::GetOldest(channel) => {
1423                let id =
1424                    window_manager.iter_mut().next().map(|(id, _window)| id);
1425
1426                let _ = channel.send(id);
1427            }
1428            window::Action::GetLatest(channel) => {
1429                let id =
1430                    window_manager.iter_mut().last().map(|(id, _window)| id);
1431
1432                let _ = channel.send(id);
1433            }
1434            window::Action::Drag(id) => {
1435                if let Some(window) = window_manager.get_mut(id) {
1436                    let _ = window.raw.drag_window();
1437                }
1438            }
1439            window::Action::DragResize(id, direction) => {
1440                if let Some(window) = window_manager.get_mut(id) {
1441                    let _ = window.raw.drag_resize_window(
1442                        conversion::resize_direction(direction),
1443                    );
1444                }
1445            }
1446            window::Action::Resize(id, size) => {
1447                if let Some(window) = window_manager.get_mut(id) {
1448                    let _ = window.raw.request_inner_size(
1449                        winit::dpi::LogicalSize {
1450                            width: size.width,
1451                            height: size.height,
1452                        }
1453                        .to_physical::<f32>(f64::from(
1454                            window.state.scale_factor(),
1455                        )),
1456                    );
1457                }
1458            }
1459            window::Action::SetMinSize(id, size) => {
1460                if let Some(window) = window_manager.get_mut(id) {
1461                    window.raw.set_min_inner_size(size.map(|size| {
1462                        winit::dpi::LogicalSize {
1463                            width: size.width,
1464                            height: size.height,
1465                        }
1466                        .to_physical::<f32>(f64::from(
1467                            window.state.scale_factor(),
1468                        ))
1469                    }));
1470                }
1471            }
1472            window::Action::SetMaxSize(id, size) => {
1473                if let Some(window) = window_manager.get_mut(id) {
1474                    window.raw.set_max_inner_size(size.map(|size| {
1475                        winit::dpi::LogicalSize {
1476                            width: size.width,
1477                            height: size.height,
1478                        }
1479                        .to_physical::<f32>(f64::from(
1480                            window.state.scale_factor(),
1481                        ))
1482                    }));
1483                }
1484            }
1485            window::Action::SetResizeIncrements(id, increments) => {
1486                if let Some(window) = window_manager.get_mut(id) {
1487                    window.raw.set_resize_increments(increments.map(|size| {
1488                        winit::dpi::LogicalSize {
1489                            width: size.width,
1490                            height: size.height,
1491                        }
1492                        .to_physical::<f32>(f64::from(
1493                            window.state.scale_factor(),
1494                        ))
1495                    }));
1496                }
1497            }
1498            window::Action::SetResizable(id, resizable) => {
1499                if let Some(window) = window_manager.get_mut(id) {
1500                    window.raw.set_resizable(resizable);
1501                }
1502            }
1503            window::Action::GetSize(id, channel) => {
1504                if let Some(window) = window_manager.get_mut(id) {
1505                    let size = window.logical_size();
1506                    let _ = channel.send(Size::new(size.width, size.height));
1507                }
1508            }
1509            window::Action::GetMaximized(id, channel) => {
1510                if let Some(window) = window_manager.get_mut(id) {
1511                    let _ = channel.send(window.raw.is_maximized());
1512                }
1513            }
1514            window::Action::Maximize(id, maximized) => {
1515                if let Some(window) = window_manager.get_mut(id) {
1516                    window.raw.set_maximized(maximized);
1517                }
1518            }
1519            window::Action::GetMinimized(id, channel) => {
1520                if let Some(window) = window_manager.get_mut(id) {
1521                    let _ = channel.send(window.raw.is_minimized());
1522                }
1523            }
1524            window::Action::Minimize(id, minimized) => {
1525                if let Some(window) = window_manager.get_mut(id) {
1526                    window.raw.set_minimized(minimized);
1527                }
1528            }
1529            window::Action::GetPosition(id, channel) => {
1530                if let Some(window) = window_manager.get(id) {
1531                    let position = window
1532                        .raw
1533                        .outer_position()
1534                        .map(|position| {
1535                            let position = position
1536                                .to_logical::<f32>(window.raw.scale_factor());
1537
1538                            Point::new(position.x, position.y)
1539                        })
1540                        .ok();
1541
1542                    let _ = channel.send(position);
1543                }
1544            }
1545            window::Action::GetScaleFactor(id, channel) => {
1546                if let Some(window) = window_manager.get_mut(id) {
1547                    let scale_factor = window.raw.scale_factor();
1548
1549                    let _ = channel.send(scale_factor as f32);
1550                }
1551            }
1552            window::Action::Move(id, position) => {
1553                if let Some(window) = window_manager.get_mut(id) {
1554                    window.raw.set_outer_position(
1555                        winit::dpi::LogicalPosition {
1556                            x: position.x,
1557                            y: position.y,
1558                        },
1559                    );
1560                }
1561            }
1562            window::Action::SetMode(id, mode) => {
1563                if let Some(window) = window_manager.get_mut(id) {
1564                    window.raw.set_visible(conversion::visible(mode));
1565                    window.raw.set_fullscreen(conversion::fullscreen(
1566                        window.raw.current_monitor(),
1567                        mode,
1568                    ));
1569                }
1570            }
1571            window::Action::SetIcon(id, icon) => {
1572                if let Some(window) = window_manager.get_mut(id) {
1573                    window.raw.set_window_icon(conversion::icon(icon));
1574                }
1575            }
1576            window::Action::GetMode(id, channel) => {
1577                if let Some(window) = window_manager.get_mut(id) {
1578                    let mode = if window.raw.is_visible().unwrap_or(true) {
1579                        conversion::mode(window.raw.fullscreen())
1580                    } else {
1581                        core::window::Mode::Hidden
1582                    };
1583
1584                    let _ = channel.send(mode);
1585                }
1586            }
1587            window::Action::ToggleMaximize(id) => {
1588                if let Some(window) = window_manager.get_mut(id) {
1589                    window.raw.set_maximized(!window.raw.is_maximized());
1590                }
1591            }
1592            window::Action::ToggleDecorations(id) => {
1593                if let Some(window) = window_manager.get_mut(id) {
1594                    window.raw.set_decorations(!window.raw.is_decorated());
1595                }
1596            }
1597            window::Action::RequestUserAttention(id, attention_type) => {
1598                if let Some(window) = window_manager.get_mut(id) {
1599                    window.raw.request_user_attention(
1600                        attention_type.map(conversion::user_attention),
1601                    );
1602                }
1603            }
1604            window::Action::GainFocus(id) => {
1605                if let Some(window) = window_manager.get_mut(id) {
1606                    window.raw.focus_window();
1607                }
1608            }
1609            window::Action::SetLevel(id, level) => {
1610                if let Some(window) = window_manager.get_mut(id) {
1611                    window
1612                        .raw
1613                        .set_window_level(conversion::window_level(level));
1614                }
1615            }
1616            window::Action::ShowSystemMenu(id) => {
1617                if let Some(window) = window_manager.get_mut(id)
1618                    && let mouse::Cursor::Available(point) =
1619                        window.state.cursor()
1620                {
1621                    window.raw.show_window_menu(winit::dpi::LogicalPosition {
1622                        x: point.x,
1623                        y: point.y,
1624                    });
1625                }
1626            }
1627            window::Action::GetRawId(id, channel) => {
1628                if let Some(window) = window_manager.get_mut(id) {
1629                    let _ = channel.send(window.raw.id().into());
1630                }
1631            }
1632            window::Action::Run(id, f) => {
1633                if let Some(window) = window_manager.get_mut(id) {
1634                    f(window);
1635                }
1636            }
1637            window::Action::Screenshot(id, channel) => {
1638                if let Some(window) = window_manager.get_mut(id)
1639                    && let Some(compositor) = compositor
1640                {
1641                    let bytes = compositor.screenshot(
1642                        &mut window.renderer,
1643                        window.state.viewport(),
1644                        window.state.background_color(),
1645                    );
1646
1647                    let _ = channel.send(core::window::Screenshot::new(
1648                        bytes,
1649                        window.state.physical_size(),
1650                        window.state.scale_factor(),
1651                    ));
1652                }
1653            }
1654            window::Action::EnableMousePassthrough(id) => {
1655                if let Some(window) = window_manager.get_mut(id) {
1656                    let _ = window.raw.set_cursor_hittest(false);
1657                }
1658            }
1659            window::Action::DisableMousePassthrough(id) => {
1660                if let Some(window) = window_manager.get_mut(id) {
1661                    let _ = window.raw.set_cursor_hittest(true);
1662                }
1663            }
1664            window::Action::GetMonitorSize(id, channel) => {
1665                if let Some(window) = window_manager.get(id) {
1666                    let size = window.raw.current_monitor().map(|monitor| {
1667                        let scale = window.state.scale_factor();
1668                        let size = monitor.size().to_logical(f64::from(scale));
1669
1670                        Size::new(size.width, size.height)
1671                    });
1672
1673                    let _ = channel.send(size);
1674                }
1675            }
1676            window::Action::SetAllowAutomaticTabbing(enabled) => {
1677                control_sender
1678                    .start_send(Control::SetAutomaticWindowTabbing(enabled))
1679                    .expect("Send control action");
1680            }
1681            window::Action::RedrawAll => {
1682                for (_id, window) in window_manager.iter_mut() {
1683                    window.raw.request_redraw();
1684                }
1685            }
1686            window::Action::RelayoutAll => {
1687                for (id, window) in window_manager.iter_mut() {
1688                    if let Some(ui) = interfaces.remove(&id) {
1689                        let _ = interfaces.insert(
1690                            id,
1691                            ui.relayout(
1692                                window.state.logical_size(),
1693                                &mut window.renderer,
1694                            ),
1695                        );
1696                    }
1697
1698                    window.raw.request_redraw();
1699                }
1700            }
1701        },
1702        Action::System(action) => match action {
1703            system::Action::GetInformation(_channel) => {
1704                #[cfg(feature = "sysinfo")]
1705                {
1706                    if let Some(compositor) = compositor {
1707                        let graphics_info = compositor.information();
1708
1709                        let _ = std::thread::spawn(move || {
1710                            let information = system_information(graphics_info);
1711
1712                            let _ = _channel.send(information);
1713                        });
1714                    }
1715                }
1716            }
1717            system::Action::GetTheme(channel) => {
1718                let _ = channel.send(*system_theme);
1719            }
1720            system::Action::NotifyTheme(mode) => {
1721                if mode != *system_theme {
1722                    *system_theme = mode;
1723
1724                    runtime.broadcast(subscription::Event::SystemThemeChanged(
1725                        mode,
1726                    ));
1727                }
1728
1729                let Some(theme) = conversion::window_theme(mode) else {
1730                    return;
1731                };
1732
1733                for (_id, window) in window_manager.iter_mut() {
1734                    window.state.update(
1735                        program,
1736                        &window.raw,
1737                        &winit::event::WindowEvent::ThemeChanged(theme),
1738                    );
1739                }
1740            }
1741        },
1742        Action::Widget(operation) => {
1743            let mut current_operation = Some(operation);
1744
1745            while let Some(mut operation) = current_operation.take() {
1746                for (id, ui) in interfaces.iter_mut() {
1747                    if let Some(window) = window_manager.get_mut(*id) {
1748                        ui.operate(&window.renderer, operation.as_mut());
1749                    }
1750                }
1751
1752                match operation.finish() {
1753                    operation::Outcome::None => {}
1754                    operation::Outcome::Some(()) => {}
1755                    operation::Outcome::Chain(next) => {
1756                        current_operation = Some(next);
1757                    }
1758                }
1759            }
1760        }
1761        Action::Image(action) => match action {
1762            image::Action::Allocate(handle, sender) => {
1763                use core::Renderer as _;
1764
1765                // TODO: Shared image cache in compositor
1766                if let Some((_id, window)) = window_manager.iter_mut().next() {
1767                    window.renderer.allocate_image(
1768                        &handle,
1769                        move |allocation| {
1770                            let _ = sender.send(allocation);
1771                        },
1772                    );
1773                }
1774            }
1775        },
1776        Action::LoadFont { bytes, channel } => {
1777            if let Some(compositor) = compositor {
1778                // TODO: Error handling (?)
1779                compositor.load_font(bytes.clone());
1780
1781                let _ = channel.send(Ok(()));
1782            }
1783        }
1784        Action::Reload => {
1785            for (id, window) in window_manager.iter_mut() {
1786                let Some(ui) = interfaces.remove(&id) else {
1787                    continue;
1788                };
1789
1790                let cache = ui.into_cache();
1791                let size = window.logical_size();
1792
1793                let _ = interfaces.insert(
1794                    id,
1795                    build_user_interface(
1796                        program,
1797                        cache,
1798                        &mut window.renderer,
1799                        size,
1800                        id,
1801                    ),
1802                );
1803
1804                window.raw.request_redraw();
1805            }
1806        }
1807        Action::Exit => {
1808            control_sender
1809                .start_send(Control::Exit)
1810                .expect("Send control action");
1811        }
1812    }
1813}
1814
1815/// Build the user interface for every window.
1816pub fn build_user_interfaces<'a, P: Program, C>(
1817    program: &'a program::Instance<P>,
1818    window_manager: &mut WindowManager<P, C>,
1819    mut cached_user_interfaces: FxHashMap<window::Id, user_interface::Cache>,
1820) -> FxHashMap<window::Id, UserInterface<'a, P::Message, P::Theme, P::Renderer>>
1821where
1822    C: Compositor<Renderer = P::Renderer>,
1823    P::Theme: theme::Base,
1824{
1825    for (id, window) in window_manager.iter_mut() {
1826        window.state.synchronize(program, id, &window.raw);
1827    }
1828
1829    debug::theme_changed(|| {
1830        window_manager
1831            .first()
1832            .and_then(|window| theme::Base::palette(window.state.theme()))
1833    });
1834
1835    cached_user_interfaces
1836        .drain()
1837        .filter_map(|(id, cache)| {
1838            let window = window_manager.get_mut(id)?;
1839
1840            Some((
1841                id,
1842                build_user_interface(
1843                    program,
1844                    cache,
1845                    &mut window.renderer,
1846                    window.state.logical_size(),
1847                    id,
1848                ),
1849            ))
1850        })
1851        .collect()
1852}
1853
1854/// Returns true if the provided event should cause a [`Program`] to
1855/// exit.
1856pub fn user_force_quit(
1857    event: &winit::event::WindowEvent,
1858    _modifiers: winit::keyboard::ModifiersState,
1859) -> bool {
1860    match event {
1861        #[cfg(target_os = "macos")]
1862        winit::event::WindowEvent::KeyboardInput {
1863            event:
1864                winit::event::KeyEvent {
1865                    logical_key: winit::keyboard::Key::Character(c),
1866                    state: winit::event::ElementState::Pressed,
1867                    ..
1868                },
1869            ..
1870        } if c == "q" && _modifiers.super_key() => true,
1871        _ => false,
1872    }
1873}
1874
1875#[cfg(feature = "sysinfo")]
1876fn system_information(
1877    graphics: compositor::Information,
1878) -> system::Information {
1879    use sysinfo::{Process, System};
1880
1881    let mut system = System::new_all();
1882    system.refresh_all();
1883
1884    let cpu_brand = system
1885        .cpus()
1886        .first()
1887        .map(|cpu| cpu.brand().to_string())
1888        .unwrap_or_default();
1889
1890    let memory_used = sysinfo::get_current_pid()
1891        .and_then(|pid| system.process(pid).ok_or("Process not found"))
1892        .map(Process::memory)
1893        .ok();
1894
1895    system::Information {
1896        system_name: System::name(),
1897        system_kernel: System::kernel_version(),
1898        system_version: System::long_os_version(),
1899        system_short_version: System::os_version(),
1900        cpu_brand,
1901        cpu_cores: system.physical_core_count(),
1902        memory_total: system.total_memory(),
1903        memory_used,
1904        graphics_adapter: graphics.adapter,
1905        graphics_backend: graphics.backend,
1906    }
1907}