mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2026-09-15 22:28:10 +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:
@@ -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,
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user