use std::{ num::NonZeroU32, sync::{Arc, RwLock}, }; use softbuffer::{Context, Surface}; use winit::{ application::ApplicationHandler, dpi::{LogicalPosition, LogicalSize, PhysicalPosition}, event::{ElementState, Ime, MouseButton, MouseScrollDelta, WindowEvent}, event_loop::{ActiveEventLoop, EventLoopProxy}, keyboard::{Key, ModifiersState, NamedKey}, window::{Window, WindowAttributes, WindowId}, }; use crate::{ activity::Activity, developer_tools::DeveloperToolsState, document::NamespaceState, geometry::Point, scene::{self, SceneLayout}, software_renderer::SoftwareRenderer, }; #[derive(Clone, Debug)] pub enum AppEvent { RuntimeFailed { message: String }, DocumentUpdated, ConsoleFailed { message: String, source: String }, } pub struct TesseraApp { window: Option>, context: Option>>, surface: Option, Arc>>, namespace: Arc>, developer_tools: DeveloperToolsState, layout: SceneLayout, pointer: Point, modifiers: ModifiersState, activity: Activity, event_client: crate::events::Client, guest_pressed: Option, guest_focus: Option, guest_key: Option<(NamedKey, crate::events::Input)>, guest_keyboard: bool, } impl TesseraApp { pub fn new( namespace: Arc>, activity: Activity, proxy: EventLoopProxy, ) -> anyhow::Result { let event_client = crate::events::Client::new(proxy.clone())?; Ok(Self { window: None, context: None, surface: None, namespace, developer_tools: { let mut state = DeveloperToolsState::default(); state.proxy = Some(proxy); state }, layout: SceneLayout::default(), pointer: Point::default(), modifiers: ModifiersState::default(), activity, event_client, guest_pressed: None, guest_focus: None, guest_key: None, guest_keyboard: true, }) } fn apply_window_commands(&self) { let Some(window) = &self.window else { return; }; let Ok(mut namespace) = self.namespace.write() else { return; }; let commands = std::mem::take(&mut namespace.window_commands); for envelope in commands { if envelope.session == 0 || envelope.session != namespace.runtime_session || envelope.command.window != crate::window::MAIN_WINDOW { continue; } match envelope.command.action { crate::window::Action::Resize { width, height } => { let _ = window.request_inner_size(LogicalSize::new(width, height)); } crate::window::Action::SetTitle(title) => window.set_title(&title), crate::window::Action::Focus => window.focus_window(), } } drop(namespace); self.publish_viewport(); } fn publish_viewport(&self) { let Some(window) = &self.window else { return; }; let size = window.inner_size().to_logical::(window.scale_factor()); if size.width <= 0.0 || size.height <= 0.0 || !size.width.is_finite() || !size.height.is_finite() { return; } if let Ok(mut namespace) = self.namespace.write() { namespace.viewport = Some(crate::layout::Size { width: size.width.min(crate::ui_bridge::MAX_DIMENSION as f32), height: size.height.min(crate::ui_bridge::MAX_DIMENSION as f32), }); } } fn request_redraw(&self) { if let Some(window) = &self.window { window.request_redraw(); } } fn toggle_developer_tools(&mut self) { if let Ok(namespace) = self.namespace.read() { self.developer_tools.toggle(&namespace.document); } self.guest_keyboard = !self.developer_tools.open; self.request_redraw(); } fn document_updated(&mut self) { self.apply_window_commands(); if let Ok(namespace) = self.namespace.read() { self.developer_tools.document_updated(&namespace.document); let frame = &namespace.document.frame; self.guest_focus = frame .focus .filter(|_| frame.fault.is_none()) .map(|mount| crate::events::Input { session: frame.session, layout_version: frame.layout_version, mount, kind: "focus".into(), point: None, press_point: None, }); } self.request_redraw(); } fn handle_keyboard(&mut self, event: &winit::event::KeyEvent) { if self.guest_keyboard && self.handle_guest_key(event) { self.request_redraw(); return; } if self.developer_tools.keyboard_control( event, self.modifiers.shift_key(), &self.layout.developer_tools, &self.activity, ) { self.request_redraw(); return; } if event.state != ElementState::Pressed { return; } let is_f12 = matches!(event.logical_key, Key::Named(NamedKey::F12)); let is_i = matches!(&event.logical_key, Key::Character(value) if value.eq_ignore_ascii_case("i")); let mac_shortcut = self.modifiers.super_key() && self.modifiers.alt_key() && is_i; let other_shortcut = self.modifiers.control_key() && self.modifiers.shift_key() && is_i; if is_f12 || mac_shortcut || other_shortcut { if !event.repeat { self.toggle_developer_tools(); } return; } if !self.developer_tools.open || self.developer_tools.focus.is_none() { return; } let command = self.modifiers.super_key() || self.modifiers.control_key(); if command && let Key::Character(value) = &event.logical_key { match value.to_lowercase().as_str() { "a" => self.developer_tools.selection = true, "v" => match crate::clipboard::read() { Ok(value) => self.developer_tools.insert(&value), Err(error) => self.developer_tools.input_error = format!("Clipboard: {error}"), }, "c" | "x" => { let selected = self.developer_tools.selected_text(); if !selected.is_empty() { match crate::clipboard::write(&selected) { Ok(()) if value.eq_ignore_ascii_case("x") => { self.developer_tools.insert("") } Ok(()) => {} Err(error) => { self.developer_tools.input_error = format!("Clipboard: {error}") } } } } _ => {} } } else if let Key::Named(key) = &event.logical_key { if !self.developer_tools.preedit.is_empty() { return; } self.developer_tools .key(key, self.modifiers.shift_key(), command); } else if !command && self.developer_tools.preedit.is_empty() && let Some(text) = &event.text { let text: String = text.chars().filter(|c| !c.is_control()).collect(); self.developer_tools.insert(&text); } self.request_redraw(); } fn guest_covered(&self, point: Point) -> bool { let panel = self.layout.developer_tools.panel; self.developer_tools.open && crate::geometry::Rect::new( panel.x - 6.0, panel.y - 6.0, panel.width + 12.0, panel.height + 12.0, ) .contains(point) } fn guest_hit(&self) -> Option { if self.guest_covered(self.pointer) { return None; } let current = self.namespace.read().ok()?; if current.viewport.is_some_and(|size| { self.pointer.x < 0.0 || self.pointer.y < 0.0 || self.pointer.x >= size.width || self.pointer.y >= size.height }) { return None; } let frame = ¤t.document.frame; if frame.session == 0 { return None; } let mount = frame.hit_test(self.pointer)?; Some(crate::events::Input { session: frame.session, layout_version: frame.layout_version, mount: mount.id, kind: "click".into(), point: Some([self.pointer.x, self.pointer.y]), press_point: Some([self.pointer.x, self.pointer.y]), }) } fn send_guest(&mut self, event: crate::events::Input) { if !self.event_client.send(event) { self.developer_tools.input_error = "Native event queue is unavailable or full".into(); self.developer_tools.open = true; } } fn handle_guest_key(&mut self, event: &winit::event::KeyEvent) -> bool { let Key::Named(key) = &event.logical_key else { return false; }; if *key == NamedKey::Tab { if event.state == ElementState::Pressed && !event.repeat { let next = self.namespace.read().ok().and_then(|namespace| { let frame = &namespace.document.frame; if frame.session == 0 || frame.fault.is_some() { return None; } let targets: Vec<_> = frame .mounts .iter() .filter(|mount| mount.interactive) .collect(); if targets.is_empty() { return None; } let current = self .guest_focus .as_ref() .and_then(|focus| targets.iter().position(|mount| mount.id == focus.mount)); let index = if self.modifiers.shift_key() { current .map(|i| (i + targets.len() - 1) % targets.len()) .unwrap_or(targets.len() - 1) } else { current.map(|i| (i + 1) % targets.len()).unwrap_or(0) }; Some(crate::events::Input { session: frame.session, layout_version: frame.layout_version, mount: targets[index].id, kind: "focus".into(), point: None, press_point: None, }) }); if let Some(next) = next { self.guest_key = None; self.guest_focus = Some(next.clone()); self.send_guest(next); return true; } return false; } return self.guest_focus.is_some(); } if matches!(key, NamedKey::Enter | NamedKey::Space) && self.guest_focus.is_some() { if event.state == ElementState::Pressed { if !event.repeat { self.guest_key = self.guest_focus.clone().map(|focus| (*key, focus)); } } else if let Some((pressed, mut input)) = self.guest_key.take() && pressed == *key { input.kind = "click".into(); self.send_guest(input); } return true; } false } fn redraw(&mut self) { let guest_cursor = self.guest_hit().is_some(); let (Some(window), Some(surface)) = (self.window.as_ref(), self.surface.as_mut()) else { return; }; let size = window.inner_size(); let (Some(width), Some(height)) = (NonZeroU32::new(size.width), NonZeroU32::new(size.height)) else { return; }; let Ok(namespace) = self.namespace.read() else { return; }; let scene = scene::build_scene( size.width, size.height, window.scale_factor() as f32, &namespace.document, &mut self.developer_tools, &self.activity, ); drop(namespace); self.layout = scene.layout; window.set_cursor(if guest_cursor { winit::window::CursorIcon::Pointer } else { self.developer_tools .cursor_kind(&self.layout.developer_tools) }); if self.developer_tools.repaint { self.developer_tools.repaint = false; window.request_redraw(); } let ime = self .developer_tools .ime_position(&self.layout.developer_tools); window.set_ime_cursor_area( LogicalPosition::new(ime.x as f64, ime.y as f64), LogicalSize::new(1.0, 19.0), ); surface .resize(width, height) .expect("resize software surface"); let mut buffer = surface.buffer_mut().expect("acquire software buffer"); SoftwareRenderer::render(&scene.display_list, &mut buffer, size.width, size.height); buffer.present().expect("present software-rendered frame"); } } impl ApplicationHandler for TesseraApp { fn resumed(&mut self, event_loop: &ActiveEventLoop) { if self.window.is_some() { return; } let attributes = WindowAttributes::default() .with_title("Tessera") .with_inner_size(LogicalSize::new(1_280.0, 800.0)) .with_min_inner_size(LogicalSize::new(720.0, 480.0)) .with_max_inner_size(LogicalSize::new( crate::window::MAX_WINDOW_DIMENSION, crate::window::MAX_WINDOW_DIMENSION, )); let window = Arc::new( event_loop .create_window(attributes) .expect("create Tessera window"), ); let context = Context::new(window.clone()).expect("create software-rendering context"); let surface = Surface::new(&context, window.clone()).expect("create software-rendering surface"); window.set_ime_allowed(true); self.window = Some(window.clone()); self.context = Some(context); self.surface = Some(surface); self.apply_window_commands(); self.publish_viewport(); window.request_redraw(); } fn user_event(&mut self, _event_loop: &ActiveEventLoop, event: AppEvent) { match event { AppEvent::RuntimeFailed { message } => { self.developer_tools.input_error = message; self.developer_tools.open = true; self.guest_pressed = None; self.guest_key = None; self.request_redraw(); } AppEvent::DocumentUpdated => self.document_updated(), AppEvent::ConsoleFailed { message, source } => { self.developer_tools.input_error = format!("Request failed: {message}"); if self.developer_tools.input.is_empty() { self.developer_tools.input = source; self.developer_tools.cursor = self.developer_tools.input.len(); self.developer_tools.reveal_cursor(); } self.request_redraw(); } } } fn window_event( &mut self, event_loop: &ActiveEventLoop, window_id: WindowId, event: WindowEvent, ) { let Some(window) = self.window.as_ref().cloned() else { return; }; if window.id() != window_id { return; } match event { WindowEvent::CloseRequested => event_loop.exit(), WindowEvent::Resized(_) | WindowEvent::ScaleFactorChanged { .. } => { self.publish_viewport(); window.request_redraw(); } WindowEvent::CursorMoved { position: PhysicalPosition { x, y }, .. } => { let scale = window.scale_factor() as f32; let next = Point::new(x as f32 / scale, y as f32 / scale); let delta = Point::new(next.x - self.pointer.x, next.y - self.pointer.y); self.pointer = next; self.developer_tools.move_pointer( next, delta, &self.layout.developer_tools, window.inner_size().width as f32 / scale, ); window.set_cursor(if self.guest_hit().is_some() { winit::window::CursorIcon::Pointer } else { self.developer_tools .cursor_kind(&self.layout.developer_tools) }); window.request_redraw(); } WindowEvent::CursorLeft { .. } => { self.guest_pressed = None; self.developer_tools.pointer_left(); window.set_cursor(winit::window::CursorIcon::Default); window.request_redraw(); } WindowEvent::MouseInput { state, button: MouseButton::Left, .. } => { if state == ElementState::Pressed { self.guest_keyboard = !self.guest_covered(self.pointer); self.guest_pressed = self.guest_hit(); if self.guest_keyboard { self.developer_tools.blur(); } self.developer_tools.press( &self.layout.developer_tools, self.pointer, &self.activity, ); } else { if let Some(pressed) = self.guest_pressed.take() && let Some(mut released) = self.guest_hit() && released.session == pressed.session && released.layout_version == pressed.layout_version { released.press_point = pressed.point; self.send_guest(released); } self.developer_tools.release( &self.layout.developer_tools, self.pointer, &self.activity, ); } window.request_redraw(); } WindowEvent::MouseWheel { delta, .. } => { let (mut dx, mut dy) = match delta { MouseScrollDelta::LineDelta(x, y) => (x * 38.0, y * 38.0), MouseScrollDelta::PixelDelta(p) => ( p.x as f32 / window.scale_factor() as f32, p.y as f32 / window.scale_factor() as f32, ), }; if self.modifiers.shift_key() && dx.abs() < 0.1 { dx = dy; dy = 0.0; } self.developer_tools .wheel(&self.layout.developer_tools, self.pointer, dx, dy); window.request_redraw(); } WindowEvent::Ime(Ime::Preedit(value, _)) => { self.developer_tools.preedit = value; window.request_redraw(); } WindowEvent::Ime(Ime::Commit(value)) => { self.developer_tools.preedit.clear(); if self.developer_tools.open { self.developer_tools.insert(&value); } window.request_redraw(); } WindowEvent::Focused(false) => { self.guest_pressed = None; self.guest_key = None; self.developer_tools.blur(); self.modifiers = ModifiersState::default(); window.request_redraw(); } WindowEvent::ModifiersChanged(modifiers) => self.modifiers = modifiers.state(), WindowEvent::KeyboardInput { event, .. } => self.handle_keyboard(&event), WindowEvent::RedrawRequested => self.redraw(), _ => {} } } }