Skip to main content

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;