Skip to main content

densa_window/
lib.rs

1//! Runs a densa interface in a native window.
2//!
3//! [`run`] opens a window, creates a [`Ui`] for it and hands it to an [`App`]
4//! to fill with widgets. From then on it translates the window system's input (winit) into
5//! `densa_ui` events, passes the actions widgets report to the app, and paints the interface
6//! with `densa_render` (wgpu) in one draw call per frame, presented with V-Sync.
7//!
8//! Frames are painted when something changed, when another thread asks for one through a
9//! [`Waker`] (new data arrived), or continuously while the app asks for it (animation).
10//!
11//! Screen readers (Narrator, VoiceOver, Orca) read the interface through AccessKit: the
12//! window sends the [`Ui`]'s accessibility tree after each frame, only while one is
13//! listening, and carries out what it asks.
14//!
15//! The [`Ui`]'s text uses `densa_paragraph::TextSystem::new`: the bundled font, and the
16//! system's fonts for characters it lacks (Korean, Japanese, Chinese, ...). Turning off the
17//! default `system-fonts` feature leaves the bundled font alone.
18//!
19//! ```no_run
20//! use std::error::Error;
21//! use densa_ui::{Ui, Style};
22//! use densa_window::{App, Host, WindowOptions};
23//!
24//! struct Hello;
25//!
26//! impl App for Hello {
27//!     fn build(&mut self, ui: &mut Ui, host: &mut Host<'_>) -> Result<(), Box<dyn Error>> {
28//!         // Add widgets under ui.root() here; hand host.waker() to threads that feed them.
29//!         let _ = (ui.root(), host.waker());
30//!         Ok(())
31//!     }
32//! }
33//!
34//! densa_window::run(WindowOptions::new("Hello"), Hello)?;
35//! # Ok::<(), Box<dyn Error>>(())
36//! ```
37
38mod access;
39mod gpu;
40mod translate;
41
42use std::error::Error;
43use std::fmt;
44use std::sync::Arc;
45use std::time::Instant;
46
47use densa_core::{Point, Size};
48use densa_draw::{Canvas, InstanceBuffer};
49use densa_paragraph::TextSystem;
50use densa_text::{AtlasConfig, GlyphCache};
51use densa_ui::{Action, Cursor, KeyEvent, PointerEvent, PointerKind, Theme, Ui};
52use winit::application::ApplicationHandler;
53use winit::dpi::{LogicalSize, PhysicalPosition, PhysicalSize};
54use winit::event::{ElementState, WindowEvent};
55use winit::event_loop::{ActiveEventLoop, EventLoop, EventLoopProxy};
56use winit::keyboard::ModifiersState;
57use winit::window::{Window, WindowId};
58
59use crate::gpu::Gpu;
60
61/// An application: it builds its interface once, then reacts to what widgets report and
62/// updates live content before each frame. Every call gets the [`Host`]: the window it runs
63/// in.
64pub trait App: 'static {
65    /// Fills the interface: called once, when the window and its [`Ui`] exist.
66    ///
67    /// # Errors
68    /// Any error stops [`run`], which returns it.
69    fn build(&mut self, ui: &mut Ui, host: &mut Host<'_>) -> Result<(), Box<dyn Error>>;
70
71    /// Handles an action a widget reported (a click, an edit), right after the input that
72    /// caused it.
73    fn action(&mut self, ui: &mut Ui, host: &mut Host<'_>, action: Action) {
74        let _ = (ui, host, action);
75    }
76
77    /// Updates live content before a frame is painted. Return `true` to keep painting frames
78    /// continuously (animation), `false` to paint only when something changes or a
79    /// [`Waker`] asks for a frame.
80    fn frame(&mut self, ui: &mut Ui, host: &mut Host<'_>) -> bool {
81        let _ = (ui, host);
82        false
83    }
84}
85
86/// The window an [`App`] runs in.
87#[derive(Debug)]
88pub struct Host<'a> {
89    window: &'a Window,
90    proxy: &'a EventLoopProxy<UserEvent>,
91    close: &'a mut bool,
92}
93
94impl Host<'_> {
95    /// A handle any thread can use to ask for a frame: a feed thread calls
96    /// [`Waker::wake`] after publishing new data.
97    pub fn waker(&self) -> Waker {
98        Waker(self.proxy.clone())
99    }
100
101    /// Sets the window's title.
102    pub fn set_title(&self, title: &str) {
103        self.window.set_title(title);
104    }
105
106    /// Closes the window once the current call returns, which ends [`run`].
107    pub fn close(&mut self) {
108        *self.close = true;
109    }
110}
111
112/// Asks the window's thread for a frame, from any thread.
113#[derive(Debug, Clone)]
114pub struct Waker(EventLoopProxy<UserEvent>);
115
116impl Waker {
117    /// Asks for a frame: [`App::frame`] runs and the interface is painted soon. `false` if
118    /// the window is gone.
119    pub fn wake(&self) -> bool {
120        self.0.send_event(UserEvent::Wake).is_ok()
121    }
122}
123
124/// What other threads send the window's thread: a [`Waker`]'s request for a frame, or a
125/// screen reader's request through AccessKit.
126#[derive(Debug)]
127enum UserEvent {
128    Wake,
129    Access(accesskit_winit::Event),
130}
131
132impl From<accesskit_winit::Event> for UserEvent {
133    fn from(event: accesskit_winit::Event) -> Self {
134        Self::Access(event)
135    }
136}
137
138/// The window [`run`] opens.
139#[derive(Debug, Clone)]
140#[non_exhaustive]
141pub struct WindowOptions {
142    /// The window's title.
143    pub title: String,
144    /// The window's inner size, in logical pixels.
145    pub size: Size,
146    /// Colors and sizes of the widgets.
147    pub theme: Theme,
148    /// Rectangles and glyphs a frame can hold; more are dropped (and counted).
149    pub instances: usize,
150}
151
152impl WindowOptions {
153    /// A 1280x800 window titled `title`, with the default theme.
154    pub fn new(title: impl Into<String>) -> Self {
155        Self {
156            title: title.into(),
157            size: Size::new(1280.0, 800.0),
158            theme: Theme::default(),
159            instances: 65_536,
160        }
161    }
162
163    /// The options with an inner size in logical pixels.
164    #[must_use]
165    pub fn with_size(mut self, width: f32, height: f32) -> Self {
166        self.size = Size::new(width, height);
167        self
168    }
169
170    /// The options with `theme`.
171    #[must_use]
172    pub fn with_theme(mut self, theme: Theme) -> Self {
173        self.theme = theme;
174        self
175    }
176
177    /// The options holding up to `instances` rectangles and glyphs a frame.
178    #[must_use]
179    pub fn with_instances(mut self, instances: usize) -> Self {
180        self.instances = instances;
181        self
182    }
183}
184
185/// Why [`run`] stopped with an error.
186#[derive(Debug)]
187#[non_exhaustive]
188pub enum WindowError {
189    /// The window system's event loop or window failed.
190    Window(String),
191    /// The GPU could not be set up or drawn to.
192    Gpu(String),
193    /// The fonts could not be loaded.
194    Text(String),
195    /// The application's [`App::build`] failed.
196    App(Box<dyn Error>),
197}
198
199impl fmt::Display for WindowError {
200    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
201        match self {
202            Self::Window(message) => write!(f, "window system: {message}"),
203            Self::Gpu(message) => write!(f, "GPU: {message}"),
204            Self::Text(message) => write!(f, "fonts: {message}"),
205            Self::App(error) => write!(f, "application: {error}"),
206        }
207    }
208}
209
210impl Error for WindowError {
211    fn source(&self) -> Option<&(dyn Error + 'static)> {
212        match self {
213            Self::App(error) => Some(error.as_ref()),
214            _ => None,
215        }
216    }
217}
218
219/// Opens a window for `app` and runs until it is closed.
220///
221/// # Errors
222/// [`WindowError`] if the window, the GPU or the fonts cannot be set up, a frame cannot be
223/// drawn, or [`App::build`] fails.
224pub fn run(options: WindowOptions, app: impl App) -> Result<(), WindowError> {
225    let event_loop = EventLoop::<UserEvent>::with_user_event()
226        .build()
227        .map_err(|e| WindowError::Window(e.to_string()))?;
228    let mut shell = Shell {
229        app,
230        options,
231        proxy: event_loop.create_proxy(),
232        session: None,
233        error: None,
234        close: false,
235    };
236    event_loop
237        .run_app(&mut shell)
238        .map_err(|e| WindowError::Window(e.to_string()))?;
239    shell.error.map_or(Ok(()), Err)
240}
241
242struct Shell<A: App> {
243    app: A,
244    options: WindowOptions,
245    proxy: EventLoopProxy<UserEvent>,
246    session: Option<Session>,
247    error: Option<WindowError>,
248    /// The app asked to close the window while building its interface.
249    close: bool,
250}
251
252/// The window, its GPU surface and its interface.
253struct Session {
254    window: Arc<Window>,
255    /// Hands the interface to screen readers, and their requests back.
256    access: accesskit_winit::Adapter,
257    gpu: Gpu,
258    ui: Ui,
259    cache: GlyphCache,
260    instances: InstanceBuffer,
261    start: Instant,
262    pointer: Point,
263    modifiers: ModifiersState,
264    cursor: Cursor,
265    continuous: bool,
266}
267
268impl<A: App> Shell<A> {
269    fn fail(&mut self, event_loop: &ActiveEventLoop, error: WindowError) {
270        self.error = Some(error);
271        event_loop.exit();
272    }
273
274    fn open(&mut self, event_loop: &ActiveEventLoop) -> Result<Session, WindowError> {
275        let size = self.options.size;
276        // Hidden until AccessKit's adapter is attached, which must happen before it is shown.
277        let attributes = Window::default_attributes()
278            .with_title(self.options.title.clone())
279            .with_inner_size(LogicalSize::new(size.width, size.height))
280            .with_visible(false);
281        let window = event_loop
282            .create_window(attributes)
283            .map_err(|e| WindowError::Window(e.to_string()))?;
284        let access = accesskit_winit::Adapter::with_event_loop_proxy(
285            event_loop,
286            &window,
287            self.proxy.clone(),
288        );
289        let window = Arc::new(window);
290        window.set_ime_allowed(true);
291        let mut cache = GlyphCache::new(AtlasConfig::DEFAULT);
292        let gpu = Gpu::new(event_loop, &window, self.options.instances, &mut cache)?;
293        let text = TextSystem::new().map_err(|e| WindowError::Text(e.to_string()))?;
294        let mut ui = Ui::new(text);
295        ui.set_theme(self.options.theme.clone());
296        ui.set_scale(window.scale_factor() as f32);
297        let inner = window.inner_size();
298        ui.resize(Size::new(inner.width as f32, inner.height as f32));
299        let mut host = Host {
300            window: &window,
301            proxy: &self.proxy,
302            close: &mut self.close,
303        };
304        self.app
305            .build(&mut ui, &mut host)
306            .map_err(WindowError::App)?;
307        window.set_visible(true);
308        Ok(Session {
309            window,
310            access,
311            gpu,
312            ui,
313            cache,
314            instances: InstanceBuffer::with_capacity(self.options.instances),
315            start: Instant::now(),
316            pointer: Point::ORIGIN,
317            modifiers: ModifiersState::empty(),
318            cursor: Cursor::Default,
319            continuous: false,
320        })
321    }
322}
323
324impl<A: App> ApplicationHandler<UserEvent> for Shell<A> {
325    fn resumed(&mut self, event_loop: &ActiveEventLoop) {
326        if self.session.is_some() {
327            return;
328        }
329        match self.open(event_loop) {
330            Ok(session) => {
331                session.window.request_redraw();
332                self.session = Some(session);
333                if self.close {
334                    event_loop.exit();
335                }
336            }
337            Err(error) => self.fail(event_loop, error),
338        }
339    }
340
341    fn window_event(&mut self, event_loop: &ActiveEventLoop, _: WindowId, event: WindowEvent) {
342        let Some(session) = self.session.as_mut() else {
343            return;
344        };
345        let window = Arc::clone(&session.window);
346        session.access.process_event(&window, &event);
347        let redraw = matches!(event, WindowEvent::RedrawRequested);
348        let mut close = false;
349        let mut failed = None;
350        {
351            let mut host = Host {
352                window: &window,
353                proxy: &self.proxy,
354                close: &mut close,
355            };
356            match event {
357                WindowEvent::CloseRequested => host.close(),
358                WindowEvent::RedrawRequested => {
359                    session.continuous = self.app.frame(&mut session.ui, &mut host);
360                    failed = session.paint().err();
361                }
362                other => session.input(&other),
363            }
364            if failed.is_none() {
365                while let Some(action) = session.ui.next_action() {
366                    self.app.action(&mut session.ui, &mut host, action);
367                }
368                session.reflect();
369                // After a frame and what the app did about it: the tree screen readers see
370                // matches the screen.
371                if redraw {
372                    session.publish_access();
373                }
374            }
375        }
376        if let Some(error) = failed {
377            self.fail(event_loop, error);
378        } else if close {
379            event_loop.exit();
380        }
381    }
382
383    fn user_event(&mut self, event_loop: &ActiveEventLoop, event: UserEvent) {
384        let Some(session) = self.session.as_mut() else {
385            return;
386        };
387        let access = match event {
388            UserEvent::Wake => {
389                session.window.request_redraw();
390                return;
391            }
392            UserEvent::Access(access) => access.window_event,
393        };
394        match access {
395            accesskit_winit::WindowEvent::InitialTreeRequested => session.publish_access(),
396            accesskit_winit::WindowEvent::ActionRequested(request) => {
397                access::perform(&mut session.ui, &request);
398                let window = Arc::clone(&session.window);
399                let mut close = false;
400                let mut host = Host {
401                    window: &window,
402                    proxy: &self.proxy,
403                    close: &mut close,
404                };
405                while let Some(action) = session.ui.next_action() {
406                    self.app.action(&mut session.ui, &mut host, action);
407                }
408                session.reflect();
409                session.publish_access();
410                if close {
411                    event_loop.exit();
412                }
413            }
414            accesskit_winit::WindowEvent::AccessibilityDeactivated => {}
415        }
416    }
417
418    fn about_to_wait(&mut self, _: &ActiveEventLoop) {
419        if let Some(session) = &self.session {
420            if session.continuous {
421                session.window.request_redraw();
422            }
423        }
424    }
425}
426
427impl Session {
428    /// Hands a window event to the interface.
429    fn input(&mut self, event: &WindowEvent) {
430        let modifiers = translate::modifiers(self.modifiers);
431        let at = |kind: PointerKind, position: Point, start: Instant| {
432            PointerEvent::new(kind, position)
433                .with_modifiers(modifiers)
434                .at(start.elapsed())
435        };
436        match event {
437            WindowEvent::Resized(size) => {
438                self.gpu.resize(size.width, size.height);
439                self.ui
440                    .resize(Size::new(size.width as f32, size.height as f32));
441            }
442            WindowEvent::ScaleFactorChanged { scale_factor, .. } => {
443                self.ui.set_scale(*scale_factor as f32);
444            }
445            WindowEvent::ModifiersChanged(state) => self.modifiers = state.state(),
446            WindowEvent::CursorMoved { position, .. } => {
447                self.pointer = Point::new(position.x as f32, position.y as f32);
448                self.ui
449                    .pointer(at(PointerKind::Move, self.pointer, self.start));
450            }
451            WindowEvent::CursorLeft { .. } => self.ui.pointer_left(self.pointer),
452            // Alt+Tab mid-drag: no release will come, and modifiers may be released elsewhere.
453            WindowEvent::Focused(false) => {
454                self.ui.cancel_pointer();
455                self.modifiers = ModifiersState::empty();
456            }
457            WindowEvent::MouseInput { state, button, .. } => {
458                let button = translate::button(*button);
459                let kind = match state {
460                    ElementState::Pressed => PointerKind::Down(button),
461                    ElementState::Released => PointerKind::Up(button),
462                };
463                self.ui.pointer(at(kind, self.pointer, self.start));
464            }
465            WindowEvent::MouseWheel { delta, .. } => {
466                let kind = PointerKind::Scroll(translate::scroll(*delta));
467                self.ui.pointer(at(kind, self.pointer, self.start));
468            }
469            WindowEvent::KeyboardInput {
470                event,
471                is_synthetic: false,
472                ..
473            } => {
474                let Some(key) = translate::key(&event.logical_key) else {
475                    return;
476                };
477                let mut key = if event.state == ElementState::Pressed {
478                    KeyEvent::down(key)
479                } else {
480                    KeyEvent::up(key)
481                }
482                .with_modifiers(modifiers);
483                if let Some(text) = translate::text(event.text.as_deref()) {
484                    key = key.with_text(text);
485                }
486                if event.repeat {
487                    key = key.repeated();
488                }
489                self.ui.key(&key);
490            }
491            WindowEvent::Ime(ime) => {
492                self.ui.ime(&translate::ime(ime));
493            }
494            _ => {}
495        }
496    }
497
498    /// Paints the interface and presents it.
499    fn paint(&mut self) -> Result<(), WindowError> {
500        {
501            let mut canvas = Canvas::begin(&mut self.cache, &mut self.instances);
502            self.ui.paint(&mut canvas);
503        }
504        let background = self.ui.theme().background;
505        self.gpu.draw(
506            &self.window,
507            &mut self.cache,
508            self.instances.as_slice(),
509            background,
510        )?;
511        Ok(())
512    }
513
514    /// Sends screen readers the interface as it is now; nothing is built unless one is
515    /// listening.
516    fn publish_access(&mut self) {
517        let Self {
518            access, ui, window, ..
519        } = self;
520        access.update_if_active(|| access::tree_update(&ui.access_tree(), &window.title()));
521    }
522
523    /// Shows the interface's state in the window: cursor, input method area, repaint.
524    fn reflect(&mut self) {
525        let cursor = self.ui.cursor();
526        if cursor != self.cursor {
527            self.cursor = cursor;
528            self.window.set_cursor(translate::cursor(cursor));
529        }
530        if let Some(area) = self.ui.ime_area() {
531            self.window.set_ime_cursor_area(
532                PhysicalPosition::new(area.left(), area.top()),
533                PhysicalSize::new(area.width().max(1.0), area.height().max(1.0)),
534            );
535        }
536        if self.ui.needs_paint() {
537            self.window.request_redraw();
538        }
539    }
540}