Skip to content

Improvements to the layer transform cage UX #589

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 8 commits into from
Apr 21, 2022
Merged
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
1 change: 1 addition & 0 deletions editor/src/input/input_mapper.rs
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ impl Default for Mapping {
entry! {action=SelectToolMessage::PointerMove { axis_align: KeyShift, snap_angle: KeyControl, center: KeyAlt }, message=InputMapperMessage::PointerMove},
entry! {action=SelectToolMessage::DragStart { add_to_selection: KeyShift }, key_down=Lmb},
entry! {action=SelectToolMessage::DragStop, key_up=Lmb},
entry! {action=SelectToolMessage::DragStop, key_down=KeyEnter},
entry! {action=SelectToolMessage::EditLayer, message=InputMapperMessage::DoubleClick},
entry! {action=SelectToolMessage::Abort, key_down=Rmb},
entry! {action=SelectToolMessage::Abort, key_down=KeyEscape},
Expand Down
44 changes: 18 additions & 26 deletions editor/src/input/input_preprocessor_message_handler.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use super::input_preprocessor::{KeyPosition, ModifierKeys};
use super::input_preprocessor::ModifierKeys;
use super::keyboard::{Key, KeyStates};
use super::mouse::{MouseKeys, MouseState, ViewportBounds};
use crate::message_prelude::*;
Expand Down Expand Up @@ -83,9 +83,7 @@ impl MessageHandler<InputPreprocessorMessage, ()> for InputPreprocessorMessageHa
let mouse_state = editor_mouse_state.to_mouse_state(&self.viewport_bounds);
self.mouse.position = mouse_state.position;

if let Some(message) = self.translate_mouse_event(mouse_state, KeyPosition::Pressed) {
responses.push_back(message);
}
self.translate_mouse_event(mouse_state, responses);
}
InputPreprocessorMessage::PointerMove { editor_mouse_state, modifier_keys } => {
self.handle_modifier_keys(modifier_keys, responses);
Expand All @@ -94,16 +92,17 @@ impl MessageHandler<InputPreprocessorMessage, ()> for InputPreprocessorMessageHa
self.mouse.position = mouse_state.position;

responses.push_back(InputMapperMessage::PointerMove.into());

// While any pointer button is already down, additional button down events are not reported, but they are sent as `pointermove` events
self.translate_mouse_event(mouse_state, responses);
}
InputPreprocessorMessage::PointerUp { editor_mouse_state, modifier_keys } => {
self.handle_modifier_keys(modifier_keys, responses);

let mouse_state = editor_mouse_state.to_mouse_state(&self.viewport_bounds);
self.mouse.position = mouse_state.position;

if let Some(message) = self.translate_mouse_event(mouse_state, KeyPosition::Released) {
responses.push_back(message);
}
self.translate_mouse_event(mouse_state, responses);
}
};
}
Expand All @@ -115,27 +114,20 @@ impl MessageHandler<InputPreprocessorMessage, ()> for InputPreprocessorMessageHa
}

impl InputPreprocessorMessageHandler {
fn translate_mouse_event(&mut self, new_state: MouseState, position: KeyPosition) -> Option<Message> {
// Calculate the difference between the two key states (binary xor)
let difference = self.mouse.mouse_keys ^ new_state.mouse_keys;

self.mouse = new_state;

let key = match difference {
MouseKeys::LEFT => Key::Lmb,
MouseKeys::RIGHT => Key::Rmb,
MouseKeys::MIDDLE => Key::Mmb,
MouseKeys::NONE => return None, // self.mouse.mouse_keys was invalid, e.g. when a drag began outside the client
_ => {
log::warn!("The number of buttons modified at the same time was greater than 1. Modification: {:#010b}", difference);
Key::UnknownKey
fn translate_mouse_event(&mut self, new_state: MouseState, responses: &mut VecDeque<Message>) {
for (bit_flag, key) in [(MouseKeys::LEFT, Key::Lmb), (MouseKeys::RIGHT, Key::Rmb), (MouseKeys::MIDDLE, Key::Mmb)] {
// Calculate the intersection between the two key states
let old_down = self.mouse.mouse_keys & bit_flag == bit_flag;
let new_down = new_state.mouse_keys & bit_flag == bit_flag;
if !old_down && new_down {
responses.push_back(InputMapperMessage::KeyDown(key).into());
}
};
if old_down && !new_down {
responses.push_back(InputMapperMessage::KeyUp(key).into());
}
}

Some(match position {
KeyPosition::Pressed => InputMapperMessage::KeyDown(key).into(),
KeyPosition::Released => InputMapperMessage::KeyUp(key).into(),
})
self.mouse = new_state;
}

fn handle_modifier_keys(&mut self, modifier_keys: ModifierKeys, responses: &mut VecDeque<Message>) {
Expand Down
19 changes: 17 additions & 2 deletions editor/src/viewport_tools/tools/select_tool.rs
Original file line number Diff line number Diff line change
Expand Up @@ -254,7 +254,6 @@ impl<'a> MessageHandler<ToolMessage, ToolActionHandlerData<'a>> for SelectTool {

match self.fsm_state {
Ready => actions!(SelectToolMessageDiscriminant; DragStart, PointerMove, EditLayer),
Dragging => actions!(SelectToolMessageDiscriminant; DragStop, PointerMove, EditLayer),
_ => actions!(SelectToolMessageDiscriminant; DragStop, PointerMove, Abort, EditLayer),
}
}
Expand Down Expand Up @@ -588,11 +587,27 @@ impl Fsm for SelectToolFsmState {
);
Ready
}
(Dragging, Abort) => {
data.snap_handler.cleanup(responses);
responses.push_back(DocumentMessage::Undo.into());
Ready
}
(_, Abort) => {
if let Some(path) = data.drag_box_overlay_layer.take() {
responses.push_front(DocumentMessage::Overlays(Operation::DeleteLayer { path }.into()).into())
};
if let Some(bounding_box_overlays) = data.bounding_box_overlays.take() {
if let Some(mut bounding_box_overlays) = data.bounding_box_overlays.take() {
let selected = data.layers_dragging.iter().collect::<Vec<_>>();
let mut selected = Selected::new(
&mut bounding_box_overlays.original_transforms,
&mut bounding_box_overlays.pivot,
&selected,
responses,
&document.graphene_document,
);

selected.revert_operation();

bounding_box_overlays.delete(responses);
}

Expand Down
24 changes: 20 additions & 4 deletions editor/src/viewport_tools/tools/shared/transformation_cage.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ use graphene::color::Color;
use graphene::layers::style::{self, Fill, Stroke};
use graphene::Operation;

use glam::{DAffine2, DVec2, Vec2Swizzles};
use glam::{DAffine2, DVec2};

/// Contains the edges that are being dragged along with the origional bounds
#[derive(Clone, Debug, Default)]
Expand All @@ -18,11 +18,22 @@ pub struct SelectedEdges {
bottom: bool,
left: bool,
right: bool,
// Aspect ratio in the form of width/height, so x:1 = width:height
aspect_ratio: f64,
}

impl SelectedEdges {
pub fn new(top: bool, bottom: bool, left: bool, right: bool, bounds: [DVec2; 2]) -> Self {
Self { top, bottom, left, right, bounds }
let size = (bounds[0] - bounds[1]).abs();
let aspect_ratio = size.x / size.y;
Self {
top,
bottom,
left,
right,
bounds,
aspect_ratio,
}
}

/// Calculate the pivot for the operation (the opposite point to the edge dragged)
Expand Down Expand Up @@ -67,8 +78,13 @@ impl SelectedEdges {
}

let mut size = max - min;
if constrain && ((self.top || self.bottom) && (self.left || self.right)) {
size = size.abs().max(size.abs().yx()) * size.signum();
if constrain {
size = match ((self.top || self.bottom), (self.left || self.right)) {
(true, true) => DVec2::new(size.x, size.x / self.aspect_ratio).abs().max(DVec2::new(size.y * self.aspect_ratio, size.y).abs()) * size.signum(),
(true, false) => DVec2::new(size.y * self.aspect_ratio, size.y),
(false, true) => DVec2::new(size.x, size.x / self.aspect_ratio),
_ => size,
};
}
if center {
if self.left || self.right {
Expand Down
1 change: 1 addition & 0 deletions frontend/src/lifetime/input.ts
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,7 @@ export function createInputManager(editor: EditorState, container: HTMLElement,

// Pointer events

// While any pointer button is already down, additional button down events are not reported, but they are sent as `pointermove` events and these are handled in the backend
const onPointerMove = (e: PointerEvent): void => {
if (!e.buttons) viewportPointerInteractionOngoing = false;

Expand Down