From 5a8eb9dd1b0a3a6e07e18fdafd6e119d3d0433dd Mon Sep 17 00:00:00 2001 From: James Lindsay <78500760+0HyperCube@users.noreply.github.com> Date: Fri, 31 Jan 2025 02:07:40 +0000 Subject: [PATCH] Make the transform cage resize about the pivot when Alt is pressed (#2226) * Change the pivot behaviour when resizing bounds with alt in the select tool * Add scale factor maximum * Fix bug when encountering snapping; tidy up and comment the code --------- Co-authored-by: Keavon Chambers --- editor/src/consts.rs | 5 ++ .../transformation_cage.rs | 51 +++++++++++++------ 2 files changed, 41 insertions(+), 15 deletions(-) diff --git a/editor/src/consts.rs b/editor/src/consts.rs index 2df02163..0e70d4cc 100644 --- a/editor/src/consts.rs +++ b/editor/src/consts.rs @@ -68,6 +68,11 @@ pub const MIN_LENGTH_FOR_MIDPOINT_VISIBILITY: f64 = 20.; pub const MIN_LENGTH_FOR_CORNERS_VISIBILITY: f64 = 12.; /// When the width or height of the transform cage is less than this value, only the exterior of the bounding box will act as a click target for resizing. pub const MIN_LENGTH_FOR_RESIZE_TO_INCLUDE_INTERIOR: f64 = 40.; +/// When dragging the edge of a cage with Alt, it centers around the pivot. +/// However if the pivot is on or near the same edge you are dragging, we should avoid scaling by a massive factor caused by the small denominator. +/// +/// The motion of the user's cursor by an `x` pixel offset results in `x * scale_factor` pixels of offset on the other side. +pub const MAXIMUM_ALT_SCALE_FACTOR: f64 = 25.; // PATH TOOL pub const MANIPULATOR_GROUP_MARKER_SIZE: f64 = 6.; diff --git a/editor/src/messages/tool/common_functionality/transformation_cage.rs b/editor/src/messages/tool/common_functionality/transformation_cage.rs index 32be2277..2ab1d2bc 100644 --- a/editor/src/messages/tool/common_functionality/transformation_cage.rs +++ b/editor/src/messages/tool/common_functionality/transformation_cage.rs @@ -1,5 +1,6 @@ use crate::consts::{ - BOUNDS_ROTATE_THRESHOLD, BOUNDS_SELECT_THRESHOLD, MIN_LENGTH_FOR_CORNERS_VISIBILITY, MIN_LENGTH_FOR_MIDPOINT_VISIBILITY, MIN_LENGTH_FOR_RESIZE_TO_INCLUDE_INTERIOR, SELECTION_DRAG_ANGLE, + BOUNDS_ROTATE_THRESHOLD, BOUNDS_SELECT_THRESHOLD, MAXIMUM_ALT_SCALE_FACTOR, MIN_LENGTH_FOR_CORNERS_VISIBILITY, MIN_LENGTH_FOR_MIDPOINT_VISIBILITY, MIN_LENGTH_FOR_RESIZE_TO_INCLUDE_INTERIOR, + SELECTION_DRAG_ANGLE, }; use crate::messages::frontend::utility_types::MouseCursorIcon; use crate::messages::portfolio::document::overlays::utility_types::OverlayContext; @@ -8,9 +9,9 @@ use crate::messages::prelude::*; use crate::messages::tool::common_functionality::snapping::SnapTypeConfiguration; use graphene_core::renderer::Quad; +use graphene_std::renderer::Rect; use glam::{DAffine2, DVec2}; -use graphene_std::renderer::Rect; use super::snapping::{self, SnapCandidatePoint, SnapConstraint, SnapData, SnapManager, SnappedPoint}; @@ -88,6 +89,7 @@ impl SelectedEdges { let mut min = self.bounds[0]; let mut max = self.bounds[1]; + if self.top { min.y = mouse.y; } else if self.bottom { @@ -100,24 +102,43 @@ impl SelectedEdges { } let mut pivot = self.pivot_from_bounds(min, max); + + // Alt: Scaling around the pivot if let Some(center_around) = center_around { let center_around = transform.inverse().transform_point2(center_around); - if self.top { - pivot.y = center_around.y; - max.y = center_around.y * 2. - min.y; - } else if self.bottom { - pivot.y = center_around.y; - min.y = center_around.y * 2. - max.y; - } - if self.left { - pivot.x = center_around.x; - max.x = center_around.x * 2. - min.x; - } else if self.right { - pivot.x = center_around.x; - min.x = center_around.x * 2. - max.x; + + let calculate_distance = |moving_opposite_to_drag: &mut f64, center: f64, dragging: f64, original_dragging: f64, current_side: bool| { + if !current_side { + return true; + } + + // The motion of the user's cursor by an `x` pixel offset results in `x * scale_factor` pixels of offset on the other side + let scale_factor = (center - *moving_opposite_to_drag) / (center - original_dragging); + let new_distance = center - scale_factor * (center - dragging); + + // Ignore the Alt key press and scale the dragged edge normally + if !new_distance.is_finite() || scale_factor.abs() > MAXIMUM_ALT_SCALE_FACTOR { + // Don't go on to check the other sides since this side is already invalid, so Alt-dragging is disabled and updating the pivot would be incorrect + return false; + } + + *moving_opposite_to_drag = new_distance; + + true + }; + + // Update the value of the first argument through mutation, and if we make it through all of them without + // encountering a case where the pivot is too near the edge, we also update the pivot so scaling occurs around it + if calculate_distance(&mut max.y, center_around.y, min.y, self.bounds[0].y, self.top) + && calculate_distance(&mut min.y, center_around.y, max.y, self.bounds[1].y, self.bottom) + && calculate_distance(&mut max.x, center_around.x, min.x, self.bounds[0].x, self.left) + && calculate_distance(&mut min.x, center_around.x, max.x, self.bounds[1].x, self.right) + { + pivot = center_around; } } + // Shift: Aspect ratio constraint if constrain { let size = max - min; let min_pivot = (pivot - min) / size;