diff --git a/editor/src/messages/portfolio/document/document_message_handler.rs b/editor/src/messages/portfolio/document/document_message_handler.rs index dffca23799..63ad365785 100644 --- a/editor/src/messages/portfolio/document/document_message_handler.rs +++ b/editor/src/messages/portfolio/document/document_message_handler.rs @@ -342,13 +342,26 @@ impl MessageHandler> for DocumentMessag continue; } let Some(bounds) = self.metadata().bounding_box_document(layer) else { continue }; + let [min, max] = [bounds[0].min(bounds[1]), bounds[0].max(bounds[1])]; let name = self.network_interface.frontend_display_name(&layer.to_node(), &[]); + // Calculate position of the text + let corner_pos = if !self.document_ptz.canvas_flipped { + min // Use the top left corner + } else { + DVec2::new(max.x, min.y) // Use the top right corner (appears to be the top left due to flipping) + }; + + // When canvas is flipped, reverse the flip so the text reads in the same direction + let scale = if !self.document_ptz.canvas_flipped { DVec2::ONE } else { DVec2::new(-1., 1.) }; + + // Create a transform that puts the text at the true top-left regardless of flip let transform = self.metadata().document_to_viewport - * DAffine2::from_translation(bounds[0].min(bounds[1])) + * DAffine2::from_translation(corner_pos) * DAffine2::from_scale(DVec2::splat(self.document_ptz.zoom().recip())) - * DAffine2::from_translation(-DVec2::Y * 4.); + * DAffine2::from_translation(-DVec2::Y * 4.) + * DAffine2::from_scale(scale); // Counter the flip for the text itself overlay_context.text(&name, COLOR_OVERLAY_GRAY, None, transform, 0., [Pivot::Start, Pivot::End]); } @@ -2529,7 +2542,7 @@ impl<'a> ClickXRayIter<'a> { } } -pub fn navigation_controls(ptz: &PTZ, navigation_handler: &NavigationMessageHandler, tooltip_name: &str) -> [WidgetHolder; 5] { +pub fn navigation_controls(ptz: &PTZ, navigation_handler: &NavigationMessageHandler, tooltip_name: &str) -> [WidgetHolder; 7] { [ IconButton::new("ZoomIn", 24) .tooltip("Zoom In") @@ -2547,6 +2560,11 @@ pub fn navigation_controls(ptz: &PTZ, navigation_handler: &NavigationMessageHand .on_update(|_| NavigationMessage::CanvasTiltResetAndZoomTo100Percent.into()) .disabled(ptz.tilt().abs() < 1e-4 && (ptz.zoom() - 1.).abs() < 1e-4) .widget_holder(), + CheckboxInput::new(ptz.canvas_flipped) + .on_update(|_| NavigationMessage::FlipCanvas.into()) + .tooltip("Flip Canvas Horizontally") + .widget_holder(), + TextLabel::new("Flip Canvas").widget_holder(), // PopoverButton::new() // .popover_layout(vec![ // LayoutGroup::Row { diff --git a/editor/src/messages/portfolio/document/navigation/navigation_message.rs b/editor/src/messages/portfolio/document/navigation/navigation_message.rs index b5c4095222..986c6f4532 100644 --- a/editor/src/messages/portfolio/document/navigation/navigation_message.rs +++ b/editor/src/messages/portfolio/document/navigation/navigation_message.rs @@ -26,4 +26,5 @@ pub enum NavigationMessage { FitViewportToBounds { bounds: [DVec2; 2], prevent_zoom_past_100: bool }, FitViewportToSelection, PointerMove { snap: Key }, + FlipCanvas, } diff --git a/editor/src/messages/portfolio/document/navigation/navigation_message_handler.rs b/editor/src/messages/portfolio/document/navigation/navigation_message_handler.rs index 10e4564a26..86c87b487b 100644 --- a/editor/src/messages/portfolio/document/navigation/navigation_message_handler.rs +++ b/editor/src/messages/portfolio/document/navigation/navigation_message_handler.rs @@ -396,9 +396,11 @@ impl MessageHandler> for Navigation .. } => { let tilt_raw_not_snapped = { + // Compute the angle in document space to counter for any flipping + let viewport_to_document = network_interface.document_metadata().document_to_viewport.inverse(); let half_viewport = ipp.viewport_bounds.size() / 2.; - let start_offset = self.mouse_position - half_viewport; - let end_offset = ipp.mouse.position - half_viewport; + let start_offset = viewport_to_document.transform_vector2(self.mouse_position - half_viewport); + let end_offset = viewport_to_document.transform_vector2(ipp.mouse.position - half_viewport); let angle = start_offset.angle_to(end_offset); tilt_raw_not_snapped + angle @@ -459,6 +461,17 @@ impl MessageHandler> for Navigation self.mouse_position = ipp.mouse.position; } + NavigationMessage::FlipCanvas => { + let Some(ptz) = get_ptz_mut(document_ptz, network_interface, graph_view_overlay_open, breadcrumb_network_path) else { + log::error!("Could not get mutable PTZ in FlipCanvas"); + return; + }; + + ptz.canvas_flipped = !ptz.canvas_flipped; + + responses.add(DocumentMessage::PTZUpdate); + responses.add(BroadcastEvent::CanvasTransformed); + } } } @@ -516,15 +529,16 @@ impl NavigationMessageHandler { let tilt = ptz.tilt(); let zoom = ptz.zoom(); - let scaled_center = viewport_center / self.snapped_zoom(zoom); + let scale = self.snapped_zoom(zoom); + let scale_vec = if ptz.canvas_flipped { DVec2::new(-scale, scale) } else { DVec2::splat(scale) }; + let scaled_center = viewport_center / scale_vec; // Try to avoid fractional coordinates to reduce anti aliasing. - let scale = self.snapped_zoom(zoom); let rounded_pan = ((pan + scaled_center) * scale).round() / scale - scaled_center; // TODO: replace with DAffine2::from_scale_angle_translation and fix the errors let offset_transform = DAffine2::from_translation(scaled_center); - let scale_transform = DAffine2::from_scale(DVec2::splat(scale)); + let scale_transform = DAffine2::from_scale(scale_vec); let angle_transform = DAffine2::from_angle(self.snapped_tilt(tilt)); let translation_transform = DAffine2::from_translation(rounded_pan); scale_transform * offset_transform * angle_transform * translation_transform diff --git a/editor/src/messages/portfolio/document/utility_types/misc.rs b/editor/src/messages/portfolio/document/utility_types/misc.rs index 02f26d51f7..03a8e8b4e9 100644 --- a/editor/src/messages/portfolio/document/utility_types/misc.rs +++ b/editor/src/messages/portfolio/document/utility_types/misc.rs @@ -639,11 +639,18 @@ pub struct PTZ { tilt: f64, /// Scale factor. zoom: f64, + /// Whether the canvas is horizontally flipped. + pub canvas_flipped: bool, } impl Default for PTZ { fn default() -> Self { - Self { pan: DVec2::ZERO, tilt: 0., zoom: 1. } + Self { + pan: DVec2::ZERO, + tilt: 0., + zoom: 1., + canvas_flipped: false, + } } }