denise-winit 0.31.0

Desktop development and preview backend for Denise, on winit + softbuffer.
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
//! Desktop development and preview backend for Denise.
//!
//! This backend exists so the core abstraction can be proven — and iterated on —
//! without a Raspberry Pi on the desk. It is not a deployment target: shipping
//! Denise on a desktop means shipping a compositor you did not need.
//!
//! Pixels come from the software rasteriser into a buffer the compositor
//! uploads — or, behind the `gpu` feature and [`Present::Gpu`], from
//! `denise-wgpu` into a swapchain. The second is for the designer on a large
//! display; an application chooses it per window and draws through
//! [`DeniseApp::paint`], which is the same call on either path.
//!
//! ```no_run
//! use denise::{Color, DamageTracker, Frame, InputEvent, Rect};
//! use denise_render::Canvas;
//! use denise_winit::{DeniseApp, WindowConfig, run};
//!
//! struct Hello;
//!
//! impl DeniseApp for Hello {
//!     fn update(&mut self, _events: &[InputEvent], _damage: &mut DamageTracker) {}
//!
//!     fn render(&mut self, frame: &mut Frame<'_>, damage: &[Rect]) {
//!         let mut canvas = Canvas::new(frame);
//!         for region in damage {
//!             canvas.with_clip(*region).clear(Color::from_rgb888(0x1E1E2E));
//!         }
//!     }
//! }
//!
//! run(WindowConfig::default(), Hello).unwrap();
//! ```

#[cfg(feature = "gpu")]
mod gpu;
mod keymap;
#[cfg(target_os = "macos")]
mod macos;
mod owner;
mod runner;
#[cfg(not(target_os = "macos"))]
mod surface;

use std::time::Duration;

use denise::{BufferAge, DamageTracker, Frame, InputEvent, Pen, Point, Rect, Size};
use winit::event_loop::{EventLoop, EventLoopProxy};

use runner::Runner;

#[cfg(feature = "gpu")]
pub use gpu::GpuSurface;
#[cfg(target_os = "macos")]
pub use macos::MacSurface;
#[cfg(not(target_os = "macos"))]
pub use surface::WinitSurface;

/// The surface this backend presents through, which is not the same everywhere.
///
/// softbuffer on every platform but one; on macOS an `IOSurface` handed straight
/// to the window's layer, because softbuffer's CoreGraphics backend copies the
/// whole surface three times per present and ignores the damage. See
/// [`macos`](self) for the measurements.
#[cfg(target_os = "macos")]
type PlatformSurface = MacSurface;
#[cfg(not(target_os = "macos"))]
type PlatformSurface = WinitSurface;

/// Nominal pixels per wheel notch, for platforms that report scroll in lines.
const LINE_HEIGHT_PX: f32 = 16.0;

/// Failures from this backend.
#[derive(Debug)]
pub enum Error {
    /// The event loop could not be created or run.
    EventLoop(winit::error::EventLoopError),

    /// The window could not be created.
    Window(winit::error::OsError),

    /// softbuffer could not bind to the window or present.
    ///
    /// Absent on macOS, which does not present through softbuffer.
    #[cfg(not(target_os = "macos"))]
    Softbuffer(softbuffer::SoftBufferError),

    /// A surface operation failed.
    Surface(denise::SurfaceError),

    /// The platform's presentation path could not be set up.
    Present(String),

    /// The GPU path was asked for and could not be taken: no adapter, a device
    /// that would not open, or a build without the `gpu` feature.
    Gpu(String),
}

impl core::fmt::Display for Error {
    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
        match self {
            Self::EventLoop(err) => write!(f, "event loop: {err}"),
            Self::Window(err) => write!(f, "window creation: {err}"),
            #[cfg(not(target_os = "macos"))]
            Self::Softbuffer(err) => write!(f, "softbuffer: {err}"),
            Self::Surface(err) => core::fmt::Display::fmt(err, f),
            Self::Present(msg) => write!(f, "present: {msg}"),
            Self::Gpu(msg) => write!(f, "gpu: {msg}"),
        }
    }
}

impl core::error::Error for Error {
    fn source(&self) -> Option<&(dyn core::error::Error + 'static)> {
        match self {
            Self::EventLoop(err) => Some(err),
            Self::Window(err) => Some(err),
            #[cfg(not(target_os = "macos"))]
            Self::Softbuffer(err) => Some(err),
            Self::Surface(err) => core::error::Error::source(err),
            Self::Present(_) | Self::Gpu(_) => None,
        }
    }
}

impl From<winit::error::EventLoopError> for Error {
    fn from(err: winit::error::EventLoopError) -> Self {
        Self::EventLoop(err)
    }
}

impl From<winit::error::OsError> for Error {
    fn from(err: winit::error::OsError) -> Self {
        Self::Window(err)
    }
}

#[cfg(not(target_os = "macos"))]
impl From<softbuffer::SoftBufferError> for Error {
    fn from(err: softbuffer::SoftBufferError) -> Self {
        Self::Softbuffer(err)
    }
}

impl From<denise::SurfaceError> for Error {
    fn from(err: denise::SurfaceError) -> Self {
        Self::Surface(err)
    }
}

/// What draws a window's pixels.
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub enum Present {
    /// The software rasteriser into a buffer of words, copied to the
    /// compositor by damage. The default, and what every kiosk does.
    #[default]
    Software,
    /// `denise-wgpu` into a swapchain, every frame a full repaint.
    ///
    /// Needs the `gpu` feature and an application that implements
    /// [`DeniseApp::paint`]; without either, opening the window fails with
    /// [`Error::Gpu`] rather than silently drawing the other way. For the
    /// designer on a large display, not for a preview of a panel.
    Gpu,
    /// [`Gpu`](Self::Gpu) where a GPU can present to the window, and
    /// [`Software`](Self::Software) where none can — decided for each window
    /// as it opens, with the reason written to stderr when it falls back.
    ///
    /// For an application that ships to machines nobody has seen: a virtual
    /// machine, a remote display, a driver that is not there. The fallback has
    /// to happen here rather than around [`run_with`], because winit allows
    /// one event loop per process and a failed run cannot be started again.
    /// Without the `gpu` feature it is `Software`.
    ///
    /// The application should implement [`DeniseApp::paint`], as for `Gpu`.
    GpuOrSoftware,
}

/// How the preview window is created.
#[derive(Clone, Debug)]
pub struct WindowConfig {
    /// Window title.
    pub title: String,
    /// Initial inner size in **logical** pixels.
    ///
    /// Logical, not physical, so one number describes the same amount of desk on
    /// every machine: a panel designed at 800×480 covers 800×480 of a Pi's
    /// framebuffer and the same apparent area on a 2× Retina display, where the
    /// surface it gets is 1600×960 physical pixels. Asking in physical pixels
    /// instead is how a window ends up a quarter of its intended size on a Mac.
    ///
    /// The surface — and therefore every coordinate the application works in —
    /// stays physical. Scaling the content to match is the application's job, and
    /// it is handed the factor at construction by [`run_with`].
    pub size: Size,
    /// Whether the user may resize the window.
    pub resizable: bool,
    /// Target frame interval. Defaults to 60 Hz.
    ///
    /// The loop sleeps until the next deadline rather than spinning, so an idle UI
    /// costs close to nothing — which is the behaviour we actually care about on
    /// the target hardware.
    pub frame_interval: Duration,
    /// What draws the pixels. See [`Present`].
    pub present: Present,
    /// Open at the size of the monitor instead of at [`size`](Self::size).
    ///
    /// For an application that is going to cover the screen anyway — a kiosk, a
    /// panel, anything a window manager is about to fullscreen — where asking
    /// for a window the screen cannot hold is asking to be resized on the way
    /// up. Some window managers hand such a window a size, then the size it
    /// asked for, then the size again, and what reaches the glass afterwards is
    /// not always the last of them.
    ///
    /// Falls back to `size` when no monitor can be identified, which is the
    /// case on some headless and remote displays.
    pub fill_monitor: bool,
    /// Where to put the window's top-left, frame included, in the desktop's
    /// **physical** pixels — or `None` to let the window manager place it.
    ///
    /// The units are the ones
    /// [`InputEvent::SurfaceMoved`] reports
    /// in, so an application that keeps what it was last told and hands it back
    /// here opens where it closed, with nothing to convert on the way. Logical
    /// pixels are right for [`size`](Self::size) and wrong for this: a desktop
    /// spanning a Retina display and a 1× one beside it has no single logical
    /// grid to name a point in.
    ///
    /// A position no monitor covers is ignored rather than honoured — a
    /// display that has since been unplugged would otherwise open the window
    /// where nobody can reach it.
    pub position: Option<Point>,
    /// Open maximised, whatever [`size`](Self::size) says.
    ///
    /// The size is still worth setting: it is what the window goes back to when
    /// the user un-maximises it.
    pub maximized: bool,
    /// The name the desktop knows this application by — Wayland's `app_id`,
    /// X11's `WM_CLASS` — or `None` for none.
    ///
    /// It is what ties a window to the application's desktop entry, so a
    /// launcher and a task bar show its name and icon, and what a window
    /// manager's rules match. Give it the entry's file name without
    /// `.desktop`: a window of `squint.desktop` says `squint`. Without one a
    /// Wayland compositor has nothing to go on, and every window rule and
    /// taskbar icon for the application is lost.
    ///
    /// Set it on every window the application opens, the secondary ones
    /// included: each is a window of its own to the compositor. Ignored on
    /// macOS and Windows, which know an application by its bundle and its
    /// executable.
    pub app_id: Option<String>,
}

impl Default for WindowConfig {
    fn default() -> Self {
        Self {
            title: "Denise".into(),
            size: Size::new(800, 480),
            resizable: true,
            frame_interval: Duration::from_nanos(1_000_000_000 / 60),
            present: Present::Software,
            fill_monitor: false,
            position: None,
            maximized: false,
            app_id: None,
        }
    }
}

/// An application driven by this backend.
///
/// This is M0 scaffolding, not the eventual public API. From M3 the scene stack and
/// component tree sit between the application and these two methods.
pub trait DeniseApp {
    /// Handles input and records what that changed.
    ///
    /// Marking damage here rather than during `render` is deliberate: the renderer
    /// needs to know what to repaint *before* it starts, and on a real swapchain
    /// the region it must cover is wider than what changed this frame.
    fn update(&mut self, events: &[InputEvent], damage: &mut DamageTracker);

    /// Draws the frame.
    ///
    /// `damage` is the region that must be repainted for this particular buffer,
    /// already widened for its age and clipped to the surface. Drawing outside it
    /// is wasted work; drawing less than it leaves stale pixels.
    fn render(&mut self, frame: &mut Frame<'_>, damage: &[Rect]) {
        let age = frame.age();
        let mut canvas = denise_render::Canvas::new(frame);
        let drawn = self.paint(&mut canvas.pen(), age, damage);
        assert!(
            drawn,
            "a DeniseApp must implement `render` or `paint`; this one implements neither"
        );
    }

    /// Draws every region in `damage` through `pen`, whatever is behind it.
    ///
    /// The painter-agnostic half of [`render`](DeniseApp::render): the same
    /// call reaches a `Frame` through the rasteriser and a swapchain through
    /// `denise-wgpu`. `age` is what the target remembers of previous frames —
    /// [`BufferAge::Undefined`] on the GPU, where every frame starts blank — and
    /// a `Ui` wants it for [`paint_with`](https://docs.rs/denise-ui).
    ///
    /// Return `true` if this application draws this way. The default returns
    /// `false`, which means "frames only": the software path keeps calling
    /// `render`, and the GPU path refuses the window with [`Error::Gpu`]
    /// instead of showing nothing.
    ///
    /// Implement this alone and `render` is provided: a `Canvas` over the frame,
    /// then this. Implement both when the frame path wants something a pen
    /// cannot offer, which for a `Ui` is the scroll optimisation `Ui::paint`
    /// does with the frame's own words.
    fn paint(&mut self, pen: &mut Pen<'_>, age: BufferAge, damage: &[Rect]) -> bool {
        let _ = (pen, age, damage);
        false
    }

    /// Return `true` to quit after the current frame.
    fn exit_requested(&self) -> bool {
        false
    }

    /// What the title bar should say, when the application wants a say in it.
    ///
    /// [`WindowConfig::title`] is read once, when the window is made, which is
    /// enough for a window whose title is a constant and not enough for one
    /// naming a document: a file opened after start-up leaves the bar naming
    /// whatever was open before it.
    ///
    /// Asked once a frame and compared with what the window was last given, so
    /// an application answering the same string pays a comparison rather than a
    /// trip through the window system. Borrowed rather than owned for the same
    /// reason -- an answer built fresh each frame would allocate for every one
    /// of them. `None` leaves the title alone.
    fn title(&self) -> Option<&str> {
        None
    }

    /// How long the loop may sleep before asking for another frame.
    ///
    /// The default — `Some(Duration::ZERO)` — means "as often as
    /// [`WindowConfig::frame_interval`] allows", which is what this backend has
    /// always done. Answering with a longer wait, or with `None` for "nothing is
    /// animating, wake me on input", is how an application stops the loop doing
    /// work nobody asked for.
    ///
    /// A tree already knows the answer: `Ui::next_wake_ms` is the deadline of the
    /// most impatient animation in it. Ignoring it is not free. A `Spinner` asks
    /// to be woken every 50 ms and moves its arc exactly that often; ticked at
    /// 60 Hz instead it reports a repaint three times as often as it has anything
    /// new to show, and every one of those is a present. The kiosk backends have
    /// always slept on `next_wake_ms` — this is what lets a window agree with
    /// them.
    ///
    /// Input does not wait for this: an event wakes the loop immediately,
    /// whatever was asked for here.
    fn next_frame_in(&self) -> Option<Duration> {
        Some(Duration::ZERO)
    }

    /// Whether the window manager's close request should end the run.
    ///
    /// Defaults to `true`, because a close button that does not close is a bug in
    /// every application that has not deliberately decided otherwise. The request
    /// is also queued for [`update`](DeniseApp::update) as
    /// [`InputEvent::CloseRequested`], but an accepted close takes effect at once
    /// and a window on its way out is not drawn again, so `update` may never see
    /// it. Saving on the way out belongs in [`exiting`](DeniseApp::exiting)
    /// instead, which every way out reaches — including the ones that never ask
    /// this at all, like ⌘Q on macOS.
    ///
    /// Override it to `false` to *veto* the close — an unsaved-changes prompt is
    /// the reason to, and the application then quits by way of
    /// [`exit_requested`](DeniseApp::exit_requested) once the answer comes back.
    /// A veto is only as good as that follow-up: an application that never sets
    /// `exit_requested` has made its window unclosable by anything short of the
    /// platform's own kill.
    fn close_requested(&mut self) -> bool {
        true
    }

    /// The last call this application gets: its window is closing or the run is
    /// ending, and nothing here will be asked anything again.
    ///
    /// Called once for every window, whatever ends it — its close button,
    /// [`exit_requested`](DeniseApp::exit_requested), the window that opened it
    /// closing, the main window closing, an error, and the ways out that never
    /// pass through a window at all: on macOS, ⌘Q and the application menu's
    /// Quit, Quit from the Dock, and logging out. Those last ones send no close
    /// request and no further [`update`](DeniseApp::update), and the process
    /// ends as soon as this returns: [`run`] and [`run_with`] never return, so
    /// nothing written after them runs, and neither does `Drop`. That makes this
    /// the one place saving on the way out is sure to happen.
    ///
    /// It cannot stop anything, so it answers nothing — by the time it is called
    /// the decision has been made. Asking first is
    /// [`close_requested`](DeniseApp::close_requested), which only a window's own
    /// close request reaches.
    ///
    /// A window is told before the window that opened it, and the main window
    /// last, so a form that writes into state it shares with its owner has
    /// written it before the owner saves. Nothing is drawn while this runs, and
    /// at logout the system is waiting on it: keep it to the saving.
    fn exiting(&mut self) {}

    /// Handed the run's [`Waker`] once, as the window opens, before the first
    /// frame.
    ///
    /// For an application with work arriving from somewhere the loop cannot
    /// see — a socket, a file watcher, another thread — which would otherwise
    /// have to answer [`next_frame_in`](DeniseApp::next_frame_in) with a short
    /// wait forever just to look. Keep it, or a clone of it where the work
    /// arrives, and let the loop sleep.
    fn set_waker(&mut self, waker: Waker) {
        let _ = waker;
    }

    /// Told how the window draws once it is open, before the first frame:
    /// [`Present::Gpu`] or [`Present::Software`], never
    /// [`Present::GpuOrSoftware`].
    ///
    /// Where [`Present::GpuOrSoftware`] was asked for, this is the answer, and
    /// it is worth keeping: a machine where no GPU could present has paid for
    /// finding out — graphics drivers loaded, tried and given up on, which can
    /// be seconds and tens of megabytes that stay mapped — and the next run, or
    /// the next window, can ask for [`Present::Software`] and skip it.
    fn presenting(&mut self, present: Present) {
        let _ = present;
    }

    /// Windows this application wants opened, taken once per frame.
    ///
    /// This is the whole of the secondary-window API, and what it hands back is
    /// another [`DeniseApp`] — so a settings form, an "edit details" window and
    /// the main window are the same kind of thing, built the same way, running in
    /// the same loop. The backend supplies a window, a surface and a place in the
    /// event loop; **what is inside one is entirely the application's**, exactly
    /// as `Ui::push_scene` knows nothing about the scene it pushed.
    ///
    /// Called immediately after [`update`](DeniseApp::update) on every frame,
    /// including frames that draw nothing. Returning the same request twice opens
    /// two windows: an application that must not open its settings form twice
    /// remembers that it has one open, which it needs to do anyway to know what to
    /// tell the second click.
    ///
    /// The new window is owned by the window whose application asked for it — so
    /// a modal opened from a settings form is modal to *that* form, not to the
    /// main window, and closing the form takes the modal with it.
    ///
    /// # Talking to a window you opened
    ///
    /// Nothing here carries state back, on purpose. A form is built by the
    /// application, so the application can give it whatever it likes to hold —
    /// and `Rc<RefCell<_>>` is the whole mechanism:
    ///
    /// ```no_run
    /// # use std::cell::RefCell;
    /// # use std::rc::Rc;
    /// #[derive(Default)]
    /// struct Settings {
    ///     brightness: u8,
    ///     /// Set by the form when it wants to go; read by its `exit_requested`.
    ///     closing: bool,
    /// }
    ///
    /// // The main window keeps one handle, the form gets another. Whichever one
    /// // writes, both see it.
    /// let shared = Rc::new(RefCell::new(Settings::default()));
    /// let for_the_form = shared.clone();
    /// ```
    ///
    /// The form's `exit_requested` returns `shared.borrow().closing`, which is
    /// also how the main window closes it from the outside. Nothing in the
    /// backend needs to know any of this happened.
    fn take_windows(&mut self) -> Vec<WindowRequest> {
        Vec::new()
    }
}

/// Opens a window and runs `app` until it exits.
///
/// The application is built before the window exists, so it cannot know the
/// display's scale factor. On a 1× display that is exactly right; on a HiDPI one
/// it means a tree laid out in physical pixels comes out half size. Use
/// [`run_with`] there.
pub fn run<A: DeniseApp + 'static>(config: WindowConfig, app: A) -> Result<(), Error> {
    run_with(config, move |_, _| app)
}

/// Opens a window and builds the application once the surface behind it is known.
///
/// The builder is handed the surface size in **physical** pixels and the display's
/// scale factor — the two facts a scale-aware tree needs and cannot obtain any
/// earlier. This is the whole of Denise's DPI story on the desktop: the application
/// scales, once, at construction, through `Theme::scaled`, `Rect::scaled` and its
/// own text sizes. Coordinates stay physical everywhere afterwards.
///
/// A later scale change — dragging the window to a display with a different DPI —
/// arrives as [`InputEvent::SurfaceResized`], carrying the new factor. An
/// application that wants to follow it rebuilds its tree there; one that does not
/// keeps the scale it was built with and is merely sized wrong on the second
/// display.
pub fn run_with<A, B>(config: WindowConfig, build: B) -> Result<(), Error>
where
    A: DeniseApp + 'static,
    B: FnOnce(Size, f32) -> A + 'static,
{
    let event_loop = EventLoop::with_user_event().build()?;
    let waker = Waker(event_loop.create_proxy());
    let mut runner = Runner::new(config, boxed(build), waker);
    event_loop.run_app(&mut runner)?;
    match runner.error {
        Some(err) => Err(err),
        None => Ok(()),
    }
}

/// How a secondary window relates to the one that opened it.
///
/// How much of this the platform will actually enforce differs sharply: Windows
/// does all of it, macOS keeps the z-order and blocks nothing, and X11 and Wayland
/// offer neither through winit. So the one guarantee that holds everywhere is
/// [`Modal`](Modality::Modal) input blocking, because the runner does that itself
/// rather than asking. The rest is appearance, and the crate's README has the
/// table.
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
pub enum Modality {
    /// Above its owner, closed with it, and input still reaches both.
    ///
    /// The default, and what a settings form or an "edit details" window wants: a
    /// window belonging to this application rather than a second application that
    /// happens to share a process. The user can keep working in the main window
    /// while it is open.
    #[default]
    Owned,

    /// Owned, and the owner takes no input until this window closes.
    ///
    /// The window-sized version of `Ui::push_scene` with a dim: a confirmation, a
    /// wizard, anything the user must answer before going back. The owner keeps
    /// drawing — animations there do not freeze — it simply stops listening, and
    /// a press on it raises this window instead.
    Modal,

    /// A window of its own, with no relationship to its opener at all.
    ///
    /// A second document window, or a tool palette the user may want behind the
    /// main window. It does not close when its opener does, so an application that
    /// opens one is responsible for it — including for the fact that closing the
    /// main window ends the run and takes it down regardless.
    Independent,
}

/// A window the application wants opened, and the application that will run in it.
///
/// Handed back from [`DeniseApp::take_windows`]. The builder is called once, at
/// the moment the window's surface exists, and is given its size in **physical**
/// pixels and its display's scale factor — the same contract [`run_with`] makes
/// for the main window, and for the same reason: a form built without them is
/// laid out for a display it may not be opening on.
pub struct WindowRequest {
    /// Title, size, resizability and frame cadence for the new window.
    pub config: WindowConfig,
    /// How it relates to the window that asked for it.
    pub modality: Modality,
    pub(crate) build: Build,
}

impl WindowRequest {
    /// A window of `config`, whose application is built once its surface exists.
    ///
    /// [`Modality::Owned`] unless [`with_modality`](WindowRequest::with_modality)
    /// says otherwise.
    pub fn new<A, B>(config: WindowConfig, build: B) -> Self
    where
        A: DeniseApp + 'static,
        B: FnOnce(Size, f32) -> A + 'static,
    {
        Self {
            config,
            modality: Modality::default(),
            build: boxed(build),
        }
    }

    /// A window whose application is already built.
    ///
    /// Convenient, and wrong on a HiDPI display unless the tree inside it does not
    /// care about scale: the application cannot have been told the scale factor,
    /// because the window it will open on does not exist yet.
    /// [`new`](WindowRequest::new) is the one to reach for.
    pub fn ready<A: DeniseApp + 'static>(config: WindowConfig, app: A) -> Self {
        Self::new(config, move |_, _| app)
    }

    /// Sets how this window relates to the one opening it.
    #[must_use]
    pub fn with_modality(self, modality: Modality) -> Self {
        Self { modality, ..self }
    }
}

/// Builds an application once the surface it will draw to is known.
///
/// Boxed because the windows in one run do not share an application type — a
/// settings form is not the main window with different data, it is a different
/// program — and the loop holds them in one collection.
type Build = Box<dyn FnOnce(Size, f32) -> Box<dyn DeniseApp>>;

/// Wakes a running loop from any thread, so every window is asked for a frame
/// as soon as one can be drawn.
///
/// An application's [`update`](DeniseApp::update) then runs with whatever
/// input there is, possibly none, and can take up what the waking thread left
/// for it. Waking a loop that is already awake costs a message; waking one that
/// has finished does nothing. See [`DeniseApp::set_waker`].
#[derive(Clone, Debug)]
pub struct Waker(EventLoopProxy<()>);

impl Waker {
    /// Asks for a frame in every window.
    pub fn wake(&self) {
        // The only failure is a loop that has already ended, which has nothing
        // left to draw.
        let _ = self.0.send_event(());
    }
}

// A waker is for handing to other threads, so it has to be allowed there.
const _: fn() = || {
    fn shareable<T: Send + Sync>() {}
    shareable::<Waker>();
};

/// Erases a builder's application type.
fn boxed<A, B>(build: B) -> Build
where
    A: DeniseApp + 'static,
    B: FnOnce(Size, f32) -> A + 'static,
{
    Box::new(move |size, scale| Box::new(build(size, scale)))
}

/// Compiles the examples in this crate's README, so they cannot drift from the API
/// they claim to demonstrate. Never built except under `cargo test --doc`.
#[cfg(doctest)]
#[doc = include_str!("../README.md")]
struct Readme;