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;