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..943c47c7f9 100644 --- a/editor/src/messages/tool/tool_messages/select_tool.rs +++ b/editor/src/messages/tool/tool_messages/select_tool.rs @@ -394,14 +394,17 @@ struct SelectToolData { pivot_gizmo_start: Option, pivot_gizmo_shift: Option, compass_rose: CompassRose, - line_center: DVec2, + line_center_document: DVec2, skew_edge: EdgeBool, nested_selection_behavior: NestedSelectionBehavior, selected_layers_count: usize, selected_layers_changed: bool, snap_candidates: Vec, auto_panning: AutoPanning, - drag_start_center: ViewportPosition, + /// Drag start position in document coordinates, used for the center of the axis-aligned snapping and the compass rose + drag_start_center_document: DVec2, + /// Drag start mouse position in document coordinates, used for axis-aligned snapping that follows canvas pan/tilt + drag_start_document: DVec2, } impl SelectToolData { @@ -871,7 +874,7 @@ impl Fsm for SelectToolFsmState { tool_data.compass_rose.refresh_position(document); let compass_center = tool_data.compass_rose.compass_rose_position(); if !matches!(self, Self::Dragging { .. }) { - tool_data.line_center = compass_center; + tool_data.line_center_document = document.metadata().document_to_viewport.inverse().transform_point2(compass_center); } overlay_context.compass_rose(compass_center, angle, show_compass_with_ring); @@ -906,17 +909,21 @@ impl Fsm for SelectToolFsmState { let color_string = &graphene_std::Color::from_rgb_str(color.strip_prefix('#').unwrap()).unwrap().with_alpha(0.25).to_rgba_hex_srgb(); &format!("#{color_string}") }; - let line_center = tool_data.line_center; + let line_center = document.metadata().document_to_viewport.transform_point2(tool_data.line_center_document); overlay_context.line(line_center - direction * viewport_diagonal, line_center + direction * viewport_diagonal, Some(color), None); } 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 = document.metadata().document_to_viewport.transform_point2(tool_data.drag_start_center_document); let viewport_diagonal = viewport.size().into_dvec2().length(); let edge = DVec2::from_angle(snapped_angle).normalize_or(DVec2::X); @@ -1036,7 +1043,8 @@ impl Fsm for SelectToolFsmState { let position = tool_data.pivot_gizmo().position(document); 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_center_document = document.metadata().document_to_viewport.inverse().transform_point2(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 +1187,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()