use crate::prelude::*; use arboard::Clipboard; use std::{marker::Sized, sync::Arc, time::Instant}; use winit::{ event::{Ime, WindowEvent}, event_loop::{ActiveEventLoop, EventLoopProxy}, window::{Window, WindowAttributes}, }; mod access; mod app; mod attr; mod input; mod logging; mod platform; mod render; pub use access::*; pub use app::*; pub use input::*; pub use render::*; pub type Proxy = EventLoopProxy; /// Physical pixels per dp. Layout and input stay in physical pixels; only /// `dp(...)` resolves through this scale. pub fn content_scale(window: &Window) -> f32 { match std::env::var("IRIS_SCALE") { Err(_) => window.scale_factor() as f32, Ok(text) => match text.trim().parse::() { Ok(scale) if scale > 0.0 => scale, _ => { log::warn!("IRIS_SCALE={text:?} is not a positive number; using the window's own"); window.scale_factor() as f32 } }, } } pub struct DesktopUiState { pub root: Option, pub renderer: UiRenderer, pub input: Input, pub focus: Option>, pub clipboard: Clipboard, pub window: Arc, pub ime: usize, pub last_click: Instant, pub access_adapter: accesskit_winit::Adapter, pub access: AccessTree, } impl HasRoot> for DesktopUiState { fn set_root(&mut self, rsc: &mut StdRsc, root: StrongWidget) { self.root = Some(crate::overlay::default_overlay_root(rsc, root)); } } impl DesktopUiState { pub fn new(window: impl Into>, access_adapter: accesskit_winit::Adapter) -> Self { let window = window.into(); Self { root: None, renderer: UiRenderer::new(window.clone()), window, input: Input::default(), clipboard: Clipboard::new().unwrap(), ime: 0, last_click: Instant::now(), focus: None, access_adapter, access: AccessTree::new(), } } } pub trait HasDesktopUiState: Sized + 'static { fn desktop_state(&self) -> &DesktopUiState; fn desktop_state_mut(&mut self) -> &mut DesktopUiState; } pub trait DesktopAppState: HasDesktopUiState { type Event = (); fn new(ui_state: DesktopUiState, rsc: &mut StdRsc, proxy: Proxy) -> Self; #[allow(unused_variables)] fn event(&mut self, event: Self::Event, rsc: &mut StdRsc) {} #[allow(unused_variables)] fn exit(&mut self, rsc: &mut StdRsc) {} #[allow(unused_variables)] fn window_event(&mut self, event: WindowEvent, rsc: &mut StdRsc) {} fn window_attributes() -> WindowAttributes { Default::default() } } pub struct DesktopApp { rsc: StdRsc, state: State, task_recv: TaskMsgReceiver>, } impl AppState for DesktopApp { type Event = State::Event; fn new(event_loop: &ActiveEventLoop, proxy: EventLoopProxy) -> Self { let window = event_loop .create_window(State::window_attributes().with_visible(false)) .unwrap(); let access_adapter = accesskit_winit::Adapter::with_direct_handlers( event_loop, &window, NullActivationHandler, NullActionHandler, NullDeactivationHandler, ); window.set_visible(true); let desktop_state = DesktopUiState::new(window, access_adapter); let (mut rsc, task_recv) = StdRsc::new(desktop_state.window.clone()); // Set before building widgets so the first text shape uses the right density. let scale = content_scale(desktop_state.window.as_ref()); rsc.ui.set_density(scale); let state = State::new(desktop_state, &mut rsc, proxy); Self { rsc, state, task_recv, } } fn event(&mut self, event: Self::Event, _: &ActiveEventLoop) { self.state.event(event, &mut self.rsc); } fn window_event(&mut self, event: WindowEvent, event_loop: &ActiveEventLoop) { let Self { rsc, state, task_recv, } = self; for update in task_recv.try_iter() { update(state, rsc); } let ui_state = state.desktop_state_mut(); // Required by `accesskit_winit` on every window event, not just the // ones this backend otherwise cares about -- some platform adapters // rely on it to notice activation (a screen reader turning on). ui_state .access_adapter .process_event(&ui_state.window, &event); let input_changed = ui_state.input.event(&event); let cursor_state = ui_state.cursor_state().clone(); let old = ui_state.focus; if cursor_state.buttons.left.is_start() { ui_state.focus = None; } if input_changed { // Winit delivers one sample at a time, so there is no history batch. if crate::diagnostics::trace_enabled() { let action = if cursor_state.buttons.left.is_start() { "down" } else if cursor_state.buttons.left.is_end() { "up" } else { "move" }; let render_state = rsc.ui.render_state(); let t_ms = cursor_state .time .duration_since(render_state.get().epoch()) .as_millis() as u64; crate::sense::log_input_event( action, cursor_state.pos.x, cursor_state.pos.y, t_ms, &[], ); } let window_size = ui_state.window_size(); let render_state = rsc.ui.render_state(); render_state .get() .run_sensors(rsc, state, cursor_state, window_size); } let ui_state = state.desktop_state_mut(); if old != ui_state.focus && let Some(old) = old { old.edit(rsc).deselect(); } match &event { WindowEvent::CloseRequested => event_loop.exit(), WindowEvent::RedrawRequested => { // Advance animations before drawing and keep requesting frames while active. let frame_start = std::time::Instant::now(); let animating = rsc.ui_mut().tick_animations(frame_start); let ui_state = state.desktop_state_mut(); if animating { ui_state.window.request_redraw(); } rsc.draw(&ui_state.root); ui_state.renderer.update(&mut rsc.ui); let mut parts = ui_state.renderer.draw(); parts.total = frame_start.elapsed(); let render_state = rsc.ui.render_state(); let render_state = render_state.get(); crate::diagnostics::log_frame(&render_state, frame_start, parts, animating); if let Some(tree_update) = ui_state.access.update(rsc.widgets(), &render_state, rsc) { ui_state.access_adapter.update_if_active(|| tree_update); } } WindowEvent::Resized(size) => { rsc.ui.resize((size.width, size.height)); ui_state.renderer.resize(size) } WindowEvent::ScaleFactorChanged { .. } => { let scale = content_scale(ui_state.window.as_ref()); rsc.ui.set_density(scale); ui_state.window.request_redraw(); } WindowEvent::KeyboardInput { event, .. } => { let requested = event.state.is_pressed().then(|| match &event.logical_key { winit::keyboard::Key::Character(c) if ui_state.input.modifiers.control => { match c.as_str().to_ascii_lowercase().as_str() { "c" => Some(Command::Copy), "a" => Some(Command::SelectAll), _ => None, } } winit::keyboard::Key::Named(winit::keyboard::NamedKey::Escape) => { Some(Command::Escape) } _ => None, }); let command = requested .flatten() .map_or(CommandResult::Unused, |command| rsc.run_command(command)); let command_used = match command { CommandResult::Copy(text) => { if let Err(err) = ui_state.clipboard.set_text(text) { eprintln!("failed to copy text to clipboard: {err}") } true } CommandResult::Used => true, CommandResult::Unused => false, }; if !command_used && !rsc.events.controllers.command_target_blocks_input() && let Some(sel) = ui_state.focus && event.state.is_pressed() { let mut text = sel.edit(rsc); match text.apply_event(event, &ui_state.input.modifiers) { TextInputResult::Unfocus => { ui_state.focus = None; ui_state.window.set_ime_allowed(false); } TextInputResult::Submit => { rsc.run_event::(sel, (), state); } TextInputResult::Paste => { if let Ok(t) = ui_state.clipboard.get_text() { text.insert(&t); } rsc.run_event::(sel, (), state); } TextInputResult::Copy(text) => { if let Err(err) = ui_state.clipboard.set_text(text) { eprintln!("failed to copy text to clipboard: {err}") } } TextInputResult::Used => { rsc.run_event::(sel, (), state); } TextInputResult::Unused => {} } } } WindowEvent::Ime(ime) => { if !rsc.events.controllers.command_target_blocks_input() && let Some(sel) = ui_state.focus { let mut text = sel.edit(rsc); match ime { Ime::Enabled | Ime::Disabled => (), Ime::Preedit(content, _pos) => { // TODO: highlight once that's real text.replace(ui_state.ime, content); ui_state.ime = content.chars().count(); } Ime::Commit(content) => { text.insert(content); } } } } _ => (), } state.window_event(event, rsc); let ui_state = self.state.desktop_state_mut(); let render_state = rsc.ui.render_state(); if render_state .get() .needs_redraw(&ui_state.root, rsc.widgets()) { ui_state.renderer.window().request_redraw(); } ui_state.input.end_frame(); } fn exit(&mut self) { self.state.exit(&mut self.rsc); } }