Skip to main content

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