mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2026-09-23 21:48:12 +08:00
Desktop: Isolate CEF-rendered UI into separate crate and process (#4321)
* Extract CEF rendered UI into a separate process and crate * Review * Review * Review * Review * Remove necessary workarounds * Block on frame copy ack * Crop and resample frames correctly * Skip blank frames * Fix deps * Fix fmt * Fix clippy warning * Review * Fix todo
This commit is contained in:
@@ -0,0 +1,275 @@
|
||||
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 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_default()
|
||||
}
|
||||
#[cfg(any(target_os = "macos", target_os = "windows"))]
|
||||
{
|
||||
self.to_scancode().map(|c| c as i32).unwrap_or_default()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) trait ToVKBits {
|
||||
fn to_vk_bits(&self) -> i32;
|
||||
}
|
||||
|
||||
macro_rules! map_enum {
|
||||
($target:expr, $enum:ident, $( ($code:expr, $variant:ident), )+ ) => {
|
||||
match $target {
|
||||
$(
|
||||
$enum::$variant => $code,
|
||||
)+
|
||||
_ => 0,
|
||||
}
|
||||
};
|
||||
}
|
||||
impl ToVKBits for winit::keyboard::NamedKey {
|
||||
fn to_vk_bits(&self) -> i32 {
|
||||
map_enum!(
|
||||
self,
|
||||
NamedKey,
|
||||
(0x12, Alt),
|
||||
(0xA5, AltGraph),
|
||||
(0x14, CapsLock),
|
||||
(0x11, Control),
|
||||
(0x90, NumLock),
|
||||
(0x91, ScrollLock),
|
||||
(0x10, Shift),
|
||||
(0x5B, Meta),
|
||||
(0x0D, Enter),
|
||||
(0x09, Tab),
|
||||
(0x25, ArrowLeft),
|
||||
(0x26, ArrowUp),
|
||||
(0x27, ArrowRight),
|
||||
(0x28, ArrowDown),
|
||||
(0x23, End),
|
||||
(0x24, Home),
|
||||
(0x22, PageDown),
|
||||
(0x21, PageUp),
|
||||
(0x08, Backspace),
|
||||
(0x0C, Clear),
|
||||
(0xF7, CrSel),
|
||||
(0x2E, Delete),
|
||||
(0xF9, EraseEof),
|
||||
(0xF8, ExSel),
|
||||
(0x2D, Insert),
|
||||
(0x1E, Accept),
|
||||
(0xF6, Attn),
|
||||
(0x03, Cancel),
|
||||
(0x5D, ContextMenu),
|
||||
(0x1B, Escape),
|
||||
(0x2B, Execute),
|
||||
(0x2F, Help),
|
||||
(0x13, Pause),
|
||||
(0xFA, Play),
|
||||
(0x5D, Props),
|
||||
(0x29, Select),
|
||||
(0xFB, ZoomIn),
|
||||
(0xFB, ZoomOut),
|
||||
(0x2C, PrintScreen),
|
||||
(0x5F, Standby),
|
||||
(0x1C, Convert),
|
||||
(0x18, FinalMode),
|
||||
(0x1F, ModeChange),
|
||||
(0x1D, NonConvert),
|
||||
(0xE5, Process),
|
||||
(0x15, HangulMode),
|
||||
(0x19, HanjaMode),
|
||||
(0x17, JunjaMode),
|
||||
(0x15, KanaMode),
|
||||
(0x19, KanjiMode),
|
||||
(0xB0, MediaFastForward),
|
||||
(0xB3, MediaPause),
|
||||
(0xB3, MediaPlay),
|
||||
(0xB3, MediaPlayPause),
|
||||
(0xB1, MediaRewind),
|
||||
(0xB2, MediaStop),
|
||||
(0xB0, MediaTrackNext),
|
||||
(0xB1, MediaTrackPrevious),
|
||||
(0x2A, Print),
|
||||
(0xAE, AudioVolumeDown),
|
||||
(0xAF, AudioVolumeUp),
|
||||
(0xAD, AudioVolumeMute),
|
||||
(0xB6, LaunchApplication1),
|
||||
(0xB7, LaunchApplication2),
|
||||
(0xB4, LaunchMail),
|
||||
(0xB5, LaunchMediaPlayer),
|
||||
(0xB5, LaunchMusicPlayer),
|
||||
(0xA6, BrowserBack),
|
||||
(0xAB, BrowserFavorites),
|
||||
(0xA7, BrowserForward),
|
||||
(0xAC, BrowserHome),
|
||||
(0xA8, BrowserRefresh),
|
||||
(0xAA, BrowserSearch),
|
||||
(0xA9, BrowserStop),
|
||||
(0xFB, ZoomToggle),
|
||||
(0x70, F1),
|
||||
(0x71, F2),
|
||||
(0x72, F3),
|
||||
(0x73, F4),
|
||||
(0x74, F5),
|
||||
(0x75, F6),
|
||||
(0x76, F7),
|
||||
(0x77, F8),
|
||||
(0x78, F9),
|
||||
(0x79, F10),
|
||||
(0x7A, F11),
|
||||
(0x7B, F12),
|
||||
(0x7C, F13),
|
||||
(0x7D, F14),
|
||||
(0x7E, F15),
|
||||
(0x7F, F16),
|
||||
(0x80, F17),
|
||||
(0x81, F18),
|
||||
(0x82, F19),
|
||||
(0x83, F20),
|
||||
(0x84, F21),
|
||||
(0x85, F22),
|
||||
(0x86, F23),
|
||||
(0x87, F24),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
macro_rules! map {
|
||||
($target:expr, $( ($code:expr, $variant:literal), )+ ) => {
|
||||
match $target {
|
||||
$(
|
||||
$variant => $code,
|
||||
)+
|
||||
_ => 0,
|
||||
}
|
||||
};
|
||||
}
|
||||
impl ToVKBits for char {
|
||||
fn to_vk_bits(&self) -> i32 {
|
||||
map!(
|
||||
self,
|
||||
(0x41, 'a'),
|
||||
(0x42, 'b'),
|
||||
(0x43, 'c'),
|
||||
(0x44, 'd'),
|
||||
(0x45, 'e'),
|
||||
(0x46, 'f'),
|
||||
(0x47, 'g'),
|
||||
(0x48, 'h'),
|
||||
(0x49, 'i'),
|
||||
(0x4a, 'j'),
|
||||
(0x4b, 'k'),
|
||||
(0x4c, 'l'),
|
||||
(0x4d, 'm'),
|
||||
(0x4e, 'n'),
|
||||
(0x4f, 'o'),
|
||||
(0x50, 'p'),
|
||||
(0x51, 'q'),
|
||||
(0x52, 'r'),
|
||||
(0x53, 's'),
|
||||
(0x54, 't'),
|
||||
(0x55, 'u'),
|
||||
(0x56, 'v'),
|
||||
(0x57, 'w'),
|
||||
(0x58, 'x'),
|
||||
(0x59, 'y'),
|
||||
(0x5a, 'z'),
|
||||
(0x41, 'A'),
|
||||
(0x42, 'B'),
|
||||
(0x43, 'C'),
|
||||
(0x44, 'D'),
|
||||
(0x45, 'E'),
|
||||
(0x46, 'F'),
|
||||
(0x47, 'G'),
|
||||
(0x48, 'H'),
|
||||
(0x49, 'I'),
|
||||
(0x4a, 'J'),
|
||||
(0x4b, 'K'),
|
||||
(0x4c, 'L'),
|
||||
(0x4d, 'M'),
|
||||
(0x4e, 'N'),
|
||||
(0x4f, 'O'),
|
||||
(0x50, 'P'),
|
||||
(0x51, 'Q'),
|
||||
(0x52, 'R'),
|
||||
(0x53, 'S'),
|
||||
(0x54, 'T'),
|
||||
(0x55, 'U'),
|
||||
(0x56, 'V'),
|
||||
(0x57, 'W'),
|
||||
(0x58, 'X'),
|
||||
(0x59, 'Y'),
|
||||
(0x5a, 'Z'),
|
||||
(0x31, '1'),
|
||||
(0x32, '2'),
|
||||
(0x33, '3'),
|
||||
(0x34, '4'),
|
||||
(0x35, '5'),
|
||||
(0x36, '6'),
|
||||
(0x37, '7'),
|
||||
(0x38, '8'),
|
||||
(0x39, '9'),
|
||||
(0x30, '0'),
|
||||
(0x31, '!'),
|
||||
(0x32, '@'),
|
||||
(0x33, '#'),
|
||||
(0x34, '$'),
|
||||
(0x35, '%'),
|
||||
(0x36, '^'),
|
||||
(0x37, '&'),
|
||||
(0x38, '*'),
|
||||
(0x39, '('),
|
||||
(0x30, ')'),
|
||||
(0xC0, '`'),
|
||||
(0xC0, '~'),
|
||||
(0xBD, '-'),
|
||||
(0xBD, '_'),
|
||||
(0xBB, '='),
|
||||
(0xBB, '+'),
|
||||
(0xDB, '['),
|
||||
(0xDB, '{'),
|
||||
(0xDD, ']'),
|
||||
(0xDD, '}'),
|
||||
(0xDC, '\\'),
|
||||
(0xDC, '|'),
|
||||
(0xBA, ';'),
|
||||
(0xBA, ':'),
|
||||
(0xBC, ','),
|
||||
(0xBC, '<'),
|
||||
(0xBE, '.'),
|
||||
(0xBE, '>'),
|
||||
(0xDE, '\''),
|
||||
(0xDE, '"'),
|
||||
(0xBF, '/'),
|
||||
(0xBF, '?'),
|
||||
(0x20, ' '),
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,256 @@
|
||||
use cef::sys::cef_event_flags_t;
|
||||
use std::time::Instant;
|
||||
use winit::dpi::PhysicalPosition;
|
||||
use winit::event::{ElementState, MouseButton};
|
||||
use winit::keyboard::{Key, KeyLocation, ModifiersState, NamedKey};
|
||||
|
||||
use super::MouseData;
|
||||
use crate::consts::{MULTICLICK_ALLOWED_TRAVEL, MULTICLICK_TIMEOUT};
|
||||
|
||||
#[derive(Default)]
|
||||
pub(crate) struct InputState {
|
||||
modifiers: ModifiersState,
|
||||
mouse_position: MousePosition,
|
||||
mouse_state: MouseState,
|
||||
mouse_click_tracker: ClickTracker,
|
||||
}
|
||||
impl InputState {
|
||||
pub(crate) fn modifiers_changed(&mut self, modifiers: &ModifiersState) {
|
||||
self.modifiers = *modifiers;
|
||||
}
|
||||
|
||||
pub(crate) fn modifiers_apply_key_event(&mut self, key: &Key, state: &ElementState) {
|
||||
let bits = match key {
|
||||
Key::Named(NamedKey::Shift) => ModifiersState::SHIFT,
|
||||
Key::Named(NamedKey::Control) => ModifiersState::CONTROL,
|
||||
Key::Named(NamedKey::Alt) => ModifiersState::ALT,
|
||||
Key::Named(NamedKey::Meta) => ModifiersState::META,
|
||||
_ => return,
|
||||
};
|
||||
let is_pressed = matches!(state, ElementState::Pressed);
|
||||
self.modifiers.set(bits, is_pressed);
|
||||
}
|
||||
|
||||
pub(crate) fn cursor_move(&mut self, position: &PhysicalPosition<f64>) -> bool {
|
||||
let new = position.into();
|
||||
if self.mouse_position == new {
|
||||
return false;
|
||||
}
|
||||
self.mouse_position = new;
|
||||
true
|
||||
}
|
||||
|
||||
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: &KeyLocation, is_repeat: bool) -> CefModifiers {
|
||||
CefModifiers::new(self, location, is_repeat)
|
||||
}
|
||||
|
||||
pub(crate) fn cef_mouse_modifiers(&self) -> CefModifiers {
|
||||
self.cef_modifiers(&KeyLocation::Standard, false)
|
||||
}
|
||||
|
||||
pub(crate) fn mouse_data(&self) -> MouseData {
|
||||
MouseData {
|
||||
x: self.mouse_position.x,
|
||||
y: self.mouse_position.y,
|
||||
modifiers: self.cef_mouse_modifiers().into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default, Clone, Copy, Eq, PartialEq)]
|
||||
pub(crate) struct MousePosition {
|
||||
x: i32,
|
||||
y: i32,
|
||||
}
|
||||
impl From<&PhysicalPosition<f64>> for MousePosition {
|
||||
fn from(position: &PhysicalPosition<f64>) -> Self {
|
||||
Self {
|
||||
x: position.x as i32,
|
||||
y: position.y as i32,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[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 {
|
||||
down_position: position,
|
||||
up_position: position,
|
||||
..Default::default()
|
||||
});
|
||||
return ClickCount::Single;
|
||||
};
|
||||
|
||||
let now = Instant::now();
|
||||
let within_time = now.saturating_duration_since(record.time) <= MULTICLICK_TIMEOUT;
|
||||
|
||||
let (prev_count, prev_position) = match state {
|
||||
ElementState::Pressed => (record.down_count, record.down_position),
|
||||
ElementState::Released => (record.up_count, record.up_position),
|
||||
};
|
||||
|
||||
let dx = position.x.abs_diff(prev_position.x) as usize;
|
||||
let dy = position.y.abs_diff(prev_position.y) as usize;
|
||||
let within_dist = dx <= MULTICLICK_ALLOWED_TRAVEL && dy <= MULTICLICK_ALLOWED_TRAVEL;
|
||||
|
||||
let count = match (prev_count, within_time, within_dist) {
|
||||
(ClickCount::Single, true, true) => ClickCount::Double,
|
||||
(ClickCount::Double, true, true) => ClickCount::Triple,
|
||||
(ClickCount::Triple, true, true) => ClickCount::Double,
|
||||
_ => ClickCount::Single,
|
||||
};
|
||||
|
||||
record.time = now;
|
||||
|
||||
match state {
|
||||
ElementState::Pressed => {
|
||||
record.down_position = position;
|
||||
record.down_count = count;
|
||||
}
|
||||
ElementState::Released => {
|
||||
record.up_position = position;
|
||||
record.up_count = count;
|
||||
}
|
||||
}
|
||||
|
||||
count
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, PartialEq, Default)]
|
||||
pub(crate) enum ClickCount {
|
||||
#[default]
|
||||
Single,
|
||||
Double,
|
||||
Triple,
|
||||
}
|
||||
impl From<ClickCount> for i32 {
|
||||
fn from(count: ClickCount) -> i32 {
|
||||
match count {
|
||||
ClickCount::Single => 1,
|
||||
ClickCount::Double => 2,
|
||||
ClickCount::Triple => 3,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
struct ClickRecord {
|
||||
time: Instant,
|
||||
down_position: MousePosition,
|
||||
up_position: MousePosition,
|
||||
down_count: ClickCount,
|
||||
up_count: ClickCount,
|
||||
}
|
||||
|
||||
impl Default for ClickRecord {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
time: Instant::now(),
|
||||
down_position: Default::default(),
|
||||
up_position: Default::default(),
|
||||
down_count: Default::default(),
|
||||
up_count: Default::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) struct CefModifiers(cef_event_flags_t);
|
||||
impl CefModifiers {
|
||||
fn new(input_state: &InputState, location: &KeyLocation, is_repeat: bool) -> Self {
|
||||
let mut inner = cef_event_flags_t::EVENTFLAG_NONE;
|
||||
|
||||
if input_state.modifiers.shift_key() {
|
||||
inner |= cef_event_flags_t::EVENTFLAG_SHIFT_DOWN;
|
||||
}
|
||||
if input_state.modifiers.control_key() {
|
||||
inner |= cef_event_flags_t::EVENTFLAG_CONTROL_DOWN;
|
||||
}
|
||||
if input_state.modifiers.alt_key() {
|
||||
inner |= cef_event_flags_t::EVENTFLAG_ALT_DOWN;
|
||||
}
|
||||
if input_state.modifiers.meta_key() {
|
||||
inner |= cef_event_flags_t::EVENTFLAG_COMMAND_DOWN;
|
||||
}
|
||||
|
||||
if input_state.mouse_state.left {
|
||||
inner |= cef_event_flags_t::EVENTFLAG_LEFT_MOUSE_BUTTON;
|
||||
}
|
||||
if input_state.mouse_state.right {
|
||||
inner |= cef_event_flags_t::EVENTFLAG_RIGHT_MOUSE_BUTTON;
|
||||
}
|
||||
if input_state.mouse_state.middle {
|
||||
inner |= cef_event_flags_t::EVENTFLAG_MIDDLE_MOUSE_BUTTON;
|
||||
}
|
||||
|
||||
if is_repeat {
|
||||
inner |= cef_event_flags_t::EVENTFLAG_IS_REPEAT;
|
||||
}
|
||||
|
||||
inner |= match location {
|
||||
KeyLocation::Left => cef_event_flags_t::EVENTFLAG_IS_LEFT,
|
||||
KeyLocation::Right => cef_event_flags_t::EVENTFLAG_IS_RIGHT,
|
||||
KeyLocation::Numpad => cef_event_flags_t::EVENTFLAG_IS_KEY_PAD,
|
||||
KeyLocation::Standard => cef_event_flags_t::EVENTFLAG_NONE,
|
||||
};
|
||||
|
||||
Self(inner)
|
||||
}
|
||||
|
||||
pub(super) const PINCH_MODIFIERS: Self = Self(cef_event_flags_t(
|
||||
cef_event_flags_t::EVENTFLAG_CONTROL_DOWN.0 | cef_event_flags_t::EVENTFLAG_PRECISION_SCROLLING_DELTA.0,
|
||||
));
|
||||
}
|
||||
|
||||
impl From<CefModifiers> for u32 {
|
||||
fn from(val: CefModifiers) -> Self {
|
||||
#[cfg(not(target_os = "windows"))]
|
||||
return val.0.0;
|
||||
#[cfg(target_os = "windows")]
|
||||
return val.0.0 as u32;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user