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