diff --git a/Cargo.lock b/Cargo.lock index 94fdbf12a9..1d7599a62b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -725,14 +725,13 @@ dependencies = [ [[package]] name = "cef" version = "141.4.0+141.0.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e05b0ae455c0cf0693da242b3336586cd0057702c0516731d8ba72962559a625" +source = "git+https://github.com/timon-schelling/cef-rs.git?rev=98493a182928f1ff8d5bf8b9eea61483235df75d#98493a182928f1ff8d5bf8b9eea61483235df75d" dependencies = [ "ash", "cef-dll-sys", "libc", "libloading", - "metal 0.32.0", + "metal", "objc", "objc2-io-surface", "thiserror 2.0.16", @@ -744,9 +743,8 @@ dependencies = [ [[package]] name = "cef-dll-sys" -version = "141.6.0+141.0.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c95e419bbb8b2190df8b6eb3934ecc4137533803fcc5ae2b076e591ee789071" +version = "141.4.0+141.0.9" +source = "git+https://github.com/timon-schelling/cef-rs.git?rev=98493a182928f1ff8d5bf8b9eea61483235df75d#98493a182928f1ff8d5bf8b9eea61483235df75d" dependencies = [ "anyhow", "cmake", @@ -1016,17 +1014,6 @@ version = "0.8.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" -[[package]] -name = "core-graphics-types" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "45390e6114f68f718cc7a830514a96f903cccd70d02a8f6d9f643ac4ba45afaf" -dependencies = [ - "bitflags 1.3.2", - "core-foundation 0.9.4", - "libc", -] - [[package]] name = "core-graphics-types" version = "0.2.0" @@ -1337,8 +1324,7 @@ checksum = "75b325c5dbd37f80359721ad39aca5a29fb04c89279657cffdda8736d0c0b9d2" [[package]] name = "download-cef" version = "2.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d98178d9254efef0f69c1f584713d69c790ec00668cd98f783a5085fbefdbddc" +source = "git+https://github.com/timon-schelling/cef-rs.git?rev=98493a182928f1ff8d5bf8b9eea61483235df75d#98493a182928f1ff8d5bf8b9eea61483235df75d" dependencies = [ "bzip2", "clap", @@ -2302,22 +2288,17 @@ dependencies = [ name = "graphite-desktop" version = "0.1.0" dependencies = [ - "ash", "bytemuck", "cef", "cef-dll-sys", "clap", - "core-foundation 0.10.1", "derivative", "dirs", "futures", "glam", "graphite-desktop-embedded-resources", "graphite-desktop-wrapper", - "libc", - "metal 0.31.0", "muda", - "objc", "open", "rand 0.9.2", "rfd", @@ -3422,21 +3403,6 @@ dependencies = [ "autocfg", ] -[[package]] -name = "metal" -version = "0.31.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f569fb946490b5743ad69813cb19629130ce9374034abe31614a36402d18f99e" -dependencies = [ - "bitflags 2.9.3", - "block", - "core-graphics-types 0.1.3", - "foreign-types 0.5.0", - "log", - "objc", - "paste", -] - [[package]] name = "metal" version = "0.32.0" @@ -3445,7 +3411,7 @@ checksum = "00c15a6f673ff72ddcc22394663290f870fb224c1bfce55734a75c414150e605" dependencies = [ "bitflags 2.9.3", "block", - "core-graphics-types 0.2.0", + "core-graphics-types", "foreign-types 0.5.0", "log", "objc", @@ -6893,7 +6859,7 @@ dependencies = [ "bytemuck", "cfg-if", "cfg_aliases", - "core-graphics-types 0.2.0", + "core-graphics-types", "glow", "glutin_wgl_sys", "gpu-alloc", @@ -6905,7 +6871,7 @@ dependencies = [ "libc", "libloading", "log", - "metal 0.32.0", + "metal", "naga 27.0.3", "ndk-sys", "objc", diff --git a/Cargo.toml b/Cargo.toml index 91e8fa5d00..1f0250b878 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -188,8 +188,9 @@ iai-callgrind = { version = "0.16" } ndarray = "0.16" strum = { version = "0.27", features = ["derive"] } dirs = "6.0" -cef = { version = "141", features = ["accelerated_osr"] } -cef-dll-sys = "141" +# TODO: remove fork usage when https://github.com/tauri-apps/cef-rs/pull/272 is merged and published +cef = { git = "https://github.com/timon-schelling/cef-rs.git", rev = "98493a182928f1ff8d5bf8b9eea61483235df75d" } +cef-dll-sys = { git = "https://github.com/timon-schelling/cef-rs.git", rev = "98493a182928f1ff8d5bf8b9eea61483235df75d" } include_dir = "0.7" tracing-subscriber = { version = "0.3", features = ["env-filter"] } tracing = "0.1" diff --git a/desktop/Cargo.toml b/desktop/Cargo.toml index 10143c358b..6aad2144ec 100644 --- a/desktop/Cargo.toml +++ b/desktop/Cargo.toml @@ -17,12 +17,7 @@ default = ["recommended", "embedded_resources"] recommended = ["gpu", "accelerated_paint"] embedded_resources = ["dep:graphite-desktop-embedded-resources"] gpu = ["graphite-desktop-wrapper/gpu"] - -# Hardware acceleration features -accelerated_paint = ["accelerated_paint_dmabuf", "accelerated_paint_d3d11", "accelerated_paint_iosurface"] -accelerated_paint_dmabuf = ["libc", "ash"] -accelerated_paint_d3d11 = ["windows", "ash"] -accelerated_paint_iosurface = ["metal", "objc", "core-foundation"] +accelerated_paint = ["cef/accelerated_osr"] [dependencies] # Local dependencies @@ -49,32 +44,19 @@ rand = { workspace = true, features = ["thread_rng"] } serde = { workspace = true } clap = { workspace = true, features = ["derive"] } -# Hardware acceleration dependencies -ash = { version = "0.38", optional = true } - # Windows-specific dependencies [target.'cfg(target_os = "windows")'.dependencies] windows = { version = "0.58.0", features = [ "Win32_Foundation", - "Win32_Graphics_Direct3D11", - "Win32_Graphics_Direct3D12", - "Win32_Graphics_Dxgi", - "Win32_Graphics_Dxgi_Common", "Win32_Graphics_Dwm", "Win32_Graphics_Gdi", "Win32_System_LibraryLoader", "Win32_UI_Controls", "Win32_UI_WindowsAndMessaging", "Win32_UI_HiDpi", -], optional = true } +] } # macOS-specific dependencies [target.'cfg(target_os = "macos")'.dependencies] muda = { git = "https://github.com/tauri-apps/muda.git", rev = "3f460b8fbaed59cda6d95ceea6904f000f093f15", default-features = false } -metal = { version = "0.31.0", optional = true } -objc = { version = "0.2", optional = true } -core-foundation = { version = "0.10", optional = true } -# Linux-specific dependencies -[target.'cfg(target_os = "linux")'.dependencies] -libc = { version = "0.2", optional = true } diff --git a/desktop/src/cef/input.rs b/desktop/src/cef/input.rs index d2a99d3baa..2961166764 100644 --- a/desktop/src/cef/input.rs +++ b/desktop/src/cef/input.rs @@ -1,29 +1,30 @@ use cef::sys::{cef_event_flags_t, cef_key_event_type_t, cef_mouse_button_type_t}; -use cef::{Browser, ImplBrowser, ImplBrowserHost, KeyEvent, KeyEventType, MouseEvent}; -use std::time::Instant; -use winit::dpi::PhysicalPosition; +use cef::{Browser, ImplBrowser, ImplBrowserHost, KeyEvent, MouseEvent}; use winit::event::{ButtonSource, ElementState, MouseButton, MouseScrollDelta, WindowEvent}; mod keymap; -use keymap::{ToNativeKeycode, ToVKBits}; +use keymap::{ToCharRepresentation, ToNativeKeycode, ToVKBits}; -use super::consts::{MULTICLICK_ALLOWED_TRAVEL, MULTICLICK_TIMEOUT, PINCH_ZOOM_SPEED, SCROLL_LINE_HEIGHT, SCROLL_LINE_WIDTH, SCROLL_SPEED_X, SCROLL_SPEED_Y}; +mod state; +pub(crate) use state::InputState; + +use super::consts::{PINCH_ZOOM_SPEED, SCROLL_LINE_HEIGHT, SCROLL_LINE_WIDTH, SCROLL_SPEED_X, SCROLL_SPEED_Y}; pub(crate) fn handle_window_event(browser: &Browser, input_state: &mut InputState, event: &WindowEvent) { match event { WindowEvent::PointerMoved { position, .. } | WindowEvent::PointerEntered { position, .. } => { - input_state.cursor_move(&position); + input_state.cursor_move(position); let Some(host) = browser.host() else { return }; host.send_mouse_move_event(Some(&input_state.into()), 0); } WindowEvent::PointerLeft { position, .. } => { if let Some(position) = position { - input_state.cursor_move(&position); + input_state.cursor_move(position); } let Some(host) = browser.host() else { return }; - host.send_mouse_move_event(Some(&input_state.into()), 1); + host.send_mouse_move_event(Some(&(input_state.into())), 1); } WindowEvent::PointerButton { state, button, .. } => { let mouse_button = match button { @@ -64,70 +65,45 @@ pub(crate) fn handle_window_event(browser: &Browser, input_state: &mut InputStat input_state.modifiers_changed(&modifiers.state()); } WindowEvent::KeyboardInput { device_id: _, event, is_synthetic: _ } => { - let (named_key, character) = match &event.logical_key { - winit::keyboard::Key::Named(named_key) => ( - Some(named_key), - match named_key { - winit::keyboard::NamedKey::Enter => Some('\u{000d}'), - _ => None, - }, - ), - winit::keyboard::Key::Character(str) => { - let char = str.chars().next().unwrap_or('\0'); - (None, Some(char)) + let Some(host) = browser.host() else { return }; + + let mut key_event = KeyEvent { + type_: match (event.state, &event.logical_key) { + (ElementState::Pressed, winit::keyboard::Key::Character(_)) => cef_key_event_type_t::KEYEVENT_CHAR, + (ElementState::Pressed, _) => cef_key_event_type_t::KEYEVENT_RAWKEYDOWN, + (ElementState::Released, _) => cef_key_event_type_t::KEYEVENT_KEYUP, } - _ => return, + .into(), + ..Default::default() }; - let native_key_code = event.physical_key.to_native_keycode(); - - let modifiers = input_state.cef_modifiers(&event.location, event.repeat).raw(); + key_event.modifiers = input_state.cef_modifiers(&event.location, event.repeat).raw(); - let mut key_event = KeyEvent { - size: size_of::(), - modifiers, - ..Default::default() + key_event.windows_key_code = match &event.logical_key { + winit::keyboard::Key::Named(named) => named.to_vk_bits(), + winit::keyboard::Key::Character(char) => char.chars().next().unwrap_or_default().to_vk_bits(), + _ => 0, }; - if let Some(named_key) = named_key { - key_event.windows_key_code = named_key.to_vk_bits(); - } else if let Some(char) = character { - key_event.windows_key_code = char.to_vk_bits(); - } + key_event.native_key_code = event.physical_key.to_native_keycode(); - key_event.native_key_code = native_key_code; + key_event.character = event.logical_key.to_char_representation() as u16; + key_event.unmodified_character = event.key_without_modifiers.to_char_representation() as u16; - let Some(host) = browser.host() else { return }; + #[cfg(target_os = "macos")] // See https://www.magpcss.org/ceforum/viewtopic.php?start=10&t=11650 + if key_event.character == 0 && key_event.unmodified_character == 0 && event.text_with_all_modifiers.is_some() { + key_event.unmodified_character = 1; + } - match event.state { - ElementState::Pressed => { - key_event.type_ = KeyEventType::from(cef_key_event_type_t::KEYEVENT_RAWKEYDOWN); - host.send_key_event(Some(&key_event)); + if key_event.type_ == cef_key_event_type_t::KEYEVENT_CHAR.into() { + let mut key_down_event = key_event.clone(); + key_down_event.type_ = cef_key_event_type_t::KEYEVENT_RAWKEYDOWN.into(); + host.send_key_event(Some(&key_down_event)); - if let Some(char) = character { - let mut char_key_event = KeyEvent { - size: size_of::(), - modifiers, - is_system_key: 0, - ..Default::default() - }; - let mut buf = [0; 2]; - char.encode_utf16(&mut buf); - char_key_event.windows_key_code = buf[0] as i32; - char_key_event.character = buf[0]; - char_key_event.native_key_code = native_key_code; - let mut buf = [0; 2]; - char.to_lowercase().next().unwrap().encode_utf16(&mut buf); - char_key_event.unmodified_character = buf[0]; - char_key_event.type_ = KeyEventType::from(cef_key_event_type_t::KEYEVENT_CHAR); - host.send_key_event(Some(&char_key_event)); - } - } - ElementState::Released => { - key_event.type_ = KeyEventType::from(cef_key_event_type_t::KEYEVENT_KEYUP); - host.send_key_event(Some(&key_event)); - } + key_event.windows_key_code = event.logical_key.to_char_representation() as i32; } + + host.send_key_event(Some(&key_event)); } WindowEvent::PinchGesture { delta, .. } => { if !delta.is_normal() { @@ -146,237 +122,3 @@ pub(crate) fn handle_window_event(browser: &Browser, input_state: &mut InputStat _ => {} } } - -#[derive(Default)] -pub(crate) struct InputState { - modifiers: winit::keyboard::ModifiersState, - mouse_position: MousePosition, - mouse_state: MouseState, - mouse_click_tracker: ClickTracker, -} -impl InputState { - fn modifiers_changed(&mut self, modifiers: &winit::keyboard::ModifiersState) { - self.modifiers = *modifiers; - } - - fn cursor_move(&mut self, position: &PhysicalPosition) { - self.mouse_position = position.into(); - } - - fn mouse_input(&mut self, button: &MouseButton, state: &ElementState) -> ClickCount { - self.mouse_state.update(button, state); - self.mouse_click_tracker.input(button, state, self.mouse_position) - } - - fn cef_modifiers(&self, location: &winit::keyboard::KeyLocation, is_repeat: bool) -> CefModifiers { - CefModifiers::new(self, location, is_repeat) - } - - fn cef_modifiers_mouse_event(&self) -> CefModifiers { - self.cef_modifiers(&winit::keyboard::KeyLocation::Standard, false) - } -} - -impl From for CefModifiers { - fn from(val: InputState) -> Self { - CefModifiers::new(&val, &winit::keyboard::KeyLocation::Standard, false) - } -} -impl From<&InputState> for MouseEvent { - fn from(val: &InputState) -> Self { - MouseEvent { - x: val.mouse_position.x as i32, - y: val.mouse_position.y as i32, - modifiers: val.cef_modifiers_mouse_event().raw(), - } - } -} -impl From<&mut InputState> for MouseEvent { - fn from(val: &mut InputState) -> Self { - MouseEvent { - x: val.mouse_position.x as i32, - y: val.mouse_position.y as i32, - modifiers: val.cef_modifiers_mouse_event().raw(), - } - } -} - -#[derive(Default, Clone, Copy)] -pub(crate) struct MousePosition { - x: usize, - y: usize, -} -impl From<&PhysicalPosition> for MousePosition { - fn from(position: &PhysicalPosition) -> Self { - Self { - x: position.x as usize, - y: position.y as usize, - } - } -} - -#[derive(Default, Clone)] -pub(crate) struct MouseState { - left: bool, - right: bool, - middle: bool, -} -impl MouseState { - pub(crate) fn update(&mut self, button: &MouseButton, state: &ElementState) { - match state { - ElementState::Pressed => match button { - MouseButton::Left => self.left = true, - MouseButton::Right => self.right = true, - MouseButton::Middle => self.middle = true, - _ => {} - }, - ElementState::Released => match button { - MouseButton::Left => self.left = false, - MouseButton::Right => self.right = false, - MouseButton::Middle => self.middle = false, - _ => {} - }, - } - } -} - -#[derive(Default)] -struct ClickTracker { - left: Option, - middle: Option, - right: Option, -} -impl ClickTracker { - fn input(&mut self, button: &MouseButton, state: &ElementState, position: MousePosition) -> ClickCount { - let record = match button { - MouseButton::Left => &mut self.left, - MouseButton::Right => &mut self.right, - MouseButton::Middle => &mut self.middle, - _ => return ClickCount::Single, - }; - - let Some(record) = record else { - *record = Some(ClickRecord { position, ..Default::default() }); - return ClickCount::Single; - }; - - let prev_time = record.time; - let prev_position = record.position; - - let now = Instant::now(); - record.time = now; - record.position = position; - - match state { - ElementState::Pressed if record.down_count == ClickCount::Double => { - *record = ClickRecord { - down_count: ClickCount::Single, - ..*record - }; - return ClickCount::Single; - } - ElementState::Released if record.up_count == ClickCount::Double => { - *record = ClickRecord { - up_count: ClickCount::Single, - ..*record - }; - return ClickCount::Single; - } - _ => {} - } - - let dx = position.x.abs_diff(prev_position.x); - let dy = position.y.abs_diff(prev_position.y); - let within_dist = dx <= MULTICLICK_ALLOWED_TRAVEL && dy <= MULTICLICK_ALLOWED_TRAVEL; - let within_time = now.saturating_duration_since(prev_time) <= MULTICLICK_TIMEOUT; - - let count = if within_time && within_dist { ClickCount::Double } else { ClickCount::Single }; - - *record = match state { - ElementState::Pressed => ClickRecord { down_count: count, ..*record }, - ElementState::Released => ClickRecord { up_count: count, ..*record }, - }; - count - } -} - -#[derive(Clone, Copy, PartialEq, Default)] -enum ClickCount { - #[default] - Single, - Double, -} -impl From for i32 { - fn from(count: ClickCount) -> i32 { - match count { - ClickCount::Single => 1, - ClickCount::Double => 2, - } - } -} - -#[derive(Clone, Copy)] -struct ClickRecord { - time: Instant, - position: MousePosition, - down_count: ClickCount, - up_count: ClickCount, -} - -impl Default for ClickRecord { - fn default() -> Self { - Self { - time: Instant::now(), - position: Default::default(), - down_count: Default::default(), - up_count: Default::default(), - } - } -} - -struct CefModifiers(u32); -impl CefModifiers { - fn new(input_state: &InputState, location: &winit::keyboard::KeyLocation, is_repeat: bool) -> Self { - let mut inner = 0; - - if input_state.modifiers.shift_key() { - inner |= cef_event_flags_t::EVENTFLAG_SHIFT_DOWN as u32; - } - if input_state.modifiers.control_key() { - inner |= cef_event_flags_t::EVENTFLAG_CONTROL_DOWN as u32; - } - if input_state.modifiers.alt_key() { - inner |= cef_event_flags_t::EVENTFLAG_ALT_DOWN as u32; - } - if input_state.modifiers.meta_key() { - inner |= cef_event_flags_t::EVENTFLAG_COMMAND_DOWN as u32; - } - - if input_state.mouse_state.left { - inner |= cef_event_flags_t::EVENTFLAG_LEFT_MOUSE_BUTTON as u32; - } - if input_state.mouse_state.right { - inner |= cef_event_flags_t::EVENTFLAG_RIGHT_MOUSE_BUTTON as u32; - } - if input_state.mouse_state.middle { - inner |= cef_event_flags_t::EVENTFLAG_MIDDLE_MOUSE_BUTTON as u32; - } - - if is_repeat { - inner |= cef_event_flags_t::EVENTFLAG_IS_REPEAT as u32; - } - - inner |= match location { - winit::keyboard::KeyLocation::Left => cef_event_flags_t::EVENTFLAG_IS_LEFT as u32, - winit::keyboard::KeyLocation::Right => cef_event_flags_t::EVENTFLAG_IS_RIGHT as u32, - winit::keyboard::KeyLocation::Numpad => cef_event_flags_t::EVENTFLAG_IS_KEY_PAD as u32, - winit::keyboard::KeyLocation::Standard => 0, - }; - - Self(inner) - } - - fn raw(&self) -> u32 { - self.0 - } -} diff --git a/desktop/src/cef/input/keymap.rs b/desktop/src/cef/input/keymap.rs index 44cd56f43e..17816fb694 100644 --- a/desktop/src/cef/input/keymap.rs +++ b/desktop/src/cef/input/keymap.rs @@ -1,23 +1,44 @@ -pub trait ToNativeKeycode { +use winit::keyboard::{Key, NamedKey, PhysicalKey}; + +pub(crate) trait ToCharRepresentation { + fn to_char_representation(&self) -> char; +} + +impl ToCharRepresentation for Key { + fn to_char_representation(&self) -> char { + match self { + Key::Named(named) => match named { + NamedKey::Tab => '\t', + NamedKey::Enter => '\r', + NamedKey::Backspace => '\x08', + NamedKey::Escape => '\x1b', + _ => '\0', + }, + Key::Character(char) => char.chars().next().unwrap_or_default(), + _ => '\0', + } + } +} + +pub(crate) trait ToNativeKeycode { fn to_native_keycode(&self) -> i32; } -impl ToNativeKeycode for winit::keyboard::PhysicalKey { +impl ToNativeKeycode for PhysicalKey { fn to_native_keycode(&self) -> i32 { use winit::platform::scancode::PhysicalKeyExtScancode; #[cfg(target_os = "linux")] { - self.to_scancode().map(|evdev| (evdev + 8) as i32).unwrap_or(0) + self.to_scancode().map(|evdev| (evdev + 8) as i32).unwrap_or_default() } #[cfg(any(target_os = "macos", target_os = "windows"))] { - self.to_scancode().map(|c| c as i32).unwrap_or(0) + self.to_scancode().map(|c| c as i32).unwrap_or_default() } } } -// Windows Virtual keyboard binary representation pub(crate) trait ToVKBits { fn to_vk_bits(&self) -> i32; } @@ -32,10 +53,8 @@ macro_rules! map_enum { } }; } - impl ToVKBits for winit::keyboard::NamedKey { fn to_vk_bits(&self) -> i32 { - use winit::keyboard::NamedKey; map_enum!( self, NamedKey, @@ -49,10 +68,10 @@ impl ToVKBits for winit::keyboard::NamedKey { (0x5B, Meta), (0x0D, Enter), (0x09, Tab), - (0x28, ArrowDown), (0x25, ArrowLeft), - (0x27, ArrowRight), (0x26, ArrowUp), + (0x27, ArrowRight), + (0x28, ArrowDown), (0x23, End), (0x24, Home), (0x22, PageDown), @@ -152,7 +171,6 @@ macro_rules! map { } }; } - impl ToVKBits for char { fn to_vk_bits(&self) -> i32 { map!( diff --git a/desktop/src/cef/input/state.rs b/desktop/src/cef/input/state.rs new file mode 100644 index 0000000000..52abead503 --- /dev/null +++ b/desktop/src/cef/input/state.rs @@ -0,0 +1,242 @@ +use cef::MouseEvent; +use std::time::Instant; +use winit::dpi::PhysicalPosition; +use winit::event::{ElementState, MouseButton}; + +use crate::cef::consts::{MULTICLICK_ALLOWED_TRAVEL, MULTICLICK_TIMEOUT}; + +#[derive(Default)] +pub(crate) struct InputState { + modifiers: winit::keyboard::ModifiersState, + mouse_position: MousePosition, + mouse_state: MouseState, + mouse_click_tracker: ClickTracker, +} +impl InputState { + pub(crate) fn modifiers_changed(&mut self, modifiers: &winit::keyboard::ModifiersState) { + self.modifiers = *modifiers; + } + + pub(crate) fn cursor_move(&mut self, position: &PhysicalPosition) { + self.mouse_position = position.into(); + } + + pub(crate) fn mouse_input(&mut self, button: &MouseButton, state: &ElementState) -> ClickCount { + self.mouse_state.update(button, state); + self.mouse_click_tracker.input(button, state, self.mouse_position) + } + + pub(crate) fn cef_modifiers(&self, location: &winit::keyboard::KeyLocation, is_repeat: bool) -> CefModifiers { + CefModifiers::new(self, location, is_repeat) + } + + pub(crate) fn cef_mouse_modifiers(&self) -> CefModifiers { + self.cef_modifiers(&winit::keyboard::KeyLocation::Standard, false) + } +} + +impl From for CefModifiers { + fn from(val: InputState) -> Self { + CefModifiers::new(&val, &winit::keyboard::KeyLocation::Standard, false) + } +} +impl From<&InputState> for MouseEvent { + fn from(val: &InputState) -> Self { + MouseEvent { + x: val.mouse_position.x as i32, + y: val.mouse_position.y as i32, + modifiers: val.cef_mouse_modifiers().raw(), + } + } +} +impl From<&mut InputState> for MouseEvent { + fn from(val: &mut InputState) -> Self { + MouseEvent { + x: val.mouse_position.x as i32, + y: val.mouse_position.y as i32, + modifiers: val.cef_mouse_modifiers().raw(), + } + } +} + +#[derive(Default, Clone, Copy)] +pub(crate) struct MousePosition { + x: usize, + y: usize, +} +impl From<&PhysicalPosition> for MousePosition { + fn from(position: &PhysicalPosition) -> Self { + Self { + x: position.x as usize, + y: position.y as usize, + } + } +} + +#[derive(Default, Clone)] +pub(crate) struct MouseState { + left: bool, + right: bool, + middle: bool, +} +impl MouseState { + pub(crate) fn update(&mut self, button: &MouseButton, state: &ElementState) { + match state { + ElementState::Pressed => match button { + MouseButton::Left => self.left = true, + MouseButton::Right => self.right = true, + MouseButton::Middle => self.middle = true, + _ => {} + }, + ElementState::Released => match button { + MouseButton::Left => self.left = false, + MouseButton::Right => self.right = false, + MouseButton::Middle => self.middle = false, + _ => {} + }, + } + } +} + +#[derive(Default)] +struct ClickTracker { + left: Option, + middle: Option, + right: Option, +} +impl ClickTracker { + fn input(&mut self, button: &MouseButton, state: &ElementState, position: MousePosition) -> ClickCount { + let record = match button { + MouseButton::Left => &mut self.left, + MouseButton::Right => &mut self.right, + MouseButton::Middle => &mut self.middle, + _ => return ClickCount::Single, + }; + + let Some(record) = record else { + *record = Some(ClickRecord { position, ..Default::default() }); + return ClickCount::Single; + }; + + let prev_time = record.time; + let prev_position = record.position; + + let now = Instant::now(); + record.time = now; + record.position = position; + + match state { + ElementState::Pressed if record.down_count == ClickCount::Double => { + *record = ClickRecord { + down_count: ClickCount::Single, + ..*record + }; + return ClickCount::Single; + } + ElementState::Released if record.up_count == ClickCount::Double => { + *record = ClickRecord { + up_count: ClickCount::Single, + ..*record + }; + return ClickCount::Single; + } + _ => {} + } + + let dx = position.x.abs_diff(prev_position.x); + let dy = position.y.abs_diff(prev_position.y); + let within_dist = dx <= MULTICLICK_ALLOWED_TRAVEL && dy <= MULTICLICK_ALLOWED_TRAVEL; + let within_time = now.saturating_duration_since(prev_time) <= MULTICLICK_TIMEOUT; + + let count = if within_time && within_dist { ClickCount::Double } else { ClickCount::Single }; + + *record = match state { + ElementState::Pressed => ClickRecord { down_count: count, ..*record }, + ElementState::Released => ClickRecord { up_count: count, ..*record }, + }; + count + } +} + +#[derive(Clone, Copy, PartialEq, Default)] +pub(crate) enum ClickCount { + #[default] + Single, + Double, +} +impl From for i32 { + fn from(count: ClickCount) -> i32 { + match count { + ClickCount::Single => 1, + ClickCount::Double => 2, + } + } +} + +#[derive(Clone, Copy)] +struct ClickRecord { + time: Instant, + position: MousePosition, + down_count: ClickCount, + up_count: ClickCount, +} + +impl Default for ClickRecord { + fn default() -> Self { + Self { + time: Instant::now(), + position: Default::default(), + down_count: Default::default(), + up_count: Default::default(), + } + } +} + +pub(crate) struct CefModifiers(u32); +impl CefModifiers { + fn new(input_state: &InputState, location: &winit::keyboard::KeyLocation, is_repeat: bool) -> Self { + use cef::sys::cef_event_flags_t; + + let mut inner = 0; + + if input_state.modifiers.shift_key() { + inner |= cef_event_flags_t::EVENTFLAG_SHIFT_DOWN as u32; + } + if input_state.modifiers.control_key() { + inner |= cef_event_flags_t::EVENTFLAG_CONTROL_DOWN as u32; + } + if input_state.modifiers.alt_key() { + inner |= cef_event_flags_t::EVENTFLAG_ALT_DOWN as u32; + } + if input_state.modifiers.meta_key() { + inner |= cef_event_flags_t::EVENTFLAG_COMMAND_DOWN as u32; + } + + if input_state.mouse_state.left { + inner |= cef_event_flags_t::EVENTFLAG_LEFT_MOUSE_BUTTON as u32; + } + if input_state.mouse_state.right { + inner |= cef_event_flags_t::EVENTFLAG_RIGHT_MOUSE_BUTTON as u32; + } + if input_state.mouse_state.middle { + inner |= cef_event_flags_t::EVENTFLAG_MIDDLE_MOUSE_BUTTON as u32; + } + + if is_repeat { + inner |= cef_event_flags_t::EVENTFLAG_IS_REPEAT as u32; + } + + inner |= match location { + winit::keyboard::KeyLocation::Left => cef_event_flags_t::EVENTFLAG_IS_LEFT as u32, + winit::keyboard::KeyLocation::Right => cef_event_flags_t::EVENTFLAG_IS_RIGHT as u32, + winit::keyboard::KeyLocation::Numpad => cef_event_flags_t::EVENTFLAG_IS_KEY_PAD as u32, + winit::keyboard::KeyLocation::Standard => 0, + }; + + Self(inner) + } + + pub(crate) fn raw(&self) -> u32 { + self.0 + } +} diff --git a/desktop/src/cef/internal/browser_process_app.rs b/desktop/src/cef/internal/browser_process_app.rs index 75fa5a812e..ef5ba2ce34 100644 --- a/desktop/src/cef/internal/browser_process_app.rs +++ b/desktop/src/cef/internal/browser_process_app.rs @@ -72,6 +72,14 @@ impl ImplApp for BrowserProcessAppImpl { // Hide user prompt asking for keychain access cmd.append_switch(Some(&CefString::from("use-mock-keychain"))); } + + // Enable browser debugging via environment variable + if let Some(env) = std::env::var("GRAPHITE_BROWSER_DEBUG_PORT").ok() + && let Some(port) = env.parse::().ok() + { + cmd.append_switch_with_value(Some(&CefString::from("remote-debugging-port")), Some(&CefString::from(port.to_string().as_str()))); + cmd.append_switch_with_value(Some(&CefString::from("remote-allow-origins")), Some(&CefString::from("*"))); + } } } diff --git a/desktop/src/window/mac.rs b/desktop/src/window/mac.rs index 4c4010a8b1..f6cd4330d0 100644 --- a/desktop/src/window/mac.rs +++ b/desktop/src/window/mac.rs @@ -1,4 +1,5 @@ use winit::event_loop::ActiveEventLoop; +use winit::platform::macos::WindowAttributesMacOS; use winit::window::{Window, WindowAttributes}; use crate::consts::APP_NAME; @@ -11,7 +12,7 @@ pub(super) struct NativeWindowImpl { impl super::NativeWindow for NativeWindowImpl { fn configure(attributes: WindowAttributes, _event_loop: &dyn ActiveEventLoop) -> WindowAttributes { - let mac_window = winit::platform::macos::WindowAttributesMacOS::default() + let mac_window = WindowAttributesMacOS::default() .with_titlebar_transparent(true) .with_fullsize_content_view(true) .with_title_hidden(true); diff --git a/desktop/src/window/win.rs b/desktop/src/window/win.rs index 24ee0ef452..1dfe50aea3 100644 --- a/desktop/src/window/win.rs +++ b/desktop/src/window/win.rs @@ -1,4 +1,6 @@ use winit::event_loop::ActiveEventLoop; +use winit::icon::Icon; +use winit::platform::windows::WinIcon; use winit::window::{Window, WindowAttributes}; use crate::event::AppEventScheduler; @@ -9,8 +11,8 @@ pub(super) struct NativeWindowImpl { impl super::NativeWindow for NativeWindowImpl { fn configure(attributes: WindowAttributes, _event_loop: &dyn ActiveEventLoop) -> WindowAttributes { - if let Ok(win_icon) = winit::platform::windows::WinIcon::from_resource(1, None) { - let icon = winit::icon::Icon(std::sync::Arc::new(win_icon)); + if let Ok(win_icon) = WinIcon::from_resource(1, None) { + let icon = Icon(std::sync::Arc::new(win_icon)); attributes.with_window_icon(Some(icon)) } else { attributes diff --git a/desktop/wrapper/src/intercept_editor_message.rs b/desktop/wrapper/src/intercept_editor_message.rs index 0f3bf93967..933de19b95 100644 --- a/desktop/wrapper/src/intercept_editor_message.rs +++ b/desktop/wrapper/src/intercept_editor_message.rs @@ -2,6 +2,6 @@ use super::DesktopWrapperMessageDispatcher; use super::messages::EditorMessage; pub(super) fn intercept_editor_message(_dispatcher: &mut DesktopWrapperMessageDispatcher, message: EditorMessage) -> Option { - // TODO: remove it turns out to be unnecessary + // TODO: remove if it turns out to be unnecessary Some(message) } diff --git a/desktop/wrapper/src/intercept_frontend_message.rs b/desktop/wrapper/src/intercept_frontend_message.rs index 97adb05cc6..1c4d1991b6 100644 --- a/desktop/wrapper/src/intercept_frontend_message.rs +++ b/desktop/wrapper/src/intercept_frontend_message.rs @@ -164,7 +164,8 @@ fn convert_menu_bar_entry_to_menu_item( } let shortcut = match shortcut { - Some(ActionKeys::Keys(LayoutKeysGroup(keys))) => convert_layout_keys_to_shortcut(keys), + //TODO: Reenable shortcuts once a workaround for missing keyboard events is found + Some(ActionKeys::Keys(LayoutKeysGroup(keys))) if false => convert_layout_keys_to_shortcut(keys), _ => None, }; @@ -321,6 +322,7 @@ fn convert_layout_keys_to_shortcut(layout_keys: &Vec) -> Option key = Some(KeyCode::ScrollLock), Key::Pause => key = Some(KeyCode::Pause), Key::Unidentified => key = Some(KeyCode::Unidentified), + Key::FakeKeyPlus => key = Some(KeyCode::Equal), _ => key = None, } } diff --git a/editor/src/messages/viewport/viewport_message_handler.rs b/editor/src/messages/viewport/viewport_message_handler.rs index d562917715..91014965a9 100644 --- a/editor/src/messages/viewport/viewport_message_handler.rs +++ b/editor/src/messages/viewport/viewport_message_handler.rs @@ -37,13 +37,16 @@ impl MessageHandler for ViewportMessageHandler { ViewportMessage::RepropagateUpdate => {} } - let physical_bounds = self.bounds().to_physical(); - responses.add(FrontendMessage::UpdateViewportPhysicalBounds { - x: physical_bounds.x(), - y: physical_bounds.y(), - width: physical_bounds.width(), - height: physical_bounds.height(), - }); + #[cfg(not(target_family = "wasm"))] + { + let physical_bounds = self.bounds().to_physical(); + responses.add(FrontendMessage::UpdateViewportPhysicalBounds { + x: physical_bounds.x(), + y: physical_bounds.y(), + width: physical_bounds.width(), + height: physical_bounds.height(), + }); + } responses.add(NavigationMessage::CanvasPan { delta: DVec2::ZERO }); responses.add(NodeGraphMessage::SetGridAlignedEdges); diff --git a/frontend/src/io-managers/persistence.ts b/frontend/src/io-managers/persistence.ts index 4ce45933f2..be9bde6530 100644 --- a/frontend/src/io-managers/persistence.ts +++ b/frontend/src/io-managers/persistence.ts @@ -10,6 +10,7 @@ import { TriggerLoadFirstAutoSaveDocument, TriggerLoadRestAutoSaveDocuments, TriggerSaveActiveDocument, + TriggerOpenLaunchDocuments, } from "@graphite/messages"; import { type PortfolioState } from "@graphite/state-providers/portfolio"; @@ -171,6 +172,9 @@ export function createPersistenceManager(editor: Editor, portfolio: PortfolioSta editor.subscriptions.subscribeJsMessage(TriggerLoadRestAutoSaveDocuments, async () => { await loadRestDocuments(); }); + editor.subscriptions.subscribeJsMessage(TriggerOpenLaunchDocuments, async () => { + // TODO: Could be used to load documents from URL params or similar on launch + }); editor.subscriptions.subscribeJsMessage(TriggerSaveActiveDocument, async (triggerSaveActiveDocument) => { const documentId = String(triggerSaveActiveDocument.documentId); const previouslySavedDocuments = await get>("documents", graphiteStore); diff --git a/frontend/src/messages.ts b/frontend/src/messages.ts index 45253cb2f5..0661e1f9ab 100644 --- a/frontend/src/messages.ts +++ b/frontend/src/messages.ts @@ -305,6 +305,13 @@ export class UpdateViewportHolePunch extends JsMessage { readonly active!: boolean; } +export class UpdateViewportPhysicalBounds extends JsMessage { + readonly x!: number; + readonly y!: number; + readonly width!: number; + readonly height!: number; +} + export class UpdateInputHints extends JsMessage { @Type(() => HintInfo) readonly hintData!: HintData; @@ -724,6 +731,8 @@ export class UpdateMouseCursor extends JsMessage { export class TriggerLoadFirstAutoSaveDocument extends JsMessage {} export class TriggerLoadRestAutoSaveDocuments extends JsMessage {} +export class TriggerOpenLaunchDocuments extends JsMessage {} + export class TriggerLoadPreferences extends JsMessage {} export class TriggerFetchAndOpenDocument extends JsMessage { @@ -1653,6 +1662,7 @@ export const messageMakers: Record = { TriggerLoadFirstAutoSaveDocument, TriggerLoadPreferences, TriggerLoadRestAutoSaveDocuments, + TriggerOpenLaunchDocuments, TriggerOpenDocument, TriggerPaste, TriggerSaveActiveDocument, @@ -1705,6 +1715,7 @@ export const messageMakers: Record = { UpdateToolOptionsLayout, UpdateToolShelfLayout, UpdateViewportHolePunch, + UpdateViewportPhysicalBounds, UpdateVisibleNodes, UpdateWirePathInProgress, UpdateWorkingColorsLayout,