mod access;
mod clipboard;
mod gpu;
mod translate;
use std::error::Error;
use std::fmt;
use std::path::PathBuf;
use std::sync::Arc;
use std::time::Instant;
use densa_core::{Point, Size};
use densa_draw::{Canvas, InstanceBuffer};
use densa_paragraph::TextSystem;
use densa_text::{AtlasConfig, GlyphCache};
use densa_ui::{Action, Cursor, KeyEvent, PointerEvent, PointerKind, Theme, Ui};
use winit::application::ApplicationHandler;
use winit::dpi::{LogicalSize, PhysicalPosition, PhysicalSize};
use winit::event::{ElementState, WindowEvent};
use winit::event_loop::{ActiveEventLoop, EventLoop, EventLoopProxy};
use winit::keyboard::ModifiersState;
use winit::window::{Window, WindowId};
use crate::gpu::Gpu;
pub trait App: 'static {
fn build(&mut self, ui: &mut Ui, host: &mut Host<'_>) -> Result<(), Box<dyn Error>>;
fn action(&mut self, ui: &mut Ui, host: &mut Host<'_>, action: Action) {
let _ = (ui, host, action);
}
fn file_drop(&mut self, ui: &mut Ui, host: &mut Host<'_>, drop: FileDrop) {
let _ = (ui, host, drop);
}
fn frame(&mut self, ui: &mut Ui, host: &mut Host<'_>) -> bool {
let _ = (ui, host);
false
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[non_exhaustive]
pub enum FileDrop {
Hovering(PathBuf),
Left,
Dropped(PathBuf),
}
#[derive(Debug)]
pub struct Host<'a> {
window: &'a Window,
proxy: &'a EventLoopProxy<UserEvent>,
close: &'a mut bool,
}
impl Host<'_> {
pub fn waker(&self) -> Waker {
Waker(self.proxy.clone())
}
pub fn set_title(&self, title: &str) {
self.window.set_title(title);
}
pub fn close(&mut self) {
*self.close = true;
}
}
#[derive(Debug, Clone)]
pub struct Waker(EventLoopProxy<UserEvent>);
impl Waker {
pub fn wake(&self) -> bool {
self.0.send_event(UserEvent::Wake).is_ok()
}
}
#[derive(Debug)]
enum UserEvent {
Wake,
Access(accesskit_winit::Event),
}
impl From<accesskit_winit::Event> for UserEvent {
fn from(event: accesskit_winit::Event) -> Self {
Self::Access(event)
}
}
#[derive(Debug, Clone)]
#[non_exhaustive]
pub struct WindowOptions {
pub title: String,
pub size: Size,
pub theme: Theme,
pub instances: usize,
pub backend: Backend,
pub file_drops: bool,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
#[non_exhaustive]
pub enum Backend {
#[default]
Auto,
Vulkan,
Metal,
Dx12,
Gl,
}
impl WindowOptions {
pub fn new(title: impl Into<String>) -> Self {
Self {
title: title.into(),
size: Size::new(1280.0, 800.0),
theme: Theme::default(),
instances: 65_536,
backend: Backend::Auto,
file_drops: true,
}
}
#[must_use]
pub fn with_size(mut self, width: f32, height: f32) -> Self {
self.size = Size::new(width, height);
self
}
#[must_use]
pub fn with_theme(mut self, theme: Theme) -> Self {
self.theme = theme;
self
}
#[must_use]
pub fn with_file_drops(mut self, file_drops: bool) -> Self {
self.file_drops = file_drops;
self
}
#[must_use]
pub fn with_backend(mut self, backend: Backend) -> Self {
self.backend = backend;
self
}
#[must_use]
pub fn with_instances(mut self, instances: usize) -> Self {
self.instances = instances;
self
}
}
#[derive(Debug)]
#[non_exhaustive]
pub enum WindowError {
Window(String),
Gpu(String),
Text(String),
App(Box<dyn Error>),
}
impl fmt::Display for WindowError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Window(message) => write!(f, "window system: {message}"),
Self::Gpu(message) => write!(f, "GPU: {message}"),
Self::Text(message) => write!(f, "fonts: {message}"),
Self::App(error) => write!(f, "application: {error}"),
}
}
}
impl Error for WindowError {
fn source(&self) -> Option<&(dyn Error + 'static)> {
match self {
Self::App(error) => Some(error.as_ref()),
_ => None,
}
}
}
pub fn run(options: WindowOptions, app: impl App) -> Result<(), WindowError> {
let event_loop = EventLoop::<UserEvent>::with_user_event()
.build()
.map_err(|e| WindowError::Window(e.to_string()))?;
let mut shell = Shell {
app,
options,
proxy: event_loop.create_proxy(),
session: None,
error: None,
close: false,
};
event_loop
.run_app(&mut shell)
.map_err(|e| WindowError::Window(e.to_string()))?;
shell.error.map_or(Ok(()), Err)
}
struct Shell<A: App> {
app: A,
options: WindowOptions,
proxy: EventLoopProxy<UserEvent>,
session: Option<Session>,
error: Option<WindowError>,
close: bool,
}
struct Session {
window: Arc<Window>,
access: accesskit_winit::Adapter,
gpu: Gpu,
ui: Ui,
cache: GlyphCache,
instances: InstanceBuffer,
start: Instant,
pointer: Point,
modifiers: ModifiersState,
cursor: Cursor,
continuous: bool,
}
impl<A: App> Shell<A> {
fn fail(&mut self, event_loop: &ActiveEventLoop, error: WindowError) {
self.error = Some(error);
event_loop.exit();
}
fn open(&mut self, event_loop: &ActiveEventLoop) -> Result<Session, WindowError> {
let size = self.options.size;
let attributes = Window::default_attributes()
.with_title(self.options.title.clone())
.with_inner_size(LogicalSize::new(size.width, size.height))
.with_visible(false);
#[cfg(windows)]
let attributes = {
use winit::platform::windows::WindowAttributesExtWindows;
attributes.with_drag_and_drop(self.options.file_drops)
};
let window = event_loop
.create_window(attributes)
.map_err(|e| WindowError::Window(e.to_string()))?;
let access = accesskit_winit::Adapter::with_event_loop_proxy(
event_loop,
&window,
self.proxy.clone(),
);
let window = Arc::new(window);
window.set_ime_allowed(true);
let mut cache = GlyphCache::new(AtlasConfig::DEFAULT);
let gpu = Gpu::new(
event_loop,
&window,
self.options.instances,
&mut cache,
self.options.backend,
)?;
let text = TextSystem::new().map_err(|e| WindowError::Text(e.to_string()))?;
let mut ui = Ui::new(text);
ui.set_theme(self.options.theme.clone());
ui.set_clipboard(clipboard::SystemClipboard::default());
ui.set_scale(window.scale_factor() as f32);
let inner = window.inner_size();
ui.resize(Size::new(inner.width as f32, inner.height as f32));
let mut host = Host {
window: &window,
proxy: &self.proxy,
close: &mut self.close,
};
self.app
.build(&mut ui, &mut host)
.map_err(WindowError::App)?;
window.set_visible(true);
Ok(Session {
window,
access,
gpu,
ui,
cache,
instances: InstanceBuffer::with_capacity(self.options.instances),
start: Instant::now(),
pointer: Point::ORIGIN,
modifiers: ModifiersState::empty(),
cursor: Cursor::Default,
continuous: false,
})
}
}
impl<A: App> ApplicationHandler<UserEvent> for Shell<A> {
fn resumed(&mut self, event_loop: &ActiveEventLoop) {
if self.session.is_some() {
return;
}
match self.open(event_loop) {
Ok(session) => {
session.window.request_redraw();
self.session = Some(session);
if self.close {
event_loop.exit();
}
}
Err(error) => self.fail(event_loop, error),
}
}
fn window_event(&mut self, event_loop: &ActiveEventLoop, _: WindowId, event: WindowEvent) {
let Some(session) = self.session.as_mut() else {
return;
};
let window = Arc::clone(&session.window);
session.access.process_event(&window, &event);
let redraw = matches!(event, WindowEvent::RedrawRequested);
let mut close = false;
let mut failed = None;
{
let mut host = Host {
window: &window,
proxy: &self.proxy,
close: &mut close,
};
match event {
WindowEvent::CloseRequested => host.close(),
WindowEvent::RedrawRequested => {
session.continuous = self.app.frame(&mut session.ui, &mut host);
failed = session.paint().err();
}
other => match translate::file_drop(&other) {
Some(drop) if self.options.file_drops => {
self.app.file_drop(&mut session.ui, &mut host, drop);
window.request_redraw();
}
Some(_) => {}
None => session.input(&other),
},
}
if failed.is_none() {
while let Some(action) = session.ui.next_action() {
self.app.action(&mut session.ui, &mut host, action);
}
session.reflect();
if redraw {
session.publish_access();
}
}
}
if let Some(error) = failed {
self.fail(event_loop, error);
} else if close {
event_loop.exit();
}
}
fn user_event(&mut self, event_loop: &ActiveEventLoop, event: UserEvent) {
let Some(session) = self.session.as_mut() else {
return;
};
let access = match event {
UserEvent::Wake => {
session.window.request_redraw();
return;
}
UserEvent::Access(access) => access.window_event,
};
match access {
accesskit_winit::WindowEvent::InitialTreeRequested => session.publish_access(),
accesskit_winit::WindowEvent::ActionRequested(request) => {
access::perform(&mut session.ui, &request);
let window = Arc::clone(&session.window);
let mut close = false;
let mut host = Host {
window: &window,
proxy: &self.proxy,
close: &mut close,
};
while let Some(action) = session.ui.next_action() {
self.app.action(&mut session.ui, &mut host, action);
}
session.reflect();
session.publish_access();
if close {
event_loop.exit();
}
}
accesskit_winit::WindowEvent::AccessibilityDeactivated => {}
}
}
fn about_to_wait(&mut self, _: &ActiveEventLoop) {
if let Some(session) = &self.session {
if session.continuous {
session.window.request_redraw();
}
}
}
}
impl Session {
fn input(&mut self, event: &WindowEvent) {
let modifiers = translate::modifiers(self.modifiers);
let at = |kind: PointerKind, position: Point, start: Instant| {
PointerEvent::new(kind, position)
.with_modifiers(modifiers)
.at(start.elapsed())
};
match event {
WindowEvent::Resized(size) => {
self.gpu.resize(size.width, size.height);
let size = Size::new(size.width as f32, size.height as f32);
if size != self.ui.viewport() {
self.ui.close_popups();
}
self.ui.resize(size);
}
WindowEvent::ScaleFactorChanged { scale_factor, .. } => {
self.ui.close_popups();
self.ui.set_scale(*scale_factor as f32);
}
WindowEvent::ModifiersChanged(state) => self.modifiers = state.state(),
WindowEvent::CursorMoved { position, .. } => {
self.pointer = Point::new(position.x as f32, position.y as f32);
self.ui
.pointer(at(PointerKind::Move, self.pointer, self.start));
}
WindowEvent::CursorLeft { .. } => self.ui.pointer_left(self.pointer),
WindowEvent::Focused(false) => {
self.ui.cancel_pointer();
self.ui.close_popups();
self.modifiers = ModifiersState::empty();
}
WindowEvent::MouseInput { state, button, .. } => {
let button = translate::button(*button);
let kind = match state {
ElementState::Pressed => PointerKind::Down(button),
ElementState::Released => PointerKind::Up(button),
};
self.ui.pointer(at(kind, self.pointer, self.start));
}
WindowEvent::MouseWheel { delta, .. } => {
let kind = PointerKind::Scroll(translate::scroll(*delta));
self.ui.pointer(at(kind, self.pointer, self.start));
}
WindowEvent::KeyboardInput {
event,
is_synthetic: false,
..
} => {
let Some(key) = translate::key(&event.logical_key) else {
return;
};
let mut key = if event.state == ElementState::Pressed {
KeyEvent::down(key)
} else {
KeyEvent::up(key)
}
.with_modifiers(modifiers);
if let Some(text) = translate::text(event.text.as_deref()) {
key = key.with_text(text);
}
if event.repeat {
key = key.repeated();
}
self.ui.key(&key);
}
WindowEvent::Ime(ime) => {
self.ui.ime(&translate::ime(ime));
}
_ => {}
}
}
fn paint(&mut self) -> Result<(), WindowError> {
{
let mut canvas = Canvas::begin(&mut self.cache, &mut self.instances);
self.ui.paint(&mut canvas);
}
let background = self.ui.theme().background;
self.gpu.draw(
&self.window,
&mut self.cache,
self.instances.as_slice(),
background,
)?;
Ok(())
}
fn publish_access(&mut self) {
let Self {
access, ui, window, ..
} = self;
access.update_if_active(|| access::tree_update(&ui.access_tree(), &window.title()));
}
fn reflect(&mut self) {
let cursor = self.ui.cursor();
if cursor != self.cursor {
self.cursor = cursor;
self.window.set_cursor(translate::cursor(cursor));
}
if let Some(area) = self.ui.ime_area() {
self.window.set_ime_cursor_area(
PhysicalPosition::new(area.left(), area.top()),
PhysicalSize::new(area.width().max(1.0), area.height().max(1.0)),
);
}
if self.ui.needs_paint() {
self.window.request_redraw();
}
}
}