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                                scale_factor,
314                                monitor,
315                                on_open,
316                            } => {
317                                let exit_on_close_request =
318                                    settings.exit_on_close_request;
319
320                                let visible = settings.visible;
321
322                                #[cfg(target_arch = "wasm32")]
323                                let target =
324                                    settings.platform_specific.target.clone();
325
326                                let window_attributes =
327                                    conversion::window_attributes(
328                                        settings,
329                                        &title,
330                                        scale_factor,
331                                        monitor
332                                            .or(event_loop.primary_monitor()),
333                                        self.id.clone(),
334                                    )
335                                    .with_visible(false);
336
337                                #[cfg(target_arch = "wasm32")]
338                                let window_attributes = {
339                                    use winit::platform::web::WindowAttributesExtWebSys;
340                                    window_attributes
341                                        .with_canvas(self.canvas.take())
342                                };
343
344                                log::info!(
345                                    "Window attributes for id `{id:#?}`: {window_attributes:#?}"
346                                );
347
348                                // On macOS, the `position` in `WindowAttributes` represents the "inner"
349                                // position of the window; while on other platforms it's the "outer" position.
350                                // We fix the inconsistency on macOS by positioning the window after creation.
351                                #[cfg(target_os = "macos")]
352                                let mut window_attributes = window_attributes;
353
354                                #[cfg(target_os = "macos")]
355                                let position =
356                                    window_attributes.position.take();
357
358                                let window = event_loop
359                                    .create_window(window_attributes)
360                                    .expect("Create window");
361
362                                #[cfg(target_os = "macos")]
363                                if let Some(position) = position {
364                                    window.set_outer_position(position);
365                                }
366
367                                #[cfg(target_arch = "wasm32")]
368                                {
369                                    use winit::platform::web::WindowExtWebSys;
370
371                                    let canvas = window
372                                        .canvas()
373                                        .expect("Get window canvas");
374
375                                    let _ = canvas.set_attribute(
376                                        "style",
377                                        "display: block; width: 100%; height: 100%",
378                                    );
379
380                                    let window = web_sys::window().unwrap();
381                                    let document = window.document().unwrap();
382                                    let body = document.body().unwrap();
383
384                                    let target = target.and_then(|target| {
385                                        body.query_selector(&format!(
386                                            "#{target}"
387                                        ))
388                                        .ok()
389                                        .unwrap_or(None)
390                                    });
391
392                                    match target {
393                                        Some(node) => {
394                                            let _ = node
395                                                .replace_with_with_node_1(
396                                                    &canvas,
397                                                )
398                                                .expect(&format!(
399                                                    "Could not replace #{}",
400                                                    node.id()
401                                                ));
402                                        }
403                                        None => {
404                                            let _ = body
405                                                .append_child(&canvas)
406                                                .expect(
407                                                "Append canvas to HTML body",
408                                            );
409                                        }
410                                    };
411                                }
412
413                                self.process_event(
414                                    event_loop,
415                                    Event::WindowCreated {
416                                        id,
417                                        window: Arc::new(window),
418                                        exit_on_close_request,
419                                        make_visible: visible,
420                                        on_open,
421                                    },
422                                );
423                            }
424                            Control::Exit => {
425                                event_loop.exit();
426                            }
427                            Control::Crash(error) => {
428                                self.error = Some(error);
429                                event_loop.exit();
430                            }
431                        },
432                        _ => {
433                            break;
434                        }
435                    },
436                    task::Poll::Ready(_) => {
437                        event_loop.exit();
438                        break;
439                    }
440                };
441            }
442        }
443    }
444
445    #[cfg(not(target_arch = "wasm32"))]
446    {
447        let mut runner = runner;
448        let _ = event_loop.run_app(&mut runner);
449
450        runner.error.map(Err).unwrap_or(Ok(()))
451    }
452
453    #[cfg(target_arch = "wasm32")]
454    {
455        use winit::platform::web::EventLoopExtWebSys;
456        let _ = event_loop.spawn_app(runner);
457
458        Ok(())
459    }
460}
461
462#[derive(Debug)]
463enum Event<Message: 'static> {
464    WindowCreated {
465        id: window::Id,
466        window: Arc<winit::window::Window>,
467        exit_on_close_request: bool,
468        make_visible: bool,
469        on_open: oneshot::Sender<window::Id>,
470    },
471    EventLoopAwakened(winit::event::Event<Message>),
472}
473
474#[derive(Debug)]
475enum Control {
476    ChangeFlow(winit::event_loop::ControlFlow),
477    Exit,
478    Crash(Error),
479    CreateWindow {
480        id: window::Id,
481        settings: window::Settings,
482        title: String,
483        monitor: Option<winit::monitor::MonitorHandle>,
484        on_open: oneshot::Sender<window::Id>,
485        scale_factor: f32,
486    },
487}
488
489async fn run_instance<P>(
490    mut program: program::Instance<P>,
491    mut runtime: Runtime<P::Executor, Proxy<P::Message>, Action<P::Message>>,
492    mut proxy: Proxy<P::Message>,
493    mut event_receiver: mpsc::UnboundedReceiver<Event<Action<P::Message>>>,
494    mut control_sender: mpsc::UnboundedSender<Control>,
495    is_daemon: bool,
496    graphics_settings: graphics::Settings,
497    default_fonts: Vec<Cow<'static, [u8]>>,
498) where
499    P: Program + 'static,
500    P::Theme: theme::Base,
501{
502    use winit::event;
503    use winit::event_loop::ControlFlow;
504
505    let mut window_manager = WindowManager::new();
506    let mut is_window_opening = !is_daemon;
507
508    let mut compositor = None;
509    let mut events = Vec::new();
510    let mut messages = Vec::new();
511    let mut actions = 0;
512
513    let mut ui_caches = FxHashMap::default();
514    let mut user_interfaces = ManuallyDrop::new(FxHashMap::default());
515    let mut clipboard = Clipboard::unconnected();
516
517    loop {
518        // Empty the queue if possible
519        let event = if let Ok(event) = event_receiver.try_next() {
520            event
521        } else {
522            event_receiver.next().await
523        };
524
525        let Some(event) = event else {
526            break;
527        };
528
529        match event {
530            Event::WindowCreated {
531                id,
532                window,
533                exit_on_close_request,
534                make_visible,
535                on_open,
536            } => {
537                if compositor.is_none() {
538                    let (compositor_sender, compositor_receiver) =
539                        oneshot::channel();
540
541                    let create_compositor = {
542                        let window = window.clone();
543                        let proxy = proxy.clone();
544                        let default_fonts = default_fonts.clone();
545
546                        async move {
547                            let mut compositor =
548                                <P::Renderer as compositor::Default>::Compositor::new(graphics_settings, window).await;
549
550                            if let Ok(compositor) = &mut compositor {
551                                for font in default_fonts {
552                                    compositor.load_font(font.clone());
553                                }
554                            }
555
556                            compositor_sender
557                                .send(compositor)
558                                .ok()
559                                .expect("Send compositor");
560
561                            // HACK! Send a proxy event on completion to trigger
562                            // a runtime re-poll
563                            // TODO: Send compositor through proxy (?)
564                            {
565                                let (sender, _receiver) = oneshot::channel();
566
567                                proxy.send_action(Action::Window(
568                                    runtime::window::Action::GetLatest(sender),
569                                ));
570                            }
571                        }
572                    };
573
574                    #[cfg(target_arch = "wasm32")]
575                    wasm_bindgen_futures::spawn_local(create_compositor);
576
577                    #[cfg(not(target_arch = "wasm32"))]
578                    runtime.block_on(create_compositor);
579
580                    match compositor_receiver
581                        .await
582                        .expect("Wait for compositor")
583                    {
584                        Ok(new_compositor) => {
585                            compositor = Some(new_compositor);
586                        }
587                        Err(error) => {
588                            let _ = control_sender
589                                .start_send(Control::Crash(error.into()));
590                            continue;
591                        }
592                    }
593                }
594
595                debug::theme_changed(|| {
596                    if window_manager.is_empty() {
597                        theme::Base::palette(&program.theme(id))
598                    } else {
599                        None
600                    }
601                });
602
603                let window = window_manager.insert(
604                    id,
605                    window,
606                    &program,
607                    compositor
608                        .as_mut()
609                        .expect("Compositor must be initialized"),
610                    exit_on_close_request,
611                );
612
613                let logical_size = window.state.logical_size();
614
615                let _ = user_interfaces.insert(
616                    id,
617                    build_user_interface(
618                        &program,
619                        user_interface::Cache::default(),
620                        &mut window.renderer,
621                        logical_size,
622                        id,
623                    ),
624                );
625                let _ = ui_caches.insert(id, user_interface::Cache::default());
626
627                if make_visible {
628                    window.raw.set_visible(true);
629                }
630
631                events.push((
632                    id,
633                    core::Event::Window(window::Event::Opened {
634                        position: window.position(),
635                        size: window.size(),
636                    }),
637                ));
638
639                if clipboard.window_id().is_none() {
640                    clipboard = Clipboard::connect(window.raw.clone());
641                }
642
643                let _ = on_open.send(id);
644                is_window_opening = false;
645            }
646            Event::EventLoopAwakened(event) => {
647                match event {
648                    event::Event::NewEvents(event::StartCause::Init) => {
649                        for (_id, window) in window_manager.iter_mut() {
650                            window.raw.request_redraw();
651                        }
652                    }
653                    event::Event::NewEvents(
654                        event::StartCause::ResumeTimeReached { .. },
655                    ) => {
656                        let now = Instant::now();
657
658                        for (_id, window) in window_manager.iter_mut() {
659                            if let Some(redraw_at) = window.redraw_at
660                                && redraw_at <= now
661                            {
662                                window.raw.request_redraw();
663                                window.redraw_at = None;
664                            }
665                        }
666
667                        if let Some(redraw_at) = window_manager.redraw_at() {
668                            let _ =
669                                control_sender.start_send(Control::ChangeFlow(
670                                    ControlFlow::WaitUntil(redraw_at),
671                                ));
672                        } else {
673                            let _ = control_sender.start_send(
674                                Control::ChangeFlow(ControlFlow::Wait),
675                            );
676                        }
677                    }
678                    event::Event::PlatformSpecific(
679                        event::PlatformSpecific::MacOS(
680                            event::MacOS::ReceivedUrl(url),
681                        ),
682                    ) => {
683                        runtime.broadcast(
684                            subscription::Event::PlatformSpecific(
685                                subscription::PlatformSpecific::MacOS(
686                                    subscription::MacOS::ReceivedUrl(url),
687                                ),
688                            ),
689                        );
690                    }
691                    event::Event::UserEvent(action) => {
692                        run_action(
693                            action,
694                            &program,
695                            &mut compositor,
696                            &mut events,
697                            &mut messages,
698                            &mut clipboard,
699                            &mut control_sender,
700                            &mut user_interfaces,
701                            &mut window_manager,
702                            &mut ui_caches,
703                            &mut is_window_opening,
704                        );
705                        actions += 1;
706                    }
707                    event::Event::WindowEvent {
708                        window_id: id,
709                        event: event::WindowEvent::RedrawRequested,
710                        ..
711                    } => {
712                        let Some(compositor) = &mut compositor else {
713                            continue;
714                        };
715
716                        let Some((id, window)) =
717                            window_manager.get_mut_alias(id)
718                        else {
719                            continue;
720                        };
721
722                        let physical_size = window.state.physical_size();
723
724                        if physical_size.width == 0 || physical_size.height == 0
725                        {
726                            continue;
727                        }
728
729                        if window.viewport_version
730                            != window.state.viewport_version()
731                        {
732                            let logical_size = window.state.logical_size();
733
734                            let layout_span = debug::layout(id);
735                            let ui = user_interfaces
736                                .remove(&id)
737                                .expect("Remove user interface");
738
739                            let _ = user_interfaces.insert(
740                                id,
741                                ui.relayout(logical_size, &mut window.renderer),
742                            );
743                            layout_span.finish();
744
745                            compositor.configure_surface(
746                                &mut window.surface,
747                                physical_size.width,
748                                physical_size.height,
749                            );
750
751                            window.viewport_version =
752                                window.state.viewport_version();
753                        }
754
755                        let redraw_event = core::Event::Window(
756                            window::Event::RedrawRequested(Instant::now()),
757                        );
758
759                        let cursor = window.state.cursor();
760
761                        let ui = user_interfaces
762                            .get_mut(&id)
763                            .expect("Get user interface");
764
765                        let draw_span = debug::draw(id);
766                        let (ui_state, _) = ui.update(
767                            slice::from_ref(&redraw_event),
768                            cursor,
769                            &mut window.renderer,
770                            &mut clipboard,
771                            &mut messages,
772                        );
773
774                        ui.draw(
775                            &mut window.renderer,
776                            window.state.theme(),
777                            &renderer::Style {
778                                text_color: window.state.text_color(),
779                            },
780                            cursor,
781                        );
782                        draw_span.finish();
783
784                        runtime.broadcast(subscription::Event::Interaction {
785                            window: id,
786                            event: redraw_event,
787                            status: core::event::Status::Ignored,
788                        });
789
790                        if let user_interface::State::Updated {
791                            redraw_request,
792                            input_method,
793                            mouse_interaction,
794                        } = ui_state
795                        {
796                            window.request_redraw(redraw_request);
797                            window.request_input_method(input_method);
798                            window.update_mouse(mouse_interaction);
799                        }
800
801                        window.draw_preedit();
802
803                        let present_span = debug::present(id);
804                        match compositor.present(
805                            &mut window.renderer,
806                            &mut window.surface,
807                            window.state.viewport(),
808                            window.state.background_color(),
809                            || window.raw.pre_present_notify(),
810                        ) {
811                            Ok(()) => {
812                                present_span.finish();
813                            }
814                            Err(error) => match error {
815                                // This is an unrecoverable error.
816                                compositor::SurfaceError::OutOfMemory => {
817                                    panic!("{error:?}");
818                                }
819                                _ => {
820                                    present_span.finish();
821
822                                    log::error!(
823                                        "Error {error:?} when \
824                                        presenting surface."
825                                    );
826
827                                    // Try rendering all windows again next frame.
828                                    for (_id, window) in
829                                        window_manager.iter_mut()
830                                    {
831                                        window.raw.request_redraw();
832                                    }
833                                }
834                            },
835                        }
836                    }
837                    event::Event::WindowEvent {
838                        event: window_event,
839                        window_id,
840                    } => {
841                        if !is_daemon
842                            && matches!(
843                                window_event,
844                                winit::event::WindowEvent::Destroyed
845                            )
846                            && !is_window_opening
847                            && window_manager.is_empty()
848                        {
849                            control_sender
850                                .start_send(Control::Exit)
851                                .expect("Send control action");
852
853                            continue;
854                        }
855
856                        let Some((id, window)) =
857                            window_manager.get_mut_alias(window_id)
858                        else {
859                            continue;
860                        };
861
862                        if matches!(
863                            window_event,
864                            winit::event::WindowEvent::Resized(_)
865                        ) {
866                            window.raw.request_redraw();
867                        }
868
869                        if matches!(
870                            window_event,
871                            winit::event::WindowEvent::CloseRequested
872                        ) && window.exit_on_close_request
873                        {
874                            run_action(
875                                Action::Window(runtime::window::Action::Close(
876                                    id,
877                                )),
878                                &program,
879                                &mut compositor,
880                                &mut events,
881                                &mut messages,
882                                &mut clipboard,
883                                &mut control_sender,
884                                &mut user_interfaces,
885                                &mut window_manager,
886                                &mut ui_caches,
887                                &mut is_window_opening,
888                            );
889                        } else {
890                            window.state.update(&window.raw, &window_event);
891
892                            if let Some(event) = conversion::window_event(
893                                window_event,
894                                window.state.scale_factor(),
895                                window.state.modifiers(),
896                            ) {
897                                events.push((id, event));
898                            }
899                        }
900                    }
901                    event::Event::AboutToWait => {
902                        if actions > 0 {
903                            proxy.free_slots(actions);
904                            actions = 0;
905                        }
906
907                        if events.is_empty()
908                            && messages.is_empty()
909                            && window_manager.is_idle()
910                        {
911                            continue;
912                        }
913
914                        let mut uis_stale = false;
915
916                        for (id, window) in window_manager.iter_mut() {
917                            let interact_span = debug::interact(id);
918                            let mut window_events = vec![];
919
920                            events.retain(|(window_id, event)| {
921                                if *window_id == id {
922                                    window_events.push(event.clone());
923                                    false
924                                } else {
925                                    true
926                                }
927                            });
928
929                            if window_events.is_empty() && messages.is_empty() {
930                                continue;
931                            }
932
933                            let (ui_state, statuses) = user_interfaces
934                                .get_mut(&id)
935                                .expect("Get user interface")
936                                .update(
937                                    &window_events,
938                                    window.state.cursor(),
939                                    &mut window.renderer,
940                                    &mut clipboard,
941                                    &mut messages,
942                                );
943
944                            #[cfg(feature = "unconditional-rendering")]
945                            window.request_redraw(
946                                window::RedrawRequest::NextFrame,
947                            );
948
949                            match ui_state {
950                                user_interface::State::Updated {
951                                    redraw_request: _redraw_request,
952                                    mouse_interaction,
953                                    ..
954                                } => {
955                                    window.update_mouse(mouse_interaction);
956
957                                    #[cfg(not(
958                                        feature = "unconditional-rendering"
959                                    ))]
960                                    window.request_redraw(_redraw_request);
961                                }
962                                user_interface::State::Outdated => {
963                                    uis_stale = true;
964                                }
965                            }
966
967                            for (event, status) in window_events
968                                .into_iter()
969                                .zip(statuses.into_iter())
970                            {
971                                runtime.broadcast(
972                                    subscription::Event::Interaction {
973                                        window: id,
974                                        event,
975                                        status,
976                                    },
977                                );
978                            }
979
980                            interact_span.finish();
981                        }
982
983                        for (id, event) in events.drain(..) {
984                            runtime.broadcast(
985                                subscription::Event::Interaction {
986                                    window: id,
987                                    event,
988                                    status: core::event::Status::Ignored,
989                                },
990                            );
991                        }
992
993                        if !messages.is_empty() || uis_stale {
994                            let cached_interfaces: FxHashMap<
995                                window::Id,
996                                user_interface::Cache,
997                            > = ManuallyDrop::into_inner(user_interfaces)
998                                .drain()
999                                .map(|(id, ui)| (id, ui.into_cache()))
1000                                .collect();
1001
1002                            update(&mut program, &mut runtime, &mut messages);
1003
1004                            for (id, window) in window_manager.iter_mut() {
1005                                window.state.synchronize(
1006                                    &program,
1007                                    id,
1008                                    &window.raw,
1009                                );
1010
1011                                window.raw.request_redraw();
1012                            }
1013
1014                            debug::theme_changed(|| {
1015                                window_manager.first().and_then(|window| {
1016                                    theme::Base::palette(window.state.theme())
1017                                })
1018                            });
1019
1020                            user_interfaces =
1021                                ManuallyDrop::new(build_user_interfaces(
1022                                    &program,
1023                                    &mut window_manager,
1024                                    cached_interfaces,
1025                                ));
1026                        }
1027
1028                        if let Some(redraw_at) = window_manager.redraw_at() {
1029                            let _ =
1030                                control_sender.start_send(Control::ChangeFlow(
1031                                    ControlFlow::WaitUntil(redraw_at),
1032                                ));
1033                        } else {
1034                            let _ = control_sender.start_send(
1035                                Control::ChangeFlow(ControlFlow::Wait),
1036                            );
1037                        }
1038                    }
1039                    _ => {}
1040                }
1041            }
1042        }
1043    }
1044
1045    let _ = ManuallyDrop::into_inner(user_interfaces);
1046}
1047
1048/// Builds a window's [`UserInterface`] for the [`Program`].
1049fn build_user_interface<'a, P: Program>(
1050    program: &'a program::Instance<P>,
1051    cache: user_interface::Cache,
1052    renderer: &mut P::Renderer,
1053    size: Size,
1054    id: window::Id,
1055) -> UserInterface<'a, P::Message, P::Theme, P::Renderer>
1056where
1057    P::Theme: theme::Base,
1058{
1059    let view_span = debug::view(id);
1060    let view = program.view(id);
1061    view_span.finish();
1062
1063    let layout_span = debug::layout(id);
1064    let user_interface = UserInterface::build(view, size, cache, renderer);
1065    layout_span.finish();
1066
1067    user_interface
1068}
1069
1070fn update<P: Program, E: Executor>(
1071    program: &mut program::Instance<P>,
1072    runtime: &mut Runtime<E, Proxy<P::Message>, Action<P::Message>>,
1073    messages: &mut Vec<P::Message>,
1074) where
1075    P::Theme: theme::Base,
1076{
1077    for message in messages.drain(..) {
1078        let task = runtime.enter(|| program.update(message));
1079
1080        if let Some(stream) = runtime::task::into_stream(task) {
1081            runtime.run(stream);
1082        }
1083    }
1084
1085    let subscription = runtime.enter(|| program.subscription());
1086    let recipes = subscription::into_recipes(subscription.map(Action::Output));
1087
1088    runtime.track(recipes);
1089}
1090
1091fn run_action<'a, P, C>(
1092    action: Action<P::Message>,
1093    program: &'a program::Instance<P>,
1094    compositor: &mut Option<C>,
1095    events: &mut Vec<(window::Id, core::Event)>,
1096    messages: &mut Vec<P::Message>,
1097    clipboard: &mut Clipboard,
1098    control_sender: &mut mpsc::UnboundedSender<Control>,
1099    interfaces: &mut FxHashMap<
1100        window::Id,
1101        UserInterface<'a, P::Message, P::Theme, P::Renderer>,
1102    >,
1103    window_manager: &mut WindowManager<P, C>,
1104    ui_caches: &mut FxHashMap<window::Id, user_interface::Cache>,
1105    is_window_opening: &mut bool,
1106) where
1107    P: Program,
1108    C: Compositor<Renderer = P::Renderer> + 'static,
1109    P::Theme: theme::Base,
1110{
1111    use crate::runtime::clipboard;
1112    use crate::runtime::system;
1113    use crate::runtime::window;
1114
1115    match action {
1116        Action::Output(message) => {
1117            messages.push(message);
1118        }
1119        Action::Clipboard(action) => match action {
1120            clipboard::Action::Read { target, channel } => {
1121                let _ = channel.send(clipboard.read(target));
1122            }
1123            clipboard::Action::Write { target, contents } => {
1124                clipboard.write(target, contents);
1125            }
1126        },
1127        Action::Window(action) => match action {
1128            window::Action::Open(id, settings, channel) => {
1129                let monitor = window_manager.last_monitor();
1130
1131                control_sender
1132                    .start_send(Control::CreateWindow {
1133                        id,
1134                        settings,
1135                        title: program.title(id),
1136                        scale_factor: program.scale_factor(id),
1137                        monitor,
1138                        on_open: channel,
1139                    })
1140                    .expect("Send control action");
1141
1142                *is_window_opening = true;
1143            }
1144            window::Action::Close(id) => {
1145                let _ = ui_caches.remove(&id);
1146                let _ = interfaces.remove(&id);
1147
1148                if let Some(window) = window_manager.remove(id) {
1149                    if clipboard.window_id() == Some(window.raw.id()) {
1150                        *clipboard = window_manager
1151                            .first()
1152                            .map(|window| window.raw.clone())
1153                            .map(Clipboard::connect)
1154                            .unwrap_or_else(Clipboard::unconnected);
1155                    }
1156
1157                    events.push((
1158                        id,
1159                        core::Event::Window(core::window::Event::Closed),
1160                    ));
1161                }
1162
1163                if window_manager.is_empty() {
1164                    *compositor = None;
1165                }
1166            }
1167            window::Action::GetOldest(channel) => {
1168                let id =
1169                    window_manager.iter_mut().next().map(|(id, _window)| id);
1170
1171                let _ = channel.send(id);
1172            }
1173            window::Action::GetLatest(channel) => {
1174                let id =
1175                    window_manager.iter_mut().last().map(|(id, _window)| id);
1176
1177                let _ = channel.send(id);
1178            }
1179            window::Action::Drag(id) => {
1180                if let Some(window) = window_manager.get_mut(id) {
1181                    let _ = window.raw.drag_window();
1182                }
1183            }
1184            window::Action::DragResize(id, direction) => {
1185                if let Some(window) = window_manager.get_mut(id) {
1186                    let _ = window.raw.drag_resize_window(
1187                        conversion::resize_direction(direction),
1188                    );
1189                }
1190            }
1191            window::Action::Resize(id, size) => {
1192                if let Some(window) = window_manager.get_mut(id) {
1193                    let _ = window.raw.request_inner_size(
1194                        winit::dpi::LogicalSize {
1195                            width: size.width,
1196                            height: size.height,
1197                        }
1198                        .to_physical::<f32>(f64::from(
1199                            window.state.scale_factor(),
1200                        )),
1201                    );
1202                }
1203            }
1204            window::Action::SetMinSize(id, size) => {
1205                if let Some(window) = window_manager.get_mut(id) {
1206                    window.raw.set_min_inner_size(size.map(|size| {
1207                        winit::dpi::LogicalSize {
1208                            width: size.width,
1209                            height: size.height,
1210                        }
1211                        .to_physical::<f32>(f64::from(
1212                            window.state.scale_factor(),
1213                        ))
1214                    }));
1215                }
1216            }
1217            window::Action::SetMaxSize(id, size) => {
1218                if let Some(window) = window_manager.get_mut(id) {
1219                    window.raw.set_max_inner_size(size.map(|size| {
1220                        winit::dpi::LogicalSize {
1221                            width: size.width,
1222                            height: size.height,
1223                        }
1224                        .to_physical::<f32>(f64::from(
1225                            window.state.scale_factor(),
1226                        ))
1227                    }));
1228                }
1229            }
1230            window::Action::SetResizeIncrements(id, increments) => {
1231                if let Some(window) = window_manager.get_mut(id) {
1232                    window.raw.set_resize_increments(increments.map(|size| {
1233                        winit::dpi::LogicalSize {
1234                            width: size.width,
1235                            height: size.height,
1236                        }
1237                        .to_physical::<f32>(f64::from(
1238                            window.state.scale_factor(),
1239                        ))
1240                    }));
1241                }
1242            }
1243            window::Action::SetResizable(id, resizable) => {
1244                if let Some(window) = window_manager.get_mut(id) {
1245                    window.raw.set_resizable(resizable);
1246                }
1247            }
1248            window::Action::GetSize(id, channel) => {
1249                if let Some(window) = window_manager.get_mut(id) {
1250                    let size = window
1251                        .raw
1252                        .inner_size()
1253                        .to_logical(f64::from(window.state.scale_factor()));
1254
1255                    let _ = channel.send(Size::new(size.width, size.height));
1256                }
1257            }
1258            window::Action::GetMaximized(id, channel) => {
1259                if let Some(window) = window_manager.get_mut(id) {
1260                    let _ = channel.send(window.raw.is_maximized());
1261                }
1262            }
1263            window::Action::Maximize(id, maximized) => {
1264                if let Some(window) = window_manager.get_mut(id) {
1265                    window.raw.set_maximized(maximized);
1266                }
1267            }
1268            window::Action::GetMinimized(id, channel) => {
1269                if let Some(window) = window_manager.get_mut(id) {
1270                    let _ = channel.send(window.raw.is_minimized());
1271                }
1272            }
1273            window::Action::Minimize(id, minimized) => {
1274                if let Some(window) = window_manager.get_mut(id) {
1275                    window.raw.set_minimized(minimized);
1276                }
1277            }
1278            window::Action::GetPosition(id, channel) => {
1279                if let Some(window) = window_manager.get(id) {
1280                    let position = window
1281                        .raw
1282                        .outer_position()
1283                        .map(|position| {
1284                            let position = position
1285                                .to_logical::<f32>(window.raw.scale_factor());
1286
1287                            Point::new(position.x, position.y)
1288                        })
1289                        .ok();
1290
1291                    let _ = channel.send(position);
1292                }
1293            }
1294            window::Action::GetScaleFactor(id, channel) => {
1295                if let Some(window) = window_manager.get_mut(id) {
1296                    let scale_factor = window.raw.scale_factor();
1297
1298                    let _ = channel.send(scale_factor as f32);
1299                }
1300            }
1301            window::Action::Move(id, position) => {
1302                if let Some(window) = window_manager.get_mut(id) {
1303                    window.raw.set_outer_position(
1304                        winit::dpi::LogicalPosition {
1305                            x: position.x,
1306                            y: position.y,
1307                        },
1308                    );
1309                }
1310            }
1311            window::Action::SetMode(id, mode) => {
1312                if let Some(window) = window_manager.get_mut(id) {
1313                    window.raw.set_visible(conversion::visible(mode));
1314                    window.raw.set_fullscreen(conversion::fullscreen(
1315                        window.raw.current_monitor(),
1316                        mode,
1317                    ));
1318                }
1319            }
1320            window::Action::SetIcon(id, icon) => {
1321                if let Some(window) = window_manager.get_mut(id) {
1322                    window.raw.set_window_icon(conversion::icon(icon));
1323                }
1324            }
1325            window::Action::GetMode(id, channel) => {
1326                if let Some(window) = window_manager.get_mut(id) {
1327                    let mode = if window.raw.is_visible().unwrap_or(true) {
1328                        conversion::mode(window.raw.fullscreen())
1329                    } else {
1330                        core::window::Mode::Hidden
1331                    };
1332
1333                    let _ = channel.send(mode);
1334                }
1335            }
1336            window::Action::ToggleMaximize(id) => {
1337                if let Some(window) = window_manager.get_mut(id) {
1338                    window.raw.set_maximized(!window.raw.is_maximized());
1339                }
1340            }
1341            window::Action::ToggleDecorations(id) => {
1342                if let Some(window) = window_manager.get_mut(id) {
1343                    window.raw.set_decorations(!window.raw.is_decorated());
1344                }
1345            }
1346            window::Action::RequestUserAttention(id, attention_type) => {
1347                if let Some(window) = window_manager.get_mut(id) {
1348                    window.raw.request_user_attention(
1349                        attention_type.map(conversion::user_attention),
1350                    );
1351                }
1352            }
1353            window::Action::GainFocus(id) => {
1354                if let Some(window) = window_manager.get_mut(id) {
1355                    window.raw.focus_window();
1356                }
1357            }
1358            window::Action::SetLevel(id, level) => {
1359                if let Some(window) = window_manager.get_mut(id) {
1360                    window
1361                        .raw
1362                        .set_window_level(conversion::window_level(level));
1363                }
1364            }
1365            window::Action::ShowSystemMenu(id) => {
1366                if let Some(window) = window_manager.get_mut(id)
1367                    && let mouse::Cursor::Available(point) =
1368                        window.state.cursor()
1369                {
1370                    window.raw.show_window_menu(winit::dpi::LogicalPosition {
1371                        x: point.x,
1372                        y: point.y,
1373                    });
1374                }
1375            }
1376            window::Action::GetRawId(id, channel) => {
1377                if let Some(window) = window_manager.get_mut(id) {
1378                    let _ = channel.send(window.raw.id().into());
1379                }
1380            }
1381            window::Action::RunWithHandle(id, f) => {
1382                use window::raw_window_handle::HasWindowHandle;
1383
1384                if let Some(handle) = window_manager
1385                    .get_mut(id)
1386                    .and_then(|window| window.raw.window_handle().ok())
1387                {
1388                    f(handle);
1389                }
1390            }
1391            window::Action::Screenshot(id, channel) => {
1392                if let Some(window) = window_manager.get_mut(id)
1393                    && let Some(compositor) = compositor
1394                {
1395                    let bytes = compositor.screenshot(
1396                        &mut window.renderer,
1397                        window.state.viewport(),
1398                        window.state.background_color(),
1399                    );
1400
1401                    let _ = channel.send(core::window::Screenshot::new(
1402                        bytes,
1403                        window.state.physical_size(),
1404                        window.state.viewport().scale_factor(),
1405                    ));
1406                }
1407            }
1408            window::Action::EnableMousePassthrough(id) => {
1409                if let Some(window) = window_manager.get_mut(id) {
1410                    let _ = window.raw.set_cursor_hittest(false);
1411                }
1412            }
1413            window::Action::DisableMousePassthrough(id) => {
1414                if let Some(window) = window_manager.get_mut(id) {
1415                    let _ = window.raw.set_cursor_hittest(true);
1416                }
1417            }
1418        },
1419        Action::System(action) => match action {
1420            system::Action::QueryInformation(_channel) => {
1421                #[cfg(feature = "system")]
1422                {
1423                    if let Some(compositor) = compositor {
1424                        let graphics_info = compositor.fetch_information();
1425
1426                        let _ = std::thread::spawn(move || {
1427                            let information =
1428                                crate::system::information(graphics_info);
1429
1430                            let _ = _channel.send(information);
1431                        });
1432                    }
1433                }
1434            }
1435        },
1436        Action::Widget(operation) => {
1437            let mut current_operation = Some(operation);
1438
1439            while let Some(mut operation) = current_operation.take() {
1440                for (id, ui) in interfaces.iter_mut() {
1441                    if let Some(window) = window_manager.get_mut(*id) {
1442                        ui.operate(&window.renderer, operation.as_mut());
1443                    }
1444                }
1445
1446                match operation.finish() {
1447                    operation::Outcome::None => {}
1448                    operation::Outcome::Some(()) => {}
1449                    operation::Outcome::Chain(next) => {
1450                        current_operation = Some(next);
1451                    }
1452                }
1453            }
1454        }
1455        Action::LoadFont { bytes, channel } => {
1456            if let Some(compositor) = compositor {
1457                // TODO: Error handling (?)
1458                compositor.load_font(bytes.clone());
1459
1460                let _ = channel.send(Ok(()));
1461            }
1462        }
1463        Action::Reload => {
1464            for (id, window) in window_manager.iter_mut() {
1465                let Some(ui) = interfaces.remove(&id) else {
1466                    continue;
1467                };
1468
1469                let cache = ui.into_cache();
1470                let size = window.size();
1471
1472                let _ = interfaces.insert(
1473                    id,
1474                    build_user_interface(
1475                        program,
1476                        cache,
1477                        &mut window.renderer,
1478                        size,
1479                        id,
1480                    ),
1481                );
1482
1483                window.raw.request_redraw();
1484            }
1485        }
1486        Action::Exit => {
1487            control_sender
1488                .start_send(Control::Exit)
1489                .expect("Send control action");
1490        }
1491    }
1492}
1493
1494/// Build the user interface for every window.
1495pub fn build_user_interfaces<'a, P: Program, C>(
1496    program: &'a program::Instance<P>,
1497    window_manager: &mut WindowManager<P, C>,
1498    mut cached_user_interfaces: FxHashMap<window::Id, user_interface::Cache>,
1499) -> FxHashMap<window::Id, UserInterface<'a, P::Message, P::Theme, P::Renderer>>
1500where
1501    C: Compositor<Renderer = P::Renderer>,
1502    P::Theme: theme::Base,
1503{
1504    cached_user_interfaces
1505        .drain()
1506        .filter_map(|(id, cache)| {
1507            let window = window_manager.get_mut(id)?;
1508
1509            Some((
1510                id,
1511                build_user_interface(
1512                    program,
1513                    cache,
1514                    &mut window.renderer,
1515                    window.state.logical_size(),
1516                    id,
1517                ),
1518            ))
1519        })
1520        .collect()
1521}
1522
1523/// Returns true if the provided event should cause a [`Program`] to
1524/// exit.
1525pub fn user_force_quit(
1526    event: &winit::event::WindowEvent,
1527    _modifiers: winit::keyboard::ModifiersState,
1528) -> bool {
1529    match event {
1530        #[cfg(target_os = "macos")]
1531        winit::event::WindowEvent::KeyboardInput {
1532            event:
1533                winit::event::KeyEvent {
1534                    logical_key: winit::keyboard::Key::Character(c),
1535                    state: winit::event::ElementState::Pressed,
1536                    ..
1537                },
1538            ..
1539        } if c == "q" && _modifiers.super_key() => true,
1540        _ => false,
1541    }
1542}