diff --git a/editor/src/messages/tool/common_functionality/transformation_cage.rs b/editor/src/messages/tool/common_functionality/transformation_cage.rs index dba65b4080..cdbbf723d4 100644 --- a/editor/src/messages/tool/common_functionality/transformation_cage.rs +++ b/editor/src/messages/tool/common_functionality/transformation_cage.rs @@ -303,15 +303,19 @@ impl SelectedEdges { } } -/// Aligns the mouse position to the closest axis -pub fn axis_align_drag(axis_align: bool, axis: Axis, position: DVec2, start: DVec2) -> DVec2 { +/// Aligns the mouse position to the closest axis, accounting for canvas rotation. +/// `canvas_rotation` is the angle in radians by which the canvas is tilted. +pub fn axis_align_drag(axis_align: bool, axis: Axis, position: DVec2, start: DVec2, canvas_rotation: f64) -> DVec2 { if axis_align { let mouse_position = position - start; let snap_resolution = SELECTION_DRAG_ANGLE.to_radians(); - let angle = -mouse_position.angle_to(DVec2::X); + // Subtract canvas rotation to work in canvas-local space + let angle = -mouse_position.angle_to(DVec2::X) - canvas_rotation; let snapped_angle = (angle / snap_resolution).round() * snap_resolution; - let axis_vector = DVec2::from_angle(snapped_angle); - if snapped_angle.is_finite() { + // Add canvas rotation back to get the final screen-space angle + let final_angle = snapped_angle + canvas_rotation; + let axis_vector = DVec2::from_angle(final_angle); + if final_angle.is_finite() { start + axis_vector * mouse_position.dot(axis_vector).abs() } else { start @@ -327,8 +331,10 @@ pub fn axis_align_drag(axis_align: bool, axis: Axis, position: DVec2, start: DVe /// Snaps a dragging event from the artboard or select tool pub fn snap_drag(start: DVec2, current: DVec2, snap_to_axis: bool, axis: Axis, snap_data: SnapData, snap_manager: &mut SnapManager, candidates: &[SnapCandidatePoint]) -> DVec2 { - let mouse_position = axis_align_drag(snap_to_axis, axis, snap_data.input.mouse.position, start); let document = snap_data.document; + // Extract canvas rotation from the document_to_viewport transform + let canvas_rotation = document.metadata().document_to_viewport.matrix2.y_axis.to_angle() - std::f64::consts::FRAC_PI_2; + let mouse_position = axis_align_drag(snap_to_axis, axis, snap_data.input.mouse.position, start, canvas_rotation); let total_mouse_delta_document = document.metadata().document_to_viewport.inverse().transform_vector2(mouse_position - start); let mouse_delta_document = document.metadata().document_to_viewport.inverse().transform_vector2(mouse_position - current); let mut offset = mouse_delta_document; diff --git a/editor/src/messages/tool/tool_messages/select_tool.rs b/editor/src/messages/tool/tool_messages/select_tool.rs index e5738e7770..08fb5e2b81 100644 --- a/editor/src/messages/tool/tool_messages/select_tool.rs +++ b/editor/src/messages/tool/tool_messages/select_tool.rs @@ -402,6 +402,8 @@ struct SelectToolData { snap_candidates: Vec, auto_panning: AutoPanning, drag_start_center: ViewportPosition, + /// Drag start position in document coordinates, used for axis-aligned snapping that follows canvas pan/tilt + drag_start_document: DVec2, } impl SelectToolData { @@ -911,12 +913,16 @@ impl Fsm for SelectToolFsmState { } if axis_state.is_none_or(|(axis, _)| !axis.is_constraint()) && tool_data.axis_align { - let mouse_position = mouse_position - tool_data.drag_start; + // Convert document-space origin to current viewport (follows pan/tilt) + let viewport_origin = document.metadata().document_to_viewport.transform_point2(tool_data.drag_start_document); + let mouse_position = mouse_position - viewport_origin; let snap_resolution = SELECTION_DRAG_ANGLE.to_radians(); - let angle = -mouse_position.angle_to(DVec2::X); - let snapped_angle = (angle / snap_resolution).round() * snap_resolution; + // Account for canvas rotation + let canvas_rotation = document.metadata().document_to_viewport.matrix2.y_axis.to_angle() - std::f64::consts::FRAC_PI_2; + let angle = -mouse_position.angle_to(DVec2::X) - canvas_rotation; + let snapped_angle = (angle / snap_resolution).round() * snap_resolution + canvas_rotation; - let origin = tool_data.drag_start_center; + let origin = viewport_origin; let viewport_diagonal = viewport.size().into_dvec2().length(); let edge = DVec2::from_angle(snapped_angle).normalize_or(DVec2::X); @@ -1037,6 +1043,7 @@ impl Fsm for SelectToolFsmState { let (resize, rotate, skew) = transforming_transform_cage(document, &mut tool_data.bounding_box_manager, input, responses, &mut tool_data.layers_dragging, Some(position)); tool_data.drag_start_center = position; + tool_data.drag_start_document = document.metadata().document_to_viewport.inverse().transform_point2(input.mouse.position); // If the user is dragging the bounding box bounds, go into ResizingBounds mode. // If the user is dragging the rotate trigger, go into RotatingBounds mode. @@ -1179,7 +1186,9 @@ impl Fsm for SelectToolFsmState { let ignore = tool_data.non_duplicated_layers.as_ref().filter(|_| !layers_exist).unwrap_or(&tool_data.layers_dragging); let snap_data = SnapData::ignore(document, input, viewport, ignore); - let (start, current) = (tool_data.drag_start, tool_data.drag_current); + // Convert document-space origin to current viewport (follows pan/tilt) + let start = document.metadata().document_to_viewport.transform_point2(tool_data.drag_start_document); + let current = tool_data.drag_current; let e0 = tool_data .bounding_box_manager .as_ref()