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