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