From 2c29ec7d03e0336d760d96e46c3ba2ff95e48e60 Mon Sep 17 00:00:00 2001 From: Chetan Sahney Date: Sat, 11 Jul 2026 02:30:08 +0530 Subject: [PATCH] Show local-axis grab as a single tilted component When grabbing (G) and pressing X/Y a second time to constrain to a local axis, the measurement overlay drew an axis-aligned box showing both the X and Y screen components (and printed a typed value on both axes) instead of a single line along the tilted local axis. In the Grabbing overlay branch, when constrained to a single axis in local space, draw one dashed line along the (already tilted) translation vector with a single measurement label, mirroring how Scale (S) already behaves. The unconstrained and global-axis cases are unchanged. Closes #3341 --- .../transform_layer_message_handler.rs | 23 +++++++++++++++++-- 1 file changed, 21 insertions(+), 2 deletions(-) diff --git a/editor/src/messages/tool/transform_layer/transform_layer_message_handler.rs b/editor/src/messages/tool/transform_layer/transform_layer_message_handler.rs index d489c95377..7d6d1e52a8 100644 --- a/editor/src/messages/tool/transform_layer/transform_layer_message_handler.rs +++ b/editor/src/messages/tool/transform_layer/transform_layer_message_handler.rs @@ -234,7 +234,6 @@ impl MessageHandler> for TransformOperation::Grabbing(translation) => { let translation_viewport = self.state.local_to_viewport_transform().matrix2 * translation.to_dvec(&self.state, document); let pivot = document_to_viewport.transform_point2(self.grab_target); - let quad = Quad::from_box([pivot, pivot + translation_viewport]); responses.add(SelectToolMessage::PivotShift { offset: Some(translation_viewport), @@ -242,7 +241,27 @@ impl MessageHandler> for }); let typed_string = (!self.typing.digits.is_empty() && self.transform_operation.can_begin_typing()).then(|| self.typing.string.clone()); - overlay_context.translation_box(translation_viewport / document_to_viewport.matrix2.y_axis.length(), quad, typed_string); + + // When constrained to a single axis in local space, the translation vector is tilted along the layer's + // local axis. Draw one dashed line (with a single measurement label) along that axis, instead of the + // axis-aligned box, which would incorrectly show both the X and Y screen-space components. + if self.state.is_transforming_in_local_space && axis_constraint != Axis::Both { + let end_point = pivot + translation_viewport; + overlay_context.dashed_line(pivot, end_point, None, None, Some(2.), Some(2.), Some(0.5)); + + let axis_direction = self.state.constraint_axis(axis_constraint).unwrap_or(DVec2::X).normalize_or(DVec2::X); + let length = translation_viewport.dot(axis_direction) / document_to_viewport.matrix2.y_axis.length(); + let label = match typed_string { + Some(typed_string) => typed_string, + None => format!("{length:.2}").trim_end_matches('0').trim_end_matches('.').to_string(), + }; + let text_position = pivot.midpoint(end_point) + translation_viewport.perp().normalize_or(DVec2::X) * 24.; + let transform = DAffine2::from_translation(text_position); + overlay_context.text(&label, COLOR_OVERLAY_BLUE, None, transform, 16., [Pivot::Middle, Pivot::Middle]); + } else { + let quad = Quad::from_box([pivot, pivot + translation_viewport]); + overlay_context.translation_box(translation_viewport / document_to_viewport.matrix2.y_axis.length(), quad, typed_string); + } } TransformOperation::Scaling(scale) => { let scale = scale.to_f64(self.state.is_rounded_to_intervals);