denise_winit/lib.rs
1//! Desktop development and preview backend for Denise.
2//!
3//! This backend exists so the core abstraction can be proven — and iterated on —
4//! without a Raspberry Pi on the desk. It is not a deployment target: shipping
5//! Denise on a desktop means shipping a compositor you did not need.
6//!
7//! Pixels come from the software rasteriser into a buffer the compositor
8//! uploads — or, behind the `gpu` feature and [`Present::Gpu`], from
9//! `denise-wgpu` into a swapchain. The second is for the designer on a large
10//! display; an application chooses it per window and draws through
11//! [`DeniseApp::paint`], which is the same call on either path.
12//!
13//! ```no_run
14//! use denise::{Color, DamageTracker, Frame, InputEvent, Rect};
15//! use denise_render::Canvas;
16//! use denise_winit::{DeniseApp, WindowConfig, run};
17//!
18//! struct Hello;
19//!
20//! impl DeniseApp for Hello {
21//! fn update(&mut self, _events: &[InputEvent], _damage: &mut DamageTracker) {}
22//!
23//! fn render(&mut self, frame: &mut Frame<'_>, damage: &[Rect]) {
24//! let mut canvas = Canvas::new(frame);
25//! for region in damage {
26//! canvas.with_clip(*region).clear(Color::from_rgb888(0x1E1E2E));
27//! }
28//! }
29//! }
30//!
31//! run(WindowConfig::default(), Hello).unwrap();
32//! ```
33
34#[cfg(feature = "gpu")]
35mod gpu;
36mod keymap;
37#[cfg(target_os = "macos")]
38mod macos;
39mod owner;
40mod runner;
41#[cfg(not(target_os = "macos"))]
42mod surface;
43
44use std::time::Duration;
45
46use denise::{BufferAge, DamageTracker, Frame, InputEvent, Pen, Point, Rect, Size};
47use winit::event_loop::{EventLoop, EventLoopProxy};
48
49use runner::Runner;
50
51#[cfg(feature = "gpu")]
52pub use gpu::GpuSurface;
53#[cfg(target_os = "macos")]
54pub use macos::MacSurface;
55#[cfg(not(target_os = "macos"))]
56pub use surface::WinitSurface;
57
58/// The surface this backend presents through, which is not the same everywhere.
59///
60/// softbuffer on every platform but one; on macOS an `IOSurface` handed straight
61/// to the window's layer, because softbuffer's CoreGraphics backend copies the
62/// whole surface three times per present and ignores the damage. See
63/// [`macos`](self) for the measurements.
64#[cfg(target_os = "macos")]
65type PlatformSurface = MacSurface;
66#[cfg(not(target_os = "macos"))]
67type PlatformSurface = WinitSurface;
68
69/// Nominal pixels per wheel notch, for platforms that report scroll in lines.
70const LINE_HEIGHT_PX: f32 = 16.0;
71
72/// Failures from this backend.
73#[derive(Debug)]
74pub enum Error {
75 /// The event loop could not be created or run.
76 EventLoop(winit::error::EventLoopError),
77
78 /// The window could not be created.
79 Window(winit::error::OsError),
80
81 /// softbuffer could not bind to the window or present.
82 ///
83 /// Absent on macOS, which does not present through softbuffer.
84 #[cfg(not(target_os = "macos"))]
85 Softbuffer(softbuffer::SoftBufferError),
86
87 /// A surface operation failed.
88 Surface(denise::SurfaceError),
89
90 /// The platform's presentation path could not be set up.
91 Present(String),
92
93 /// The GPU path was asked for and could not be taken: no adapter, a device
94 /// that would not open, or a build without the `gpu` feature.
95 Gpu(String),
96}
97
98impl core::fmt::Display for Error {
99 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
100 match self {
101 Self::EventLoop(err) => write!(f, "event loop: {err}"),
102 Self::Window(err) => write!(f, "window creation: {err}"),
103 #[cfg(not(target_os = "macos"))]
104 Self::Softbuffer(err) => write!(f, "softbuffer: {err}"),
105 Self::Surface(err) => core::fmt::Display::fmt(err, f),
106 Self::Present(msg) => write!(f, "present: {msg}"),
107 Self::Gpu(msg) => write!(f, "gpu: {msg}"),
108 }
109 }
110}
111
112impl core::error::Error for Error {
113 fn source(&self) -> Option<&(dyn core::error::Error + 'static)> {
114 match self {
115 Self::EventLoop(err) => Some(err),
116 Self::Window(err) => Some(err),
117 #[cfg(not(target_os = "macos"))]
118 Self::Softbuffer(err) => Some(err),
119 Self::Surface(err) => core::error::Error::source(err),
120 Self::Present(_) | Self::Gpu(_) => None,
121 }
122 }
123}
124
125impl From<winit::error::EventLoopError> for Error {
126 fn from(err: winit::error::EventLoopError) -> Self {
127 Self::EventLoop(err)
128 }
129}
130
131impl From<winit::error::OsError> for Error {
132 fn from(err: winit::error::OsError) -> Self {
133 Self::Window(err)
134 }
135}
136
137#[cfg(not(target_os = "macos"))]
138impl From<softbuffer::SoftBufferError> for Error {
139 fn from(err: softbuffer::SoftBufferError) -> Self {
140 Self::Softbuffer(err)
141 }
142}
143
144impl From<denise::SurfaceError> for Error {
145 fn from(err: denise::SurfaceError) -> Self {
146 Self::Surface(err)
147 }
148}
149
150/// What draws a window's pixels.
151#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
152pub enum Present {
153 /// The software rasteriser into a buffer of words, copied to the
154 /// compositor by damage. The default, and what every kiosk does.
155 #[default]
156 Software,
157 /// `denise-wgpu` into a swapchain, every frame a full repaint.
158 ///
159 /// Needs the `gpu` feature and an application that implements
160 /// [`DeniseApp::paint`]; without either, opening the window fails with
161 /// [`Error::Gpu`] rather than silently drawing the other way. For the
162 /// designer on a large display, not for a preview of a panel.
163 Gpu,
164 /// [`Gpu`](Self::Gpu) where a GPU can present to the window, and
165 /// [`Software`](Self::Software) where none can — decided for each window
166 /// as it opens, with the reason written to stderr when it falls back.
167 ///
168 /// For an application that ships to machines nobody has seen: a virtual
169 /// machine, a remote display, a driver that is not there. The fallback has
170 /// to happen here rather than around [`run_with`], because winit allows
171 /// one event loop per process and a failed run cannot be started again.
172 /// Without the `gpu` feature it is `Software`.
173 ///
174 /// The application should implement [`DeniseApp::paint`], as for `Gpu`.
175 GpuOrSoftware,
176}
177
178/// How the preview window is created.
179#[derive(Clone, Debug)]
180pub struct WindowConfig {
181 /// Window title.
182 pub title: String,
183 /// Initial inner size in **logical** pixels.
184 ///
185 /// Logical, not physical, so one number describes the same amount of desk on
186 /// every machine: a panel designed at 800×480 covers 800×480 of a Pi's
187 /// framebuffer and the same apparent area on a 2× Retina display, where the
188 /// surface it gets is 1600×960 physical pixels. Asking in physical pixels
189 /// instead is how a window ends up a quarter of its intended size on a Mac.
190 ///
191 /// The surface — and therefore every coordinate the application works in —
192 /// stays physical. Scaling the content to match is the application's job, and
193 /// it is handed the factor at construction by [`run_with`].
194 pub size: Size,
195 /// Whether the user may resize the window.
196 pub resizable: bool,
197 /// Target frame interval. Defaults to 60 Hz.
198 ///
199 /// The loop sleeps until the next deadline rather than spinning, so an idle UI
200 /// costs close to nothing — which is the behaviour we actually care about on
201 /// the target hardware.
202 pub frame_interval: Duration,
203 /// What draws the pixels. See [`Present`].
204 pub present: Present,
205 /// Open at the size of the monitor instead of at [`size`](Self::size).
206 ///
207 /// For an application that is going to cover the screen anyway — a kiosk, a
208 /// panel, anything a window manager is about to fullscreen — where asking
209 /// for a window the screen cannot hold is asking to be resized on the way
210 /// up. Some window managers hand such a window a size, then the size it
211 /// asked for, then the size again, and what reaches the glass afterwards is
212 /// not always the last of them.
213 ///
214 /// Falls back to `size` when no monitor can be identified, which is the
215 /// case on some headless and remote displays.
216 pub fill_monitor: bool,
217 /// Where to put the window's top-left, frame included, in the desktop's
218 /// **physical** pixels — or `None` to let the window manager place it.
219 ///
220 /// The units are the ones
221 /// [`InputEvent::SurfaceMoved`] reports
222 /// in, so an application that keeps what it was last told and hands it back
223 /// here opens where it closed, with nothing to convert on the way. Logical
224 /// pixels are right for [`size`](Self::size) and wrong for this: a desktop
225 /// spanning a Retina display and a 1× one beside it has no single logical
226 /// grid to name a point in.
227 ///
228 /// A position no monitor covers is ignored rather than honoured — a
229 /// display that has since been unplugged would otherwise open the window
230 /// where nobody can reach it.
231 pub position: Option<Point>,
232 /// Open maximised, whatever [`size`](Self::size) says.
233 ///
234 /// The size is still worth setting: it is what the window goes back to when
235 /// the user un-maximises it.
236 pub maximized: bool,
237 /// The name the desktop knows this application by — Wayland's `app_id`,
238 /// X11's `WM_CLASS` — or `None` for none.
239 ///
240 /// It is what ties a window to the application's desktop entry, so a
241 /// launcher and a task bar show its name and icon, and what a window
242 /// manager's rules match. Give it the entry's file name without
243 /// `.desktop`: a window of `squint.desktop` says `squint`. Without one a
244 /// Wayland compositor has nothing to go on, and every window rule and
245 /// taskbar icon for the application is lost.
246 ///
247 /// Set it on every window the application opens, the secondary ones
248 /// included: each is a window of its own to the compositor. Ignored on
249 /// macOS and Windows, which know an application by its bundle and its
250 /// executable.
251 pub app_id: Option<String>,
252}
253
254impl Default for WindowConfig {
255 fn default() -> Self {
256 Self {
257 title: "Denise".into(),
258 size: Size::new(800, 480),
259 resizable: true,
260 frame_interval: Duration::from_nanos(1_000_000_000 / 60),
261 present: Present::Software,
262 fill_monitor: false,
263 position: None,
264 maximized: false,
265 app_id: None,
266 }
267 }
268}
269
270/// An application driven by this backend.
271///
272/// This is M0 scaffolding, not the eventual public API. From M3 the scene stack and
273/// component tree sit between the application and these two methods.
274pub trait DeniseApp {
275 /// Handles input and records what that changed.
276 ///
277 /// Marking damage here rather than during `render` is deliberate: the renderer
278 /// needs to know what to repaint *before* it starts, and on a real swapchain
279 /// the region it must cover is wider than what changed this frame.
280 fn update(&mut self, events: &[InputEvent], damage: &mut DamageTracker);
281
282 /// Draws the frame.
283 ///
284 /// `damage` is the region that must be repainted for this particular buffer,
285 /// already widened for its age and clipped to the surface. Drawing outside it
286 /// is wasted work; drawing less than it leaves stale pixels.
287 fn render(&mut self, frame: &mut Frame<'_>, damage: &[Rect]) {
288 let age = frame.age();
289 let mut canvas = denise_render::Canvas::new(frame);
290 let drawn = self.paint(&mut canvas.pen(), age, damage);
291 assert!(
292 drawn,
293 "a DeniseApp must implement `render` or `paint`; this one implements neither"
294 );
295 }
296
297 /// Draws every region in `damage` through `pen`, whatever is behind it.
298 ///
299 /// The painter-agnostic half of [`render`](DeniseApp::render): the same
300 /// call reaches a `Frame` through the rasteriser and a swapchain through
301 /// `denise-wgpu`. `age` is what the target remembers of previous frames —
302 /// [`BufferAge::Undefined`] on the GPU, where every frame starts blank — and
303 /// a `Ui` wants it for [`paint_with`](https://docs.rs/denise-ui).
304 ///
305 /// Return `true` if this application draws this way. The default returns
306 /// `false`, which means "frames only": the software path keeps calling
307 /// `render`, and the GPU path refuses the window with [`Error::Gpu`]
308 /// instead of showing nothing.
309 ///
310 /// Implement this alone and `render` is provided: a `Canvas` over the frame,
311 /// then this. Implement both when the frame path wants something a pen
312 /// cannot offer, which for a `Ui` is the scroll optimisation `Ui::paint`
313 /// does with the frame's own words.
314 fn paint(&mut self, pen: &mut Pen<'_>, age: BufferAge, damage: &[Rect]) -> bool {
315 let _ = (pen, age, damage);
316 false
317 }
318
319 /// Return `true` to quit after the current frame.
320 fn exit_requested(&self) -> bool {
321 false
322 }
323
324 /// What the title bar should say, when the application wants a say in it.
325 ///
326 /// [`WindowConfig::title`] is read once, when the window is made, which is
327 /// enough for a window whose title is a constant and not enough for one
328 /// naming a document: a file opened after start-up leaves the bar naming
329 /// whatever was open before it.
330 ///
331 /// Asked once a frame and compared with what the window was last given, so
332 /// an application answering the same string pays a comparison rather than a
333 /// trip through the window system. Borrowed rather than owned for the same
334 /// reason -- an answer built fresh each frame would allocate for every one
335 /// of them. `None` leaves the title alone.
336 fn title(&self) -> Option<&str> {
337 None
338 }
339
340 /// How long the loop may sleep before asking for another frame.
341 ///
342 /// The default — `Some(Duration::ZERO)` — means "as often as
343 /// [`WindowConfig::frame_interval`] allows", which is what this backend has
344 /// always done. Answering with a longer wait, or with `None` for "nothing is
345 /// animating, wake me on input", is how an application stops the loop doing
346 /// work nobody asked for.
347 ///
348 /// A tree already knows the answer: `Ui::next_wake_ms` is the deadline of the
349 /// most impatient animation in it. Ignoring it is not free. A `Spinner` asks
350 /// to be woken every 50 ms and moves its arc exactly that often; ticked at
351 /// 60 Hz instead it reports a repaint three times as often as it has anything
352 /// new to show, and every one of those is a present. The kiosk backends have
353 /// always slept on `next_wake_ms` — this is what lets a window agree with
354 /// them.
355 ///
356 /// Input does not wait for this: an event wakes the loop immediately,
357 /// whatever was asked for here.
358 fn next_frame_in(&self) -> Option<Duration> {
359 Some(Duration::ZERO)
360 }
361
362 /// Whether the window manager's close request should end the run.
363 ///
364 /// Defaults to `true`, because a close button that does not close is a bug in
365 /// every application that has not deliberately decided otherwise. The request
366 /// is also queued for [`update`](DeniseApp::update) as
367 /// [`InputEvent::CloseRequested`], but an accepted close takes effect at once
368 /// and a window on its way out is not drawn again, so `update` may never see
369 /// it. Saving on the way out belongs in [`exiting`](DeniseApp::exiting)
370 /// instead, which every way out reaches — including the ones that never ask
371 /// this at all, like ⌘Q on macOS.
372 ///
373 /// Override it to `false` to *veto* the close — an unsaved-changes prompt is
374 /// the reason to, and the application then quits by way of
375 /// [`exit_requested`](DeniseApp::exit_requested) once the answer comes back.
376 /// A veto is only as good as that follow-up: an application that never sets
377 /// `exit_requested` has made its window unclosable by anything short of the
378 /// platform's own kill.
379 fn close_requested(&mut self) -> bool {
380 true
381 }
382
383 /// The last call this application gets: its window is closing or the run is
384 /// ending, and nothing here will be asked anything again.
385 ///
386 /// Called once for every window, whatever ends it — its close button,
387 /// [`exit_requested`](DeniseApp::exit_requested), the window that opened it
388 /// closing, the main window closing, an error, and the ways out that never
389 /// pass through a window at all: on macOS, ⌘Q and the application menu's
390 /// Quit, Quit from the Dock, and logging out. Those last ones send no close
391 /// request and no further [`update`](DeniseApp::update), and the process
392 /// ends as soon as this returns: [`run`] and [`run_with`] never return, so
393 /// nothing written after them runs, and neither does `Drop`. That makes this
394 /// the one place saving on the way out is sure to happen.
395 ///
396 /// It cannot stop anything, so it answers nothing — by the time it is called
397 /// the decision has been made. Asking first is
398 /// [`close_requested`](DeniseApp::close_requested), which only a window's own
399 /// close request reaches.
400 ///
401 /// A window is told before the window that opened it, and the main window
402 /// last, so a form that writes into state it shares with its owner has
403 /// written it before the owner saves. Nothing is drawn while this runs, and
404 /// at logout the system is waiting on it: keep it to the saving.
405 fn exiting(&mut self) {}
406
407 /// Handed the run's [`Waker`] once, as the window opens, before the first
408 /// frame.
409 ///
410 /// For an application with work arriving from somewhere the loop cannot
411 /// see — a socket, a file watcher, another thread — which would otherwise
412 /// have to answer [`next_frame_in`](DeniseApp::next_frame_in) with a short
413 /// wait forever just to look. Keep it, or a clone of it where the work
414 /// arrives, and let the loop sleep.
415 fn set_waker(&mut self, waker: Waker) {
416 let _ = waker;
417 }
418
419 /// Told how the window draws once it is open, before the first frame:
420 /// [`Present::Gpu`] or [`Present::Software`], never
421 /// [`Present::GpuOrSoftware`].
422 ///
423 /// Where [`Present::GpuOrSoftware`] was asked for, this is the answer, and
424 /// it is worth keeping: a machine where no GPU could present has paid for
425 /// finding out — graphics drivers loaded, tried and given up on, which can
426 /// be seconds and tens of megabytes that stay mapped — and the next run, or
427 /// the next window, can ask for [`Present::Software`] and skip it.
428 fn presenting(&mut self, present: Present) {
429 let _ = present;
430 }
431
432 /// Windows this application wants opened, taken once per frame.
433 ///
434 /// This is the whole of the secondary-window API, and what it hands back is
435 /// another [`DeniseApp`] — so a settings form, an "edit details" window and
436 /// the main window are the same kind of thing, built the same way, running in
437 /// the same loop. The backend supplies a window, a surface and a place in the
438 /// event loop; **what is inside one is entirely the application's**, exactly
439 /// as `Ui::push_scene` knows nothing about the scene it pushed.
440 ///
441 /// Called immediately after [`update`](DeniseApp::update) on every frame,
442 /// including frames that draw nothing. Returning the same request twice opens
443 /// two windows: an application that must not open its settings form twice
444 /// remembers that it has one open, which it needs to do anyway to know what to
445 /// tell the second click.
446 ///
447 /// The new window is owned by the window whose application asked for it — so
448 /// a modal opened from a settings form is modal to *that* form, not to the
449 /// main window, and closing the form takes the modal with it.
450 ///
451 /// # Talking to a window you opened
452 ///
453 /// Nothing here carries state back, on purpose. A form is built by the
454 /// application, so the application can give it whatever it likes to hold —
455 /// and `Rc<RefCell<_>>` is the whole mechanism:
456 ///
457 /// ```no_run
458 /// # use std::cell::RefCell;
459 /// # use std::rc::Rc;
460 /// #[derive(Default)]
461 /// struct Settings {
462 /// brightness: u8,
463 /// /// Set by the form when it wants to go; read by its `exit_requested`.
464 /// closing: bool,
465 /// }
466 ///
467 /// // The main window keeps one handle, the form gets another. Whichever one
468 /// // writes, both see it.
469 /// let shared = Rc::new(RefCell::new(Settings::default()));
470 /// let for_the_form = shared.clone();
471 /// ```
472 ///
473 /// The form's `exit_requested` returns `shared.borrow().closing`, which is
474 /// also how the main window closes it from the outside. Nothing in the
475 /// backend needs to know any of this happened.
476 fn take_windows(&mut self) -> Vec<WindowRequest> {
477 Vec::new()
478 }
479}
480
481/// Opens a window and runs `app` until it exits.
482///
483/// The application is built before the window exists, so it cannot know the
484/// display's scale factor. On a 1× display that is exactly right; on a HiDPI one
485/// it means a tree laid out in physical pixels comes out half size. Use
486/// [`run_with`] there.
487pub fn run<A: DeniseApp + 'static>(config: WindowConfig, app: A) -> Result<(), Error> {
488 run_with(config, move |_, _| app)
489}
490
491/// Opens a window and builds the application once the surface behind it is known.
492///
493/// The builder is handed the surface size in **physical** pixels and the display's
494/// scale factor — the two facts a scale-aware tree needs and cannot obtain any
495/// earlier. This is the whole of Denise's DPI story on the desktop: the application
496/// scales, once, at construction, through `Theme::scaled`, `Rect::scaled` and its
497/// own text sizes. Coordinates stay physical everywhere afterwards.
498///
499/// A later scale change — dragging the window to a display with a different DPI —
500/// arrives as [`InputEvent::SurfaceResized`], carrying the new factor. An
501/// application that wants to follow it rebuilds its tree there; one that does not
502/// keeps the scale it was built with and is merely sized wrong on the second
503/// display.
504pub fn run_with<A, B>(config: WindowConfig, build: B) -> Result<(), Error>
505where
506 A: DeniseApp + 'static,
507 B: FnOnce(Size, f32) -> A + 'static,
508{
509 let event_loop = EventLoop::with_user_event().build()?;
510 let waker = Waker(event_loop.create_proxy());
511 let mut runner = Runner::new(config, boxed(build), waker);
512 event_loop.run_app(&mut runner)?;
513 match runner.error {
514 Some(err) => Err(err),
515 None => Ok(()),
516 }
517}
518
519/// How a secondary window relates to the one that opened it.
520///
521/// How much of this the platform will actually enforce differs sharply: Windows
522/// does all of it, macOS keeps the z-order and blocks nothing, and X11 and Wayland
523/// offer neither through winit. So the one guarantee that holds everywhere is
524/// [`Modal`](Modality::Modal) input blocking, because the runner does that itself
525/// rather than asking. The rest is appearance, and the crate's README has the
526/// table.
527#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
528pub enum Modality {
529 /// Above its owner, closed with it, and input still reaches both.
530 ///
531 /// The default, and what a settings form or an "edit details" window wants: a
532 /// window belonging to this application rather than a second application that
533 /// happens to share a process. The user can keep working in the main window
534 /// while it is open.
535 #[default]
536 Owned,
537
538 /// Owned, and the owner takes no input until this window closes.
539 ///
540 /// The window-sized version of `Ui::push_scene` with a dim: a confirmation, a
541 /// wizard, anything the user must answer before going back. The owner keeps
542 /// drawing — animations there do not freeze — it simply stops listening, and
543 /// a press on it raises this window instead.
544 Modal,
545
546 /// A window of its own, with no relationship to its opener at all.
547 ///
548 /// A second document window, or a tool palette the user may want behind the
549 /// main window. It does not close when its opener does, so an application that
550 /// opens one is responsible for it — including for the fact that closing the
551 /// main window ends the run and takes it down regardless.
552 Independent,
553}
554
555/// A window the application wants opened, and the application that will run in it.
556///
557/// Handed back from [`DeniseApp::take_windows`]. The builder is called once, at
558/// the moment the window's surface exists, and is given its size in **physical**
559/// pixels and its display's scale factor — the same contract [`run_with`] makes
560/// for the main window, and for the same reason: a form built without them is
561/// laid out for a display it may not be opening on.
562pub struct WindowRequest {
563 /// Title, size, resizability and frame cadence for the new window.
564 pub config: WindowConfig,
565 /// How it relates to the window that asked for it.
566 pub modality: Modality,
567 pub(crate) build: Build,
568}
569
570impl WindowRequest {
571 /// A window of `config`, whose application is built once its surface exists.
572 ///
573 /// [`Modality::Owned`] unless [`with_modality`](WindowRequest::with_modality)
574 /// says otherwise.
575 pub fn new<A, B>(config: WindowConfig, build: B) -> Self
576 where
577 A: DeniseApp + 'static,
578 B: FnOnce(Size, f32) -> A + 'static,
579 {
580 Self {
581 config,
582 modality: Modality::default(),
583 build: boxed(build),
584 }
585 }
586
587 /// A window whose application is already built.
588 ///
589 /// Convenient, and wrong on a HiDPI display unless the tree inside it does not
590 /// care about scale: the application cannot have been told the scale factor,
591 /// because the window it will open on does not exist yet.
592 /// [`new`](WindowRequest::new) is the one to reach for.
593 pub fn ready<A: DeniseApp + 'static>(config: WindowConfig, app: A) -> Self {
594 Self::new(config, move |_, _| app)
595 }
596
597 /// Sets how this window relates to the one opening it.
598 #[must_use]
599 pub fn with_modality(self, modality: Modality) -> Self {
600 Self { modality, ..self }
601 }
602}
603
604/// Builds an application once the surface it will draw to is known.
605///
606/// Boxed because the windows in one run do not share an application type — a
607/// settings form is not the main window with different data, it is a different
608/// program — and the loop holds them in one collection.
609type Build = Box<dyn FnOnce(Size, f32) -> Box<dyn DeniseApp>>;
610
611/// Wakes a running loop from any thread, so every window is asked for a frame
612/// as soon as one can be drawn.
613///
614/// An application's [`update`](DeniseApp::update) then runs with whatever
615/// input there is, possibly none, and can take up what the waking thread left
616/// for it. Waking a loop that is already awake costs a message; waking one that
617/// has finished does nothing. See [`DeniseApp::set_waker`].
618#[derive(Clone, Debug)]
619pub struct Waker(EventLoopProxy<()>);
620
621impl Waker {
622 /// Asks for a frame in every window.
623 pub fn wake(&self) {
624 // The only failure is a loop that has already ended, which has nothing
625 // left to draw.
626 let _ = self.0.send_event(());
627 }
628}
629
630// A waker is for handing to other threads, so it has to be allowed there.
631const _: fn() = || {
632 fn shareable<T: Send + Sync>() {}
633 shareable::<Waker>();
634};
635
636/// Erases a builder's application type.
637fn boxed<A, B>(build: B) -> Build
638where
639 A: DeniseApp + 'static,
640 B: FnOnce(Size, f32) -> A + 'static,
641{
642 Box::new(move |size, scale| Box::new(build(size, scale)))
643}
644
645/// Compiles the examples in this crate's README, so they cannot drift from the API
646/// they claim to demonstrate. Never built except under `cargo test --doc`.
647#[cfg(doctest)]
648#[doc = include_str!("../README.md")]
649struct Readme;