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