Add draggable skew triangles to the transform cage (#2300)

* Add triangle handles to transform cage for skew transform

Fixes #2299

* Add skew triangles

* Fix conflicts which github didn't show

* cargo fmt

* Fix needed

* remove unreachable

* use the trap and rect logic

* fix quad checks

* cursor fix; no triangles if already dragging and not skewing

* cargo fmt

* Resolve Clippy lints

* Add min length for triangle visibility

* Code review

---------

Co-authored-by: Keavon Chambers <keavon@keavon.com>
This commit is contained in:
mTvare
2025-03-01 10:24:56 +00:00
committed by GitHub
co-authored by Keavon Chambers
parent bc6e76208d
commit 1510ad820c
10 changed files with 304 additions and 51 deletions
@@ -393,7 +393,10 @@ impl Fsm for ArtboardToolFsmState {
}
(ArtboardToolFsmState::Ready { .. }, ArtboardToolMessage::PointerMove { .. }) => {
let mut cursor = tool_data.bounding_box_manager.as_ref().map_or(MouseCursorIcon::Default, |bounds| bounds.get_cursor(input, false));
let mut cursor = tool_data
.bounding_box_manager
.as_ref()
.map_or(MouseCursorIcon::Default, |bounds| bounds.get_cursor(input, false, false, None));
if cursor == MouseCursorIcon::Default && !hovered {
tool_data.snap_manager.preview_draw(&SnapData::new(document, input), input.mouse.position);
@@ -280,7 +280,7 @@ enum SelectToolFsmState {
Drawing { selection_shape: SelectionShapeType },
Dragging { axis: Axis, using_compass: bool },
ResizingBounds,
SkewingBounds,
SkewingBounds { skew: Key },
RotatingBounds,
DraggingPivot,
}
@@ -309,6 +309,7 @@ struct SelectToolData {
cursor: MouseCursorIcon,
pivot: Pivot,
compass_rose: CompassRose,
skew_edge: EdgeBool,
nested_selection_behavior: NestedSelectionBehavior,
selected_layers_count: usize,
selected_layers_changed: bool,
@@ -577,8 +578,23 @@ impl Fsm for SelectToolFsmState {
.map(|bounding_box| bounding_box.check_rotate(input.mouse.position))
.unwrap_or_default();
let is_resizing_or_rotating = matches!(self, SelectToolFsmState::ResizingBounds | SelectToolFsmState::SkewingBounds { .. } | SelectToolFsmState::RotatingBounds);
if let Some(bounds) = tool_data.bounding_box_manager.as_mut() {
let edges = bounds.check_selected_edges(input.mouse.position);
let is_skewing = matches!(self, SelectToolFsmState::SkewingBounds { .. });
let is_near_square = edges.is_some_and(|hover_edge| bounds.over_extended_edge_midpoint(input.mouse.position, hover_edge));
if is_skewing || (dragging_bounds && is_near_square && !is_resizing_or_rotating) {
bounds.render_skew_gizmos(&mut overlay_context, tool_data.skew_edge);
}
if !is_skewing && dragging_bounds {
if let Some(edges) = edges {
tool_data.skew_edge = bounds.get_closest_edge(edges, input.mouse.position);
}
}
}
let might_resize_or_rotate = dragging_bounds || rotating_bounds;
let is_resizing_or_rotating = matches!(self, SelectToolFsmState::ResizingBounds { .. } | SelectToolFsmState::SkewingBounds | SelectToolFsmState::RotatingBounds);
let can_get_into_other_states = might_resize_or_rotate && !matches!(self, SelectToolFsmState::Dragging { .. });
let show_compass = !(can_get_into_other_states || is_resizing_or_rotating);
@@ -842,14 +858,19 @@ impl Fsm for SelectToolFsmState {
None
);
bounds.center_of_transformation = selected.mean_average_of_pivots();
// Check if we're hovering over a skew triangle
let edges = bounds.check_selected_edges(input.mouse.position);
if let Some(edges) = edges {
let closest_edge = bounds.get_closest_edge(edges, input.mouse.position);
if bounds.check_skew_handle(input.mouse.position, closest_edge) {
tool_data.get_snap_candidates(document, input);
return SelectToolFsmState::SkewingBounds { skew };
}
}
}
tool_data.get_snap_candidates(document, input);
if input.keyboard.key(skew) {
SelectToolFsmState::SkewingBounds
} else {
SelectToolFsmState::ResizingBounds
}
SelectToolFsmState::ResizingBounds
}
// Dragging the selected layers around to transform them
else if can_grab_compass_rose || intersection.is_some_and(|intersection| selected.iter().any(|selected_layer| intersection.starts_with(*selected_layer, document.metadata()))) {
@@ -1034,10 +1055,11 @@ impl Fsm for SelectToolFsmState {
}
SelectToolFsmState::ResizingBounds
}
(SelectToolFsmState::SkewingBounds, SelectToolMessage::PointerMove(_)) => {
(SelectToolFsmState::SkewingBounds { skew }, SelectToolMessage::PointerMove(_)) => {
if let Some(ref mut bounds) = &mut tool_data.bounding_box_manager {
if let Some(movement) = &mut bounds.selected_edges {
let transformation = movement.skew_transform(input.mouse.position, bounds.original_bound_transform);
let free_movement = input.keyboard.key(skew);
let transformation = movement.skew_transform(input.mouse.position, bounds.original_bound_transform, free_movement);
tool_data.layers_dragging.retain(|layer| {
if *layer != LayerNodeIdentifier::ROOT_PARENT {
@@ -1063,7 +1085,7 @@ impl Fsm for SelectToolFsmState {
selected.apply_transformation(bounds.original_bound_transform * transformation * bounds.original_bound_transform.inverse(), None);
}
}
SelectToolFsmState::SkewingBounds
SelectToolFsmState::SkewingBounds { skew }
}
(SelectToolFsmState::RotatingBounds, SelectToolMessage::PointerMove(modifier_keys)) => {
if let Some(bounds) = &mut tool_data.bounding_box_manager {
@@ -1139,7 +1161,16 @@ impl Fsm for SelectToolFsmState {
SelectToolFsmState::Drawing { selection_shape }
}
(SelectToolFsmState::Ready { .. }, SelectToolMessage::PointerMove(_)) => {
let mut cursor = tool_data.bounding_box_manager.as_ref().map_or(MouseCursorIcon::Default, |bounds| bounds.get_cursor(input, true));
let dragging_bounds = tool_data
.bounding_box_manager
.as_mut()
.and_then(|bounding_box| bounding_box.check_selected_edges(input.mouse.position))
.is_some();
let mut cursor = tool_data
.bounding_box_manager
.as_ref()
.map_or(MouseCursorIcon::Default, |bounds| bounds.get_cursor(input, true, dragging_bounds, Some(tool_data.skew_edge)));
// Dragging the pivot overrules the other operations
if tool_data.pivot.is_over(input.mouse.position) {
@@ -1166,7 +1197,7 @@ impl Fsm for SelectToolFsmState {
SelectToolFsmState::Dragging { axis, using_compass }
}
(SelectToolFsmState::ResizingBounds | SelectToolFsmState::SkewingBounds, SelectToolMessage::PointerOutsideViewport(_)) => {
(SelectToolFsmState::ResizingBounds | SelectToolFsmState::SkewingBounds { .. }, SelectToolMessage::PointerOutsideViewport(_)) => {
// AutoPanning
if let Some(shift) = tool_data.auto_panning.shift_viewport(input, responses) {
if let Some(ref mut bounds) = &mut tool_data.bounding_box_manager {
@@ -1271,7 +1302,7 @@ impl Fsm for SelectToolFsmState {
let selection = tool_data.nested_selection_behavior;
SelectToolFsmState::Ready { selection }
}
(SelectToolFsmState::ResizingBounds | SelectToolFsmState::SkewingBounds, SelectToolMessage::DragStop { .. } | SelectToolMessage::Enter) => {
(SelectToolFsmState::ResizingBounds | SelectToolFsmState::SkewingBounds { .. }, SelectToolMessage::DragStop { .. } | SelectToolMessage::Enter) => {
let response = match input.mouse.position.distance(tool_data.drag_start) < 10. * f64::EPSILON {
true => DocumentMessage::AbortTransaction,
false => DocumentMessage::EndTransaction,
@@ -1523,7 +1554,7 @@ impl Fsm for SelectToolFsmState {
]);
responses.add(FrontendMessage::UpdateInputHints { hint_data });
}
SelectToolFsmState::DraggingPivot | SelectToolFsmState::SkewingBounds => {
SelectToolFsmState::DraggingPivot | SelectToolFsmState::SkewingBounds { .. } => {
let hint_data = HintData(vec![HintGroup(vec![HintInfo::mouse(MouseMotion::Rmb, ""), HintInfo::keys([Key::Escape], "Cancel").prepend_slash()])]);
responses.add(FrontendMessage::UpdateInputHints { hint_data });
}
@@ -325,17 +325,14 @@ impl Fsm for SplineToolFsmState {
let append_to_selected_layer = input.keyboard.key(append_to_selected);
// Create new path in the selected layer when shift is down
match (selected_layer, append_to_selected_layer) {
(Some(layer), true) => {
tool_data.current_layer = Some(layer);
if let (Some(layer), true) = (selected_layer, append_to_selected_layer) {
tool_data.current_layer = Some(layer);
let transform = document.metadata().transform_to_viewport(layer);
let position = transform.inverse().transform_point2(input.mouse.position);
tool_data.next_point = position;
let transform = document.metadata().transform_to_viewport(layer);
let position = transform.inverse().transform_point2(input.mouse.position);
tool_data.next_point = position;
return SplineToolFsmState::Drawing;
}
_ => {}
return SplineToolFsmState::Drawing;
}
responses.add(DocumentMessage::DeselectAllLayers);