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