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