Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
63 changes: 51 additions & 12 deletions desktop/src/app.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ use crate::preferences;
use crate::render::{RenderError, RenderState};
use crate::ui::{UiCommand, UiInstance};
use crate::window::Window;
use crate::wrapper::messages::{DesktopFrontendMessage, DesktopWrapperMessage, Preferences};
use crate::wrapper::messages::{DesktopFrontendMessage, DesktopWrapperMessage, InputMessage, Key, ModifierKeys, Preferences};
use crate::wrapper::{DesktopWrapper, MmapResourceStorage, NodeGraphExecutionResult, WgpuContext, serialize_frontend_messages};

pub(crate) struct App {
Expand Down Expand Up @@ -349,6 +349,9 @@ impl App {
window.start_pointer_lock();
}
}
DesktopFrontendMessage::PointerUnlock => {
self.unlock_pointer();
}
DesktopFrontendMessage::WindowClose => {
self.app_event_scheduler.schedule(AppEvent::Exit);
}
Expand Down Expand Up @@ -506,6 +509,40 @@ impl App {
}
}
}

fn unlock_pointer(&mut self) {
if let Some(pos) = self.input_state.unlock_pointer()
&& let Some(window) = &self.window
{
window.end_pointer_lock();
self.ui.send(UiCommand::Input(WindowEvent::PointerMoved {
device_id: None,
position: pos,
primary: true,
source: winit::event::PointerSource::Mouse,
}));
} else if let Some(window) = &self.window {
window.end_pointer_lock();
}
}

/// Synthesize an Escape press/release so the editor cancels the active transform (same as a user pressing Escape)
fn send_cancel_escape(&mut self) {
for message in [
InputMessage::KeyDown {
key: Key::Escape,
key_repeat: false,
modifier_keys: ModifierKeys::empty(),
},
InputMessage::KeyUp {
key: Key::Escape,
key_repeat: false,
modifier_keys: ModifierKeys::empty(),
},
] {
self.app_event_scheduler.schedule(AppEvent::DesktopWrapperMessage(DesktopWrapperMessage::Input(message)));
}
}
}
impl ApplicationHandler for App {
fn can_create_surfaces(&mut self, event_loop: &dyn ActiveEventLoop) {
Expand Down Expand Up @@ -536,23 +573,22 @@ impl ApplicationHandler for App {
}

fn window_event(&mut self, _event_loop: &dyn ActiveEventLoop, _window_id: WindowId, event: WindowEvent) {
// Handle pointer lock release
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.input_state.pointer_locked()
{
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.unlock_pointer();
}

// The editor can no longer track the pointer, so cancel any pointer-locked operation (G/R/S) and release the grab
if let WindowEvent::Focused(false) = &event
&& self.input_state.pointer_locked()
{
self.unlock_pointer();
self.send_cancel_escape();
}

for action in self.input_state.process(&event) {
Expand Down Expand Up @@ -642,6 +678,9 @@ impl ApplicationHandler for App {
if self.input_state.pointer_locked()
&& let winit::event::DeviceEvent::PointerMotion { delta: (x, y) } = event
{
// DeviceEvent deltas are physical pixels
let scale = self.input_state.viewport_scale();
let (x, y) = if scale != 0. { (x / scale, y / scale) } else { (x, y) };
let message = DesktopWrapperMessage::PointerLockMove { x, y };
self.app_event_scheduler.schedule(AppEvent::DesktopWrapperMessage(message));
}
Expand Down
4 changes: 4 additions & 0 deletions desktop/src/input.rs
Original file line number Diff line number Diff line change
Expand Up @@ -187,6 +187,10 @@ impl InputState {
self.viewport_info.as_ref().map_or(1., |info| info.scale)
}

pub(crate) fn viewport_scale(&self) -> f64 {
self.scale()
}

fn in_viewport(&self, position: PhysicalPosition<f64>) -> bool {
self.viewport_info.as_ref().is_some_and(|info| info.contains(position))
}
Expand Down
3 changes: 3 additions & 0 deletions desktop/wrapper/src/intercept_frontend_message.rs
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,9 @@ pub(super) fn intercept_frontend_message(dispatcher: &mut DesktopWrapperMessageD
FrontendMessage::WindowPointerLock => {
dispatcher.respond(DesktopFrontendMessage::PointerLock);
}
FrontendMessage::WindowPointerUnlock => {
dispatcher.respond(DesktopFrontendMessage::PointerUnlock);
}
FrontendMessage::WindowClose => {
dispatcher.respond(DesktopFrontendMessage::WindowClose);
}
Expand Down
1 change: 1 addition & 0 deletions desktop/wrapper/src/messages.rs
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ pub enum DesktopFrontendMessage {
content: String,
},
PointerLock,
PointerUnlock,
WindowClose,
WindowMinimize,
WindowMaximize,
Expand Down
1 change: 1 addition & 0 deletions editor/src/messages/app_window/app_window_message.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ use crate::messages::prelude::*;
#[derive(PartialEq, Clone, Debug, serde::Serialize, serde::Deserialize)]
pub enum AppWindowMessage {
PointerLock,
PointerUnlock,
PointerLockMove { x: f64, y: f64 },
Restart,
Close,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,14 @@ impl MessageHandler<AppWindowMessage, ()> for AppWindowMessageHandler {
#[cfg(not(target_family = "wasm"))]
responses.add(FrontendMessage::WindowPointerLock);
}
AppWindowMessage::PointerUnlock => {
#[cfg(not(target_family = "wasm"))]
responses.add(FrontendMessage::WindowPointerUnlock);
}
AppWindowMessage::PointerLockMove { x, y } => {
responses.add(FrontendMessage::WindowPointerLockMove { position: (x, y) });
// Keep G/R/S dragging moving while the pointer is locked
responses.add(InputPreprocessorMessage::RelativePointerMove { delta: glam::DVec2::new(x, y) });
}
AppWindowMessage::Close => {
#[cfg(not(target_family = "wasm"))]
Expand Down
7 changes: 7 additions & 0 deletions editor/src/messages/frontend/frontend_message.rs
Original file line number Diff line number Diff line change
Expand Up @@ -344,6 +344,13 @@ pub enum FrontendMessage {
position: (f64, f64),
},
#[cfg(not(target_family = "wasm"))]
WindowPointerUnlock,
UpdateSoftwareCursor {
visible: bool,
x: f64,
y: f64,
},
#[cfg(not(target_family = "wasm"))]
WindowClose,
#[cfg(not(target_family = "wasm"))]
WindowMinimize,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
use crate::messages::input_mapper::utility_types::keyboard::{Key, ModifierKeys};
use crate::messages::input_mapper::utility_types::pointer::EditorPointerState;
use crate::messages::prelude::*;
use glam::DVec2;

#[impl_message(Message, InputPreprocessor)]
#[derive(PartialEq, Clone, Debug, serde::Serialize, serde::Deserialize)]
Expand All @@ -14,4 +15,5 @@ pub enum InputPreprocessorMessage {
PointerShake { editor_mouse_state: EditorPointerState, modifier_keys: ModifierKeys },
CurrentTime { timestamp: u64 },
WheelScroll { editor_mouse_state: EditorPointerState, modifier_keys: ModifierKeys },
RelativePointerMove { delta: DVec2 },
}
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,11 @@ impl<'a> MessageHandler<InputPreprocessorMessage, InputPreprocessorMessageContex

responses.add(InputMapperMessage::WheelScroll);
}
InputPreprocessorMessage::RelativePointerMove { delta } => {
self.mouse.position += delta;

responses.add(InputMapperMessage::PointerMove);
}
};
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
use crate::consts::{ANGLE_MEASURE_RADIUS_FACTOR, ARC_MEASURE_RADIUS_FACTOR_RANGE, COLOR_OVERLAY_BLUE, COLOR_OVERLAY_GRAY, SLOWING_DIVISOR};
use crate::messages::frontend::utility_types::MouseCursorIcon;
use crate::messages::input_mapper::utility_types::pointer::{DocumentPosition, ViewportPosition};
use crate::messages::portfolio::document::overlays::utility_functions::text_width;
use crate::messages::portfolio::document::overlays::utility_types::{OverlayProvider, Pivot};
Expand All @@ -12,6 +13,7 @@ use crate::messages::tool::common_functionality::shapes::shape_utility::format_r
use crate::messages::tool::tool_messages::select_tool;
use crate::messages::tool::tool_messages::tool_prelude::Key;
use crate::messages::tool::utility_types::{ToolData, ToolType};
use crate::messages::viewport::Position;
use glam::{DAffine2, DVec2};
use graphene_std::renderer::Quad;
use graphene_std::vector::click_target::ClickTargetType;
Expand Down Expand Up @@ -95,6 +97,10 @@ pub struct TransformLayerMessageHandler {

// Path tool (ghost outlines showing pre-transform geometry)
ghost_outline: Vec<(Vec<ClickTargetType>, DAffine2)>,

// Software cursor for wrap-around (visible fake cursor on Wayland/Web, OS warp on X11)
software_cursor_active: bool,
software_cursor_pos: ViewportPosition,
}

#[message_handler_data]
Expand Down Expand Up @@ -339,6 +345,7 @@ impl MessageHandler<TransformLayerMessage, TransformLayerMessageContext<'_>> for
responses.add(OverlaysMessage::RemoveProvider {
provider: TRANSFORM_GRS_OVERLAY_PROVIDER,
});
self.disable_software_cursor(responses);
}
}
TransformLayerMessage::BeginTransformOperation { operation } => {
Expand Down Expand Up @@ -384,6 +391,7 @@ impl MessageHandler<TransformLayerMessage, TransformLayerMessageContext<'_>> for
responses.add(OverlaysMessage::AddProvider {
provider: TRANSFORM_GRS_OVERLAY_PROVIDER,
});
self.enable_software_cursor(responses, input.mouse.position);
// Find a way better than this hack
responses.add(TransformLayerMessage::PointerMove {
slow_key: SLOW_KEY,
Expand Down Expand Up @@ -471,6 +479,7 @@ impl MessageHandler<TransformLayerMessage, TransformLayerMessageContext<'_>> for
responses.add(OverlaysMessage::AddProvider {
provider: TRANSFORM_GRS_OVERLAY_PROVIDER,
});
self.enable_software_cursor(responses, input.mouse.position);
}
responses.add(TransformLayerMessage::BeginTransformOperation { operation: transform_type });
responses.add(TransformLayerMessage::PointerMove {
Expand Down Expand Up @@ -510,6 +519,7 @@ impl MessageHandler<TransformLayerMessage, TransformLayerMessageContext<'_>> for
responses.add(OverlaysMessage::RemoveProvider {
provider: TRANSFORM_GRS_OVERLAY_PROVIDER,
});
self.disable_software_cursor(responses);
}
TransformLayerMessage::ConstrainX => {
self.state.is_transforming_in_local_space = self.transform_operation.constrain_axis(Axis::X, &mut selected, &self.state, document);
Expand Down Expand Up @@ -581,6 +591,26 @@ impl MessageHandler<TransformLayerMessage, TransformLayerMessageContext<'_>> for
};
}

if self.software_cursor_active {
let delta = input.mouse.position - self.mouse_position;
self.software_cursor_pos += delta;

// Wrap around the viewport edges
let size = viewport.size();
if size.x() > 0. && size.y() > 0. {
self.software_cursor_pos = DVec2::new(
((self.software_cursor_pos.x % size.x()) + size.x()) % size.x(),
((self.software_cursor_pos.y % size.y()) + size.y()) % size.y(),
);
}

responses.add(FrontendMessage::UpdateSoftwareCursor {
visible: true,
x: self.software_cursor_pos.x,
y: self.software_cursor_pos.y,
});
}

self.mouse_position = input.mouse.position;
}
TransformLayerMessage::SelectionChanged => {
Expand Down Expand Up @@ -651,6 +681,27 @@ impl TransformLayerMessageHandler {
self.transform_operation.hints(responses, self.state.is_transforming_in_local_space);
}

fn enable_software_cursor(&mut self, responses: &mut VecDeque<Message>, pos: ViewportPosition) {
if self.software_cursor_active {
return;
}
self.software_cursor_active = true;
self.software_cursor_pos = pos;
responses.add(FrontendMessage::UpdateSoftwareCursor { visible: true, x: pos.x, y: pos.y });
responses.add(FrontendMessage::UpdateMouseCursor { cursor: MouseCursorIcon::None });
responses.add(AppWindowMessage::PointerLock);
}

fn disable_software_cursor(&mut self, responses: &mut VecDeque<Message>) {
if !self.software_cursor_active {
return;
}
self.software_cursor_active = false;
responses.add(FrontendMessage::UpdateSoftwareCursor { visible: false, x: 0., y: 0. });
responses.add(FrontendMessage::UpdateMouseCursor { cursor: MouseCursorIcon::Default });
responses.add(AppWindowMessage::PointerUnlock);
}

fn set_ghost_outline(ghost_outline: &mut Vec<(Vec<ClickTargetType>, DAffine2)>, shape_editor: &ShapeState, document: &DocumentMessageHandler) {
ghost_outline.clear();
for &layer in shape_editor.selected_shape_state.keys() {
Expand Down
Loading