1mod access;
42mod clipboard;
43mod gpu;
44mod translate;
45
46use std::error::Error;
47use std::fmt;
48use std::path::PathBuf;
49use std::sync::Arc;
50use std::time::Instant;
51
52use densa_core::{Point, Size};
53use densa_draw::{Canvas, InstanceBuffer};
54use densa_paragraph::TextSystem;
55use densa_text::{AtlasConfig, GlyphCache};
56use densa_ui::{Action, Cursor, KeyEvent, PointerEvent, PointerKind, Theme, Ui};
57use winit::application::ApplicationHandler;
58use winit::dpi::{LogicalSize, PhysicalPosition, PhysicalSize};
59use winit::event::{ElementState, WindowEvent};
60use winit::event_loop::{ActiveEventLoop, EventLoop, EventLoopProxy};
61use winit::keyboard::ModifiersState;
62use winit::window::{Window, WindowId};
63
64use crate::gpu::Gpu;
65
66pub trait App: 'static {
70 fn build(&mut self, ui: &mut Ui, host: &mut Host<'_>) -> Result<(), Box<dyn Error>>;
80
81 fn action(&mut self, ui: &mut Ui, host: &mut Host<'_>, action: Action) {
84 let _ = (ui, host, action);
85 }
86
87 fn file_drop(&mut self, ui: &mut Ui, host: &mut Host<'_>, drop: FileDrop) {
90 let _ = (ui, host, drop);
91 }
92
93 fn frame(&mut self, ui: &mut Ui, host: &mut Host<'_>) -> bool {
99 let _ = (ui, host);
100 false
101 }
102}
103
104#[derive(Debug, Clone, PartialEq, Eq)]
110#[non_exhaustive]
111pub enum FileDrop {
112 Hovering(PathBuf),
114 Left,
116 Dropped(PathBuf),
118}
119
120#[derive(Debug)]
122pub struct Host<'a> {
123 window: &'a Window,
124 proxy: &'a EventLoopProxy<UserEvent>,
125 close: &'a mut bool,
126}
127
128impl Host<'_> {
129 pub fn waker(&self) -> Waker {
132 Waker(self.proxy.clone())
133 }
134
135 pub fn set_title(&self, title: &str) {
137 self.window.set_title(title);
138 }
139
140 pub fn close(&mut self) {
142 *self.close = true;
143 }
144}
145
146#[derive(Debug, Clone)]
148pub struct Waker(EventLoopProxy<UserEvent>);
149
150impl Waker {
151 pub fn wake(&self) -> bool {
154 self.0.send_event(UserEvent::Wake).is_ok()
155 }
156}
157
158#[derive(Debug)]
161enum UserEvent {
162 Wake,
163 Access(accesskit_winit::Event),
164}
165
166impl From<accesskit_winit::Event> for UserEvent {
167 fn from(event: accesskit_winit::Event) -> Self {
168 Self::Access(event)
169 }
170}
171
172#[derive(Debug, Clone)]
174#[non_exhaustive]
175pub struct WindowOptions {
176 pub title: String,
178 pub size: Size,
180 pub theme: Theme,
182 pub instances: usize,
184 pub backend: Backend,
186 pub file_drops: bool,
190}
191
192#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
197#[non_exhaustive]
198pub enum Backend {
199 #[default]
202 Auto,
203 Vulkan,
205 Metal,
207 Dx12,
209 Gl,
212}
213
214impl WindowOptions {
215 pub fn new(title: impl Into<String>) -> Self {
217 Self {
218 title: title.into(),
219 size: Size::new(1280.0, 800.0),
220 theme: Theme::default(),
221 instances: 65_536,
222 backend: Backend::Auto,
223 file_drops: true,
224 }
225 }
226
227 #[must_use]
229 pub fn with_size(mut self, width: f32, height: f32) -> Self {
230 self.size = Size::new(width, height);
231 self
232 }
233
234 #[must_use]
236 pub fn with_theme(mut self, theme: Theme) -> Self {
237 self.theme = theme;
238 self
239 }
240
241 #[must_use]
244 pub fn with_file_drops(mut self, file_drops: bool) -> Self {
245 self.file_drops = file_drops;
246 self
247 }
248
249 #[must_use]
251 pub fn with_backend(mut self, backend: Backend) -> Self {
252 self.backend = backend;
253 self
254 }
255
256 #[must_use]
258 pub fn with_instances(mut self, instances: usize) -> Self {
259 self.instances = instances;
260 self
261 }
262}
263
264#[derive(Debug)]
266#[non_exhaustive]
267pub enum WindowError {
268 Window(String),
270 Gpu(String),
272 Text(String),
274 App(Box<dyn Error>),
276}
277
278impl fmt::Display for WindowError {
279 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
280 match self {
281 Self::Window(message) => write!(f, "window system: {message}"),
282 Self::Gpu(message) => write!(f, "GPU: {message}"),
283 Self::Text(message) => write!(f, "fonts: {message}"),
284 Self::App(error) => write!(f, "application: {error}"),
285 }
286 }
287}
288
289impl Error for WindowError {
290 fn source(&self) -> Option<&(dyn Error + 'static)> {
291 match self {
292 Self::App(error) => Some(error.as_ref()),
293 _ => None,
294 }
295 }
296}
297
298pub fn run(options: WindowOptions, app: impl App) -> Result<(), WindowError> {
304 let event_loop = EventLoop::<UserEvent>::with_user_event()
305 .build()
306 .map_err(|e| WindowError::Window(e.to_string()))?;
307 let mut shell = Shell {
308 app,
309 options,
310 proxy: event_loop.create_proxy(),
311 session: None,
312 error: None,
313 close: false,
314 };
315 event_loop
316 .run_app(&mut shell)
317 .map_err(|e| WindowError::Window(e.to_string()))?;
318 shell.error.map_or(Ok(()), Err)
319}
320
321struct Shell<A: App> {
322 app: A,
323 options: WindowOptions,
324 proxy: EventLoopProxy<UserEvent>,
325 session: Option<Session>,
326 error: Option<WindowError>,
327 close: bool,
329}
330
331struct Session {
333 window: Arc<Window>,
334 access: accesskit_winit::Adapter,
336 gpu: Gpu,
337 ui: Ui,
338 cache: GlyphCache,
339 instances: InstanceBuffer,
340 start: Instant,
341 pointer: Point,
342 modifiers: ModifiersState,
343 cursor: Cursor,
344 continuous: bool,
345}
346
347impl<A: App> Shell<A> {
348 fn fail(&mut self, event_loop: &ActiveEventLoop, error: WindowError) {
349 self.error = Some(error);
350 event_loop.exit();
351 }
352
353 fn open(&mut self, event_loop: &ActiveEventLoop) -> Result<Session, WindowError> {
354 let size = self.options.size;
355 let attributes = Window::default_attributes()
357 .with_title(self.options.title.clone())
358 .with_inner_size(LogicalSize::new(size.width, size.height))
359 .with_visible(false);
360 #[cfg(windows)]
361 let attributes = {
362 use winit::platform::windows::WindowAttributesExtWindows;
363 attributes.with_drag_and_drop(self.options.file_drops)
364 };
365 let window = event_loop
366 .create_window(attributes)
367 .map_err(|e| WindowError::Window(e.to_string()))?;
368 let access = accesskit_winit::Adapter::with_event_loop_proxy(
369 event_loop,
370 &window,
371 self.proxy.clone(),
372 );
373 let window = Arc::new(window);
374 window.set_ime_allowed(true);
375 let mut cache = GlyphCache::new(AtlasConfig::DEFAULT);
376 let gpu = Gpu::new(
377 event_loop,
378 &window,
379 self.options.instances,
380 &mut cache,
381 self.options.backend,
382 )?;
383 let text = TextSystem::new().map_err(|e| WindowError::Text(e.to_string()))?;
384 let mut ui = Ui::new(text);
385 ui.set_theme(self.options.theme.clone());
386 ui.set_clipboard(clipboard::SystemClipboard::default());
387 ui.set_scale(window.scale_factor() as f32);
388 let inner = window.inner_size();
389 ui.resize(Size::new(inner.width as f32, inner.height as f32));
390 let mut host = Host {
391 window: &window,
392 proxy: &self.proxy,
393 close: &mut self.close,
394 };
395 self.app
396 .build(&mut ui, &mut host)
397 .map_err(WindowError::App)?;
398 window.set_visible(true);
399 Ok(Session {
400 window,
401 access,
402 gpu,
403 ui,
404 cache,
405 instances: InstanceBuffer::with_capacity(self.options.instances),
406 start: Instant::now(),
407 pointer: Point::ORIGIN,
408 modifiers: ModifiersState::empty(),
409 cursor: Cursor::Default,
410 continuous: false,
411 })
412 }
413}
414
415impl<A: App> ApplicationHandler<UserEvent> for Shell<A> {
416 fn resumed(&mut self, event_loop: &ActiveEventLoop) {
417 if self.session.is_some() {
418 return;
419 }
420 match self.open(event_loop) {
421 Ok(session) => {
422 session.window.request_redraw();
423 self.session = Some(session);
424 if self.close {
425 event_loop.exit();
426 }
427 }
428 Err(error) => self.fail(event_loop, error),
429 }
430 }
431
432 fn window_event(&mut self, event_loop: &ActiveEventLoop, _: WindowId, event: WindowEvent) {
433 let Some(session) = self.session.as_mut() else {
434 return;
435 };
436 let window = Arc::clone(&session.window);
437 session.access.process_event(&window, &event);
438 let redraw = matches!(event, WindowEvent::RedrawRequested);
439 let mut close = false;
440 let mut failed = None;
441 {
442 let mut host = Host {
443 window: &window,
444 proxy: &self.proxy,
445 close: &mut close,
446 };
447 match event {
448 WindowEvent::CloseRequested => host.close(),
449 WindowEvent::RedrawRequested => {
450 session.continuous = self.app.frame(&mut session.ui, &mut host);
451 failed = session.paint().err();
452 }
453 other => match translate::file_drop(&other) {
454 Some(drop) if self.options.file_drops => {
455 self.app.file_drop(&mut session.ui, &mut host, drop);
456 window.request_redraw();
458 }
459 Some(_) => {}
461 None => session.input(&other),
462 },
463 }
464 if failed.is_none() {
465 while let Some(action) = session.ui.next_action() {
466 self.app.action(&mut session.ui, &mut host, action);
467 }
468 session.reflect();
469 if redraw {
472 session.publish_access();
473 }
474 }
475 }
476 if let Some(error) = failed {
477 self.fail(event_loop, error);
478 } else if close {
479 event_loop.exit();
480 }
481 }
482
483 fn user_event(&mut self, event_loop: &ActiveEventLoop, event: UserEvent) {
484 let Some(session) = self.session.as_mut() else {
485 return;
486 };
487 let access = match event {
488 UserEvent::Wake => {
489 session.window.request_redraw();
490 return;
491 }
492 UserEvent::Access(access) => access.window_event,
493 };
494 match access {
495 accesskit_winit::WindowEvent::InitialTreeRequested => session.publish_access(),
496 accesskit_winit::WindowEvent::ActionRequested(request) => {
497 access::perform(&mut session.ui, &request);
498 let window = Arc::clone(&session.window);
499 let mut close = false;
500 let mut host = Host {
501 window: &window,
502 proxy: &self.proxy,
503 close: &mut close,
504 };
505 while let Some(action) = session.ui.next_action() {
506 self.app.action(&mut session.ui, &mut host, action);
507 }
508 session.reflect();
509 session.publish_access();
510 if close {
511 event_loop.exit();
512 }
513 }
514 accesskit_winit::WindowEvent::AccessibilityDeactivated => {}
515 }
516 }
517
518 fn about_to_wait(&mut self, _: &ActiveEventLoop) {
519 if let Some(session) = &self.session {
520 if session.continuous {
521 session.window.request_redraw();
522 }
523 }
524 }
525}
526
527impl Session {
528 fn input(&mut self, event: &WindowEvent) {
530 let modifiers = translate::modifiers(self.modifiers);
531 let at = |kind: PointerKind, position: Point, start: Instant| {
532 PointerEvent::new(kind, position)
533 .with_modifiers(modifiers)
534 .at(start.elapsed())
535 };
536 match event {
537 WindowEvent::Resized(size) => {
538 self.gpu.resize(size.width, size.height);
539 let size = Size::new(size.width as f32, size.height as f32);
540 if size != self.ui.viewport() {
543 self.ui.close_popups();
544 }
545 self.ui.resize(size);
546 }
547 WindowEvent::ScaleFactorChanged { scale_factor, .. } => {
548 self.ui.close_popups();
550 self.ui.set_scale(*scale_factor as f32);
551 }
552 WindowEvent::ModifiersChanged(state) => self.modifiers = state.state(),
553 WindowEvent::CursorMoved { position, .. } => {
554 self.pointer = Point::new(position.x as f32, position.y as f32);
555 self.ui
556 .pointer(at(PointerKind::Move, self.pointer, self.start));
557 }
558 WindowEvent::CursorLeft { .. } => self.ui.pointer_left(self.pointer),
559 WindowEvent::Focused(false) => {
561 self.ui.cancel_pointer();
562 self.ui.close_popups();
564 self.modifiers = ModifiersState::empty();
565 }
566 WindowEvent::MouseInput { state, button, .. } => {
567 let button = translate::button(*button);
568 let kind = match state {
569 ElementState::Pressed => PointerKind::Down(button),
570 ElementState::Released => PointerKind::Up(button),
571 };
572 self.ui.pointer(at(kind, self.pointer, self.start));
573 }
574 WindowEvent::MouseWheel { delta, .. } => {
575 let kind = PointerKind::Scroll(translate::scroll(*delta));
576 self.ui.pointer(at(kind, self.pointer, self.start));
577 }
578 WindowEvent::KeyboardInput {
579 event,
580 is_synthetic: false,
581 ..
582 } => {
583 let Some(key) = translate::key(&event.logical_key) else {
584 return;
585 };
586 let mut key = if event.state == ElementState::Pressed {
587 KeyEvent::down(key)
588 } else {
589 KeyEvent::up(key)
590 }
591 .with_modifiers(modifiers);
592 if let Some(text) = translate::text(event.text.as_deref()) {
593 key = key.with_text(text);
594 }
595 if event.repeat {
596 key = key.repeated();
597 }
598 self.ui.key(&key);
599 }
600 WindowEvent::Ime(ime) => {
601 self.ui.ime(&translate::ime(ime));
602 }
603 _ => {}
604 }
605 }
606
607 fn paint(&mut self) -> Result<(), WindowError> {
609 {
610 let mut canvas = Canvas::begin(&mut self.cache, &mut self.instances);
611 self.ui.paint(&mut canvas);
612 }
613 let background = self.ui.theme().background;
614 self.gpu.draw(
615 &self.window,
616 &mut self.cache,
617 self.instances.as_slice(),
618 background,
619 )?;
620 Ok(())
621 }
622
623 fn publish_access(&mut self) {
626 let Self {
627 access, ui, window, ..
628 } = self;
629 access.update_if_active(|| access::tree_update(&ui.access_tree(), &window.title()));
630 }
631
632 fn reflect(&mut self) {
634 let cursor = self.ui.cursor();
635 if cursor != self.cursor {
636 self.cursor = cursor;
637 self.window.set_cursor(translate::cursor(cursor));
638 }
639 if let Some(area) = self.ui.ime_area() {
640 self.window.set_ime_cursor_area(
641 PhysicalPosition::new(area.left(), area.top()),
642 PhysicalSize::new(area.width().max(1.0), area.height().max(1.0)),
643 );
644 }
645 if self.ui.needs_paint() {
646 self.window.request_redraw();
647 }
648 }
649}