Skip to main content

smudge_platform/
window.rs

1//! The window, its run loop and the frames it presents.
2
3use std::num::NonZeroU32;
4use std::rc::Rc;
5use std::sync::Arc;
6
7use smudge_core::app::{App, AppConfig, FrameOutput, FrameStats, RepaintMode};
8use smudge_core::clipboard::{Clipboard, MemoryClipboard};
9use smudge_core::element::IntoElement;
10use smudge_core::geometry::{Color, PixelRect, Rect, Size};
11use smudge_cpu::CpuRenderer;
12use softbuffer::{Context, Surface};
13use web_time::Instant;
14use winit::application::ApplicationHandler;
15use winit::dpi::{LogicalPosition, LogicalSize, PhysicalSize};
16use winit::event::WindowEvent;
17use winit::event_loop::{ActiveEventLoop, ControlFlow, EventLoop};
18use winit::raw_window_handle::{
19    DisplayHandle, HandleError, HasDisplayHandle, HasWindowHandle, WindowHandle,
20};
21use winit::window::{Window, WindowId};
22
23#[cfg(not(target_arch = "wasm32"))]
24use crate::clipboard::SystemClipboard;
25use crate::error::PlatformError;
26use crate::events::EventTranslator;
27use crate::present::{
28    DamageHistory, PresentStats, PresentStatsHandle, copy_rect, damage_region, present_skipped,
29    tint_rects,
30};
31use crate::renderer::RendererChoice;
32#[cfg(feature = "gpu")]
33use smudge_gpu::AdapterSummary;
34
35/// The user event the run loop carries, of which the accessibility adapter
36/// sends the only kind.
37///
38/// With the `a11y` feature off the run loop carries `()` and nothing is ever
39/// sent.
40#[cfg(all(feature = "a11y", not(target_arch = "wasm32")))]
41pub(crate) enum PlatformEvent {
42    /// An event from the accessibility adapter: a request for the initial
43    /// tree, an action a user ran in an assistive tool, or a notice that the
44    /// platform stopped reading the tree.
45    A11y(accesskit_winit::Event),
46}
47
48#[cfg(all(feature = "a11y", not(target_arch = "wasm32")))]
49impl From<accesskit_winit::Event> for PlatformEvent {
50    fn from(event: accesskit_winit::Event) -> PlatformEvent {
51        PlatformEvent::A11y(event)
52    }
53}
54
55/// The user event type the run loop is built with.
56#[cfg(all(feature = "a11y", not(target_arch = "wasm32")))]
57type UserEvent = PlatformEvent;
58#[cfg(any(not(feature = "a11y"), target_arch = "wasm32"))]
59type UserEvent = ();
60
61/// The measurement hook that receives one pair of statistics per presented
62/// frame.
63///
64/// The platform calls it after each present with the frame's pipeline
65/// statistics and what the present did. Nothing in the library sets it: it
66/// exists so a benchmark can record one entry per frame while the frames run.
67pub type FrameHook = Rc<dyn Fn(FrameStats, PresentStats)>;
68
69/// The measurement hook that injects scripted input before a frame runs.
70///
71/// The platform calls it with the app before every frame but the first, which
72/// mounts the app and lays it out, and the hook delivers input through [`App::handle_event`], which is the path every real
73/// event takes. Nothing in the library sets it: it exists so a benchmark can
74/// script input on the window backend, where nothing else can reach the app
75/// between frames. The time from a call to the end of that frame's present is
76/// the event's latency. Like [`FrameHook`], the hook is shared, so it keeps
77/// any counter it needs behind a `Cell` or an `Rc`.
78pub type EventHook = Rc<dyn Fn(&mut App)>;
79
80/// The settings a window opens with.
81#[derive(Clone)]
82pub struct WindowOptions {
83    /// The text shown in the window's title bar.
84    pub title: String,
85    /// The size of the window's content area in logical pixels.
86    pub size: Size,
87    /// The color the renderer fills the surface with before it replays the
88    /// display list.
89    ///
90    /// Must be opaque, because presentation copies only the color channels of
91    /// the surface into the window. A run with a color that is not opaque
92    /// returns [`PlatformError::ClearColor`] before a window opens.
93    pub clear_color: Color,
94    /// How much of the surface each frame repaints.
95    pub repaint: RepaintMode,
96    /// Whether every present tints the pixels the frame repainted.
97    ///
98    /// A debugging aid for damage regressions: while it is on, every present
99    /// copies the whole pixmap and blends a translucent tint over the frame's
100    /// damage rectangles in the window buffer alone, so the window shows what
101    /// each frame paid for. Damage presentation is off while the overlay is
102    /// on, and neither the renderer's pixmap nor a later frame keeps a tint.
103    ///
104    /// The overlay exists on the CPU renderer only, so it makes
105    /// [`RendererChoice::Auto`] pick the CPU renderer, and it cannot be
106    /// combined with [`RendererChoice::Gpu`].
107    pub show_damage: bool,
108    /// Which renderer presents the frames.
109    ///
110    /// [`RendererChoice::Auto`] uses the GPU renderer only when the window's
111    /// adapter is a real GPU, and falls back to the CPU renderer on a software
112    /// rasterizer, without one, or on a window the GPU cannot present to. It
113    /// is the default in builds with the `gpu` feature. [`RendererChoice::Gpu`]
114    /// never falls back: it fails the run when the `gpu` feature is off or no
115    /// usable adapter exists.
116    ///
117    /// The [`crate::renderer::RENDERER_ENVIRONMENT`] variable, set to `cpu`,
118    /// `gpu` or `auto`, overrides the value here when the window opens, so a
119    /// machine can switch renderer without a rebuild.
120    pub renderer: RendererChoice,
121    /// Where the platform publishes presentation statistics, when given.
122    ///
123    /// Pass a [`PresentStatsHandle`] and keep the original to read the
124    /// [`PresentStats`] of the most recent present while the window runs.
125    /// Without one the platform skips publishing.
126    pub stats: Option<PresentStatsHandle>,
127    /// Measurement hook: runs after every presented frame.
128    ///
129    /// The hook receives the frame's [`FrameStats`] and the present's
130    /// [`PresentStats`], in that order. It runs on the main thread between
131    /// frames and must not touch the window. A frame rendered while the
132    /// surface has no pixel, for example while the window is minimized, is not
133    /// presented, so it neither calls the hook nor counts toward the frame
134    /// limit.
135    pub on_frame: Option<FrameHook>,
136    /// Measurement hook: injects input before every frame.
137    ///
138    /// The hook receives the app and may hand it input events through
139    /// [`App::handle_event`], which is the path every real event takes, so the
140    /// frame that follows shows the event and measures its latency. A run with
141    /// this hook draws frames back to back like a run with a tick. Like a
142    /// ticked frame, a frame whose input changed nothing presents nothing, so
143    /// it neither calls [`WindowOptions::on_frame`] nor counts toward the
144    /// frame limit. A hook that must reach its limit changes something in
145    /// every frame.
146    pub on_event: Option<EventHook>,
147    /// Measurement hook: closes the window after this many presented frames.
148    ///
149    /// A run that must last a fixed number of frames sets this instead of
150    /// waiting for the user to close the window. Until the limit is reached,
151    /// every presented frame asks for the next one, as if the app ran a tick,
152    /// so a window whose app has nothing more to draw still closes: such a
153    /// frame presents the last picture again, paced by the display. A frame
154    /// that presents nothing, because the surface has no pixels to show, asks
155    /// for none, so the run waits for the window to change instead of
156    /// spinning.
157    pub frame_limit: Option<u64>,
158}
159
160impl std::fmt::Debug for WindowOptions {
161    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
162        f.debug_struct("WindowOptions")
163            .field("title", &self.title)
164            .field("size", &self.size)
165            .field("clear_color", &self.clear_color)
166            .field("repaint", &self.repaint)
167            .field("show_damage", &self.show_damage)
168            .field("renderer", &self.renderer)
169            .field("stats", &self.stats)
170            .field("on_frame", &self.on_frame.is_some())
171            .field("on_event", &self.on_event.is_some())
172            .field("frame_limit", &self.frame_limit)
173            .finish()
174    }
175}
176
177/// Opens a window and runs frames until the window closes.
178///
179/// `build` runs once, when the window first appears, and mounts the element it
180/// returns. A frame runs after a reactive signal wakes an effect, after a
181/// resize or a scale change, and once per frame while a tick is installed.
182/// Otherwise the window waits for the next event.
183///
184/// Call this from the main thread. The event loop takes over the calling
185/// thread until the window closes.
186///
187/// Platform-specific:
188///
189/// - **Web:** the loop runs on the browser's own event loop, so this returns
190///   `Ok(())` as soon as the loop is started, before the window opens. See
191///   [`run_with`] for the details.
192///
193/// # Errors
194///
195/// Returns [`PlatformError::ClearColor`] when [`WindowOptions::clear_color`]
196/// is not opaque, [`PlatformError::Renderer`] when the
197/// [`WindowOptions::renderer`] choice or its environment override cannot be
198/// used, [`PlatformError::EventLoop`] when the event loop cannot be
199/// created or run, [`PlatformError::Window`] when the window cannot be
200/// created, and [`PlatformError::Surface`] when the presentation surface
201/// cannot be created, resized or presented.
202///
203/// # Examples
204///
205/// ```no_run
206/// use smudge_core::app::RepaintMode;
207/// use smudge_core::geometry::{Color, Size};
208/// use smudge_core::style::px;
209/// use smudge_core::widget::panel;
210/// use smudge_platform::{RendererChoice, WindowOptions, run};
211///
212/// run(
213///     WindowOptions {
214///         title: String::from("Smudge"),
215///         size: Size {
216///             width: 320.0,
217///             height: 200.0,
218///         },
219///         clear_color: Color::WHITE,
220///         repaint: RepaintMode::Damage,
221///         show_damage: false,
222///         renderer: RendererChoice::default(),
223///         stats: None,
224///         on_frame: None,
225///         on_event: None,
226///         frame_limit: None,
227///     },
228///     || panel().width(px(120.0)).height(px(40.0)).background(Color::BLACK),
229/// )?;
230/// # Ok::<(), smudge_platform::PlatformError>(())
231/// ```
232pub fn run<E: IntoElement + 'static>(
233    options: WindowOptions,
234    build: impl FnOnce() -> E + 'static,
235) -> Result<(), PlatformError> {
236    run_with(options, build, |_| {})
237}
238
239/// Opens a window, hands the app to `configure`, and runs frames until the
240/// window closes.
241///
242/// Behaves like [`run`], and calls `configure` once after `build` has mounted
243/// its element and before the first frame. Use it to install a per-frame
244/// driver with [`App::set_tick`], or to set up measurement that must run
245/// alongside the frames.
246///
247/// Platform-specific:
248///
249/// - **Web:** the window is a canvas appended to the page's body, at
250///   [`WindowOptions::size`] unless the page's style sheet sizes it with
251///   `!important`, and it follows the size the page gives it. The loop runs
252///   on the browser's own event loop, so this returns `Ok(())` as soon as
253///   the loop is started, before the window opens. Only
254///   failures before that point, such as a clear color that is not opaque,
255///   come back as an error; a failure later in the run stops the loop
256///   without a way to report it.
257///
258/// # Errors
259///
260/// Returns [`PlatformError::ClearColor`] when [`WindowOptions::clear_color`]
261/// is not opaque, checked before the event loop is created,
262/// [`PlatformError::Renderer`] when the [`WindowOptions::renderer`] choice
263/// or its environment override cannot be used, and otherwise the same errors
264/// as [`run`].
265///
266/// # Examples
267///
268/// ```no_run
269/// use smudge_core::app::RepaintMode;
270/// use smudge_core::geometry::{Color, Size};
271/// use smudge_core::reactive::{self, prelude::*};
272/// use smudge_core::style::px;
273/// use smudge_core::widget::panel;
274/// use smudge_platform::{RendererChoice, WindowOptions, run_with};
275///
276/// let (color, set_color) = reactive::signal_local(Color::BLACK);
277/// run_with(
278///     WindowOptions {
279///         title: String::from("Smudge"),
280///         size: Size {
281///             width: 320.0,
282///             height: 200.0,
283///         },
284///         clear_color: Color::WHITE,
285///         repaint: RepaintMode::Damage,
286///         show_damage: false,
287///         renderer: RendererChoice::default(),
288///         stats: None,
289///         on_frame: None,
290///         on_event: None,
291///         frame_limit: None,
292///     },
293///     move || panel().width(px(120.0)).background(move || color.get().into()),
294///     move |app| {
295///         let mut dark = false;
296///         app.set_tick(move |_| {
297///             dark = !dark;
298///             set_color.set(if dark { Color::BLACK } else { Color::WHITE });
299///         });
300///     },
301/// )?;
302/// # Ok::<(), smudge_platform::PlatformError>(())
303/// ```
304pub fn run_with<E: IntoElement + 'static>(
305    options: WindowOptions,
306    build: impl FnOnce() -> E + 'static,
307    configure: impl FnOnce(&mut App) + 'static,
308) -> Result<(), PlatformError> {
309    if !options.clear_color.is_opaque() {
310        return Err(PlatformError::ClearColor);
311    }
312    let renderer = options.renderer.with_env_override()?;
313
314    let event_loop = EventLoop::<UserEvent>::with_user_event()
315        .build()
316        .map_err(PlatformError::EventLoop)?;
317    #[cfg(all(feature = "a11y", not(target_arch = "wasm32")))]
318    let proxy = event_loop.create_proxy();
319    // The web loop takes the state by value, so nothing mutates it here.
320    #[cfg_attr(target_arch = "wasm32", allow(unused_mut))]
321    let mut platform = Platform {
322        renderer,
323        options,
324        build: Some(build),
325        configure: Some(Box::new(configure)),
326        state: None,
327        error: None,
328        #[cfg(all(feature = "a11y", not(target_arch = "wasm32")))]
329        proxy,
330    };
331    // The web loop runs on the browser's own event loop and never runs to
332    // completion, so it is started and can no longer return an error. A
333    // failure after it started only stops the run.
334    #[cfg(target_arch = "wasm32")]
335    {
336        use winit::platform::web::EventLoopExtWebSys;
337        event_loop.spawn_app(platform);
338        Ok(())
339    }
340    #[cfg(not(target_arch = "wasm32"))]
341    {
342        event_loop
343            .run_app(&mut platform)
344            .map_err(PlatformError::EventLoop)?;
345        match platform.error.take() {
346            Some(error) => Err(error),
347            None => Ok(()),
348        }
349    }
350}
351
352/// The state the event loop drives.
353struct Platform<E, B>
354where
355    E: IntoElement,
356    B: FnOnce() -> E,
357{
358    /// The renderer the window presents with, after the environment override.
359    renderer: RendererChoice,
360    /// The settings the window opens with.
361    options: WindowOptions,
362    /// The element to mount, until the window first appears.
363    build: Option<B>,
364    /// The caller's setup, run once the app exists.
365    configure: Option<Configure>,
366    /// The window, its app and its renderer, once the window exists.
367    state: Option<WindowState>,
368    /// The first error that stopped the run.
369    error: Option<PlatformError>,
370    /// The sender the accessibility adapter posts its events through.
371    #[cfg(all(feature = "a11y", not(target_arch = "wasm32")))]
372    proxy: winit::event_loop::EventLoopProxy<UserEvent>,
373}
374
375/// The setup a caller hands to [`run_with`], boxed so the platform state does
376/// not become generic over it.
377type Configure = Box<dyn FnOnce(&mut App)>;
378
379/// One live window and everything it presents.
380struct WindowState {
381    /// The window the surface presents to.
382    ///
383    /// The shared handle is an `Arc` because the wgpu surface target must be
384    /// sendable; everything else in the window stays on the main thread.
385    window: Arc<Window>,
386    /// The app whose frames the window runs.
387    app: App,
388    /// The translator that turns window events into input events.
389    translator: EventTranslator,
390    /// The renderer and presentation path the frames go through.
391    presenter: Presenter,
392    /// What the last present did.
393    stats: PresentStats,
394    /// Where to publish the statistics, when the caller asked for them.
395    stats_handle: Option<PresentStatsHandle>,
396    /// The measurement hook to call after every presented frame.
397    on_frame: Option<FrameHook>,
398    /// The measurement hook to call before every frame.
399    on_event: Option<EventHook>,
400    /// Whether the app has run a frame. The event hook waits for that, since
401    /// a real event cannot arrive before the app has drawn once, and a hit
402    /// test before the first layout has no rectangles to hit.
403    rendered: bool,
404    /// How many frames to present before the window closes.
405    frame_limit: Option<u64>,
406    /// How many frames the window has presented.
407    frames_presented: u64,
408    /// The number of physical pixels per logical pixel the app last used.
409    scale: f32,
410    /// The window's size in physical pixels when the app last used it.
411    physical: (u32, u32),
412    /// The IME area the window last reported, in logical pixels, or `None`
413    /// while no widget reports one.
414    ime_area: Option<Rect>,
415    /// The scale the IME area was last reported under. The window system
416    /// turns the area into physical pixels with the scale of the moment, so
417    /// an unchanged logical area is reported again after a scale change.
418    ime_scale: f32,
419    /// Whether the window currently accepts input method input.
420    ime_allowed: bool,
421    /// The bridge that maps the app's accessibility projection onto the
422    /// platform's, when the platform builds with accessibility.
423    #[cfg(all(feature = "a11y", not(target_arch = "wasm32")))]
424    adapter: accesskit_winit::Adapter,
425    /// The projection that builds the updates the adapter sends.
426    #[cfg(all(feature = "a11y", not(target_arch = "wasm32")))]
427    a11y: smudge_a11y::Projection,
428    /// Whether the accessibility backend has asked for the tree and has not
429    /// stopped reading it since. While it is false, no projection work runs.
430    #[cfg(all(feature = "a11y", not(target_arch = "wasm32")))]
431    a11y_active: bool,
432    /// Whether the next update must carry the whole tree, because the
433    /// backend asked for it or the scale moved every node's bounds.
434    #[cfg(all(feature = "a11y", not(target_arch = "wasm32")))]
435    a11y_full: bool,
436}
437
438/// The frame that was rendered and waits to be presented.
439#[derive(Default)]
440struct PendingFrame {
441    /// The rectangles the frame changed, in physical pixels.
442    damage: Vec<PixelRect>,
443    /// Whether the frame repainted the whole surface.
444    full: bool,
445}
446
447impl<E, B> Platform<E, B>
448where
449    E: IntoElement + 'static,
450    B: FnOnce() -> E + 'static,
451{
452    /// Creates the window, its app and its presentation path.
453    fn create(&mut self, event_loop: &ActiveEventLoop) -> Result<WindowState, PlatformError> {
454        let attributes = Window::default_attributes()
455            .with_title(self.options.title.clone())
456            .with_inner_size(LogicalSize::new(
457                f64::from(self.options.size.width),
458                f64::from(self.options.size.height),
459            ))
460            // The accessibility adapter must be attached before the window is
461            // shown, so the window starts hidden and is shown right after.
462            .with_visible(false);
463        // A web window draws into a canvas, which the page shows only once it
464        // is part of the document.
465        #[cfg(target_arch = "wasm32")]
466        let attributes = {
467            use winit::platform::web::WindowAttributesExtWebSys;
468            attributes.with_append(true)
469        };
470        let window = Arc::new(
471            event_loop
472                .create_window(attributes)
473                .map_err(PlatformError::Window)?,
474        );
475        #[cfg(all(feature = "a11y", not(target_arch = "wasm32")))]
476        let adapter = accesskit_winit::Adapter::with_event_loop_proxy(
477            event_loop,
478            &window,
479            self.proxy.clone(),
480        );
481        window.set_visible(true);
482
483        let scale = window.scale_factor() as f32;
484        let physical = window.inner_size();
485        let build = self
486            .build
487            .take()
488            .expect("the window is created only once, so its element is still waiting");
489        let mut app = App::new(
490            AppConfig {
491                viewport: logical_size(physical, scale),
492                scale,
493                clear_color: self.options.clear_color,
494                repaint: self.options.repaint,
495            },
496            build,
497        );
498        if let Some(configure) = self.configure.take() {
499            configure(&mut app);
500        }
501
502        let (width, height) = app.surface_size();
503        let presenter = create_presenter(
504            &window,
505            self.renderer,
506            width,
507            height,
508            self.options.show_damage,
509        )?;
510        #[cfg(feature = "gpu")]
511        if let (Some(handle), Some(summary)) =
512            (self.options.stats.as_ref(), presenter.adapter_summary())
513        {
514            handle.set_adapter(summary);
515        }
516
517        let weak = Arc::downgrade(&window);
518        app.set_wake_callback(move || {
519            if let Some(window) = weak.upgrade() {
520                window.request_redraw();
521            }
522        });
523        window.request_redraw();
524
525        // The app gets the system clipboard when it can be opened, and an
526        // in-memory one when it cannot, so cut, copy and paste keep working
527        // inside the app either way. The web page has no system clipboard to
528        // open, so it always gets the in-memory one.
529        #[cfg(not(target_arch = "wasm32"))]
530        let clipboard: Box<dyn Clipboard> = match SystemClipboard::new() {
531            Ok(clipboard) => Box::new(clipboard),
532            Err(_) => Box::new(MemoryClipboard::default()),
533        };
534        #[cfg(target_arch = "wasm32")]
535        let clipboard: Box<dyn Clipboard> = Box::new(MemoryClipboard::default());
536        app.set_clipboard(clipboard);
537
538        Ok(WindowState {
539            window,
540            app,
541            translator: EventTranslator::new(),
542            presenter,
543            stats: PresentStats::default(),
544            stats_handle: self.options.stats.clone(),
545            on_frame: self.options.on_frame.take(),
546            on_event: self.options.on_event.take(),
547            rendered: false,
548            frame_limit: self.options.frame_limit,
549            frames_presented: 0,
550            scale,
551            physical: (physical.width, physical.height),
552            ime_area: None,
553            ime_scale: scale,
554            ime_allowed: false,
555            #[cfg(all(feature = "a11y", not(target_arch = "wasm32")))]
556            adapter,
557            #[cfg(all(feature = "a11y", not(target_arch = "wasm32")))]
558            a11y: smudge_a11y::Projection::new(self.options.title.clone()),
559            #[cfg(all(feature = "a11y", not(target_arch = "wasm32")))]
560            a11y_active: false,
561            #[cfg(all(feature = "a11y", not(target_arch = "wasm32")))]
562            a11y_full: false,
563        })
564    }
565
566    /// Changes the size and the scale the app and the renderer work at.
567    fn resize(&mut self, physical: PhysicalSize<u32>, scale: f64) -> Result<(), PlatformError> {
568        let Some(state) = self.state.as_mut() else {
569            return Ok(());
570        };
571        let scale = scale as f32;
572        if !scale.is_finite() || scale <= 0.0 {
573            return Ok(());
574        }
575        if physical.width == 0 || physical.height == 0 {
576            return Ok(());
577        }
578        if state.scale == scale && state.physical == (physical.width, physical.height) {
579            return Ok(());
580        }
581
582        // The app learns the new viewport only after the presenter took the
583        // new size, so a failed resize leaves the presenter and the app
584        // consistent at the old size instead of an app whose next frame
585        // reaches past the surface it renders onto.
586        let (Some(width), Some(height)) = (
587            NonZeroU32::new(physical.width),
588            NonZeroU32::new(physical.height),
589        ) else {
590            return Ok(());
591        };
592        state.presenter.resize(width, height)?;
593        state.app.set_viewport(logical_size(physical, scale), scale);
594        #[cfg(all(feature = "a11y", not(target_arch = "wasm32")))]
595        if state.scale != scale {
596            state.a11y_full = true;
597        }
598        debug_assert_eq!(
599            state.app.surface_size(),
600            (width.get(), height.get()),
601            "the app's surface size must match the resized surface and renderer"
602        );
603        state.scale = scale;
604        state.physical = (physical.width, physical.height);
605        state.window.request_redraw();
606        Ok(())
607    }
608
609    /// Runs one frame and presents it.
610    ///
611    /// When an [`WindowOptions::on_event`] hook is set, it injects its input
612    /// first, so the frame shows it, and the run draws frames back to back
613    /// like a run whose app has a tick. The first frame gets no injection:
614    /// nothing real arrives before the app has drawn once, and a hit test
615    /// before the first layout has no rectangles to hit.
616    fn redraw(&mut self) -> Result<(), PlatformError> {
617        let Some(state) = self.state.as_mut() else {
618            return Ok(());
619        };
620        if state.rendered
621            && let Some(on_event) = &state.on_event
622        {
623            (on_event)(&mut state.app);
624        }
625        let driven = state.app.has_tick() || state.on_event.is_some();
626        let output = state.app.frame();
627        state.rendered = true;
628        let presented = state.presenter.render_and_present(&output, driven)?;
629        if let Some(stats) = presented {
630            state.stats = stats;
631            if let Some(handle) = &state.stats_handle {
632                handle.store(stats);
633            }
634            state.frames_presented += 1;
635            if let Some(on_frame) = &state.on_frame {
636                on_frame(state.app.stats().clone(), stats);
637            }
638        }
639        state.update_ime();
640        #[cfg(all(feature = "a11y", not(target_arch = "wasm32")))]
641        state.update_a11y();
642        if driven || (presented.is_some() && state.below_frame_limit()) {
643            state.window.request_redraw();
644        }
645        Ok(())
646    }
647
648    /// Runs the timers that are due and puts the event loop to sleep until
649    /// the next one.
650    ///
651    /// An app with a tick or an event hook keeps itself awake and needs
652    /// nothing here. Any
653    /// other app runs its due timers between frames: one that writes a
654    /// signal wakes the window through the app's wake callback, and one that
655    /// changes nothing draws no frame, so it never costs a present. The loop
656    /// then waits until the soonest timer, or for events when none is left.
657    /// A timer that is due again at once, such as one a callback just
658    /// scheduled with no delay, asks for a frame, which runs it.
659    fn wait_for_timers(&mut self, event_loop: &ActiveEventLoop) {
660        let Some(state) = self.state.as_mut() else {
661            return;
662        };
663        if state.app.has_tick() || state.on_event.is_some() {
664            return;
665        }
666        if wait_decision(state.app.next_deadline(), Instant::now()) == WaitDecision::Now {
667            state.app.run_due_timers();
668            if state.app.needs_frame() {
669                state.window.request_redraw();
670            }
671        }
672        match wait_decision(state.app.next_deadline(), Instant::now()) {
673            WaitDecision::Now => state.window.request_redraw(),
674            WaitDecision::Until(deadline) => {
675                event_loop.set_control_flow(ControlFlow::WaitUntil(deadline));
676            }
677            WaitDecision::Wait => event_loop.set_control_flow(ControlFlow::Wait),
678        }
679    }
680
681    /// Records `error` and stops the event loop.
682    fn fail(&mut self, event_loop: &ActiveEventLoop, error: PlatformError) {
683        if self.error.is_none() {
684            self.error = Some(error);
685        }
686        event_loop.exit();
687    }
688
689    /// Answers an event the accessibility adapter posted.
690    ///
691    /// The first request for the tree is answered with a full update, which
692    /// is also what turns the projection on: until the backend asks, no
693    /// frame spends any work on it. A request that arrives while a frame is
694    /// pending waits for that frame, so the tree it gets has been laid out
695    /// and every later change is queued after it. An action presses or focuses the widget
696    /// the request names, through the same paths a pointer press and a
697    /// keyboard focus change take. When the platform stops reading the tree,
698    /// the projection goes quiet until the backend asks again.
699    #[cfg(all(feature = "a11y", not(target_arch = "wasm32")))]
700    fn handle_a11y_event(&mut self, event: accesskit_winit::Event) {
701        let Some(state) = self.state.as_mut() else {
702            return;
703        };
704        match event.window_event {
705            accesskit_winit::WindowEvent::InitialTreeRequested => {
706                state.a11y_active = true;
707                state.a11y_full = true;
708                if state.app.needs_frame() {
709                    state.window.request_redraw();
710                } else {
711                    state.update_a11y();
712                }
713            }
714            accesskit_winit::WindowEvent::ActionRequested(request) => {
715                let id = smudge_core::tree::WidgetId::from_u64(request.target_node.0);
716                match request.action {
717                    accesskit::Action::Click => {
718                        state.app.press(id);
719                    }
720                    accesskit::Action::Focus => {
721                        state.app.request_focus(id);
722                    }
723                    _ => {}
724                }
725                if state.app.needs_frame() {
726                    state.window.request_redraw();
727                }
728            }
729            accesskit_winit::WindowEvent::AccessibilityDeactivated => {
730                state.a11y_active = false;
731            }
732        }
733    }
734}
735
736impl<E, B> ApplicationHandler<UserEvent> for Platform<E, B>
737where
738    E: IntoElement + 'static,
739    B: FnOnce() -> E + 'static,
740{
741    fn resumed(&mut self, event_loop: &ActiveEventLoop) {
742        if self.state.is_some() {
743            return;
744        }
745        match self.create(event_loop) {
746            Ok(state) => self.state = Some(state),
747            Err(error) => self.fail(event_loop, error),
748        }
749    }
750
751    #[cfg(all(feature = "a11y", not(target_arch = "wasm32")))]
752    fn user_event(&mut self, _event_loop: &ActiveEventLoop, event: UserEvent) {
753        let UserEvent::A11y(event) = event;
754        self.handle_a11y_event(event);
755    }
756
757    fn window_event(
758        &mut self,
759        event_loop: &ActiveEventLoop,
760        _window_id: WindowId,
761        event: WindowEvent,
762    ) {
763        // The accessibility adapter sees every event before the app does,
764        // because it reacts to moves, resizes and focus changes itself.
765        #[cfg(all(feature = "a11y", not(target_arch = "wasm32")))]
766        if let Some(state) = self.state.as_mut() {
767            state.adapter.process_event(&state.window, &event);
768        }
769        let result = match event {
770            WindowEvent::CloseRequested => {
771                event_loop.exit();
772                Ok(())
773            }
774            WindowEvent::Resized(physical) => {
775                let scale = self
776                    .state
777                    .as_ref()
778                    .map_or(1.0, |state| state.window.scale_factor());
779                self.resize(physical, scale)
780            }
781            WindowEvent::ScaleFactorChanged { scale_factor, .. } => {
782                match self.state.as_ref().map(|state| state.window.inner_size()) {
783                    Some(physical) => self.resize(physical, scale_factor),
784                    None => Ok(()),
785                }
786            }
787            WindowEvent::RedrawRequested => {
788                let result = self.redraw();
789                if result.is_ok() && self.state.as_ref().is_some_and(WindowState::finished) {
790                    event_loop.exit();
791                }
792                result
793            }
794            other => {
795                if let Some(state) = self.state.as_mut() {
796                    let input = state.translator.translate(&other, state.scale);
797                    if let Some(input) = input {
798                        state.app.handle_event(input);
799                        if state.app.needs_frame() {
800                            state.window.request_redraw();
801                        }
802                    }
803                    state.update_ime();
804                }
805                Ok(())
806            }
807        };
808        if let Err(error) = result {
809            self.fail(event_loop, error);
810        }
811    }
812
813    fn about_to_wait(&mut self, event_loop: &ActiveEventLoop) {
814        self.wait_for_timers(event_loop);
815    }
816}
817
818impl WindowState {
819    /// Reports the area the focused widget offers the input method to the
820    /// window system.
821    ///
822    /// This runs after every window event and after every presented frame,
823    /// because the area can move without a pixel changing anywhere: the
824    /// window asks for a frame only when the tree has work that changes
825    /// pixels, so a caret move inside composed text would otherwise leave
826    /// the input method's candidate window where it was. A widget that
827    /// reports no area turns the input method off, which is what a window
828    /// does while no text field holds the focus.
829    fn update_ime(&mut self) {
830        let area = self.app.ime_area();
831        if area == self.ime_area && self.scale == self.ime_scale {
832            return;
833        }
834        self.ime_area = area;
835        self.ime_scale = self.scale;
836        match area {
837            Some(area) => {
838                if !self.ime_allowed {
839                    self.ime_allowed = true;
840                    self.window.set_ime_allowed(true);
841                }
842                self.window.set_ime_cursor_area(
843                    LogicalPosition::new(f64::from(area.x0), f64::from(area.y0)),
844                    LogicalSize::new(f64::from(area.width()), f64::from(area.height())),
845                );
846            }
847            None => {
848                if self.ime_allowed {
849                    self.ime_allowed = false;
850                    self.window.set_ime_allowed(false);
851                }
852            }
853        }
854    }
855
856    /// Sends the accessibility projection to the adapter when the backend is
857    /// reading the tree and something changed.
858    ///
859    /// This runs after every frame. The update is whole when the backend
860    /// asked for the tree or the scale changed, and it is also whole when
861    /// the changes say a frame changed too much to describe one node at a
862    /// time. While the backend is not reading, or while nothing projected
863    /// changed, this builds nothing, and the adapter calls the closure that
864    /// builds an update only while an assistive technology listens.
865    #[cfg(all(feature = "a11y", not(target_arch = "wasm32")))]
866    fn update_a11y(&mut self) {
867        if !self.a11y_active || !(self.a11y_full || self.app.has_a11y_changes()) {
868            return;
869        }
870        let full = std::mem::take(&mut self.a11y_full);
871        let (width, height) = self.physical;
872        let (projection, app, scale) = (&mut self.a11y, &mut self.app, self.scale);
873        self.adapter.update_if_active(|| {
874            projection.update(app, f64::from(width), f64::from(height), scale, full)
875        });
876    }
877
878    /// Returns whether the window has presented the number of frames its frame
879    /// limit sets, or `false` when no limit is set.
880    fn finished(&self) -> bool {
881        self.frame_limit
882            .is_some_and(|limit| self.frames_presented >= limit)
883    }
884
885    /// Returns whether the window has a frame limit it has not reached yet.
886    fn below_frame_limit(&self) -> bool {
887        self.frame_limit
888            .is_some_and(|limit| self.frames_presented < limit)
889    }
890}
891
892/// Builds the presentation path the window opens with.
893///
894/// The GPU path asks the window system for an adapter when the choice can use
895/// one. An automatic choice falls back on the CPU renderer whenever that
896/// fails or finds only a software rasterizer; an explicit GPU request fails
897/// the run instead.
898fn create_presenter(
899    window: &Arc<Window>,
900    choice: RendererChoice,
901    width: u32,
902    height: u32,
903    show_damage: bool,
904) -> Result<Presenter, PlatformError> {
905    #[cfg(not(feature = "gpu"))]
906    {
907        match choice {
908            RendererChoice::Gpu => Err(PlatformError::Renderer(String::from(
909                "the GPU renderer needs a build with the gpu feature",
910            ))),
911            _ => Ok(Presenter::Cpu(Box::new(CpuPresenter::new(
912                window,
913                width,
914                height,
915                show_damage,
916            )?))),
917        }
918    }
919    #[cfg(feature = "gpu")]
920    match choice {
921        RendererChoice::Cpu => Ok(Presenter::Cpu(Box::new(CpuPresenter::new(
922            window,
923            width,
924            height,
925            show_damage,
926        )?))),
927        // The overlay tints the window buffer of the CPU path, so an
928        // automatic choice keeps the CPU renderer while it is on.
929        RendererChoice::Auto if show_damage => Ok(Presenter::Cpu(Box::new(CpuPresenter::new(
930            window,
931            width,
932            height,
933            show_damage,
934        )?))),
935        RendererChoice::Gpu | RendererChoice::Auto => {
936            let explicit = choice == RendererChoice::Gpu;
937            if explicit && show_damage {
938                return Err(PlatformError::Renderer(String::from(
939                    "the damage overlay tints the window buffer of the CPU \
940                     presentation path and cannot run on the GPU renderer",
941                )));
942            }
943            match crate::gpu::GpuPath::new(window, width, height, explicit)? {
944                Some(gpu) => Ok(Presenter::Gpu(Box::new(gpu))),
945                None => Ok(Presenter::Cpu(Box::new(CpuPresenter::new(
946                    window,
947                    width,
948                    height,
949                    show_damage,
950                )?))),
951            }
952        }
953    }
954}
955
956/// The renderer and presentation path a window presents with.
957///
958/// Both paths are boxed: the wgpu handles of the GPU path and the pixmap state
959/// of the CPU path dwarf a boxed pointer, and the enum only dispatches.
960enum Presenter {
961    /// The CPU renderer, presenting through the software surface.
962    Cpu(Box<CpuPresenter>),
963    /// The GPU renderer, presenting through a wgpu surface.
964    #[cfg(feature = "gpu")]
965    Gpu(Box<crate::gpu::GpuPath>),
966}
967
968impl Presenter {
969    /// Renders the frame and presents it, returning the statistics of a
970    /// presented frame.
971    ///
972    /// Returns `Ok(None)` when nothing was presented: a frame that changed
973    /// nothing while a tick runs, or a surface without a pixel or texture to
974    /// present, both of which the next redraw retries.
975    fn render_and_present(
976        &mut self,
977        frame: &FrameOutput<'_>,
978        has_tick: bool,
979    ) -> Result<Option<PresentStats>, PlatformError> {
980        match self {
981            Presenter::Cpu(cpu) => cpu.render_and_present(frame, has_tick),
982            #[cfg(feature = "gpu")]
983            Presenter::Gpu(gpu) => gpu.render_and_present(frame, has_tick),
984        }
985    }
986
987    /// Takes the window's new extent.
988    fn resize(&mut self, width: NonZeroU32, height: NonZeroU32) -> Result<(), PlatformError> {
989        match self {
990            Presenter::Cpu(cpu) => cpu.resize(width, height),
991            #[cfg(feature = "gpu")]
992            Presenter::Gpu(gpu) => gpu.resize(width.get(), height.get()),
993        }
994    }
995
996    /// Returns the GPU adapter this presenter draws on, when it has one.
997    #[cfg(feature = "gpu")]
998    fn adapter_summary(&self) -> Option<AdapterSummary> {
999        match self {
1000            Presenter::Cpu(_) => None,
1001            Presenter::Gpu(gpu) => Some(gpu.adapter_summary()),
1002        }
1003    }
1004}
1005
1006/// The CPU renderer and the software surface it presents through.
1007struct CpuPresenter {
1008    /// The surface that holds the pixels the window shows.
1009    surface: Surface<SharedWindow, SharedWindow>,
1010    /// The window whose frames are presented, notified before every present.
1011    window: SharedWindow,
1012    /// The renderer that paints the app's damage into a pixmap.
1013    renderer: CpuRenderer,
1014    /// Whether every present tints the frame's damage in the window buffer.
1015    show_damage: bool,
1016    /// The damage of the frames the window presented, most recent first.
1017    history: DamageHistory,
1018    /// The frame that was rendered and waits to be presented.
1019    pending: PendingFrame,
1020    /// Scratch for the region the next present copies.
1021    region: Vec<PixelRect>,
1022    /// Scratch for the damage rectangles the next present reports.
1023    reported: Vec<softbuffer::Rect>,
1024}
1025
1026impl CpuPresenter {
1027    /// Creates the surface and the renderer for a window of `width` by
1028    /// `height` physical pixels.
1029    ///
1030    /// A window without pixels gets a renderer without pixels, and nothing
1031    /// presents until a resize gives the window an extent.
1032    fn new(
1033        window: &Arc<Window>,
1034        width: u32,
1035        height: u32,
1036        show_damage: bool,
1037    ) -> Result<CpuPresenter, PlatformError> {
1038        let shared = SharedWindow(Arc::clone(window));
1039        let context = Context::new(shared.clone()).map_err(PlatformError::Surface)?;
1040        let mut surface = Surface::new(&context, shared).map_err(PlatformError::Surface)?;
1041        drop(context);
1042        if let (Some(width), Some(height)) = (NonZeroU32::new(width), NonZeroU32::new(height)) {
1043            surface
1044                .resize(width, height)
1045                .map_err(PlatformError::Surface)?;
1046        }
1047        Ok(CpuPresenter {
1048            surface,
1049            window: SharedWindow(Arc::clone(window)),
1050            renderer: CpuRenderer::new(width, height),
1051            show_damage,
1052            history: DamageHistory::default(),
1053            pending: PendingFrame::default(),
1054            region: Vec::new(),
1055            reported: Vec::new(),
1056        })
1057    }
1058
1059    /// Takes the window's new extent.
1060    fn resize(&mut self, width: NonZeroU32, height: NonZeroU32) -> Result<(), PlatformError> {
1061        self.surface
1062            .resize(width, height)
1063            .map_err(PlatformError::Surface)?;
1064        self.renderer.resize(width.get(), height.get());
1065        Ok(())
1066    }
1067
1068    /// Renders the frame into the pixmap and presents it.
1069    ///
1070    /// The frame's damage and the recorded damage of the frames still in a
1071    /// window buffer decide what has to be copied: when the buffer's age says
1072    /// which frame it holds and no full repaint happened since, only their
1073    /// union is copied and the present reports it as damage. Every other case
1074    /// copies the whole pixmap. While the damage overlay is on, every present
1075    /// copies the whole pixmap and blends the tint over the frame's damage in
1076    /// the window buffer, so no tint reaches the pixmap a later frame repaints
1077    /// from. It also presents a frame with no damage, so a tint never outlives
1078    /// the frame that caused it.
1079    ///
1080    /// Before every present, the window is told that a frame is about to
1081    /// show. That is the order winit needs on Wayland: it schedules a frame
1082    /// callback that paces the next redraw, and presenting without it lets
1083    /// frames outrun the compositor until a present waits forever for the
1084    /// buffer it cannot get.
1085    ///
1086    /// Returns the statistics of a presented frame, or `Ok(None)` when nothing
1087    /// was presented: a surface without pixels, or a frame with no damage
1088    /// while a tick runs. Such a frame changed nothing, so the window still
1089    /// shows the last present, no buffer is taken, and the frame limit does
1090    /// not count it. An empty frame without a tick is a redraw the window
1091    /// system asked for, such as an X11 window being shown again, and presents
1092    /// the whole pixmap to restore the window's contents.
1093    fn render_and_present(
1094        &mut self,
1095        frame: &FrameOutput<'_>,
1096        has_tick: bool,
1097    ) -> Result<Option<PresentStats>, PlatformError> {
1098        let (width, height) = (self.renderer.width(), self.renderer.height());
1099        if width == 0 || height == 0 {
1100            return Ok(None);
1101        }
1102        let raster_start = Instant::now();
1103        self.renderer.render(frame);
1104        let raster = raster_start.elapsed();
1105        self.pending.damage.clear();
1106        self.pending.damage.extend_from_slice(frame.damage);
1107        self.pending.full = frame.full;
1108        if present_skipped(&self.pending.damage, has_tick, self.show_damage) {
1109            return Ok(None);
1110        }
1111        let acquire_start = Instant::now();
1112        let mut buffer = self.surface.buffer_mut().map_err(PlatformError::Surface)?;
1113        let acquire = acquire_start.elapsed();
1114        let present_start = Instant::now();
1115        debug_assert_eq!(
1116            buffer.len(),
1117            self.renderer.pixels().len() / 4,
1118            "the window buffer must hold one entry per rendered pixel"
1119        );
1120        let age = buffer.age();
1121        let with_damage = !self.show_damage
1122            && damage_region(
1123                age,
1124                &self.pending.damage,
1125                self.pending.full,
1126                &self.history,
1127                &mut self.region,
1128            );
1129        let mut bytes_copied = 0;
1130        if with_damage {
1131            self.reported.clear();
1132            for rect in &self.region {
1133                let (Some(rect_width), Some(rect_height)) =
1134                    (NonZeroU32::new(rect.width), NonZeroU32::new(rect.height))
1135                else {
1136                    continue;
1137                };
1138                bytes_copied += u64::from(rect.width) * u64::from(rect.height) * 4;
1139                self.reported.push(softbuffer::Rect {
1140                    x: rect.x,
1141                    y: rect.y,
1142                    width: rect_width,
1143                    height: rect_height,
1144                });
1145            }
1146            for &rect in &self.region {
1147                copy_rect(self.renderer.pixels(), width, &mut buffer, rect);
1148            }
1149            self.window.0.pre_present_notify();
1150            buffer
1151                .present_with_damage(&self.reported)
1152                .map_err(PlatformError::Surface)?;
1153        } else {
1154            bytes_copied = u64::from(width) * u64::from(height) * 4;
1155            copy_rect(
1156                self.renderer.pixels(),
1157                width,
1158                &mut buffer,
1159                PixelRect {
1160                    x: 0,
1161                    y: 0,
1162                    width,
1163                    height,
1164                },
1165            );
1166            if self.show_damage {
1167                tint_rects(&mut buffer, width, &self.pending.damage);
1168            }
1169            self.window.0.pre_present_notify();
1170            buffer.present().map_err(PlatformError::Surface)?;
1171        }
1172        self.history.push(&self.pending.damage, self.pending.full);
1173        Ok(Some(PresentStats {
1174            buffer_age: age,
1175            with_damage,
1176            bytes_copied,
1177            surface_width: width,
1178            surface_height: height,
1179            raster,
1180            acquire,
1181            present: present_start.elapsed(),
1182            renderer: "cpu",
1183        }))
1184    }
1185}
1186
1187/// Converts a physical size to logical pixels for `scale`.
1188fn logical_size(physical: PhysicalSize<u32>, scale: f32) -> Size {
1189    Size {
1190        width: physical.width as f32 / scale,
1191        height: physical.height as f32 / scale,
1192    }
1193}
1194
1195/// What the window does about the next timer.
1196#[derive(Copy, Clone, Debug, PartialEq, Eq)]
1197enum WaitDecision {
1198    /// A timer is already due: run the frame that fires it now.
1199    Now,
1200    /// Sleep until the soonest timer fires.
1201    Until(Instant),
1202    /// No timer is scheduled: wait for events.
1203    Wait,
1204}
1205
1206/// Decides what the window does about the next timer due at `deadline`.
1207fn wait_decision(deadline: Option<Instant>, now: Instant) -> WaitDecision {
1208    match deadline {
1209        Some(deadline) if deadline <= now => WaitDecision::Now,
1210        Some(deadline) => WaitDecision::Until(deadline),
1211        None => WaitDecision::Wait,
1212    }
1213}
1214
1215/// A window handle that the presentation surfaces can own.
1216///
1217/// The surfaces store the window they present to and the platform state keeps
1218/// the window too, so both hold the same allocation. The `Arc` makes the
1219/// handle sendable, which the wgpu surface target requires.
1220#[derive(Clone)]
1221pub(crate) struct SharedWindow(pub(crate) Arc<Window>);
1222
1223impl HasWindowHandle for SharedWindow {
1224    fn window_handle(&self) -> Result<WindowHandle<'_>, HandleError> {
1225        self.0.window_handle()
1226    }
1227}
1228
1229impl HasDisplayHandle for SharedWindow {
1230    fn display_handle(&self) -> Result<DisplayHandle<'_>, HandleError> {
1231        self.0.display_handle()
1232    }
1233}
1234
1235#[cfg(test)]
1236#[path = "window_tests.rs"]
1237mod tests;