818 lines
31 KiB
Rust
818 lines
31 KiB
Rust
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 {
|
|
resource: u64,
|
|
native_session: u64,
|
|
children: std::collections::BTreeMap<u64, TesseraApp>,
|
|
proxy: EventLoopProxy<AppEvent>,
|
|
window: Option<Arc<Window>>,
|
|
context: Option<Context<Arc<Window>>>,
|
|
surface: Option<Surface<Arc<Window>, Arc<Window>>>,
|
|
namespace: Arc<RwLock<NamespaceState>>,
|
|
developer_tools: DeveloperToolsState,
|
|
layout: SceneLayout,
|
|
pointer: Point,
|
|
modifiers: ModifiersState,
|
|
activity: Activity,
|
|
event_client: crate::events::Client,
|
|
guest_pressed: Option<crate::events::Input>,
|
|
tab_pressed: Option<crate::events::Input>,
|
|
tab_drag_origin: Option<Point>,
|
|
guest_focus: Option<crate::events::Input>,
|
|
guest_key: Option<(NamedKey, crate::events::Input)>,
|
|
guest_keyboard: bool,
|
|
}
|
|
|
|
impl TesseraApp {
|
|
pub fn new(
|
|
namespace: Arc<RwLock<NamespaceState>>,
|
|
activity: Activity,
|
|
proxy: EventLoopProxy<AppEvent>,
|
|
) -> anyhow::Result<Self> {
|
|
let event_client = crate::events::Client::new(proxy.clone())?;
|
|
Ok(Self {
|
|
resource: crate::window::MAIN_WINDOW,
|
|
native_session: 0,
|
|
children: std::collections::BTreeMap::new(),
|
|
proxy: proxy.clone(),
|
|
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,
|
|
tab_pressed: None,
|
|
tab_drag_origin: None,
|
|
guest_focus: None,
|
|
guest_key: None,
|
|
guest_keyboard: true,
|
|
})
|
|
}
|
|
|
|
fn reconcile_windows(&mut self, event_loop: &ActiveEventLoop) {
|
|
if self.resource != crate::window::MAIN_WINDOW {
|
|
return;
|
|
}
|
|
let Ok(namespace) = self.namespace.read() else {
|
|
return;
|
|
};
|
|
let session = namespace.runtime_session;
|
|
let revision = namespace.revision;
|
|
let submission = namespace.submission;
|
|
let documents = namespace.document.windows.clone();
|
|
drop(namespace);
|
|
if session != self.native_session {
|
|
// Console reset preserves the old projection with input disabled.
|
|
// A real replacement changes frame sessions and drops old OS views.
|
|
let replaces = documents
|
|
.values()
|
|
.any(|document| document.frame.session == session && session != 0);
|
|
if replaces || (session != 0 && documents.is_empty()) {
|
|
self.children.clear();
|
|
}
|
|
self.native_session = session;
|
|
}
|
|
self.children.retain(|id, child| {
|
|
documents.get(id).is_some_and(|document| {
|
|
document.frame.session == 0 || child.native_session == document.frame.session
|
|
})
|
|
});
|
|
for (id, document) in documents {
|
|
if let Some(child) = self.children.get_mut(&id) {
|
|
if let Ok(mut namespace) = child.namespace.write() {
|
|
namespace.document = document;
|
|
namespace.runtime_session = session;
|
|
namespace.revision = revision;
|
|
namespace.submission = submission;
|
|
}
|
|
child.document_updated(event_loop);
|
|
} else {
|
|
let namespace = Arc::new(RwLock::new(NamespaceState {
|
|
document,
|
|
runtime_session: session,
|
|
revision,
|
|
submission,
|
|
..NamespaceState::default()
|
|
}));
|
|
match Self::new(namespace, self.activity.clone(), self.proxy.clone()) {
|
|
Ok(mut child) => {
|
|
child.resource = id;
|
|
child.native_session = session;
|
|
child.resumed(event_loop);
|
|
child.document_updated(event_loop);
|
|
self.children.insert(id, child);
|
|
}
|
|
Err(error) => {
|
|
self.developer_tools.input_error = format!("Window {id}: {error}");
|
|
self.developer_tools.open = true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
fn apply_window_commands(&mut self, event_loop: &ActiveEventLoop) {
|
|
let Ok(mut namespace) = self.namespace.write() else {
|
|
return;
|
|
};
|
|
let session = namespace.runtime_session;
|
|
let commands = std::mem::take(&mut namespace.window_commands);
|
|
drop(namespace);
|
|
for envelope in commands {
|
|
if envelope.session == 0 || envelope.session != session {
|
|
continue;
|
|
}
|
|
let id = envelope.command.window;
|
|
if matches!(envelope.command.action, crate::window::Action::Close) {
|
|
if id == crate::window::MAIN_WINDOW {
|
|
event_loop.exit();
|
|
} else {
|
|
self.children.remove(&id);
|
|
}
|
|
continue;
|
|
}
|
|
let target = if id == self.resource {
|
|
self.window.as_ref()
|
|
} else {
|
|
self.children
|
|
.get(&id)
|
|
.and_then(|child| child.window.as_ref())
|
|
};
|
|
let Some(window) = target else {
|
|
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(),
|
|
crate::window::Action::Close => {}
|
|
}
|
|
}
|
|
self.publish_viewport();
|
|
}
|
|
|
|
fn publish_viewport(&self) {
|
|
let Some(window) = &self.window else {
|
|
return;
|
|
};
|
|
let size = window.inner_size().to_logical::<f32>(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() {
|
|
let size = 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),
|
|
};
|
|
namespace.viewport = Some(size);
|
|
namespace.window_viewports.insert(self.resource, size);
|
|
if self.resource == crate::window::MAIN_WINDOW {
|
|
namespace.window_viewports.retain(|id, _| {
|
|
*id == crate::window::MAIN_WINDOW || self.children.contains_key(id)
|
|
});
|
|
for (id, child) in &self.children {
|
|
if let Ok(local) = child.namespace.read()
|
|
&& let Some(size) = local.viewport
|
|
{
|
|
namespace.window_viewports.insert(*id, size);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
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, event_loop: &ActiveEventLoop) {
|
|
self.reconcile_windows(event_loop);
|
|
self.apply_window_commands(event_loop);
|
|
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 {
|
|
window: self.resource,
|
|
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 && event.state == ElementState::Pressed && !event.repeat {
|
|
let close = (self.modifiers.super_key() || self.modifiers.control_key())
|
|
&& matches!(&event.logical_key, Key::Character(key) if key.eq_ignore_ascii_case("w"));
|
|
let cycle = self.modifiers.control_key()
|
|
&& matches!(event.logical_key, Key::Named(NamedKey::Tab));
|
|
if (close || cycle) && self.send_tab_shortcut(close) {
|
|
return;
|
|
}
|
|
}
|
|
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<crate::events::Input> {
|
|
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 offset = crate::tabs::height(&frame.tabs);
|
|
if self.pointer.y < offset {
|
|
return None;
|
|
}
|
|
let point = Point::new(self.pointer.x, self.pointer.y - offset);
|
|
let mount = frame.hit_test(point)?;
|
|
Some(crate::events::Input {
|
|
window: self.resource,
|
|
session: frame.session,
|
|
layout_version: frame.layout_version,
|
|
mount: mount.id,
|
|
kind: "click".into(),
|
|
point: Some([point.x, point.y]),
|
|
press_point: Some([point.x, point.y]),
|
|
})
|
|
}
|
|
|
|
fn send_tab_shortcut(&mut self, close: bool) -> bool {
|
|
let input = self.namespace.read().ok().and_then(|namespace| {
|
|
let frame = &namespace.document.frame;
|
|
if frame.session == 0 || frame.fault.is_some() || frame.tabs.is_empty() {
|
|
return None;
|
|
}
|
|
let active = frame.tabs.iter().position(|tab| tab.active).unwrap_or(0);
|
|
let index = if close {
|
|
active
|
|
} else if self.modifiers.shift_key() {
|
|
(active + frame.tabs.len() - 1) % frame.tabs.len()
|
|
} else {
|
|
(active + 1) % frame.tabs.len()
|
|
};
|
|
Some(crate::events::Input {
|
|
window: self.resource,
|
|
session: frame.session,
|
|
layout_version: frame.layout_version,
|
|
mount: frame.tabs[index].id,
|
|
kind: if close { "tab-close" } else { "tab-activate" }.into(),
|
|
point: None,
|
|
press_point: None,
|
|
})
|
|
});
|
|
if let Some(input) = input {
|
|
self.guest_pressed = None;
|
|
self.guest_key = None;
|
|
self.tab_pressed = None;
|
|
self.tab_drag_origin = None;
|
|
self.send_guest(input);
|
|
true
|
|
} else {
|
|
false
|
|
}
|
|
}
|
|
|
|
fn tab_hit(&self) -> Option<crate::events::Input> {
|
|
if self.guest_covered(self.pointer) {
|
|
return None;
|
|
}
|
|
let window = self.window.as_ref()?;
|
|
let namespace = self.namespace.read().ok()?;
|
|
let frame = &namespace.document.frame;
|
|
if frame.session == 0 || frame.fault.is_some() {
|
|
return None;
|
|
}
|
|
let width = window.inner_size().width as f32 / window.scale_factor() as f32;
|
|
let (id, close) = crate::tabs::hit(&frame.tabs, width, self.pointer)?;
|
|
Some(crate::events::Input {
|
|
window: self.resource,
|
|
session: frame.session,
|
|
layout_version: frame.layout_version,
|
|
mount: id,
|
|
kind: if close { "tab-close" } else { "tab-activate" }.into(),
|
|
point: None,
|
|
press_point: None,
|
|
})
|
|
}
|
|
|
|
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 {
|
|
window: self.resource,
|
|
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() || self.tab_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<AppEvent> for TesseraApp {
|
|
fn resumed(&mut self, event_loop: &ActiveEventLoop) {
|
|
if self.window.is_some() {
|
|
return;
|
|
}
|
|
|
|
let attributes = WindowAttributes::default()
|
|
.with_title(if self.resource == crate::window::MAIN_WINDOW {
|
|
"Tessera".into()
|
|
} else {
|
|
format!("Endor Window {}", self.resource)
|
|
})
|
|
.with_inner_size(if self.resource == crate::window::MAIN_WINDOW {
|
|
LogicalSize::new(1_280.0, 800.0)
|
|
} else {
|
|
LogicalSize::new(960.0, 640.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(event_loop);
|
|
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(event_loop),
|
|
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 {
|
|
if let Some(child) = self.children.values_mut().find(|child| {
|
|
child
|
|
.window
|
|
.as_ref()
|
|
.is_some_and(|window| window.id() == window_id)
|
|
}) {
|
|
child.window_event(event_loop, window_id, event);
|
|
self.publish_viewport();
|
|
}
|
|
return;
|
|
}
|
|
|
|
match event {
|
|
WindowEvent::CloseRequested => {
|
|
if self.resource == crate::window::MAIN_WINDOW {
|
|
event_loop.exit();
|
|
} else {
|
|
let input = self
|
|
.namespace
|
|
.read()
|
|
.ok()
|
|
.map(|namespace| crate::events::Input {
|
|
window: self.resource,
|
|
session: namespace.document.frame.session,
|
|
layout_version: namespace.document.frame.layout_version,
|
|
mount: 0,
|
|
kind: "window-close".into(),
|
|
point: None,
|
|
press_point: None,
|
|
});
|
|
if let Some(input) = input {
|
|
self.send_guest(input);
|
|
}
|
|
}
|
|
}
|
|
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() || self.tab_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;
|
|
if self.tab_drag_origin.is_none() {
|
|
self.tab_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.tab_pressed = self.tab_hit();
|
|
self.tab_drag_origin = self
|
|
.tab_pressed
|
|
.as_ref()
|
|
.filter(|input| input.kind == "tab-activate")
|
|
.map(|_| 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(mut pressed) = self.tab_pressed.take() {
|
|
let width = window.inner_size().width as f32 / window.scale_factor() as f32;
|
|
let dragged = self.tab_drag_origin.take().is_some_and(|origin| {
|
|
(self.pointer.x - origin.x).hypot(self.pointer.y - origin.y) > 12.0
|
|
&& (self.pointer.y < 0.0
|
|
|| self.pointer.y >= crate::tabs::HEIGHT + 12.0
|
|
|| self.pointer.x < 0.0
|
|
|| self.pointer.x >= width)
|
|
});
|
|
if dragged {
|
|
pressed.kind = "tab-detach".into();
|
|
self.send_guest(pressed);
|
|
} else if let Some(released) = self.tab_hit()
|
|
&& released.session == pressed.session
|
|
&& released.layout_version == pressed.layout_version
|
|
&& released.mount == pressed.mount
|
|
&& released.kind == pressed.kind
|
|
{
|
|
self.send_guest(released);
|
|
}
|
|
}
|
|
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(),
|
|
_ => {}
|
|
}
|
|
}
|
|
}
|