iced_winit/
lib.rs

1//! A windowing shell for Iced, on top of [`winit`].
2//!
3//! ![The native path of the Iced ecosystem](https://github.com/iced-rs/iced/blob/0525d76ff94e828b7b21634fa94a747022001c83/docs/graphs/native.png?raw=true)
4//!
5//! `iced_winit` offers some convenient abstractions on top of [`iced_runtime`]
6//! to quickstart development when using [`winit`].
7//!
8//! It exposes a renderer-agnostic [`Program`] trait that can be implemented
9//! and then run with a simple call. The use of this trait is optional.
10//!
11//! Additionally, a [`conversion`] module is available for users that decide to
12//! implement a custom event loop.
13//!
14//! [`iced_runtime`]: https://github.com/iced-rs/iced/tree/0.13/runtime
15//! [`winit`]: https://github.com/rust-windowing/winit
16//! [`conversion`]: crate::conversion
17#![doc(
18    html_logo_url = "https://raw.githubusercontent.com/iced-rs/iced/9ab6923e943f784985e9ef9ca28b10278297225d/docs/logo.svg"
19)]
20#![cfg_attr(docsrs, feature(doc_auto_cfg))]
21pub use iced_program as program;
22pub use program::core;
23pub use program::graphics;
24pub use program::runtime;
25pub use runtime::debug;
26pub use runtime::futures;
27pub use winit;
28
29pub mod clipboard;
30pub mod conversion;
31
32#[cfg(feature = "system")]
33pub mod system;
34
35mod error;
36mod proxy;
37mod window;
38
39pub use clipboard::Clipboard;
40pub use error::Error;
41pub use proxy::Proxy;
42
43use crate::core::mouse;
44use crate::core::renderer;
45use crate::core::theme;
46use crate::core::time::Instant;
47use crate::core::widget::operation;
48use crate::core::{Point, Settings, Size};
49use crate::futures::futures::channel::mpsc;
50use crate::futures::futures::channel::oneshot;
51use crate::futures::futures::task;
52use crate::futures::futures::{Future, StreamExt};
53use crate::futures::subscription;
54use crate::futures::{Executor, Runtime};
55use crate::graphics::{Compositor, compositor};
56use crate::runtime::user_interface::{self, UserInterface};
57use crate::runtime::{Action, Task};
58
59use program::Program;
60use window::WindowManager;
61
62use rustc_hash::FxHashMap;
63use std::borrow::Cow;
64use std::mem::ManuallyDrop;
65use std::slice;
66use std::sync::Arc;
67
68/// Runs a [`Program`] with the provided settings.
69pub fn run<P>(
70    program: P,
71    settings: Settings,
72    window_settings: Option<window::Settings>,
73) -> Result<(), Error>
74where
75    P: Program + 'static,
76    P::Theme: theme::Base,
77{
78    use winit::event_loop::EventLoop;
79
80    let boot_span = debug::boot();
81
82    let graphics_settings = settings.clone().into();
83    let event_loop = EventLoop::with_user_event()
84        .build()
85        .expect("Create event loop");
86
87    let (proxy, worker) = Proxy::new(event_loop.create_proxy());
88
89    #[cfg(feature = "debug")]
90    {
91        let proxy = proxy.clone();
92
93        debug::on_hotpatch(move || {
94            proxy.send_action(Action::Reload);
95        });
96    }
97
98    let mut runtime = {
99        let executor =
100            P::Executor::new().map_err(Error::ExecutorCreationFailed)?;
101        executor.spawn(worker);
102
103        Runtime::new(executor, proxy.clone())
104    };
105
106    let (program, task) = runtime.enter(|| program::Instance::new(program));
107    let is_daemon = window_settings.is_none();
108
109    let task = if let Some(window_settings) = window_settings {
110        let mut task = Some(task);
111
112        let (_id, open) = runtime::window::open(window_settings);
113
114        open.then(move |_| task.take().unwrap_or(Task::none()))
115    } else {
116        task
117    };
118
119    if let Some(stream) = runtime::task::into_stream(task) {
120        runtime.run(stream);
121    }
122
123    runtime.track(subscription::into_recipes(
124        runtime.enter(|| program.subscription().map(Action::Output)),
125    ));
126
127    let (event_sender, event_receiver) = mpsc::unbounded();
128    let (control_sender, control_receiver) = mpsc::unbounded();
129
130    let instance = Box::pin(run_instance::<P>(
131        program,
132        runtime,
133        proxy.clone(),
134        event_receiver,
135        control_sender,
136        is_daemon,
137        graphics_settings,
138        settings.fonts,
139    ));
140
141    let context = task::Context::from_waker(task::noop_waker_ref());
142
143    struct Runner<Message: 'static, F> {
144        instance: std::pin::Pin<Box<F>>,
145        context: task::Context<'static>,
146        id: Option<String>,
147        sender: mpsc::UnboundedSender<Event<Action<Message>>>,
148        receiver: mpsc::UnboundedReceiver<Control>,
149        error: Option<Error>,
150
151        #[cfg(target_arch = "wasm32")]
152        canvas: Option<web_sys::HtmlCanvasElement>,
153    }
154
155    let runner = Runner {
156        instance,
157        context,
158        id: settings.id,
159        sender: event_sender,
160        receiver: control_receiver,
161        error: None,
162
163        #[cfg(target_arch = "wasm32")]
164        canvas: None,
165    };
166
167    boot_span.finish();
168
169    impl<Message, F> winit::application::ApplicationHandler<Action<Message>>
170        for Runner<Message, F>
171    where
172        Message: std::fmt::Debug,
173        F: Future<Output = ()>,
174    {
175        fn resumed(
176            &mut self,
177            _event_loop: &winit::event_loop::ActiveEventLoop,
178        ) {
179        }
180
181        fn new_events(
182            &mut self,
183            event_loop: &winit::event_loop::ActiveEventLoop,
184            cause: winit::event::StartCause,
185        ) {
186            self.process_event(
187                event_loop,
188                Event::EventLoopAwakened(winit::event::Event::NewEvents(cause)),
189            );
190        }
191
192        fn window_event(
193            &mut self,
194            event_loop: &winit::event_loop::ActiveEventLoop,
195            window_id: winit::window::WindowId,
196            event: winit::event::WindowEvent,
197        ) {
198            #[cfg(target_os = "windows")]
199            let is_move_or_resize = matches!(
200                event,
201                winit::event::WindowEvent::Resized(_)
202                    | winit::event::WindowEvent::Moved(_)
203            );
204
205            self.process_event(
206                event_loop,
207                Event::EventLoopAwakened(winit::event::Event::WindowEvent {
208                    window_id,
209                    event,
210                }),
211            );
212
213            // TODO: Remove when unnecessary
214            // On Windows, we emulate an `AboutToWait` event after every `Resized` event
215            // since the event loop does not resume during resize interaction.
216            // More details: https://github.com/rust-windowing/winit/issues/3272
217            #[cfg(target_os = "windows")]
218            {
219                if is_move_or_resize {
220                    self.process_event(
221                        event_loop,
222                        Event::EventLoopAwakened(
223                            winit::event::Event::AboutToWait,
224                        ),
225                    );
226                }
227            }
228        }
229
230        fn user_event(
231            &mut self,
232            event_loop: &winit::event_loop::ActiveEventLoop,
233            action: Action<Message>,
234        ) {
235            self.process_event(
236                event_loop,
237                Event::EventLoopAwakened(winit::event::Event::UserEvent(
238                    action,
239                )),
240            );
241        }
242
243        fn received_url(
244            &mut self,
245            event_loop: &winit::event_loop::ActiveEventLoop,
246            url: String,
247        ) {
248            self.process_event(
249                event_loop,
250                Event::EventLoopAwakened(
251                    winit::event::Event::PlatformSpecific(
252                        winit::event::PlatformSpecific::MacOS(
253                            winit::event::MacOS::ReceivedUrl(url),
254                        ),
255                    ),
256                ),
257            );
258        }
259
260        fn about_to_wait(
261            &mut self,
262            event_loop: &winit::event_loop::ActiveEventLoop,
263        ) {
264            self.process_event(
265                event_loop,
266                Event::EventLoopAwakened(winit::event::Event::AboutToWait),
267            );
268        }
269    }
270
271    impl<Message, F> Runner<Message, F>
272    where
273        F: Future<Output = ()>,
274    {
275        fn process_event(
276            &mut self,
277            event_loop: &winit::event_loop::ActiveEventLoop,
278            event: Event<Action<Message>>,
279        ) {
280            if event_loop.exiting() {
281                return;
282            }
283
284            self.sender.start_send(event).expect("Send event");
285
286            loop {
287                let poll = self.instance.as_mut().poll(&mut self.context);
288
289                match poll {
290                    task::Poll::Pending => match self.receiver.try_next() {
291                        Ok(Some(control)) => match control {
292                            Control::ChangeFlow(flow) => {
293                                use winit::event_loop::ControlFlow;
294
295                                match (event_loop.control_flow(), flow) {
296                                    (
297                                        ControlFlow::WaitUntil(current),
298                                        ControlFlow::WaitUntil(new),
299                                    ) if current < new => {}
300                                    (
301                                        ControlFlow::WaitUntil(target),
302                                        ControlFlow::Wait,
303                                    ) if target > Instant::now() => {}
304                                    _ => {
305                                        event_loop.set_control_flow(flow);
306                                    }
307                                }
308                            }
309                            Control::CreateWindow {
310                                id,
311                                settings,
312                                title,
313                                monitor,
314                                on_open,
315                            } => {
316                                let exit_on_close_request =
317                                    settings.exit_on_close_request;
318
319                                let visible = settings.visible;
320
321                                #[cfg(target_arch = "wasm32")]
322                                let target =
323                                    settings.platform_specific.target.clone();
324
325                                let window_attributes =
326                                    conversion::window_attributes(
327                                        settings,
328                                        &title,
329                                        monitor
330                                            .or(event_loop.primary_monitor()),
331                                        self.id.clone(),
332                                    )
333                                    .with_visible(false);
334
335                                #[cfg(target_arch = "wasm32")]
336                                let window_attributes = {
337                                    use winit::platform::web::WindowAttributesExtWebSys;
338                                    window_attributes
339                                        .with_canvas(self.canvas.take())
340                                };
341
342                                log::info!(
343                                    "Window attributes for id `{id:#?}`: {window_attributes:#?}"
344                                );
345
346                                // On macOS, the `position` in `WindowAttributes` represents the "inner"
347                                // position of the window; while on other platforms it's the "outer" position.
348                                // We fix the inconsistency on macOS by positioning the window after creation.
349                                #[cfg(target_os = "macos")]
350                                let mut window_attributes = window_attributes;
351
352                                #[cfg(target_os = "macos")]
353                                let position =
354                                    window_attributes.position.take();
355
356                                let window = event_loop
357                                    .create_window(window_attributes)
358                                    .expect("Create window");
359
360                                #[cfg(target_os = "macos")]
361                                if let Some(position) = position {
362                                    window.set_outer_position(position);
363                                }
364
365                                #[cfg(target_arch = "wasm32")]
366                                {
367                                    use winit::platform::web::WindowExtWebSys;
368
369                                    let canvas = window
370                                        .canvas()
371                                        .expect("Get window canvas");
372
373                                    let _ = canvas.set_attribute(
374                                        "style",
375                                        "display: block; width: 100%; height: 100%",
376                                    );
377
378                                    let window = web_sys::window().unwrap();
379                                    let document = window.document().unwrap();
380                                    let body = document.body().unwrap();
381
382                                    let target = target.and_then(|target| {
383                                        body.query_selector(&format!(
384                                            "#{target}"
385                                        ))
386                                        .ok()
387                                        .unwrap_or(None)
388                                    });
389
390                                    match target {
391                                        Some(node) => {
392                                            let _ = node
393                                                .replace_with_with_node_1(
394                                                    &canvas,
395                                                )
396                                                .expect(&format!(
397                                                    "Could not replace #{}",
398                                                    node.id()
399                                                ));
400                                        }
401                                        None => {
402                                            let _ = body
403                                                .append_child(&canvas)
404                                                .expect(
405                                                "Append canvas to HTML body",
406                                            );
407                                        }
408                                    };
409                                }
410
411                                self.process_event(
412                                    event_loop,
413                                    Event::WindowCreated {
414                                        id,
415                                        window: Arc::new(window),
416                                        exit_on_close_request,
417                                        make_visible: visible,
418                                        on_open,
419                                    },
420                                );
421                            }
422                            Control::Exit => {
423                                event_loop.exit();
424                            }
425                            Control::Crash(error) => {
426                                self.error = Some(error);
427                                event_loop.exit();
428                            }
429                        },
430                        _ => {
431                            break;
432                        }
433                    },
434                    task::Poll::Ready(_) => {
435                        event_loop.exit();
436                        break;
437                    }
438                };
439            }
440        }
441    }
442
443    #[cfg(not(target_arch = "wasm32"))]
444    {
445        let mut runner = runner;
446        let _ = event_loop.run_app(&mut runner);
447
448        runner.error.map(Err).unwrap_or(Ok(()))
449    }
450
451    #[cfg(target_arch = "wasm32")]
452    {
453        use winit::platform::web::EventLoopExtWebSys;
454        let _ = event_loop.spawn_app(runner);
455
456        Ok(())
457    }
458}
459
460#[derive(Debug)]
461enum Event<Message: 'static> {
462    WindowCreated {
463        id: window::Id,
464        window: Arc<winit::window::Window>,
465        exit_on_close_request: bool,
466        make_visible: bool,
467        on_open: oneshot::Sender<window::Id>,
468    },
469    EventLoopAwakened(winit::event::Event<Message>),
470}
471
472#[derive(Debug)]
473enum Control {
474    ChangeFlow(winit::event_loop::ControlFlow),
475    Exit,
476    Crash(Error),
477    CreateWindow {
478        id: window::Id,
479        settings: window::Settings,
480        title: String,
481        monitor: Option<winit::monitor::MonitorHandle>,
482        on_open: oneshot::Sender<window::Id>,
483    },
484}
485
486async fn run_instance<P>(
487    mut program: program::Instance<P>,
488    mut runtime: Runtime<P::Executor, Proxy<P::Message>, Action<P::Message>>,
489    mut proxy: Proxy<P::Message>,
490    mut event_receiver: mpsc::UnboundedReceiver<Event<Action<P::Message>>>,
491    mut control_sender: mpsc::UnboundedSender<Control>,
492    is_daemon: bool,
493    graphics_settings: graphics::Settings,
494    default_fonts: Vec<Cow<'static, [u8]>>,
495) where
496    P: Program + 'static,
497    P::Theme: theme::Base,
498{
499    use winit::event;
500    use winit::event_loop::ControlFlow;
501
502    let mut window_manager = WindowManager::new();
503    let mut is_window_opening = !is_daemon;
504
505    let mut compositor = None;
506    let mut events = Vec::new();
507    let mut messages = Vec::new();
508    let mut actions = 0;
509
510    let mut ui_caches = FxHashMap::default();
511    let mut user_interfaces = ManuallyDrop::new(FxHashMap::default());
512    let mut clipboard = Clipboard::unconnected();
513
514    loop {
515        // Empty the queue if possible
516        let event = if let Ok(event) = event_receiver.try_next() {
517            event
518        } else {
519            event_receiver.next().await
520        };
521
522        let Some(event) = event else {
523            break;
524        };
525
526        match event {
527            Event::WindowCreated {
528                id,
529                window,
530                exit_on_close_request,
531                make_visible,
532                on_open,
533            } => {
534                if compositor.is_none() {
535                    let (compositor_sender, compositor_receiver) =
536                        oneshot::channel();
537
538                    let create_compositor = {
539                        let window = window.clone();
540                        let proxy = proxy.clone();
541                        let default_fonts = default_fonts.clone();
542
543                        async move {
544                            let mut compositor =
545                                <P::Renderer as compositor::Default>::Compositor::new(graphics_settings, window).await;
546
547                            if let Ok(compositor) = &mut compositor {
548                                for font in default_fonts {
549                                    compositor.load_font(font.clone());
550                                }
551                            }
552
553                            compositor_sender
554                                .send(compositor)
555                                .ok()
556                                .expect("Send compositor");
557
558                            // HACK! Send a proxy event on completion to trigger
559                            // a runtime re-poll
560                            // TODO: Send compositor through proxy (?)
561                            {
562                                let (sender, _receiver) = oneshot::channel();
563
564                                proxy.send_action(Action::Window(
565                                    runtime::window::Action::GetLatest(sender),
566                                ));
567                            }
568                        }
569                    };
570
571                    #[cfg(target_arch = "wasm32")]
572                    wasm_bindgen_futures::spawn_local(create_compositor);
573
574                    #[cfg(not(target_arch = "wasm32"))]
575                    runtime.block_on(create_compositor);
576
577                    match compositor_receiver
578                        .await
579                        .expect("Wait for compositor")
580                    {
581                        Ok(new_compositor) => {
582                            compositor = Some(new_compositor);
583                        }
584                        Err(error) => {
585                            let _ = control_sender
586                                .start_send(Control::Crash(error.into()));
587                            continue;
588                        }
589                    }
590                }
591
592                debug::theme_changed(|| {
593                    if window_manager.is_empty() {
594                        theme::Base::palette(&program.theme(id))
595                    } else {
596                        None
597                    }
598                });
599
600                let window = window_manager.insert(
601                    id,
602                    window,
603                    &program,
604                    compositor
605                        .as_mut()
606                        .expect("Compositor must be initialized"),
607                    exit_on_close_request,
608                );
609
610                let logical_size = window.state.logical_size();
611
612                let _ = user_interfaces.insert(
613                    id,
614                    build_user_interface(
615                        &program,
616                        user_interface::Cache::default(),
617                        &mut window.renderer,
618                        logical_size,
619                        id,
620                    ),
621                );
622                let _ = ui_caches.insert(id, user_interface::Cache::default());
623
624                if make_visible {
625                    window.raw.set_visible(true);
626                }
627
628                events.push((
629                    id,
630                    core::Event::Window(window::Event::Opened {
631                        position: window.position(),
632                        size: window.size(),
633                    }),
634                ));
635
636                if clipboard.window_id().is_none() {
637                    clipboard = Clipboard::connect(window.raw.clone());
638                }
639
640                let _ = on_open.send(id);
641                is_window_opening = false;
642            }
643            Event::EventLoopAwakened(event) => {
644                match event {
645                    event::Event::NewEvents(event::StartCause::Init) => {
646                        for (_id, window) in window_manager.iter_mut() {
647                            window.raw.request_redraw();
648                        }
649                    }
650                    event::Event::NewEvents(
651                        event::StartCause::ResumeTimeReached { .. },
652                    ) => {
653                        let now = Instant::now();
654
655                        for (_id, window) in window_manager.iter_mut() {
656                            if let Some(redraw_at) = window.redraw_at
657                                && redraw_at <= now
658                            {
659                                window.raw.request_redraw();
660                                window.redraw_at = None;
661                            }
662                        }
663
664                        if let Some(redraw_at) = window_manager.redraw_at() {
665                            let _ =
666                                control_sender.start_send(Control::ChangeFlow(
667                                    ControlFlow::WaitUntil(redraw_at),
668                                ));
669                        } else {
670                            let _ = control_sender.start_send(
671                                Control::ChangeFlow(ControlFlow::Wait),
672                            );
673                        }
674                    }
675                    event::Event::PlatformSpecific(
676                        event::PlatformSpecific::MacOS(
677                            event::MacOS::ReceivedUrl(url),
678                        ),
679                    ) => {
680                        runtime.broadcast(
681                            subscription::Event::PlatformSpecific(
682                                subscription::PlatformSpecific::MacOS(
683                                    subscription::MacOS::ReceivedUrl(url),
684                                ),
685                            ),
686                        );
687                    }
688                    event::Event::UserEvent(action) => {
689                        run_action(
690                            action,
691                            &program,
692                            &mut compositor,
693                            &mut events,
694                            &mut messages,
695                            &mut clipboard,
696                            &mut control_sender,
697                            &mut user_interfaces,
698                            &mut window_manager,
699                            &mut ui_caches,
700                            &mut is_window_opening,
701                        );
702                        actions += 1;
703                    }
704                    event::Event::WindowEvent {
705                        window_id: id,
706                        event: event::WindowEvent::RedrawRequested,
707                        ..
708                    } => {
709                        let Some(compositor) = &mut compositor else {
710                            continue;
711                        };
712
713                        let Some((id, window)) =
714                            window_manager.get_mut_alias(id)
715                        else {
716                            continue;
717                        };
718
719                        let physical_size = window.state.physical_size();
720
721                        if physical_size.width == 0 || physical_size.height == 0
722                        {
723                            continue;
724                        }
725
726                        if window.viewport_version
727                            != window.state.viewport_version()
728                        {
729                            let logical_size = window.state.logical_size();
730
731                            let layout_span = debug::layout(id);
732                            let ui = user_interfaces
733                                .remove(&id)
734                                .expect("Remove user interface");
735
736                            let _ = user_interfaces.insert(
737                                id,
738                                ui.relayout(logical_size, &mut window.renderer),
739                            );
740                            layout_span.finish();
741
742                            compositor.configure_surface(
743                                &mut window.surface,
744                                physical_size.width,
745                                physical_size.height,
746                            );
747
748                            window.viewport_version =
749                                window.state.viewport_version();
750                        }
751
752                        let redraw_event = core::Event::Window(
753                            window::Event::RedrawRequested(Instant::now()),
754                        );
755
756                        let cursor = window.state.cursor();
757
758                        let ui = user_interfaces
759                            .get_mut(&id)
760                            .expect("Get user interface");
761
762                        let draw_span = debug::draw(id);
763                        let (ui_state, _) = ui.update(
764                            slice::from_ref(&redraw_event),
765                            cursor,
766                            &mut window.renderer,
767                            &mut clipboard,
768                            &mut messages,
769                        );
770
771                        ui.draw(
772                            &mut window.renderer,
773                            window.state.theme(),
774                            &renderer::Style {
775                                text_color: window.state.text_color(),
776                            },
777                            cursor,
778                        );
779                        draw_span.finish();
780
781                        runtime.broadcast(subscription::Event::Interaction {
782                            window: id,
783                            event: redraw_event,
784                            status: core::event::Status::Ignored,
785                        });
786
787                        if let user_interface::State::Updated {
788                            redraw_request,
789                            input_method,
790                            mouse_interaction,
791                        } = ui_state
792                        {
793                            window.request_redraw(redraw_request);
794                            window.request_input_method(input_method);
795                            window.update_mouse(mouse_interaction);
796                        }
797
798                        window.draw_preedit();
799
800                        let present_span = debug::present(id);
801                        match compositor.present(
802                            &mut window.renderer,
803                            &mut window.surface,
804                            window.state.viewport(),
805                            window.state.background_color(),
806                            || window.raw.pre_present_notify(),
807                        ) {
808                            Ok(()) => {
809                                present_span.finish();
810                            }
811                            Err(error) => match error {
812                                // This is an unrecoverable error.
813                                compositor::SurfaceError::OutOfMemory => {
814                                    panic!("{error:?}");
815                                }
816                                _ => {
817                                    present_span.finish();
818
819                                    log::error!(
820                                        "Error {error:?} when \
821                                        presenting surface."
822                                    );
823
824                                    // Try rendering all windows again next frame.
825                                    for (_id, window) in
826                                        window_manager.iter_mut()
827                                    {
828                                        window.raw.request_redraw();
829                                    }
830                                }
831                            },
832                        }
833                    }
834                    event::Event::WindowEvent {
835                        event: window_event,
836                        window_id,
837                    } => {
838                        if !is_daemon
839                            && matches!(
840                                window_event,
841                                winit::event::WindowEvent::Destroyed
842                            )
843                            && !is_window_opening
844                            && window_manager.is_empty()
845                        {
846                            control_sender
847                                .start_send(Control::Exit)
848                                .expect("Send control action");
849
850                            continue;
851                        }
852
853                        let Some((id, window)) =
854                            window_manager.get_mut_alias(window_id)
855                        else {
856                            continue;
857                        };
858
859                        if matches!(
860                            window_event,
861                            winit::event::WindowEvent::Resized(_)
862                        ) {
863                            window.raw.request_redraw();
864                        }
865
866                        if matches!(
867                            window_event,
868                            winit::event::WindowEvent::CloseRequested
869                        ) && window.exit_on_close_request
870                        {
871                            run_action(
872                                Action::Window(runtime::window::Action::Close(
873                                    id,
874                                )),
875                                &program,
876                                &mut compositor,
877                                &mut events,
878                                &mut messages,
879                                &mut clipboard,
880                                &mut control_sender,
881                                &mut user_interfaces,
882                                &mut window_manager,
883                                &mut ui_caches,
884                                &mut is_window_opening,
885                            );
886                        } else {
887                            window.state.update(&window.raw, &window_event);
888
889                            if let Some(event) = conversion::window_event(
890                                window_event,
891                                window.state.scale_factor(),
892                                window.state.modifiers(),
893                            ) {
894                                events.push((id, event));
895                            }
896                        }
897                    }
898                    event::Event::AboutToWait => {
899                        if actions > 0 {
900                            proxy.free_slots(actions);
901                            actions = 0;
902                        }
903
904                        if events.is_empty()
905                            && messages.is_empty()
906                            && window_manager.is_idle()
907                        {
908                            continue;
909                        }
910
911                        let mut uis_stale = false;
912
913                        for (id, window) in window_manager.iter_mut() {
914                            let interact_span = debug::interact(id);
915                            let mut window_events = vec![];
916
917                            events.retain(|(window_id, event)| {
918                                if *window_id == id {
919                                    window_events.push(event.clone());
920                                    false
921                                } else {
922                                    true
923                                }
924                            });
925
926                            if window_events.is_empty() && messages.is_empty() {
927                                continue;
928                            }
929
930                            let (ui_state, statuses) = user_interfaces
931                                .get_mut(&id)
932                                .expect("Get user interface")
933                                .update(
934                                    &window_events,
935                                    window.state.cursor(),
936                                    &mut window.renderer,
937                                    &mut clipboard,
938                                    &mut messages,
939                                );
940
941                            #[cfg(feature = "unconditional-rendering")]
942                            window.request_redraw(
943                                window::RedrawRequest::NextFrame,
944                            );
945
946                            match ui_state {
947                                user_interface::State::Updated {
948                                    redraw_request: _redraw_request,
949                                    mouse_interaction,
950                                    ..
951                                } => {
952                                    window.update_mouse(mouse_interaction);
953
954                                    #[cfg(not(
955                                        feature = "unconditional-rendering"
956                                    ))]
957                                    window.request_redraw(_redraw_request);
958                                }
959                                user_interface::State::Outdated => {
960                                    uis_stale = true;
961                                }
962                            }
963
964                            for (event, status) in window_events
965                                .into_iter()
966                                .zip(statuses.into_iter())
967                            {
968                                runtime.broadcast(
969                                    subscription::Event::Interaction {
970                                        window: id,
971                                        event,
972                                        status,
973                                    },
974                                );
975                            }
976
977                            interact_span.finish();
978                        }
979
980                        for (id, event) in events.drain(..) {
981                            runtime.broadcast(
982                                subscription::Event::Interaction {
983                                    window: id,
984                                    event,
985                                    status: core::event::Status::Ignored,
986                                },
987                            );
988                        }
989
990                        if !messages.is_empty() || uis_stale {
991                            let cached_interfaces: FxHashMap<
992                                window::Id,
993                                user_interface::Cache,
994                            > = ManuallyDrop::into_inner(user_interfaces)
995                                .drain()
996                                .map(|(id, ui)| (id, ui.into_cache()))
997                                .collect();
998
999                            update(&mut program, &mut runtime, &mut messages);
1000
1001                            for (id, window) in window_manager.iter_mut() {
1002                                window.state.synchronize(
1003                                    &program,
1004                                    id,
1005                                    &window.raw,
1006                                );
1007
1008                                window.raw.request_redraw();
1009                            }
1010
1011                            debug::theme_changed(|| {
1012                                window_manager.first().and_then(|window| {
1013                                    theme::Base::palette(window.state.theme())
1014                                })
1015                            });
1016
1017                            user_interfaces =
1018                                ManuallyDrop::new(build_user_interfaces(
1019                                    &program,
1020                                    &mut window_manager,
1021                                    cached_interfaces,
1022                                ));
1023                        }
1024
1025                        if let Some(redraw_at) = window_manager.redraw_at() {
1026                            let _ =
1027                                control_sender.start_send(Control::ChangeFlow(
1028                                    ControlFlow::WaitUntil(redraw_at),
1029                                ));
1030                        } else {
1031                            let _ = control_sender.start_send(
1032                                Control::ChangeFlow(ControlFlow::Wait),
1033                            );
1034                        }
1035                    }
1036                    _ => {}
1037                }
1038            }
1039        }
1040    }
1041
1042    let _ = ManuallyDrop::into_inner(user_interfaces);
1043}
1044
1045/// Builds a window's [`UserInterface`] for the [`Program`].
1046fn build_user_interface<'a, P: Program>(
1047    program: &'a program::Instance<P>,
1048    cache: user_interface::Cache,
1049    renderer: &mut P::Renderer,
1050    size: Size,
1051    id: window::Id,
1052) -> UserInterface<'a, P::Message, P::Theme, P::Renderer>
1053where
1054    P::Theme: theme::Base,
1055{
1056    let view_span = debug::view(id);
1057    let view = program.view(id);
1058    view_span.finish();
1059
1060    let layout_span = debug::layout(id);
1061    let user_interface = UserInterface::build(view, size, cache, renderer);
1062    layout_span.finish();
1063
1064    user_interface
1065}
1066
1067fn update<P: Program, E: Executor>(
1068    program: &mut program::Instance<P>,
1069    runtime: &mut Runtime<E, Proxy<P::Message>, Action<P::Message>>,
1070    messages: &mut Vec<P::Message>,
1071) where
1072    P::Theme: theme::Base,
1073{
1074    for message in messages.drain(..) {
1075        let task = runtime.enter(|| program.update(message));
1076
1077        if let Some(stream) = runtime::task::into_stream(task) {
1078            runtime.run(stream);
1079        }
1080    }
1081
1082    let subscription = runtime.enter(|| program.subscription());
1083    let recipes = subscription::into_recipes(subscription.map(Action::Output));
1084
1085    runtime.track(recipes);
1086}
1087
1088fn run_action<'a, P, C>(
1089    action: Action<P::Message>,
1090    program: &'a program::Instance<P>,
1091    compositor: &mut Option<C>,
1092    events: &mut Vec<(window::Id, core::Event)>,
1093    messages: &mut Vec<P::Message>,
1094    clipboard: &mut Clipboard,
1095    control_sender: &mut mpsc::UnboundedSender<Control>,
1096    interfaces: &mut FxHashMap<
1097        window::Id,
1098        UserInterface<'a, P::Message, P::Theme, P::Renderer>,
1099    >,
1100    window_manager: &mut WindowManager<P, C>,
1101    ui_caches: &mut FxHashMap<window::Id, user_interface::Cache>,
1102    is_window_opening: &mut bool,
1103) where
1104    P: Program,
1105    C: Compositor<Renderer = P::Renderer> + 'static,
1106    P::Theme: theme::Base,
1107{
1108    use crate::runtime::clipboard;
1109    use crate::runtime::system;
1110    use crate::runtime::window;
1111
1112    match action {
1113        Action::Output(message) => {
1114            messages.push(message);
1115        }
1116        Action::Clipboard(action) => match action {
1117            clipboard::Action::Read { target, channel } => {
1118                let _ = channel.send(clipboard.read(target));
1119            }
1120            clipboard::Action::Write { target, contents } => {
1121                clipboard.write(target, contents);
1122            }
1123        },
1124        Action::Window(action) => match action {
1125            window::Action::Open(id, settings, channel) => {
1126                let monitor = window_manager.last_monitor();
1127
1128                control_sender
1129                    .start_send(Control::CreateWindow {
1130                        id,
1131                        settings,
1132                        title: program.title(id),
1133                        monitor,
1134                        on_open: channel,
1135                    })
1136                    .expect("Send control action");
1137
1138                *is_window_opening = true;
1139            }
1140            window::Action::Close(id) => {
1141                let _ = ui_caches.remove(&id);
1142                let _ = interfaces.remove(&id);
1143
1144                if let Some(window) = window_manager.remove(id) {
1145                    if clipboard.window_id() == Some(window.raw.id()) {
1146                        *clipboard = window_manager
1147                            .first()
1148                            .map(|window| window.raw.clone())
1149                            .map(Clipboard::connect)
1150                            .unwrap_or_else(Clipboard::unconnected);
1151                    }
1152
1153                    events.push((
1154                        id,
1155                        core::Event::Window(core::window::Event::Closed),
1156                    ));
1157                }
1158
1159                if window_manager.is_empty() {
1160                    *compositor = None;
1161                }
1162            }
1163            window::Action::GetOldest(channel) => {
1164                let id =
1165                    window_manager.iter_mut().next().map(|(id, _window)| id);
1166
1167                let _ = channel.send(id);
1168            }
1169            window::Action::GetLatest(channel) => {
1170                let id =
1171                    window_manager.iter_mut().last().map(|(id, _window)| id);
1172
1173                let _ = channel.send(id);
1174            }
1175            window::Action::Drag(id) => {
1176                if let Some(window) = window_manager.get_mut(id) {
1177                    let _ = window.raw.drag_window();
1178                }
1179            }
1180            window::Action::DragResize(id, direction) => {
1181                if let Some(window) = window_manager.get_mut(id) {
1182                    let _ = window.raw.drag_resize_window(
1183                        conversion::resize_direction(direction),
1184                    );
1185                }
1186            }
1187            window::Action::Resize(id, size) => {
1188                if let Some(window) = window_manager.get_mut(id) {
1189                    let _ = window.raw.request_inner_size(
1190                        winit::dpi::LogicalSize {
1191                            width: size.width,
1192                            height: size.height,
1193                        },
1194                    );
1195                }
1196            }
1197            window::Action::SetMinSize(id, size) => {
1198                if let Some(window) = window_manager.get_mut(id) {
1199                    window.raw.set_min_inner_size(size.map(|size| {
1200                        winit::dpi::LogicalSize {
1201                            width: size.width,
1202                            height: size.height,
1203                        }
1204                    }));
1205                }
1206            }
1207            window::Action::SetMaxSize(id, size) => {
1208                if let Some(window) = window_manager.get_mut(id) {
1209                    window.raw.set_max_inner_size(size.map(|size| {
1210                        winit::dpi::LogicalSize {
1211                            width: size.width,
1212                            height: size.height,
1213                        }
1214                    }));
1215                }
1216            }
1217            window::Action::SetResizeIncrements(id, increments) => {
1218                if let Some(window) = window_manager.get_mut(id) {
1219                    window.raw.set_resize_increments(increments.map(|size| {
1220                        winit::dpi::LogicalSize {
1221                            width: size.width,
1222                            height: size.height,
1223                        }
1224                    }));
1225                }
1226            }
1227            window::Action::SetResizable(id, resizable) => {
1228                if let Some(window) = window_manager.get_mut(id) {
1229                    window.raw.set_resizable(resizable);
1230                }
1231            }
1232            window::Action::GetSize(id, channel) => {
1233                if let Some(window) = window_manager.get_mut(id) {
1234                    let size = window
1235                        .raw
1236                        .inner_size()
1237                        .to_logical(window.raw.scale_factor());
1238
1239                    let _ = channel.send(Size::new(size.width, size.height));
1240                }
1241            }
1242            window::Action::GetMaximized(id, channel) => {
1243                if let Some(window) = window_manager.get_mut(id) {
1244                    let _ = channel.send(window.raw.is_maximized());
1245                }
1246            }
1247            window::Action::Maximize(id, maximized) => {
1248                if let Some(window) = window_manager.get_mut(id) {
1249                    window.raw.set_maximized(maximized);
1250                }
1251            }
1252            window::Action::GetMinimized(id, channel) => {
1253                if let Some(window) = window_manager.get_mut(id) {
1254                    let _ = channel.send(window.raw.is_minimized());
1255                }
1256            }
1257            window::Action::Minimize(id, minimized) => {
1258                if let Some(window) = window_manager.get_mut(id) {
1259                    window.raw.set_minimized(minimized);
1260                }
1261            }
1262            window::Action::GetPosition(id, channel) => {
1263                if let Some(window) = window_manager.get(id) {
1264                    let position = window
1265                        .raw
1266                        .outer_position()
1267                        .map(|position| {
1268                            let position = position
1269                                .to_logical::<f32>(window.raw.scale_factor());
1270
1271                            Point::new(position.x, position.y)
1272                        })
1273                        .ok();
1274
1275                    let _ = channel.send(position);
1276                }
1277            }
1278            window::Action::GetScaleFactor(id, channel) => {
1279                if let Some(window) = window_manager.get_mut(id) {
1280                    let scale_factor = window.raw.scale_factor();
1281
1282                    let _ = channel.send(scale_factor as f32);
1283                }
1284            }
1285            window::Action::Move(id, position) => {
1286                if let Some(window) = window_manager.get_mut(id) {
1287                    window.raw.set_outer_position(
1288                        winit::dpi::LogicalPosition {
1289                            x: position.x,
1290                            y: position.y,
1291                        },
1292                    );
1293                }
1294            }
1295            window::Action::SetMode(id, mode) => {
1296                if let Some(window) = window_manager.get_mut(id) {
1297                    window.raw.set_visible(conversion::visible(mode));
1298                    window.raw.set_fullscreen(conversion::fullscreen(
1299                        window.raw.current_monitor(),
1300                        mode,
1301                    ));
1302                }
1303            }
1304            window::Action::SetIcon(id, icon) => {
1305                if let Some(window) = window_manager.get_mut(id) {
1306                    window.raw.set_window_icon(conversion::icon(icon));
1307                }
1308            }
1309            window::Action::GetMode(id, channel) => {
1310                if let Some(window) = window_manager.get_mut(id) {
1311                    let mode = if window.raw.is_visible().unwrap_or(true) {
1312                        conversion::mode(window.raw.fullscreen())
1313                    } else {
1314                        core::window::Mode::Hidden
1315                    };
1316
1317                    let _ = channel.send(mode);
1318                }
1319            }
1320            window::Action::ToggleMaximize(id) => {
1321                if let Some(window) = window_manager.get_mut(id) {
1322                    window.raw.set_maximized(!window.raw.is_maximized());
1323                }
1324            }
1325            window::Action::ToggleDecorations(id) => {
1326                if let Some(window) = window_manager.get_mut(id) {
1327                    window.raw.set_decorations(!window.raw.is_decorated());
1328                }
1329            }
1330            window::Action::RequestUserAttention(id, attention_type) => {
1331                if let Some(window) = window_manager.get_mut(id) {
1332                    window.raw.request_user_attention(
1333                        attention_type.map(conversion::user_attention),
1334                    );
1335                }
1336            }
1337            window::Action::GainFocus(id) => {
1338                if let Some(window) = window_manager.get_mut(id) {
1339                    window.raw.focus_window();
1340                }
1341            }
1342            window::Action::SetLevel(id, level) => {
1343                if let Some(window) = window_manager.get_mut(id) {
1344                    window
1345                        .raw
1346                        .set_window_level(conversion::window_level(level));
1347                }
1348            }
1349            window::Action::ShowSystemMenu(id) => {
1350                if let Some(window) = window_manager.get_mut(id)
1351                    && let mouse::Cursor::Available(point) =
1352                        window.state.cursor()
1353                {
1354                    window.raw.show_window_menu(winit::dpi::LogicalPosition {
1355                        x: point.x,
1356                        y: point.y,
1357                    });
1358                }
1359            }
1360            window::Action::GetRawId(id, channel) => {
1361                if let Some(window) = window_manager.get_mut(id) {
1362                    let _ = channel.send(window.raw.id().into());
1363                }
1364            }
1365            window::Action::RunWithHandle(id, f) => {
1366                use window::raw_window_handle::HasWindowHandle;
1367
1368                if let Some(handle) = window_manager
1369                    .get_mut(id)
1370                    .and_then(|window| window.raw.window_handle().ok())
1371                {
1372                    f(handle);
1373                }
1374            }
1375            window::Action::Screenshot(id, channel) => {
1376                if let Some(window) = window_manager.get_mut(id)
1377                    && let Some(compositor) = compositor
1378                {
1379                    let bytes = compositor.screenshot(
1380                        &mut window.renderer,
1381                        window.state.viewport(),
1382                        window.state.background_color(),
1383                    );
1384
1385                    let _ = channel.send(core::window::Screenshot::new(
1386                        bytes,
1387                        window.state.physical_size(),
1388                        window.state.viewport().scale_factor(),
1389                    ));
1390                }
1391            }
1392            window::Action::EnableMousePassthrough(id) => {
1393                if let Some(window) = window_manager.get_mut(id) {
1394                    let _ = window.raw.set_cursor_hittest(false);
1395                }
1396            }
1397            window::Action::DisableMousePassthrough(id) => {
1398                if let Some(window) = window_manager.get_mut(id) {
1399                    let _ = window.raw.set_cursor_hittest(true);
1400                }
1401            }
1402        },
1403        Action::System(action) => match action {
1404            system::Action::QueryInformation(_channel) => {
1405                #[cfg(feature = "system")]
1406                {
1407                    if let Some(compositor) = compositor {
1408                        let graphics_info = compositor.fetch_information();
1409
1410                        let _ = std::thread::spawn(move || {
1411                            let information =
1412                                crate::system::information(graphics_info);
1413
1414                            let _ = _channel.send(information);
1415                        });
1416                    }
1417                }
1418            }
1419        },
1420        Action::Widget(operation) => {
1421            let mut current_operation = Some(operation);
1422
1423            while let Some(mut operation) = current_operation.take() {
1424                for (id, ui) in interfaces.iter_mut() {
1425                    if let Some(window) = window_manager.get_mut(*id) {
1426                        ui.operate(&window.renderer, operation.as_mut());
1427                    }
1428                }
1429
1430                match operation.finish() {
1431                    operation::Outcome::None => {}
1432                    operation::Outcome::Some(()) => {}
1433                    operation::Outcome::Chain(next) => {
1434                        current_operation = Some(next);
1435                    }
1436                }
1437            }
1438        }
1439        Action::LoadFont { bytes, channel } => {
1440            if let Some(compositor) = compositor {
1441                // TODO: Error handling (?)
1442                compositor.load_font(bytes.clone());
1443
1444                let _ = channel.send(Ok(()));
1445            }
1446        }
1447        Action::Reload => {
1448            for (id, window) in window_manager.iter_mut() {
1449                let Some(ui) = interfaces.remove(&id) else {
1450                    continue;
1451                };
1452
1453                let cache = ui.into_cache();
1454                let size = window.size();
1455
1456                let _ = interfaces.insert(
1457                    id,
1458                    build_user_interface(
1459                        program,
1460                        cache,
1461                        &mut window.renderer,
1462                        size,
1463                        id,
1464                    ),
1465                );
1466
1467                window.raw.request_redraw();
1468            }
1469        }
1470        Action::Exit => {
1471            control_sender
1472                .start_send(Control::Exit)
1473                .expect("Send control action");
1474        }
1475    }
1476}
1477
1478/// Build the user interface for every window.
1479pub fn build_user_interfaces<'a, P: Program, C>(
1480    program: &'a program::Instance<P>,
1481    window_manager: &mut WindowManager<P, C>,
1482    mut cached_user_interfaces: FxHashMap<window::Id, user_interface::Cache>,
1483) -> FxHashMap<window::Id, UserInterface<'a, P::Message, P::Theme, P::Renderer>>
1484where
1485    C: Compositor<Renderer = P::Renderer>,
1486    P::Theme: theme::Base,
1487{
1488    cached_user_interfaces
1489        .drain()
1490        .filter_map(|(id, cache)| {
1491            let window = window_manager.get_mut(id)?;
1492
1493            Some((
1494                id,
1495                build_user_interface(
1496                    program,
1497                    cache,
1498                    &mut window.renderer,
1499                    window.state.logical_size(),
1500                    id,
1501                ),
1502            ))
1503        })
1504        .collect()
1505}
1506
1507/// Returns true if the provided event should cause a [`Program`] to
1508/// exit.
1509pub fn user_force_quit(
1510    event: &winit::event::WindowEvent,
1511    _modifiers: winit::keyboard::ModifiersState,
1512) -> bool {
1513    match event {
1514        #[cfg(target_os = "macos")]
1515        winit::event::WindowEvent::KeyboardInput {
1516            event:
1517                winit::event::KeyEvent {
1518                    logical_key: winit::keyboard::Key::Character(c),
1519                    state: winit::event::ElementState::Pressed,
1520                    ..
1521                },
1522            ..
1523        } if c == "q" && _modifiers.super_key() => true,
1524        _ => false,
1525    }
1526}