Merge origin/master into the async record refactor

Scaffolding merge for the reconcile; the final series to master is
authored fresh. Rank plumbing resolves to our axis-IR model, the node
macro and the LaneSource render walk stay ours, master's vector
restructure and gradient vocabulary are adopted, and the paint and
appearance adoption is deliberately deferred behind our fill and stroke
markers.
This commit is contained in:
Dennis Kobert
2026-09-08 15:03:57 +00:00
385 changed files with 34666 additions and 20075 deletions
+1 -1
View File
@@ -3,7 +3,7 @@ name = "graphite-desktop"
version = "0.1.0"
description = "Graphite Desktop"
authors = ["Graphite Authors <contact@graphite.art>"]
license = "Apache-2.0"
license = "MIT OR Apache-2.0"
repository = ""
edition = "2024"
rust-version = "1.87"
+1 -1
View File
@@ -3,7 +3,7 @@ name = "graphite-desktop-bundle"
version = "0.0.0"
description = "Graphite Desktop Bundle"
authors = ["Graphite Authors <contact@graphite.art>"]
license = "Apache-2.0"
license = "MIT OR Apache-2.0"
repository = ""
edition = "2024"
rust-version = "1.87"
+1 -1
View File
@@ -3,7 +3,7 @@ name = "graphite-desktop-embedded-resources"
version = "0.1.0"
description = "Graphite Desktop Embedded Resources"
authors = ["Graphite Authors <contact@graphite.art>"]
license = "Apache-2.0"
license = "MIT OR Apache-2.0"
repository = ""
edition = "2024"
rust-version = "1.87"
+1 -1
View File
@@ -3,7 +3,7 @@ name = "graphite-desktop-platform-linux"
version = "0.0.0"
description = "Graphite Desktop Platform Linux"
authors = ["Graphite Authors <contact@graphite.art>"]
license = "Apache-2.0"
license = "MIT OR Apache-2.0"
repository = ""
edition = "2024"
rust-version = "1.87"
+1 -1
View File
@@ -3,7 +3,7 @@ name = "graphite-desktop-platform-mac"
version = "0.0.0"
description = "Graphite Desktop Platform Mac"
authors = ["Graphite Authors <contact@graphite.art>"]
license = "Apache-2.0"
license = "MIT OR Apache-2.0"
repository = ""
edition = "2024"
rust-version = "1.87"
+1 -1
View File
@@ -3,7 +3,7 @@ name = "graphite-desktop-platform-win"
version = "0.0.0"
description = "Graphite Desktop Platform Windows"
authors = ["Graphite Authors <contact@graphite.art>"]
license = "Apache-2.0"
license = "MIT OR Apache-2.0"
repository = ""
edition = "2024"
rust-version = "1.87"
+38 -63
View File
@@ -9,19 +9,21 @@ use std::sync::mpsc::{Receiver, SyncSender};
use std::thread;
use std::time::{Duration, Instant};
use winit::application::ApplicationHandler;
use winit::dpi::{PhysicalPosition, PhysicalSize};
use winit::event::{ButtonSource, ElementState, MouseButton, StartCause, WindowEvent};
use winit::dpi::PhysicalSize;
use winit::event::{ElementState, MouseButton, StartCause, WindowEvent};
use winit::event_loop::run_on_demand::EventLoopExtRunOnDemand;
use winit::event_loop::{ActiveEventLoop, ControlFlow, EventLoop};
use winit::window::WindowId;
use crate::dirs;
use crate::event::{AppEvent, AppEventScheduler};
use crate::input::InputState;
use crate::persist;
use crate::preferences;
use crate::render::{RenderError, RenderState};
use crate::ui::{UiCommand, UiInstance};
use crate::ui::{InputEvent, UiCommand, UiInstance};
use crate::window::Window;
use crate::wrapper::messages::{DesktopFrontendMessage, DesktopWrapperMessage, InputMessage, MouseKeys, MouseState, Preferences};
use crate::wrapper::messages::{DesktopFrontendMessage, DesktopWrapperMessage, Preferences};
use crate::wrapper::{DesktopWrapper, MmapResourceStorage, NodeGraphExecutionResult, WgpuContext, serialize_frontend_messages};
pub(crate) struct App {
@@ -33,8 +35,7 @@ pub(crate) struct App {
window_maximized: bool,
window_fullscreen: bool,
window_pending_drag: bool,
pointer_position: PhysicalPosition<f64>,
pointer_lock_position: Option<PhysicalPosition<f64>>,
input_state: InputState,
ui_scale: f64,
app_event_receiver: Receiver<AppEvent>,
app_event_scheduler: AppEventScheduler,
@@ -111,8 +112,7 @@ impl App {
window_maximized: false,
window_fullscreen: false,
window_pending_drag: false,
pointer_position: Default::default(),
pointer_lock_position: Default::default(),
input_state: InputState::new(),
ui_scale: 1.,
app_event_receiver,
app_event_scheduler,
@@ -130,8 +130,8 @@ impl App {
}
}
pub(crate) fn run(mut self, event_loop: EventLoop) -> ExitReason {
event_loop.run_app(&mut self).unwrap();
pub(crate) fn run(mut self, mut event_loop: EventLoop) -> ExitReason {
event_loop.run_app_on_demand(&mut self).unwrap();
self.exit_reason
}
@@ -269,6 +269,8 @@ impl App {
});
}
DesktopFrontendMessage::UpdateViewportPhysicalBounds { x, y, width, height } => {
self.input_state.set_viewport_info(x, y, width, height, self.window_scale);
if let Some(render_state) = &mut self.render_state
&& let Some(window) = &self.window
{
@@ -283,6 +285,9 @@ impl App {
render_state.set_viewport_scale([viewport_scale_x as f32, viewport_scale_y as f32]);
}
}
DesktopFrontendMessage::WindowUpdateDirectInput { enabled } => {
self.input_state.set_direct_input(enabled);
}
DesktopFrontendMessage::UpdateUIScale { scale } => {
self.ui_scale = scale;
self.resize();
@@ -347,7 +352,7 @@ impl App {
}
}
DesktopFrontendMessage::PointerLock => {
self.pointer_lock_position = Some(self.pointer_position);
self.input_state.lock_pointer();
if let Some(window) = &self.window {
window.start_pointer_lock();
}
@@ -540,26 +545,26 @@ impl ApplicationHandler for App {
fn window_event(&mut self, _event_loop: &dyn ActiveEventLoop, _window_id: WindowId, event: WindowEvent) {
// Handle pointer lock release
if let Some(pointer_lock_position) = self.pointer_lock_position
&& let WindowEvent::PointerButton {
state: ElementState::Released,
button: ButtonSource::Mouse(MouseButton::Left),
..
} = event
if let WindowEvent::PointerButton {
state: ElementState::Released,
button,
..
} = &event && button.clone().mouse_button() == Some(MouseButton::Left)
&& let Some(pointer_lock_position) = self.input_state.unlock_pointer()
{
self.pointer_lock_position = None;
if let Some(window) = &self.window {
window.end_pointer_lock();
}
self.ui.send(UiCommand::Input(WindowEvent::PointerMoved {
device_id: None,
position: pointer_lock_position,
primary: true,
source: winit::event::PointerSource::Mouse,
}));
self.ui.send(UiCommand::Input(
InputEvent::pointer().position(pointer_lock_position).moved().modifiers(self.input_state.modifiers()).build(),
));
}
self.ui.send(UiCommand::Input(event.clone()));
self.input_state.process(
&event,
|message| self.app_event_scheduler.schedule(AppEvent::DesktopWrapperMessage(DesktopWrapperMessage::Input(message))),
|input| self.ui.send(UiCommand::Input(input)),
);
match event {
WindowEvent::CloseRequested => {
@@ -616,50 +621,20 @@ impl ApplicationHandler for App {
}
}
// Forward and Back buttons are not supported by CEF and thus need to be directly forwarded the editor
WindowEvent::PointerButton {
button: ButtonSource::Mouse(button),
state: ElementState::Pressed,
..
} => {
let mouse_keys = match button {
MouseButton::Back => Some(MouseKeys::BACK),
MouseButton::Forward => Some(MouseKeys::FORWARD),
_ => None,
};
if let Some(mouse_keys) = mouse_keys {
let message = DesktopWrapperMessage::Input(InputMessage::PointerDown {
editor_mouse_state: MouseState { mouse_keys, ..Default::default() },
modifier_keys: Default::default(),
});
self.app_event_scheduler.schedule(AppEvent::DesktopWrapperMessage(message));
let message = DesktopWrapperMessage::Input(InputMessage::PointerUp {
editor_mouse_state: Default::default(),
modifier_keys: Default::default(),
});
self.app_event_scheduler.schedule(AppEvent::DesktopWrapperMessage(message));
}
}
WindowEvent::PointerMoved { position, .. } | WindowEvent::PointerLeft { position: Some(position), .. } | WindowEvent::PointerEntered { position, .. }
if self.pointer_lock_position.is_none() =>
WindowEvent::PointerMoved { .. } | WindowEvent::PointerLeft { position: Some(_), .. } | WindowEvent::PointerEntered { .. }
if !self.input_state.pointer_locked() && self.window_pending_drag =>
{
self.pointer_position = position;
if self.window_pending_drag {
self.window_pending_drag = false;
if let Some(window) = &self.window {
window.start_drag();
}
self.window_pending_drag = false;
if let Some(window) = &self.window {
window.start_drag();
}
}
WindowEvent::PointerButton {
button: ButtonSource::Mouse(MouseButton::Left),
button,
state: ElementState::Released,
..
} => {
} if button.clone().mouse_button() == Some(MouseButton::Left) => {
self.window_pending_drag = false;
}
@@ -668,7 +643,7 @@ impl ApplicationHandler for App {
}
fn device_event(&mut self, _event_loop: &dyn ActiveEventLoop, _device_id: Option<winit::event::DeviceId>, event: winit::event::DeviceEvent) {
if self.pointer_lock_position.is_some()
if self.input_state.pointer_locked()
&& let winit::event::DeviceEvent::PointerMotion { delta: (x, y) } = event
{
let message = DesktopWrapperMessage::PointerLockMove { x, y };
+453
View File
@@ -0,0 +1,453 @@
use std::collections::VecDeque;
use std::time::{Duration, Instant};
use winit::dpi::PhysicalPosition;
use winit::event::{ButtonSource, ElementState, MouseButton, MouseScrollDelta, PointerSource, TabletToolData, TabletToolKind, WindowEvent};
use winit::keyboard::ModifiersState;
use crate::ui::{InputEvent, MULTICLICK_ALLOWED_TRAVEL, MULTICLICK_TIMEOUT, PINCH_ZOOM_SPEED, SCROLL_LINE_HEIGHT, SCROLL_LINE_WIDTH, SCROLL_SPEED_X, SCROLL_SPEED_Y};
use crate::wrapper::messages::{EditorPointerState, InputMessage, ModifierKeys, MouseKeys, ScrollDelta};
// Marks input event as observe-only, meaning it should not result in any input messages being send by the frontend.
// And should instead be ignored by frontend input processing and only be used to update the UI state (hover, cursor).
// TODO(Timon): Remove and find a less hacky solution
trait ObserveOnlyExt {
fn observe_only(self) -> Self;
}
impl ObserveOnlyExt for crate::ui::InputEventBuilder {
fn observe_only(self) -> Self {
self.num_lock(true)
}
}
pub(crate) struct InputState {
start: Instant,
viewport_info: Option<ViewportInfo>,
direct_input: bool,
modifiers: ModifiersState,
pointer_position: PhysicalPosition<f64>,
pointer_state: PointerState,
click_tracker: ClickTracker,
shake_tracker: ShakeTracker,
}
impl InputState {
pub(crate) fn new() -> Self {
Self {
start: Instant::now(),
viewport_info: None,
direct_input: false,
modifiers: ModifiersState::default(),
pointer_position: PhysicalPosition::default(),
pointer_state: PointerState::Hover { route: Route::Ui },
click_tracker: ClickTracker::default(),
shake_tracker: ShakeTracker::default(),
}
}
pub(crate) fn set_viewport_info(&mut self, x: f64, y: f64, width: f64, height: f64, scale: f64) {
self.viewport_info = Some(ViewportInfo { x, y, width, height, scale });
}
pub(crate) fn set_direct_input(&mut self, enabled: bool) {
self.direct_input = enabled;
}
pub(crate) fn lock_pointer(&mut self) {
self.pointer_state = match self.pointer_state {
PointerState::Hover { route } => PointerState::Locked {
route,
keys: MouseKeys::empty(),
position: self.pointer_position,
},
PointerState::Stroke { route, keys } | PointerState::Locked { route, keys, .. } => PointerState::Locked {
route,
keys,
position: self.pointer_position,
},
};
}
pub(crate) fn unlock_pointer(&mut self) -> Option<PhysicalPosition<f64>> {
let PointerState::Locked {
route: resume,
keys,
position: restore,
} = self.pointer_state
else {
return None;
};
self.pointer_position = restore;
self.pointer_state = match keys.is_empty() {
true => PointerState::Hover { route: Route::Ui },
false => PointerState::Stroke { route: resume, keys },
};
Some(restore)
}
pub(crate) fn pointer_locked(&self) -> bool {
matches!(self.pointer_state, PointerState::Locked { .. })
}
pub(crate) fn modifiers(&self) -> ModifiersState {
self.modifiers
}
pub(crate) fn process(&mut self, event: &WindowEvent, mut editor_callback: impl FnMut(InputMessage), mut ui_callback: impl FnMut(InputEvent)) {
match event {
WindowEvent::PointerMoved { position, source, .. } => {
self.pointer_position = *position;
let route = match self.pointer_state {
PointerState::Hover { .. } => {
let next = self.route(*position);
self.pointer_state = PointerState::Hover { route: next };
next
}
PointerState::Stroke { route, .. } => route,
PointerState::Locked { keys, route: resume, .. } => match keys.is_empty() {
true => Route::Ui,
false => resume,
},
};
match route {
Route::Ui => ui_callback(InputEvent::pointer().position(*position).moved().modifiers(self.modifiers).build()),
Route::Editor => {
if !self.pointer_locked() {
ui_callback(InputEvent::pointer().position(*position).moved().modifiers(self.modifiers).observe_only().build());
}
let editor_mouse_state = match source {
PointerSource::TabletTool { kind, data } => self.tablet_pointer_state(kind, data),
_ => self.pointer_state(),
};
let modifier_keys = self.modifier_keys();
if self.shake_tracker.detect(*position, self.start.elapsed()) {
editor_callback(InputMessage::PointerShake { editor_mouse_state, modifier_keys });
}
editor_callback(InputMessage::PointerMove { editor_mouse_state, modifier_keys });
}
}
}
WindowEvent::PointerEntered { position, .. } => {
self.pointer_position = *position;
ui_callback(InputEvent::pointer().position(*position).entered().modifiers(self.modifiers).build())
}
WindowEvent::PointerLeft { position: Some(position), .. } => {
self.pointer_position = *position;
ui_callback(InputEvent::pointer().position(*position).exited().modifiers(self.modifiers).build())
}
WindowEvent::PointerLeft { position: None, .. } => ui_callback(InputEvent::pointer().exited().modifiers(self.modifiers).build()),
WindowEvent::PointerButton { state, button, position, .. } => {
self.pointer_position = *position;
let mouse_button = button.clone().mouse_button();
let keys = match mouse_button {
Some(MouseButton::Left) => MouseKeys::LEFT,
Some(MouseButton::Right) => MouseKeys::RIGHT,
Some(MouseButton::Middle) => MouseKeys::MIDDLE,
Some(MouseButton::Back) => MouseKeys::BACK,
Some(MouseButton::Forward) => MouseKeys::FORWARD,
_ => MouseKeys::NONE,
};
let (pointer, route) = match self.pointer_state {
PointerState::Hover { route } => match (state.is_pressed(), keys.is_empty()) {
(true, false) => {
let route = self.route(*position);
(PointerState::Stroke { route, keys }, route)
}
(true, true) => (PointerState::Hover { route }, self.route(*position)),
(false, _) => (PointerState::Hover { route }, route),
},
PointerState::Stroke { route, keys: mut held } => {
match state.is_pressed() {
true => held.insert(keys),
false => held.remove(keys),
}
match held.is_empty() {
true => (PointerState::Hover { route }, route),
false => (PointerState::Stroke { route, keys: held }, route),
}
}
PointerState::Locked { route, keys: mut held, position } => {
let resume = if state.is_pressed() && held.is_empty() { Route::Ui } else { route };
match state.is_pressed() {
true => held.insert(keys),
false => held.remove(keys),
}
(PointerState::Locked { route: resume, keys: held, position }, Route::Ui)
}
};
self.pointer_state = pointer;
let count = mouse_button.map_or(1, |button| self.click_tracker.input(*position, button, *state));
let back_or_forward = matches!(mouse_button, Some(MouseButton::Back | MouseButton::Forward));
let pointer = InputEvent::pointer().position(*position);
let input = match state {
ElementState::Pressed => pointer.pressed(button.clone(), count),
ElementState::Released => pointer.released(button.clone(), count),
}
.modifiers(self.modifiers);
if self.pointer_locked() || keys.is_empty() || !(back_or_forward || route == Route::Editor) {
ui_callback(input.build());
return;
}
if route == Route::Editor {
ui_callback(input.observe_only().build());
}
let editor_mouse_state = match button {
ButtonSource::TabletTool { kind, data, .. } => self.tablet_pointer_state(kind, data),
_ => self.pointer_state(),
};
let modifier_keys = self.modifier_keys();
match state {
ElementState::Pressed => editor_callback(InputMessage::PointerDown { editor_mouse_state, modifier_keys }),
ElementState::Released if count % 2 == 0 => {
editor_callback(InputMessage::PointerUp { editor_mouse_state, modifier_keys });
editor_callback(InputMessage::DoubleClick {
editor_mouse_state: EditorPointerState {
mouse_keys: keys,
..editor_mouse_state
},
modifier_keys,
});
}
ElementState::Released => editor_callback(InputMessage::PointerUp { editor_mouse_state, modifier_keys }),
}
}
WindowEvent::MouseWheel { delta, .. } => {
if self.pointer_locked() || self.ui_captures(self.pointer_position) {
let input = match delta {
MouseScrollDelta::LineDelta(x, y) => InputEvent::pointer().scrolled_lines(f64::from(*x), f64::from(*y)),
MouseScrollDelta::PixelDelta(position) => InputEvent::pointer().scrolled_pixels(position.x, position.y),
};
ui_callback(input.modifiers(self.modifiers).build());
return;
}
let (x, y) = match delta {
MouseScrollDelta::LineDelta(x, y) => (f64::from(*x) * SCROLL_LINE_WIDTH, f64::from(*y) * SCROLL_LINE_HEIGHT),
MouseScrollDelta::PixelDelta(position) => (position.x, position.y),
};
let scroll_delta = ScrollDelta::new(-x * SCROLL_SPEED_X, -y * SCROLL_SPEED_Y, 0.);
editor_callback(InputMessage::WheelScroll {
editor_mouse_state: EditorPointerState { scroll_delta, ..self.pointer_state() },
modifier_keys: self.modifier_keys(),
});
}
WindowEvent::PinchGesture { delta, .. } => {
if self.pointer_locked() || self.ui_captures(self.pointer_position) || !delta.is_normal() {
ui_callback(InputEvent::pointer().zoomed(*delta).modifiers(self.modifiers).build());
return;
}
// TODO: This is a temporary solution to handle pinch gestures, we should handle pinch gestures editor-side instead.
let scroll_delta = ScrollDelta::new(0., -delta * PINCH_ZOOM_SPEED, 0.);
editor_callback(InputMessage::WheelScroll {
editor_mouse_state: EditorPointerState { scroll_delta, ..self.pointer_state() },
modifier_keys: self.modifier_keys() | ModifierKeys::CONTROL,
});
}
WindowEvent::ModifiersChanged(modifiers) => {
self.modifiers = modifiers.state();
}
WindowEvent::KeyboardInput { event, .. } => ui_callback(InputEvent::key(event).modifiers(self.modifiers).build()),
_ => {}
}
}
fn scale(&self) -> f64 {
self.viewport_info.as_ref().map_or(1., |info| info.scale)
}
fn ui_captures(&self, position: PhysicalPosition<f64>) -> bool {
!self.direct_input || !self.viewport_info.as_ref().is_some_and(|info| info.contains(position))
}
fn route(&self, position: PhysicalPosition<f64>) -> Route {
if self.ui_captures(position) { Route::Ui } else { Route::Editor }
}
fn pointer_keys(&self) -> MouseKeys {
match self.pointer_state {
PointerState::Hover { .. } => MouseKeys::empty(),
PointerState::Stroke { keys, .. } | PointerState::Locked { keys, .. } => keys,
}
}
fn pointer_state(&self) -> EditorPointerState {
EditorPointerState {
editor_position: (self.pointer_position.x / self.scale(), self.pointer_position.y / self.scale()).into(),
mouse_keys: self.pointer_keys(),
time: Some(self.start.elapsed().as_secs_f64() * 1000.),
..Default::default()
}
}
fn tablet_pointer_state(&self, kind: &TabletToolKind, data: &TabletToolData) -> EditorPointerState {
EditorPointerState {
pressure: data.force.map(|force| force.normalized(None)),
tilt: data.clone().tilt().map(|tilt| (f64::from(tilt.x), f64::from(tilt.y)).into()),
twist: data.twist.map(f64::from),
wheel: data.tangential_force.map(f64::from),
eraser: matches!(kind, TabletToolKind::Eraser),
..self.pointer_state()
}
}
fn modifier_keys(&self) -> ModifierKeys {
let mut keys = ModifierKeys::empty();
keys.set(ModifierKeys::SHIFT, self.modifiers.shift_key());
keys.set(ModifierKeys::CONTROL, self.modifiers.control_key());
keys.set(ModifierKeys::ALT, self.modifiers.alt_key());
keys.set(ModifierKeys::META_OR_COMMAND, self.modifiers.meta_key());
keys
}
}
#[derive(Clone, Copy)]
enum PointerState {
Hover { route: Route },
Stroke { route: Route, keys: MouseKeys },
Locked { route: Route, keys: MouseKeys, position: PhysicalPosition<f64> },
}
#[derive(Clone, Copy, PartialEq)]
enum Route {
Ui,
Editor,
}
struct ViewportInfo {
x: f64,
y: f64,
width: f64,
height: f64,
scale: f64,
}
impl ViewportInfo {
fn contains(&self, position: PhysicalPosition<f64>) -> bool {
position.x >= self.x && position.y >= self.y && position.x <= self.x + self.width && position.y <= self.y + self.height
}
}
#[derive(Default)]
struct ClickTracker {
left: ClickChains,
right: ClickChains,
middle: ClickChains,
back: ClickChains,
forward: ClickChains,
}
impl ClickTracker {
fn input(&mut self, position: PhysicalPosition<f64>, button: MouseButton, state: ElementState) -> u32 {
let position = (position.x as i32, position.y as i32);
let clicks = match button {
MouseButton::Left => &mut self.left,
MouseButton::Right => &mut self.right,
MouseButton::Middle => &mut self.middle,
MouseButton::Back => &mut self.back,
MouseButton::Forward => &mut self.forward,
_ => return 1,
};
let chain = match state {
ElementState::Pressed => &mut clicks.down,
ElementState::Released => &mut clicks.up,
};
let now = Instant::now();
let count = match chain {
Some(previous) => {
let within_time = now.saturating_duration_since(previous.time) <= MULTICLICK_TIMEOUT;
let dx = position.0.abs_diff(previous.position.0) as usize;
let dy = position.1.abs_diff(previous.position.1) as usize;
let within_distance = dx <= MULTICLICK_ALLOWED_TRAVEL && dy <= MULTICLICK_ALLOWED_TRAVEL;
if within_time && within_distance { previous.count.saturating_add(1) } else { 1 }
}
None => 1,
};
*chain = Some(Click { time: now, position, count });
count
}
}
#[derive(Default)]
struct ClickChains {
down: Option<Click>,
up: Option<Click>,
}
struct Click {
time: Instant,
position: (i32, i32),
count: u32,
}
#[derive(Default)]
struct ShakeTracker {
samples: VecDeque<(PhysicalPosition<f64>, Duration)>,
last: Option<Duration>,
}
// Keep heuristic and constants in sync with `detectShake` in `frontend/src/utility-functions/input.ts`.
impl ShakeTracker {
const SENSITIVITY_DIRECTION_CHANGES: usize = 3;
const SENSITIVITY_DISTANCE_TO_DISPLACEMENT_RATIO: f64 = 0.1;
const DETECTION_WINDOW: Duration = Duration::from_millis(500);
const DEBOUNCE: Duration = Duration::from_millis(1000);
fn detect(&mut self, position: PhysicalPosition<f64>, now: Duration) -> bool {
self.samples.push_back((position, now));
while self.samples.front().is_some_and(|&(_, time)| now.saturating_sub(time) > Self::DETECTION_WINDOW) {
self.samples.pop_front();
}
if self.samples.len() <= 3 || self.last.is_some_and(|time| now.saturating_sub(time) <= Self::DEBOUNCE) {
return false;
}
let mut total_distance_squared = 0.;
for i in 1..self.samples.len() {
let (p1, _) = self.samples[i - 1];
let (p2, _) = self.samples[i];
total_distance_squared += (p2.x - p1.x).powi(2) + (p2.y - p1.y).powi(2);
}
let mut direction_changes = 0;
let mut average = PhysicalPosition::new(0., 0.);
let interior_count = self.samples.len() - 2;
for i in 0..interior_count {
let (p1, _) = self.samples[i];
let (p2, _) = self.samples[i + 1];
let (p3, _) = self.samples[i + 2];
let vector1 = (p2.x - p1.x, p2.y - p1.y);
let vector2 = (p3.x - p2.x, p3.y - p2.y);
if vector1.0 * vector2.0 + vector1.1 * vector2.1 < 0. {
direction_changes += 1;
}
average.x += p2.x;
average.y += p2.y;
}
average.x /= interior_count as f64;
average.y /= interior_count as f64;
let (last, _) = self.samples[self.samples.len() - 1];
let displacement_squared = (last.x - average.x).powi(2) + (last.y - average.y).powi(2);
if Self::SENSITIVITY_DISTANCE_TO_DISPLACEMENT_RATIO * total_distance_squared >= displacement_squared && direction_changes >= Self::SENSITIVITY_DIRECTION_CHANGES {
self.last = Some(now);
self.samples.clear();
return true;
}
false
}
}
+1
View File
@@ -17,6 +17,7 @@ mod cli;
mod dirs;
mod event;
mod gpu_context;
mod input;
mod persist;
mod preferences;
mod render;
+4 -5
View File
@@ -52,13 +52,12 @@ fn fs_main(in: VertexOutput) -> @location(0) vec4<f32> {
return srgb_to_linear(immediates.background_color);
}
let ui_linear = srgb_to_linear(textureSample(t_ui, s_diffuse, ui_coordinate));
if (ui_linear.a >= 0.999) {
return ui_linear;
let ui_sample = textureSample(t_ui, s_diffuse, ui_coordinate);
if (ui_sample.a >= 0.999) {
return srgb_to_linear(ui_sample);
}
// UI texture is premultiplied, we need to unpremultiply before blending
let ui_srgb = linear_to_srgb(unpremultiply(ui_linear));
let ui_srgb = unpremultiply(ui_sample);
let viewport_coordinate = (in.tex_coords - immediates.viewport_offset) * immediates.viewport_scale;
if (viewport_coordinate.x < 0.0 || viewport_coordinate.x > 1.0 ||
+8 -8
View File
@@ -1,7 +1,7 @@
use wgpu::PresentMode;
use crate::window::Window;
use crate::wrapper::{WgpuContext, WgpuCurrentSurfaceTexture, WgpuExecutor, WgpuSurface};
use crate::wrapper::{Texture, WgpuContext, WgpuCurrentSurfaceTexture, WgpuExecutor, WgpuSurface};
#[derive(derivative::Derivative)]
#[derivative(Debug)]
@@ -10,14 +10,14 @@ pub(crate) struct RenderState {
executor: WgpuExecutor,
config: wgpu::SurfaceConfiguration,
render_pipeline: wgpu::RenderPipeline,
transparent_texture: std::sync::Arc<wgpu::Texture>,
transparent_texture: Texture,
sampler: wgpu::Sampler,
desired_width: u32,
desired_height: u32,
viewport_scale: [f32; 2],
viewport_offset: [f32; 2],
viewport_texture: Option<std::sync::Arc<wgpu::Texture>>,
overlays_texture: Option<std::sync::Arc<wgpu::Texture>>,
viewport_texture: Option<Texture>,
overlays_texture: Option<Texture>,
ui_texture: Option<wgpu::Texture>,
bind_group: Option<wgpu::BindGroup>,
#[derivative(Debug = "ignore")]
@@ -46,8 +46,8 @@ impl RenderState {
surface.configure(&context.device, &config);
let transparent_texture = std::sync::Arc::new(context.device.create_texture(&wgpu::TextureDescriptor {
label: Some("Transparent Texture"),
let transparent_texture = Texture::from(context.device.create_texture(&wgpu::TextureDescriptor {
label: Some("transparent_fallback"),
size: wgpu::Extent3d {
width: 1,
height: 1,
@@ -193,7 +193,7 @@ impl RenderState {
self.surface_outdated = true;
}
pub(crate) fn bind_viewport_texture(&mut self, viewport_texture: std::sync::Arc<wgpu::Texture>) {
pub(crate) fn bind_viewport_texture(&mut self, viewport_texture: Texture) {
self.viewport_texture = Some(viewport_texture);
self.update_bindgroup();
}
@@ -231,7 +231,7 @@ impl RenderState {
let result = self.executor.render_vello_scene(&scene, size, &Default::default(), None);
match result {
Ok(texture) => {
self.overlays_texture = Some(texture.into());
self.overlays_texture = Some(texture);
}
Err(e) => {
self.overlays_texture = None;
+8
View File
@@ -344,6 +344,14 @@ unsafe fn position_helper(main: HWND, helper: HWND) {
let h = (r.bottom - r.top) + RESIZE_BAND_THICKNESS * 2;
let _ = unsafe { SetWindowPos(helper, Some(main), x, y, w, h, SWP_NOACTIVATE | SWP_NOSENDCHANGING) };
let outer = unsafe { CreateRectRgn(0, 0, w, h) };
let inner = unsafe { CreateRectRgn(RESIZE_BAND_THICKNESS, RESIZE_BAND_THICKNESS, w - RESIZE_BAND_THICKNESS, h - RESIZE_BAND_THICKNESS) };
let _ = unsafe { CombineRgn(Some(outer), Some(outer), Some(inner), RGN_DIFF) };
let _ = unsafe { DeleteObject(inner.into()) };
if unsafe { SetWindowRgn(helper, Some(outer), false) } == 0 {
let _ = unsafe { DeleteObject(outer.into()) };
}
}
unsafe fn calculate_hit(helper: HWND, lparam: LPARAM) -> u32 {
+18 -18
View File
@@ -1,5 +1,23 @@
use std::time::Duration;
pub const MULTICLICK_TIMEOUT: Duration = Duration::from_millis(500);
pub const MULTICLICK_ALLOWED_TRAVEL: usize = 4;
pub const SCROLL_LINE_HEIGHT: f64 = 40.;
pub const SCROLL_LINE_WIDTH: f64 = 40.;
#[cfg(target_os = "linux")]
pub const SCROLL_SPEED_X: f64 = 3.;
#[cfg(target_os = "linux")]
pub const SCROLL_SPEED_Y: f64 = 3.;
#[cfg(not(target_os = "linux"))]
pub const SCROLL_SPEED_X: f64 = 1.;
#[cfg(not(target_os = "linux"))]
pub const SCROLL_SPEED_Y: f64 = 1.;
pub const PINCH_ZOOM_SPEED: f64 = 300.;
pub(crate) const RESOURCE_SCHEME: &str = "resources";
pub(crate) const RESOURCE_DOMAIN: &str = "resources";
@@ -17,21 +35,3 @@ pub(crate) const HOST_SHUTDOWN_TIMEOUT: Duration = Duration::from_secs(5);
#[cfg(target_os = "macos")]
pub(crate) const IPC_BOOTSTRAP_PREFIX: &str = "art.graphite.Graphite.ipc.";
pub(crate) const SCROLL_LINE_HEIGHT: usize = 40;
pub(crate) const SCROLL_LINE_WIDTH: usize = 40;
#[cfg(target_os = "linux")]
pub(crate) const SCROLL_SPEED_X: f32 = 3.;
#[cfg(target_os = "linux")]
pub(crate) const SCROLL_SPEED_Y: f32 = 3.;
#[cfg(not(target_os = "linux"))]
pub(crate) const SCROLL_SPEED_X: f32 = 1.;
#[cfg(not(target_os = "linux"))]
pub(crate) const SCROLL_SPEED_Y: f32 = 1.;
pub(crate) const PINCH_ZOOM_SPEED: f64 = 300.;
pub(crate) const MULTICLICK_TIMEOUT: Duration = Duration::from_millis(500);
pub(crate) const MULTICLICK_ALLOWED_TRAVEL: usize = 4;
+4 -6
View File
@@ -223,6 +223,7 @@ fn create_browser(delegate: BrowserDelegate, frames: FrameStreamer, view_info_se
.map(|browser| BrowserContext {
delegate,
browser,
input: input::InputState::default(),
view_info_sender,
_instance_dir: instance_dir,
})
@@ -252,12 +253,8 @@ pub(crate) enum InitError {
pub(crate) struct CefContextHandle;
impl CefContextHandle {
pub(crate) fn apply_input(&self, events: Vec<InputEvent>) {
with_context(move |context| {
for event in &events {
input::apply(&context.browser, event);
}
});
pub(crate) fn process_input(&self, event: InputEvent) {
with_context(move |context| input::process(&mut context.input, &context.browser, &event));
}
pub(crate) fn update_view_info(&self, update: ViewInfoUpdate) {
@@ -276,6 +273,7 @@ impl CefContextHandle {
struct BrowserContext {
delegate: BrowserDelegate,
browser: Browser,
input: input::InputState,
view_info_sender: Sender<ViewInfoUpdate>,
_instance_dir: TempDir,
}
+195 -211
View File
@@ -1,251 +1,235 @@
use cef::sys::{cef_key_event_type_t, cef_mouse_button_type_t};
use cef::{Browser, ImplBrowser, ImplBrowserHost, KeyEvent, MouseEvent};
use winit::event::{ButtonSource, ElementState, MouseButton, MouseScrollDelta, WindowEvent};
pub(crate) mod event;
pub(crate) use event::InputEvent;
mod keymap;
use keymap::{ToCharRepresentation, ToNativeKeycode, ToVKBits};
mod state;
pub(crate) use state::{CefModifiers, InputState};
use cef::sys::{cef_event_flags_t, cef_key_event_type_t, cef_mouse_button_type_t};
use cef::{Browser, ImplBrowser, ImplBrowserHost, KeyEvent, MouseEvent};
use winit::dpi::PhysicalPosition;
use winit::keyboard::{Key, KeyLocation, NamedKey};
use super::consts::{PINCH_ZOOM_SPEED, SCROLL_LINE_HEIGHT, SCROLL_LINE_WIDTH, SCROLL_SPEED_X, SCROLL_SPEED_Y};
use event::{InputEventKind, KeyAction, Modifiers, MouseButton, PointerAction};
/// A window input translated into the plain data CEF consumes — no winit types, so it can
/// be applied to a browser living in another process.
#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
pub(crate) enum InputEvent {
MouseMove { data: MouseData, leave: bool },
MouseClick { data: MouseData, button: MouseButtonKind, up: bool, click_count: i32 },
MouseWheel { data: MouseData, delta_x: i32, delta_y: i32 },
Key(KeyData),
#[derive(Default)]
pub(crate) struct InputState {
position: PhysicalPosition<f64>,
buttons: ButtonStates,
}
#[derive(Clone, Copy, Debug, serde::Serialize, serde::Deserialize)]
pub(crate) struct MouseData {
pub(crate) x: i32,
pub(crate) y: i32,
pub(crate) modifiers: u32,
impl InputState {
fn pointer_move(&mut self, position: PhysicalPosition<f64>) -> bool {
let moved = (position.x as i32, position.y as i32) != (self.position.x as i32, self.position.y as i32);
self.position = position;
moved
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
pub(crate) enum MouseButtonKind {
Left,
Right,
Middle,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
pub(crate) enum KeyEventKind {
RawKeyDown,
KeyUp,
Char,
}
#[derive(Clone, Copy, Debug, serde::Serialize, serde::Deserialize)]
pub(crate) struct KeyData {
pub(crate) kind: KeyEventKind,
pub(crate) modifiers: u32,
pub(crate) windows_key_code: i32,
pub(crate) native_key_code: i32,
pub(crate) character: u16,
pub(crate) unmodified_character: u16,
}
/// Turns a winit event into zero or more [`InputEvent`]s, updating the tracked input state
/// (cursor position, click counting, modifiers) along the way.
pub(crate) fn translate(input_state: &mut InputState, event: &WindowEvent) -> Vec<InputEvent> {
match event {
WindowEvent::PointerMoved { position, .. } => {
if !input_state.cursor_move(position) {
return Vec::new();
}
vec![InputEvent::MouseMove {
data: input_state.mouse_data(),
leave: false,
}]
}
WindowEvent::PointerEntered { position, .. } => {
let _ = input_state.cursor_move(position);
vec![InputEvent::MouseMove {
data: input_state.mouse_data(),
leave: false,
}]
}
WindowEvent::PointerLeft { position, .. } => {
if let Some(position) = position {
let _ = input_state.cursor_move(position);
}
vec![InputEvent::MouseMove {
data: input_state.mouse_data(),
leave: true,
}]
}
WindowEvent::PointerButton { state, button, position, .. } => {
let mouse_button = match button {
ButtonSource::Mouse(mouse_button) => mouse_button,
_ => {
return Vec::new(); // TODO: Handle touch input
pub(crate) fn process(state: &mut InputState, browser: &Browser, event: &InputEvent) {
let Some(host) = browser.host() else { return };
match &event.kind {
InputEventKind::Pointer { position, action } => {
let position = position.unwrap_or(state.position);
match *action {
PointerAction::Move => {
if !state.pointer_move(position) {
return;
}
let flags = event_flags(&event.modifiers, &state.buttons);
host.send_mouse_move_event(Some(&mouse_event(position, flags)), 0);
}
};
PointerAction::Enter => {
state.pointer_move(position);
let flags = event_flags(&event.modifiers, &state.buttons);
host.send_mouse_move_event(Some(&mouse_event(position, flags)), 0);
}
PointerAction::Exit => {
state.pointer_move(position);
let flags = event_flags(&event.modifiers, &state.buttons);
host.send_mouse_move_event(Some(&mouse_event(position, flags)), 1);
}
PointerAction::Press { button, count } | PointerAction::Release { button, count } => {
state.pointer_move(position);
let cef_button = match button {
MouseButton::Left => cef_mouse_button_type_t::MBT_LEFT,
MouseButton::Right => cef_mouse_button_type_t::MBT_RIGHT,
MouseButton::Middle => cef_mouse_button_type_t::MBT_MIDDLE,
MouseButton::Unknown => return,
};
let up = matches!(action, PointerAction::Release { .. });
state.buttons.update(button, !up);
let _ = input_state.cursor_move(position);
let click_count = input_state.mouse_input(mouse_button, state).into();
let up = matches!(state, ElementState::Released);
let button = match mouse_button {
MouseButton::Left => MouseButtonKind::Left,
MouseButton::Right => MouseButtonKind::Right,
MouseButton::Middle => MouseButtonKind::Middle,
_ => return Vec::new(),
};
// CEF only understands single, double and triple clicks; further clicks alternate between double and triple.
let count = match count {
0 | 1 => 1,
count if count % 2 == 0 => 2,
_ => 3,
};
vec![InputEvent::MouseClick {
data: input_state.mouse_data(),
button,
up,
click_count,
}]
let flags = event_flags(&event.modifiers, &state.buttons);
host.send_mouse_click_event(Some(&mouse_event(position, flags)), cef::MouseButtonType::from(cef_button), up as i32, count);
}
PointerAction::ScrollLines { x, y } => {
let flags = event_flags(&event.modifiers, &state.buttons);
let delta_x = (x * SCROLL_LINE_WIDTH * SCROLL_SPEED_X) as i32;
let delta_y = (y * SCROLL_LINE_HEIGHT * SCROLL_SPEED_Y) as i32;
host.send_mouse_wheel_event(Some(&mouse_event(position, flags)), delta_x, delta_y);
}
PointerAction::ScrollPixels { x, y } => {
let flags = event_flags(&event.modifiers, &state.buttons) | PRECISION_SCROLLING_DELTA;
host.send_mouse_wheel_event(Some(&mouse_event(position, flags)), (x * SCROLL_SPEED_X) as i32, (y * SCROLL_SPEED_Y) as i32);
}
PointerAction::Zoom(delta) => {
if !delta.is_normal() {
return;
}
let flags = CONTROL_DOWN | PRECISION_SCROLLING_DELTA;
host.send_mouse_wheel_event(Some(&mouse_event(position, flags)), 0, (delta * PINCH_ZOOM_SPEED).round() as i32);
}
}
}
WindowEvent::MouseWheel { delta, phase: _, device_id: _, .. } => {
let (mut delta_x, mut delta_y) = match delta {
MouseScrollDelta::LineDelta(x, y) => (x * SCROLL_LINE_WIDTH as f32, y * SCROLL_LINE_HEIGHT as f32),
MouseScrollDelta::PixelDelta(physical_position) => (physical_position.x as f32, physical_position.y as f32),
};
delta_x *= SCROLL_SPEED_X;
delta_y *= SCROLL_SPEED_Y;
InputEventKind::Key {
key,
key_without_modifiers,
physical_key,
location,
text,
action,
} => {
let mut flags = event_flags(&event.modifiers, &state.buttons);
vec![InputEvent::MouseWheel {
data: input_state.mouse_data(),
delta_x: delta_x as i32,
delta_y: delta_y as i32,
}]
}
WindowEvent::ModifiersChanged(modifiers) => {
input_state.modifiers_changed(&modifiers.state());
Vec::new()
}
WindowEvent::KeyboardInput { device_id: _, event, is_synthetic: _ } => {
input_state.modifiers_apply_key_event(&event.logical_key, &event.state);
let mut kind = match (event.state, &event.logical_key) {
(ElementState::Pressed, winit::keyboard::Key::Character(_)) => KeyEventKind::Char,
(ElementState::Pressed, _) => KeyEventKind::RawKeyDown,
(ElementState::Released, _) => KeyEventKind::KeyUp,
};
let modifiers = input_state.cef_modifiers(&event.location, event.repeat).into();
let 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(),
let own_flag = match key {
Key::Named(NamedKey::Shift) => SHIFT_DOWN,
Key::Named(NamedKey::Control) => CONTROL_DOWN,
Key::Named(NamedKey::Alt) => ALT_DOWN,
Key::Named(NamedKey::AltGraph) => ALTGR_DOWN,
Key::Named(NamedKey::Meta) => COMMAND_DOWN,
_ => 0,
};
match action {
KeyAction::Press | KeyAction::Repeat => flags |= own_flag,
KeyAction::Release => flags &= !own_flag,
}
let native_key_code = event.physical_key.to_native_keycode();
flags |= match location {
KeyLocation::Left => IS_LEFT,
KeyLocation::Right => IS_RIGHT,
KeyLocation::Numpad => IS_KEY_PAD,
KeyLocation::Standard => 0,
};
if *action == KeyAction::Repeat {
flags |= IS_REPEAT;
}
let char_representation = event.logical_key.to_char_representation();
let windows_key_code = match key {
Key::Named(named) => named.to_vk_bits(),
Key::Character(char) => char.chars().next().unwrap_or_default().to_vk_bits(),
_ => 0,
};
let native_key_code = physical_key.to_native_keycode();
let char_representation = key.to_char_representation();
#[allow(unused_mut)]
let mut character = char_representation as u16;
if event.state == ElementState::Pressed && character != 0 {
kind = KeyEventKind::Char;
}
let unmodified_character = event.key_without_modifiers.to_char_representation() as u16;
let unmodified_character = 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 character == 0 && unmodified_character == 0 && event.text_with_all_modifiers.is_some() {
if character == 0 && unmodified_character == 0 && text.is_some() {
character = 1;
}
#[cfg(not(target_os = "macos"))]
let _ = text;
let key = KeyData {
kind,
modifiers,
let key_event = |kind: cef_key_event_type_t, windows_key_code: i32| KeyEvent {
type_: kind.into(),
modifiers: flags,
windows_key_code,
native_key_code,
character,
unmodified_character,
};
if kind == KeyEventKind::Char {
// CEF expects a raw key-down before the character event it produces.
vec![
InputEvent::Key(KeyData {
kind: KeyEventKind::RawKeyDown,
..key
}),
InputEvent::Key(KeyData {
windows_key_code: char_representation as i32,
..key
}),
]
} else {
vec![InputEvent::Key(key)]
}
}
WindowEvent::PinchGesture { delta, .. } => {
if !delta.is_normal() {
return Vec::new();
}
let data = MouseData {
modifiers: CefModifiers::PINCH_MODIFIERS.into(),
..input_state.mouse_data()
};
vec![InputEvent::MouseWheel {
data,
delta_x: 0,
delta_y: (delta * PINCH_ZOOM_SPEED).round() as i32,
}]
}
_ => Vec::new(),
}
}
/// Sends a translated [`InputEvent`] to the browser. Must run on the thread owning the browser.
pub(crate) fn apply(browser: &Browser, event: &InputEvent) {
let Some(host) = browser.host() else { return };
match event {
InputEvent::MouseMove { data, leave } => {
host.send_mouse_move_event(Some(&data.into()), *leave as i32);
}
InputEvent::MouseClick { data, button, up, click_count } => {
let cef_button = cef::MouseButtonType::from(match button {
MouseButtonKind::Left => cef_mouse_button_type_t::MBT_LEFT,
MouseButtonKind::Right => cef_mouse_button_type_t::MBT_RIGHT,
MouseButtonKind::Middle => cef_mouse_button_type_t::MBT_MIDDLE,
});
host.send_mouse_click_event(Some(&data.into()), cef_button, *up as i32, *click_count);
}
InputEvent::MouseWheel { data, delta_x, delta_y } => {
host.send_mouse_wheel_event(Some(&data.into()), *delta_x, *delta_y);
}
InputEvent::Key(key) => {
let key_event = KeyEvent {
type_: match key.kind {
KeyEventKind::RawKeyDown => cef_key_event_type_t::KEYEVENT_RAWKEYDOWN,
KeyEventKind::KeyUp => cef_key_event_type_t::KEYEVENT_KEYUP,
KeyEventKind::Char => cef_key_event_type_t::KEYEVENT_CHAR,
}
.into(),
modifiers: key.modifiers,
windows_key_code: key.windows_key_code,
native_key_code: key.native_key_code,
character: key.character,
unmodified_character: key.unmodified_character,
..Default::default()
};
host.send_key_event(Some(&key_event));
match action {
KeyAction::Press | KeyAction::Repeat if char_representation != '\0' => {
host.send_key_event(Some(&key_event(cef_key_event_type_t::KEYEVENT_RAWKEYDOWN, windows_key_code)));
host.send_key_event(Some(&key_event(cef_key_event_type_t::KEYEVENT_CHAR, char_representation as i32)));
}
KeyAction::Press | KeyAction::Repeat => {
host.send_key_event(Some(&key_event(cef_key_event_type_t::KEYEVENT_RAWKEYDOWN, windows_key_code)));
}
KeyAction::Release => {
host.send_key_event(Some(&key_event(cef_key_event_type_t::KEYEVENT_KEYUP, windows_key_code)));
}
}
}
}
}
impl From<&MouseData> for MouseEvent {
fn from(data: &MouseData) -> Self {
MouseEvent {
x: data.x,
y: data.y,
modifiers: data.modifiers,
#[derive(Default)]
struct ButtonStates {
left: bool,
right: bool,
middle: bool,
}
impl ButtonStates {
fn update(&mut self, button: MouseButton, held: bool) {
match button {
MouseButton::Left => self.left = held,
MouseButton::Right => self.right = held,
MouseButton::Middle => self.middle = held,
MouseButton::Unknown => {}
}
}
}
fn mouse_event(position: PhysicalPosition<f64>, flags: u32) -> MouseEvent {
MouseEvent {
x: position.x as i32,
y: position.y as i32,
modifiers: flags,
}
}
fn event_flags(modifiers: &Modifiers, buttons: &ButtonStates) -> u32 {
let bit = |condition: bool, flag: u32| if condition { flag } else { 0 };
bit(modifiers.shift, SHIFT_DOWN)
| bit(modifiers.control, CONTROL_DOWN)
| bit(modifiers.alt, ALT_DOWN)
| bit(modifiers.alt_graph, ALTGR_DOWN)
| bit(modifiers.meta, COMMAND_DOWN)
| bit(modifiers.caps_lock, CAPS_LOCK_ON)
| bit(modifiers.num_lock, NUM_LOCK_ON)
| bit(buttons.left, LEFT_MOUSE_BUTTON)
| bit(buttons.right, RIGHT_MOUSE_BUTTON)
| bit(buttons.middle, MIDDLE_MOUSE_BUTTON)
}
const fn flag(flag: cef_event_flags_t) -> u32 {
#[cfg(not(target_os = "windows"))]
{
flag.0
}
#[cfg(target_os = "windows")]
{
flag.0 as u32
}
}
const SHIFT_DOWN: u32 = flag(cef_event_flags_t::EVENTFLAG_SHIFT_DOWN);
const CONTROL_DOWN: u32 = flag(cef_event_flags_t::EVENTFLAG_CONTROL_DOWN);
const ALT_DOWN: u32 = flag(cef_event_flags_t::EVENTFLAG_ALT_DOWN);
const ALTGR_DOWN: u32 = flag(cef_event_flags_t::EVENTFLAG_ALTGR_DOWN);
const COMMAND_DOWN: u32 = flag(cef_event_flags_t::EVENTFLAG_COMMAND_DOWN);
const CAPS_LOCK_ON: u32 = flag(cef_event_flags_t::EVENTFLAG_CAPS_LOCK_ON);
const NUM_LOCK_ON: u32 = flag(cef_event_flags_t::EVENTFLAG_NUM_LOCK_ON);
const LEFT_MOUSE_BUTTON: u32 = flag(cef_event_flags_t::EVENTFLAG_LEFT_MOUSE_BUTTON);
const MIDDLE_MOUSE_BUTTON: u32 = flag(cef_event_flags_t::EVENTFLAG_MIDDLE_MOUSE_BUTTON);
const RIGHT_MOUSE_BUTTON: u32 = flag(cef_event_flags_t::EVENTFLAG_RIGHT_MOUSE_BUTTON);
const IS_LEFT: u32 = flag(cef_event_flags_t::EVENTFLAG_IS_LEFT);
const IS_RIGHT: u32 = flag(cef_event_flags_t::EVENTFLAG_IS_RIGHT);
const IS_KEY_PAD: u32 = flag(cef_event_flags_t::EVENTFLAG_IS_KEY_PAD);
const IS_REPEAT: u32 = flag(cef_event_flags_t::EVENTFLAG_IS_REPEAT);
const PRECISION_SCROLLING_DELTA: u32 = flag(cef_event_flags_t::EVENTFLAG_PRECISION_SCROLLING_DELTA);
+245
View File
@@ -0,0 +1,245 @@
use winit::dpi::PhysicalPosition;
use winit::event::{ElementState, KeyEvent};
use winit::keyboard::{Key, KeyLocation, ModifiersState, PhysicalKey, SmolStr};
#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
pub struct InputEvent {
pub(crate) kind: InputEventKind,
pub(crate) modifiers: Modifiers,
}
impl InputEvent {
pub fn pointer() -> PointerInputEventBuilder {
PointerInputEventBuilder { position: UnknownPosition }
}
pub fn key(event: &KeyEvent) -> InputEventBuilder {
let action = match (event.state, event.repeat) {
(ElementState::Pressed, false) => KeyAction::Press,
(ElementState::Pressed, true) => KeyAction::Repeat,
(ElementState::Released, _) => KeyAction::Release,
};
InputEventBuilder::new(InputEventKind::Key {
key: event.logical_key.clone(),
key_without_modifiers: event.key_without_modifiers.clone(),
physical_key: event.physical_key,
location: event.location,
text: event.text_with_all_modifiers.clone(),
action,
})
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
pub enum MouseButton {
Left,
Right,
Middle,
Unknown,
}
impl From<winit::event::MouseButton> for MouseButton {
fn from(button: winit::event::MouseButton) -> Self {
match button {
winit::event::MouseButton::Left => Self::Left,
winit::event::MouseButton::Right => Self::Right,
winit::event::MouseButton::Middle => Self::Middle,
_ => Self::Unknown,
}
}
}
impl From<winit::event::ButtonSource> for MouseButton {
fn from(button: winit::event::ButtonSource) -> Self {
button.mouse_button().map_or(Self::Unknown, Self::from)
}
}
#[must_use]
#[derive(Clone, Debug)]
pub struct PointerInputEventBuilder<P = UnknownPosition> {
position: P,
}
impl PointerInputEventBuilder<UnknownPosition> {
pub fn position(self, position: PhysicalPosition<f64>) -> PointerInputEventBuilder<PhysicalPosition<f64>> {
PointerInputEventBuilder { position }
}
}
impl PointerInputEventBuilder<PhysicalPosition<f64>> {
pub fn moved(self) -> InputEventBuilder {
self.finish(PointerAction::Move)
}
pub fn entered(self) -> InputEventBuilder {
self.finish(PointerAction::Enter)
}
}
impl<P: PointerPosition> PointerInputEventBuilder<P> {
pub fn exited(self) -> InputEventBuilder {
self.finish(PointerAction::Exit)
}
pub fn pressed(self, button: impl Into<MouseButton>, count: u32) -> InputEventBuilder {
self.finish(PointerAction::Press { button: button.into(), count })
}
pub fn released(self, button: impl Into<MouseButton>, count: u32) -> InputEventBuilder {
self.finish(PointerAction::Release { button: button.into(), count })
}
pub fn scrolled_lines(self, x: f64, y: f64) -> InputEventBuilder {
self.finish(PointerAction::ScrollLines { x, y })
}
pub fn scrolled_pixels(self, x: f64, y: f64) -> InputEventBuilder {
self.finish(PointerAction::ScrollPixels { x, y })
}
pub fn zoomed(self, delta: f64) -> InputEventBuilder {
self.finish(PointerAction::Zoom(delta))
}
fn finish(self, action: PointerAction) -> InputEventBuilder {
InputEventBuilder::new(InputEventKind::Pointer {
position: self.position.position(),
action,
})
}
}
#[must_use]
#[derive(Clone, Debug)]
pub struct InputEventBuilder {
kind: InputEventKind,
modifiers: Modifiers,
}
impl InputEventBuilder {
fn new(kind: InputEventKind) -> Self {
Self {
kind,
modifiers: Modifiers::default(),
}
}
pub fn shift(mut self, on: bool) -> Self {
self.modifiers.shift = on;
self
}
pub fn control(mut self, on: bool) -> Self {
self.modifiers.control = on;
self
}
pub fn alt(mut self, on: bool) -> Self {
self.modifiers.alt = on;
self
}
pub fn alt_graph(mut self, on: bool) -> Self {
self.modifiers.alt_graph = on;
self
}
pub fn meta(mut self, on: bool) -> Self {
self.modifiers.meta = on;
self
}
pub fn caps_lock(mut self, on: bool) -> Self {
self.modifiers.caps_lock = on;
self
}
pub fn num_lock(mut self, on: bool) -> Self {
self.modifiers.num_lock = on;
self
}
pub fn modifiers(mut self, modifiers: ModifiersState) -> Self {
self.modifiers.shift = modifiers.shift_key();
self.modifiers.control = modifiers.control_key();
self.modifiers.alt = modifiers.alt_key();
self.modifiers.meta = modifiers.meta_key();
self
}
pub fn build(self) -> InputEvent {
InputEvent {
kind: self.kind,
modifiers: self.modifiers,
}
}
}
#[expect(private_bounds)]
pub trait PointerPosition: OptionalPosition {}
impl PointerPosition for UnknownPosition {}
impl PointerPosition for PhysicalPosition<f64> {}
#[derive(Clone, Copy, Debug)]
pub struct UnknownPosition;
trait OptionalPosition {
fn position(self) -> Option<PhysicalPosition<f64>>;
}
impl OptionalPosition for UnknownPosition {
fn position(self) -> Option<PhysicalPosition<f64>> {
None
}
}
impl OptionalPosition for PhysicalPosition<f64> {
fn position(self) -> Option<PhysicalPosition<f64>> {
Some(self)
}
}
#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
pub(crate) enum InputEventKind {
Pointer {
position: Option<PhysicalPosition<f64>>,
action: PointerAction,
},
Key {
key: Key,
key_without_modifiers: Key,
physical_key: PhysicalKey,
location: KeyLocation,
text: Option<SmolStr>,
action: KeyAction,
},
}
#[derive(Clone, Copy, Debug, serde::Serialize, serde::Deserialize)]
pub(crate) enum PointerAction {
Move,
Enter,
Exit,
Press { button: MouseButton, count: u32 },
Release { button: MouseButton, count: u32 },
ScrollLines { x: f64, y: f64 },
ScrollPixels { x: f64, y: f64 },
Zoom(f64),
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
pub(crate) enum KeyAction {
Press,
Repeat,
Release,
}
#[derive(Clone, Copy, Debug, Default, serde::Serialize, serde::Deserialize)]
pub(crate) struct Modifiers {
pub(crate) shift: bool,
pub(crate) control: bool,
pub(crate) alt: bool,
pub(crate) alt_graph: bool,
pub(crate) meta: bool,
pub(crate) caps_lock: bool,
pub(crate) num_lock: bool,
}
-256
View File
@@ -1,256 +0,0 @@
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;
}
}
+5 -15
View File
@@ -22,6 +22,9 @@ mod resources;
mod utility;
mod view;
pub use consts::{MULTICLICK_ALLOWED_TRAVEL, MULTICLICK_TIMEOUT, PINCH_ZOOM_SPEED, SCROLL_LINE_HEIGHT, SCROLL_LINE_WIDTH, SCROLL_SPEED_X, SCROLL_SPEED_Y};
pub use input::event::{InputEvent, InputEventBuilder, MouseButton, PointerInputEventBuilder, PointerPosition, UnknownPosition};
pub struct UiContext<S: Stage = Started> {
inner: S::ContextData,
}
@@ -67,7 +70,6 @@ impl UiContext<Started> {
Ok(UiInstance {
inner: Arc::new(UiInstanceInner {
host: self.inner.clone(),
input: Mutex::new(input::InputState::default()),
events: Mutex::new(events),
queue,
shutdown_complete: Mutex::new(shutdown_complete),
@@ -141,7 +143,6 @@ pub struct UiInstance {
pub(crate) struct UiInstanceInner {
host: Arc<HostHandle>,
input: Mutex<input::InputState>,
events: Mutex<Receiver<UiEvent>>,
queue: events::EventQueue,
shutdown_complete: Mutex<Receiver<()>>,
@@ -152,18 +153,7 @@ impl UiInstance {
pub fn send(&self, command: UiCommand) {
let shared = &self.inner;
match command {
UiCommand::Input(event) => {
let events = {
let Ok(mut input) = shared.input.lock() else {
tracing::error!("Failed to lock the input state");
return;
};
input::translate(&mut input, &event)
};
if !events.is_empty() {
shared.host.send(HostControlMessage::Input(events));
}
}
UiCommand::Input(event) => shared.host.send(HostControlMessage::Input(event)),
UiCommand::Resized { width, height } => shared.host.send(HostControlMessage::UpdateViewInfo(view::ViewInfoUpdate::Size { width, height })),
UiCommand::ScaleChanged(scale) => shared.host.send(HostControlMessage::UpdateViewInfo(view::ViewInfoUpdate::Scale(scale))),
UiCommand::Refresh => shared.host.send(HostControlMessage::RefreshViewInfo),
@@ -220,7 +210,7 @@ impl Drop for UiInstanceInner {
#[derive(Debug)]
pub enum UiCommand {
Input(winit::event::WindowEvent),
Input(InputEvent),
Resized { width: u32, height: u32 },
ScaleChanged(f64),
Refresh,
+1 -1
View File
@@ -99,7 +99,7 @@ enum ControlOutcome {
fn control_loop(receiver: &IpcReceiver<HostControlMessage>, context: &CefContextHandle, sequence: &SequenceState) -> ControlOutcome {
loop {
match receiver.recv() {
Ok(HostControlMessage::Input(events)) => context.apply_input(events),
Ok(HostControlMessage::Input(event)) => context.process_input(event),
Ok(HostControlMessage::UpdateViewInfo(update)) => context.update_view_info(update),
Ok(HostControlMessage::RefreshViewInfo) => context.refresh_view_info(),
Ok(HostControlMessage::SendWebMessage(message)) => context.send_web_message(message),
+1 -1
View File
@@ -8,7 +8,7 @@ use crate::view::ViewInfoUpdate;
#[derive(Serialize, Deserialize)]
pub(crate) enum HostControlMessage {
Input(Vec<InputEvent>),
Input(InputEvent),
UpdateViewInfo(ViewInfoUpdate),
RefreshViewInfo,
SendWebMessage(Vec<u8>),
+1 -1
View File
@@ -3,7 +3,7 @@ name = "graphite-desktop-wrapper"
version = "0.1.0"
description = "Graphite Desktop Wrapper"
authors = ["Graphite Authors <contact@graphite.art>"]
license = "Apache-2.0"
license = "MIT OR Apache-2.0"
repository = ""
edition = "2024"
rust-version = "1.87"
@@ -3,25 +3,25 @@ use graphite_editor::messages::layout::utility_types::layout_widget::LayoutTarge
use graphite_editor::messages::prelude::FrontendMessage;
use super::DesktopWrapperMessageDispatcher;
use super::messages::{DesktopFrontendMessage, FileFilter, OpenFileDialogContext, SaveFileDialogContext};
use super::messages::{DesktopFrontendMessage, OpenFileDialogContext, SaveFileDialogContext};
pub(super) fn intercept_frontend_message(dispatcher: &mut DesktopWrapperMessageDispatcher, message: FrontendMessage) -> Option<FrontendMessage> {
match message {
FrontendMessage::RenderOverlays { context } => {
dispatcher.respond(DesktopFrontendMessage::UpdateOverlays(context.take_scene()));
}
FrontendMessage::TriggerOpen => {
FrontendMessage::TriggerOpen { filters } => {
dispatcher.respond(DesktopFrontendMessage::OpenFileDialog {
title: "Open Document".to_string(),
filters: vec![],
filters,
multiple: true,
context: OpenFileDialogContext::Open,
});
}
FrontendMessage::TriggerImport => {
FrontendMessage::TriggerImport { filters } => {
dispatcher.respond(DesktopFrontendMessage::OpenFileDialog {
title: "Import File".to_string(),
filters: vec![],
filters,
multiple: false,
context: OpenFileDialogContext::Import,
});
@@ -31,32 +31,29 @@ pub(super) fn intercept_frontend_message(dispatcher: &mut DesktopWrapperMessageD
name,
path,
folder,
filters,
content,
} => {
let content = content.into_vec();
if let Some(path) = path {
dispatcher.respond(DesktopFrontendMessage::WriteFile { path, content });
} else {
let extension = std::path::Path::new(&name).extension().and_then(|extension| extension.to_str()).unwrap_or("*").to_string();
dispatcher.respond(DesktopFrontendMessage::SaveFileDialog {
title: "Save Document".to_string(),
default_filename: name,
default_folder: folder,
filters: vec![FileFilter {
name: "Graphite Document".to_string(),
extensions: vec![extension],
}],
filters,
context: SaveFileDialogContext::Document { document_id, content },
});
}
}
FrontendMessage::TriggerSaveFile { name, folder, content } => {
FrontendMessage::TriggerSaveFile { name, folder, filters, content } => {
let content = content.into_vec();
dispatcher.respond(DesktopFrontendMessage::SaveFileDialog {
title: "Save File".to_string(),
default_filename: name,
default_folder: folder,
filters: Vec::new(),
filters,
context: SaveFileDialogContext::File { content },
});
}
@@ -125,6 +122,9 @@ pub(super) fn intercept_frontend_message(dispatcher: &mut DesktopWrapperMessageD
FrontendMessage::WindowPointerLock => {
dispatcher.respond(DesktopFrontendMessage::PointerLock);
}
FrontendMessage::WindowUpdateDirectInput { enabled } => {
dispatcher.respond(DesktopFrontendMessage::WindowUpdateDirectInput { enabled });
}
FrontendMessage::WindowClose => {
dispatcher.respond(DesktopFrontendMessage::WindowClose);
}
+3 -2
View File
@@ -10,6 +10,7 @@ use std::sync::Arc;
pub use graph_craft::application_io::resource::MmapResourceStorage;
pub use graphite_editor::consts::{DOUBLE_CLICK_MILLISECONDS, FILE_EXTENSION};
pub use wgpu_executor::Texture;
pub use wgpu_executor::WgpuBackends;
pub use wgpu_executor::WgpuContext;
pub use wgpu_executor::WgpuContextBuilder;
@@ -59,14 +60,14 @@ impl DesktopWrapper {
pub async fn execute_node_graph() -> NodeGraphExecutionResult {
let result = graphite_editor::node_graph_executor::run_node_graph().await;
match result {
(true, texture) => NodeGraphExecutionResult::HasRun(texture.map(Into::into)),
(true, texture) => NodeGraphExecutionResult::HasRun(texture),
(false, _) => NodeGraphExecutionResult::NotRun,
}
}
}
pub enum NodeGraphExecutionResult {
HasRun(Option<std::sync::Arc<wgpu::Texture>>),
HasRun(Option<Texture>),
NotRun,
}
+6 -8
View File
@@ -3,9 +3,9 @@ use std::path::PathBuf;
pub(crate) use graphite_editor::messages::prelude::Message as EditorMessage;
pub use graphite_editor::messages::frontend::utility_types::{DocumentInfo, PersistedState};
pub use graphite_editor::messages::input_mapper::utility_types::input_keyboard::{Key, ModifierKeys};
pub use graphite_editor::messages::input_mapper::utility_types::input_mouse::{EditorMouseState as MouseState, EditorPosition as Position, MouseKeys};
pub use graphite_editor::messages::frontend::utility_types::{DocumentInfo, FileFilter, PersistedState};
pub use graphite_editor::messages::input_mapper::utility_types::keyboard::{Key, ModifierKeys};
pub use graphite_editor::messages::input_mapper::utility_types::pointer::{EditorPointerState, MouseKeys, ScrollDelta};
pub use graphite_editor::messages::prelude::DocumentId;
pub use graphite_editor::messages::prelude::InputPreprocessorMessage as InputMessage;
pub use graphite_editor::messages::prelude::PreferencesMessageHandler as Preferences;
@@ -40,6 +40,9 @@ pub enum DesktopFrontendMessage {
UpdateUIScale {
scale: f64,
},
WindowUpdateDirectInput {
enabled: bool,
},
UpdateOverlays(vello::Scene),
PersistenceWriteDocument {
id: DocumentId,
@@ -100,11 +103,6 @@ pub enum DesktopWrapperMessage {
LoadThirdPartyLicenses { text: String },
}
pub struct FileFilter {
pub name: String,
pub extensions: Vec<String>,
}
#[derive(Clone, Copy)]
pub enum OpenFileDialogContext {
Open,
+1 -1
View File
@@ -3,7 +3,7 @@ pub(crate) mod menu {
use base64::engine::Engine;
use base64::engine::general_purpose::STANDARD as BASE64;
use graphite_editor::messages::input_mapper::utility_types::input_keyboard::{Key, LabeledKeyOrMouseMotion, LabeledShortcut};
use graphite_editor::messages::input_mapper::utility_types::keyboard::{Key, LabeledKeyOrMouseMotion, LabeledShortcut};
use graphite_editor::messages::input_mapper::utility_types::misc::ActionShortcut;
use graphite_editor::messages::layout::LayoutMessage;
use graphite_editor::messages::tool::tool_messages::tool_prelude::{Layout, LayoutGroup, LayoutTarget, MenuListEntry, Widget, WidgetId, WidgetRow};