1mod access;
42mod clipboard;
43mod gpu;
44mod translate;
45
46use std::error::Error;
47use std::fmt;
48use std::sync::Arc;
49use std::time::Instant;
50
51use densa_core::{Point, Size};
52use densa_draw::{Canvas, InstanceBuffer};
53use densa_paragraph::TextSystem;
54use densa_text::{AtlasConfig, GlyphCache};
55use densa_ui::{Action, Cursor, KeyEvent, PointerEvent, PointerKind, Theme, Ui};
56use winit::application::ApplicationHandler;
57use winit::dpi::{LogicalSize, PhysicalPosition, PhysicalSize};
58use winit::event::{ElementState, WindowEvent};
59use winit::event_loop::{ActiveEventLoop, EventLoop, EventLoopProxy};
60use winit::keyboard::ModifiersState;
61use winit::window::{Window, WindowId};
62
63use crate::gpu::Gpu;
64
65pub trait App: 'static {
69 fn build(&mut self, ui: &mut Ui, host: &mut Host<'_>) -> Result<(), Box<dyn Error>>;
74
75 fn action(&mut self, ui: &mut Ui, host: &mut Host<'_>, action: Action) {
78 let _ = (ui, host, action);
79 }
80
81 fn frame(&mut self, ui: &mut Ui, host: &mut Host<'_>) -> bool {
85 let _ = (ui, host);
86 false
87 }
88}
89
90#[derive(Debug)]
92pub struct Host<'a> {
93 window: &'a Window,
94 proxy: &'a EventLoopProxy<UserEvent>,
95 close: &'a mut bool,
96}
97
98impl Host<'_> {
99 pub fn waker(&self) -> Waker {
102 Waker(self.proxy.clone())
103 }
104
105 pub fn set_title(&self, title: &str) {
107 self.window.set_title(title);
108 }
109
110 pub fn close(&mut self) {
112 *self.close = true;
113 }
114}
115
116#[derive(Debug, Clone)]
118pub struct Waker(EventLoopProxy<UserEvent>);
119
120impl Waker {
121 pub fn wake(&self) -> bool {
124 self.0.send_event(UserEvent::Wake).is_ok()
125 }
126}
127
128#[derive(Debug)]
131enum UserEvent {
132 Wake,
133 Access(accesskit_winit::Event),
134}
135
136impl From<accesskit_winit::Event> for UserEvent {
137 fn from(event: accesskit_winit::Event) -> Self {
138 Self::Access(event)
139 }
140}
141
142#[derive(Debug, Clone)]
144#[non_exhaustive]
145pub struct WindowOptions {
146 pub title: String,
148 pub size: Size,
150 pub theme: Theme,
152 pub instances: usize,
154 pub backend: Backend,
156}
157
158#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
163#[non_exhaustive]
164pub enum Backend {
165 #[default]
168 Auto,
169 Vulkan,
171 Metal,
173 Dx12,
175 Gl,
178}
179
180impl WindowOptions {
181 pub fn new(title: impl Into<String>) -> Self {
183 Self {
184 title: title.into(),
185 size: Size::new(1280.0, 800.0),
186 theme: Theme::default(),
187 instances: 65_536,
188 backend: Backend::Auto,
189 }
190 }
191
192 #[must_use]
194 pub fn with_size(mut self, width: f32, height: f32) -> Self {
195 self.size = Size::new(width, height);
196 self
197 }
198
199 #[must_use]
201 pub fn with_theme(mut self, theme: Theme) -> Self {
202 self.theme = theme;
203 self
204 }
205
206 #[must_use]
208 pub fn with_backend(mut self, backend: Backend) -> Self {
209 self.backend = backend;
210 self
211 }
212
213 #[must_use]
215 pub fn with_instances(mut self, instances: usize) -> Self {
216 self.instances = instances;
217 self
218 }
219}
220
221#[derive(Debug)]
223#[non_exhaustive]
224pub enum WindowError {
225 Window(String),
227 Gpu(String),
229 Text(String),
231 App(Box<dyn Error>),
233}
234
235impl fmt::Display for WindowError {
236 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
237 match self {
238 Self::Window(message) => write!(f, "window system: {message}"),
239 Self::Gpu(message) => write!(f, "GPU: {message}"),
240 Self::Text(message) => write!(f, "fonts: {message}"),
241 Self::App(error) => write!(f, "application: {error}"),
242 }
243 }
244}
245
246impl Error for WindowError {
247 fn source(&self) -> Option<&(dyn Error + 'static)> {
248 match self {
249 Self::App(error) => Some(error.as_ref()),
250 _ => None,
251 }
252 }
253}
254
255pub fn run(options: WindowOptions, app: impl App) -> Result<(), WindowError> {
261 let event_loop = EventLoop::<UserEvent>::with_user_event()
262 .build()
263 .map_err(|e| WindowError::Window(e.to_string()))?;
264 let mut shell = Shell {
265 app,
266 options,
267 proxy: event_loop.create_proxy(),
268 session: None,
269 error: None,
270 close: false,
271 };
272 event_loop
273 .run_app(&mut shell)
274 .map_err(|e| WindowError::Window(e.to_string()))?;
275 shell.error.map_or(Ok(()), Err)
276}
277
278struct Shell<A: App> {
279 app: A,
280 options: WindowOptions,
281 proxy: EventLoopProxy<UserEvent>,
282 session: Option<Session>,
283 error: Option<WindowError>,
284 close: bool,
286}
287
288struct Session {
290 window: Arc<Window>,
291 access: accesskit_winit::Adapter,
293 gpu: Gpu,
294 ui: Ui,
295 cache: GlyphCache,
296 instances: InstanceBuffer,
297 start: Instant,
298 pointer: Point,
299 modifiers: ModifiersState,
300 cursor: Cursor,
301 continuous: bool,
302}
303
304impl<A: App> Shell<A> {
305 fn fail(&mut self, event_loop: &ActiveEventLoop, error: WindowError) {
306 self.error = Some(error);
307 event_loop.exit();
308 }
309
310 fn open(&mut self, event_loop: &ActiveEventLoop) -> Result<Session, WindowError> {
311 let size = self.options.size;
312 let attributes = Window::default_attributes()
314 .with_title(self.options.title.clone())
315 .with_inner_size(LogicalSize::new(size.width, size.height))
316 .with_visible(false);
317 let window = event_loop
318 .create_window(attributes)
319 .map_err(|e| WindowError::Window(e.to_string()))?;
320 let access = accesskit_winit::Adapter::with_event_loop_proxy(
321 event_loop,
322 &window,
323 self.proxy.clone(),
324 );
325 let window = Arc::new(window);
326 window.set_ime_allowed(true);
327 let mut cache = GlyphCache::new(AtlasConfig::DEFAULT);
328 let gpu = Gpu::new(
329 event_loop,
330 &window,
331 self.options.instances,
332 &mut cache,
333 self.options.backend,
334 )?;
335 let text = TextSystem::new().map_err(|e| WindowError::Text(e.to_string()))?;
336 let mut ui = Ui::new(text);
337 ui.set_theme(self.options.theme.clone());
338 ui.set_clipboard(clipboard::SystemClipboard::default());
339 ui.set_scale(window.scale_factor() as f32);
340 let inner = window.inner_size();
341 ui.resize(Size::new(inner.width as f32, inner.height as f32));
342 let mut host = Host {
343 window: &window,
344 proxy: &self.proxy,
345 close: &mut self.close,
346 };
347 self.app
348 .build(&mut ui, &mut host)
349 .map_err(WindowError::App)?;
350 window.set_visible(true);
351 Ok(Session {
352 window,
353 access,
354 gpu,
355 ui,
356 cache,
357 instances: InstanceBuffer::with_capacity(self.options.instances),
358 start: Instant::now(),
359 pointer: Point::ORIGIN,
360 modifiers: ModifiersState::empty(),
361 cursor: Cursor::Default,
362 continuous: false,
363 })
364 }
365}
366
367impl<A: App> ApplicationHandler<UserEvent> for Shell<A> {
368 fn resumed(&mut self, event_loop: &ActiveEventLoop) {
369 if self.session.is_some() {
370 return;
371 }
372 match self.open(event_loop) {
373 Ok(session) => {
374 session.window.request_redraw();
375 self.session = Some(session);
376 if self.close {
377 event_loop.exit();
378 }
379 }
380 Err(error) => self.fail(event_loop, error),
381 }
382 }
383
384 fn window_event(&mut self, event_loop: &ActiveEventLoop, _: WindowId, event: WindowEvent) {
385 let Some(session) = self.session.as_mut() else {
386 return;
387 };
388 let window = Arc::clone(&session.window);
389 session.access.process_event(&window, &event);
390 let redraw = matches!(event, WindowEvent::RedrawRequested);
391 let mut close = false;
392 let mut failed = None;
393 {
394 let mut host = Host {
395 window: &window,
396 proxy: &self.proxy,
397 close: &mut close,
398 };
399 match event {
400 WindowEvent::CloseRequested => host.close(),
401 WindowEvent::RedrawRequested => {
402 session.continuous = self.app.frame(&mut session.ui, &mut host);
403 failed = session.paint().err();
404 }
405 other => session.input(&other),
406 }
407 if failed.is_none() {
408 while let Some(action) = session.ui.next_action() {
409 self.app.action(&mut session.ui, &mut host, action);
410 }
411 session.reflect();
412 if redraw {
415 session.publish_access();
416 }
417 }
418 }
419 if let Some(error) = failed {
420 self.fail(event_loop, error);
421 } else if close {
422 event_loop.exit();
423 }
424 }
425
426 fn user_event(&mut self, event_loop: &ActiveEventLoop, event: UserEvent) {
427 let Some(session) = self.session.as_mut() else {
428 return;
429 };
430 let access = match event {
431 UserEvent::Wake => {
432 session.window.request_redraw();
433 return;
434 }
435 UserEvent::Access(access) => access.window_event,
436 };
437 match access {
438 accesskit_winit::WindowEvent::InitialTreeRequested => session.publish_access(),
439 accesskit_winit::WindowEvent::ActionRequested(request) => {
440 access::perform(&mut session.ui, &request);
441 let window = Arc::clone(&session.window);
442 let mut close = false;
443 let mut host = Host {
444 window: &window,
445 proxy: &self.proxy,
446 close: &mut close,
447 };
448 while let Some(action) = session.ui.next_action() {
449 self.app.action(&mut session.ui, &mut host, action);
450 }
451 session.reflect();
452 session.publish_access();
453 if close {
454 event_loop.exit();
455 }
456 }
457 accesskit_winit::WindowEvent::AccessibilityDeactivated => {}
458 }
459 }
460
461 fn about_to_wait(&mut self, _: &ActiveEventLoop) {
462 if let Some(session) = &self.session {
463 if session.continuous {
464 session.window.request_redraw();
465 }
466 }
467 }
468}
469
470impl Session {
471 fn input(&mut self, event: &WindowEvent) {
473 let modifiers = translate::modifiers(self.modifiers);
474 let at = |kind: PointerKind, position: Point, start: Instant| {
475 PointerEvent::new(kind, position)
476 .with_modifiers(modifiers)
477 .at(start.elapsed())
478 };
479 match event {
480 WindowEvent::Resized(size) => {
481 self.gpu.resize(size.width, size.height);
482 let size = Size::new(size.width as f32, size.height as f32);
483 if size != self.ui.viewport() {
486 self.ui.close_popups();
487 }
488 self.ui.resize(size);
489 }
490 WindowEvent::ScaleFactorChanged { scale_factor, .. } => {
491 self.ui.close_popups();
493 self.ui.set_scale(*scale_factor as f32);
494 }
495 WindowEvent::ModifiersChanged(state) => self.modifiers = state.state(),
496 WindowEvent::CursorMoved { position, .. } => {
497 self.pointer = Point::new(position.x as f32, position.y as f32);
498 self.ui
499 .pointer(at(PointerKind::Move, self.pointer, self.start));
500 }
501 WindowEvent::CursorLeft { .. } => self.ui.pointer_left(self.pointer),
502 WindowEvent::Focused(false) => {
504 self.ui.cancel_pointer();
505 self.ui.close_popups();
507 self.modifiers = ModifiersState::empty();
508 }
509 WindowEvent::MouseInput { state, button, .. } => {
510 let button = translate::button(*button);
511 let kind = match state {
512 ElementState::Pressed => PointerKind::Down(button),
513 ElementState::Released => PointerKind::Up(button),
514 };
515 self.ui.pointer(at(kind, self.pointer, self.start));
516 }
517 WindowEvent::MouseWheel { delta, .. } => {
518 let kind = PointerKind::Scroll(translate::scroll(*delta));
519 self.ui.pointer(at(kind, self.pointer, self.start));
520 }
521 WindowEvent::KeyboardInput {
522 event,
523 is_synthetic: false,
524 ..
525 } => {
526 let Some(key) = translate::key(&event.logical_key) else {
527 return;
528 };
529 let mut key = if event.state == ElementState::Pressed {
530 KeyEvent::down(key)
531 } else {
532 KeyEvent::up(key)
533 }
534 .with_modifiers(modifiers);
535 if let Some(text) = translate::text(event.text.as_deref()) {
536 key = key.with_text(text);
537 }
538 if event.repeat {
539 key = key.repeated();
540 }
541 self.ui.key(&key);
542 }
543 WindowEvent::Ime(ime) => {
544 self.ui.ime(&translate::ime(ime));
545 }
546 _ => {}
547 }
548 }
549
550 fn paint(&mut self) -> Result<(), WindowError> {
552 {
553 let mut canvas = Canvas::begin(&mut self.cache, &mut self.instances);
554 self.ui.paint(&mut canvas);
555 }
556 let background = self.ui.theme().background;
557 self.gpu.draw(
558 &self.window,
559 &mut self.cache,
560 self.instances.as_slice(),
561 background,
562 )?;
563 Ok(())
564 }
565
566 fn publish_access(&mut self) {
569 let Self {
570 access, ui, window, ..
571 } = self;
572 access.update_if_active(|| access::tree_update(&ui.access_tree(), &window.title()));
573 }
574
575 fn reflect(&mut self) {
577 let cursor = self.ui.cursor();
578 if cursor != self.cursor {
579 self.cursor = cursor;
580 self.window.set_cursor(translate::cursor(cursor));
581 }
582 if let Some(area) = self.ui.ime_area() {
583 self.window.set_ime_cursor_area(
584 PhysicalPosition::new(area.left(), area.top()),
585 PhysicalSize::new(area.width().max(1.0), area.height().max(1.0)),
586 );
587 }
588 if self.ui.needs_paint() {
589 self.window.request_redraw();
590 }
591 }
592}