mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2026-09-15 14:18:04 +08:00
Desktop: Mac fix keyboard input (#3371)
* add browser debug port env
* mac use option as alt
* fix cef texture double sRGB conversion by using cef-rs fork with fix
* fix keyboard input on mac
* add missing frontend messages
* fixup
* fix keyboard input mac
* dbg
* re implement keyboard mapping
Co-authored-by: csmoe <csmoe@msn.com>
* Fix double arrow keys
* try fix for non mac
* test
* Revert "test"
This reverts commit c7cde9c597.
* fix mac
* some cleanup
* fix zoom in shortcut on mac introduced in #3377
* disable menu shortcut display
* fixup
This commit is contained in:
50
Cargo.lock
generated
50
Cargo.lock
generated
@@ -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",
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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 }
|
||||
|
||||
@@ -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))
|
||||
}
|
||||
_ => return,
|
||||
};
|
||||
|
||||
let native_key_code = event.physical_key.to_native_keycode();
|
||||
|
||||
let modifiers = input_state.cef_modifiers(&event.location, event.repeat).raw();
|
||||
let Some(host) = browser.host() else { return };
|
||||
|
||||
let mut key_event = KeyEvent {
|
||||
size: size_of::<KeyEvent>(),
|
||||
modifiers,
|
||||
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,
|
||||
}
|
||||
.into(),
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
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.modifiers = input_state.cef_modifiers(&event.location, event.repeat).raw();
|
||||
|
||||
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,
|
||||
};
|
||||
|
||||
key_event.native_key_code = event.physical_key.to_native_keycode();
|
||||
|
||||
key_event.character = event.logical_key.to_char_representation() as u16;
|
||||
key_event.unmodified_character = event.key_without_modifiers.to_char_representation() as u16;
|
||||
|
||||
#[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;
|
||||
}
|
||||
|
||||
key_event.native_key_code = native_key_code;
|
||||
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));
|
||||
|
||||
let Some(host) = browser.host() else { return };
|
||||
|
||||
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 let Some(char) = character {
|
||||
let mut char_key_event = KeyEvent {
|
||||
size: size_of::<KeyEvent>(),
|
||||
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<f64>) {
|
||||
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<InputState> 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<f64>> for MousePosition {
|
||||
fn from(position: &PhysicalPosition<f64>) -> 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<ClickRecord>,
|
||||
middle: Option<ClickRecord>,
|
||||
right: Option<ClickRecord>,
|
||||
}
|
||||
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<ClickCount> 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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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!(
|
||||
|
||||
242
desktop/src/cef/input/state.rs
Normal file
242
desktop/src/cef/input/state.rs
Normal file
@@ -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<f64>) {
|
||||
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<InputState> 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<f64>> for MousePosition {
|
||||
fn from(position: &PhysicalPosition<f64>) -> 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<ClickRecord>,
|
||||
middle: Option<ClickRecord>,
|
||||
right: Option<ClickRecord>,
|
||||
}
|
||||
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<ClickCount> 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
|
||||
}
|
||||
}
|
||||
@@ -72,6 +72,14 @@ impl<H: CefEventHandler> ImplApp for BrowserProcessAppImpl<H> {
|
||||
// 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::<u16>().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("*")));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -2,6 +2,6 @@ use super::DesktopWrapperMessageDispatcher;
|
||||
use super::messages::EditorMessage;
|
||||
|
||||
pub(super) fn intercept_editor_message(_dispatcher: &mut DesktopWrapperMessageDispatcher, message: EditorMessage) -> Option<EditorMessage> {
|
||||
// TODO: remove it turns out to be unnecessary
|
||||
// TODO: remove if it turns out to be unnecessary
|
||||
Some(message)
|
||||
}
|
||||
|
||||
@@ -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<LayoutKey>) -> Option<Short
|
||||
Key::ScrollLock => key = Some(KeyCode::ScrollLock),
|
||||
Key::Pause => key = Some(KeyCode::Pause),
|
||||
Key::Unidentified => key = Some(KeyCode::Unidentified),
|
||||
Key::FakeKeyPlus => key = Some(KeyCode::Equal),
|
||||
_ => key = None,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -37,13 +37,16 @@ impl MessageHandler<ViewportMessage, ()> 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);
|
||||
|
||||
@@ -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<Record<string, TriggerPersistenceWriteDocument>>("documents", graphiteStore);
|
||||
|
||||
@@ -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<string, MessageMaker> = {
|
||||
TriggerLoadFirstAutoSaveDocument,
|
||||
TriggerLoadPreferences,
|
||||
TriggerLoadRestAutoSaveDocuments,
|
||||
TriggerOpenLaunchDocuments,
|
||||
TriggerOpenDocument,
|
||||
TriggerPaste,
|
||||
TriggerSaveActiveDocument,
|
||||
@@ -1705,6 +1715,7 @@ export const messageMakers: Record<string, MessageMaker> = {
|
||||
UpdateToolOptionsLayout,
|
||||
UpdateToolShelfLayout,
|
||||
UpdateViewportHolePunch,
|
||||
UpdateViewportPhysicalBounds,
|
||||
UpdateVisibleNodes,
|
||||
UpdateWirePathInProgress,
|
||||
UpdateWorkingColorsLayout,
|
||||
|
||||
Reference in New Issue
Block a user