diff --git a/editor/src/messages/tool/common_functionality/measure.rs b/editor/src/messages/tool/common_functionality/measure.rs index 563a25037..cb9999324 100644 --- a/editor/src/messages/tool/common_functionality/measure.rs +++ b/editor/src/messages/tool/common_functionality/measure.rs @@ -1,7 +1,6 @@ use crate::consts::COLOR_OVERLAY_BLUE; use crate::messages::portfolio::document::overlays::utility_types::{OverlayContext, Pivot}; use crate::messages::tool::tool_messages::tool_prelude::*; - use graphene_std::renderer::Rect; /// Draws a dashed line between two points transformed by the given affine transformation. @@ -25,29 +24,50 @@ fn draw_line_with_length(line_start: DVec2, line_end: DVec2, transform: DAffine2 .trim_end_matches('.') .to_string(); - const TEXT_PADDING: f64 = 5.; - // Calculate midpoint of the line - let midpoint = (min_viewport + max_viewport) / 2.; + const TOLERANCE: f64 = 0.01; + if transform_to_document.transform_vector2(line_end - line_start).length() >= TOLERANCE { + const TEXT_PADDING: f64 = 5.; + // Calculate midpoint of the line + let midpoint = (min_viewport + max_viewport) / 2.; - // Adjust text position based on line orientation and flags - // Determine text position based on line orientation and flags - let (pivot_x, pivot_y) = match (label_alignment.is_vertical_line, label_alignment.text_on_left, label_alignment.text_on_top) { - (true, true, _) => (Pivot::End, Pivot::Middle), // Vertical line, text on the left - (true, false, _) => (Pivot::Start, Pivot::Middle), // Vertical line, text on the right - (false, _, true) => (Pivot::Middle, Pivot::End), // Horizontal line, text on top - (false, _, false) => (Pivot::Middle, Pivot::Start), // Horizontal line, text on bottom - }; - overlay_context.text(&length, COLOR_OVERLAY_BLUE, None, DAffine2::from_translation(midpoint), TEXT_PADDING, [pivot_x, pivot_y]); + // Adjust text position based on line orientation and flags + // Determine text position based on line orientation and flags + let (pivot_x, pivot_y) = match (label_alignment.is_vertical_line, label_alignment.text_on_left, label_alignment.text_on_top) { + (true, true, _) => (Pivot::End, Pivot::Middle), // Vertical line, text on the left + (true, false, _) => (Pivot::Start, Pivot::Middle), // Vertical line, text on the right + (false, _, true) => (Pivot::Middle, Pivot::End), // Horizontal line, text on top + (false, _, false) => (Pivot::Middle, Pivot::Start), // Horizontal line, text on bottom + }; + overlay_context.text(&length, COLOR_OVERLAY_BLUE, None, DAffine2::from_translation(midpoint), TEXT_PADDING, [pivot_x, pivot_y]); + } +} + +/// Draws a dashed outline around a rectangle to visualize the AABB +fn draw_dashed_rect_outline(rect: Rect, transform: DAffine2, overlay_context: &mut OverlayContext) { + let min = rect.min(); + let max = rect.max(); + + // Create the four corners of the rectangle + let top_left = transform.transform_point2(DVec2::new(min.x, min.y)); + let top_right = transform.transform_point2(DVec2::new(max.x, min.y)); + let bottom_right = transform.transform_point2(DVec2::new(max.x, max.y)); + let bottom_left = transform.transform_point2(DVec2::new(min.x, max.y)); + + // Draw the four sides as dashed lines + draw_dashed_line(top_left, top_right, transform, overlay_context); + draw_dashed_line(top_right, bottom_right, transform, overlay_context); + draw_dashed_line(bottom_right, bottom_left, transform, overlay_context); + draw_dashed_line(bottom_left, top_left, transform, overlay_context); } /// Checks if the selected bounds overlap with the hovered bounds on the Y-axis. fn does_overlap_y(selected_bounds: Rect, hovered_bounds: Rect) -> bool { - selected_bounds.min().x < hovered_bounds.max().x && selected_bounds.max().x > hovered_bounds.min().x + selected_bounds.min().x <= hovered_bounds.max().x && selected_bounds.max().x >= hovered_bounds.min().x } /// Checks if the selected bounds overlap with the hovered bounds on the X-axis. fn does_overlap_x(selected_bounds: Rect, hovered_bounds: Rect) -> bool { - selected_bounds.min().y < hovered_bounds.max().y && selected_bounds.max().y > hovered_bounds.min().y + selected_bounds.min().y <= hovered_bounds.max().y && selected_bounds.max().y >= hovered_bounds.min().y } /// Draws measurements when both X and Y axes are involved in the overlap between selected and hovered bounds. @@ -91,8 +111,8 @@ fn draw_single_axis_zero_crossings(selected_bounds: Rect, hovered_bounds: Rect, let (selected_min, selected_max) = (selected_bounds.min(), selected_bounds.max()); let (hovered_min, hovered_max) = (hovered_bounds.min(), hovered_bounds.max()); - let overlap_y = does_overlap_y(selected_bounds, hovered_bounds); - let overlap_x = does_overlap_x(selected_bounds, hovered_bounds); + let overlap_y = does_overlap_y(selected_bounds, hovered_bounds) || does_overlap_y(hovered_bounds, selected_bounds); + let overlap_x = does_overlap_x(selected_bounds, hovered_bounds) || does_overlap_x(hovered_bounds, selected_bounds); let selected_on_bottom = selected_bounds.center().y > hovered_bounds.center().y; let selected_on_right = selected_bounds.center().x > hovered_bounds.center().x; @@ -151,8 +171,8 @@ fn draw_single_axis_one_crossings(selected_bounds: Rect, hovered_bounds: Rect, t let selected_center = selected_bounds.center(); let hovered_center = hovered_bounds.center(); - let overlap_y = does_overlap_y(selected_bounds, hovered_bounds); - let overlap_x = does_overlap_x(selected_bounds, hovered_bounds); + let overlap_y = does_overlap_y(selected_bounds, hovered_bounds) || does_overlap_y(hovered_bounds, selected_bounds); + let overlap_x = does_overlap_x(selected_bounds, hovered_bounds) || does_overlap_x(hovered_bounds, selected_bounds); if overlap_y { let selected_facing_edge = if hovered_max.y < selected_min.y { selected_min.y } else { selected_max.y }; @@ -423,14 +443,14 @@ fn handle_two_axis_overlap(selected_bounds: Rect, hovered_bounds: Rect, transfor /// Overlays measurement lines between selected and hovered bounds based on their spatial relationships. pub fn overlay(selected_bounds: Rect, hovered_bounds: Rect, transform: DAffine2, document_to_viewport: DAffine2, overlay_context: &mut OverlayContext) { - // TODO: Apply object rotation to bounds before drawing lines for all cases. - + draw_dashed_rect_outline(selected_bounds, transform, overlay_context); + draw_dashed_rect_outline(hovered_bounds, transform, overlay_context); let (selected_min, selected_max) = (selected_bounds.min(), selected_bounds.max()); let (hovered_min, hovered_max) = (hovered_bounds.min(), hovered_bounds.max()); // Determine axis overlaps - let overlap_x = selected_min.x <= hovered_max.x && selected_max.x >= hovered_min.x; - let overlap_y = selected_min.y <= hovered_max.y && selected_max.y >= hovered_min.y; + let overlap_y = does_overlap_y(selected_bounds, hovered_bounds) || does_overlap_y(hovered_bounds, selected_bounds); + let overlap_x = does_overlap_x(selected_bounds, hovered_bounds) || does_overlap_x(hovered_bounds, selected_bounds); let overlap_axes = match (overlap_x, overlap_y) { (true, true) => 2, (true, false) | (false, true) => 1, diff --git a/editor/src/messages/tool/tool_messages/select_tool.rs b/editor/src/messages/tool/tool_messages/select_tool.rs index d8dd40588..8f58c57a3 100644 --- a/editor/src/messages/tool/tool_messages/select_tool.rs +++ b/editor/src/messages/tool/tool_messages/select_tool.rs @@ -544,13 +544,56 @@ impl Fsm for SelectToolFsmState { }) .reduce(graphene_core::renderer::Quad::combine_bounds); + // When not in Drawing State + // Only highlight layers if the viewport is not being panned (middle mouse button is pressed) + // TODO: Don't use `Key::MouseMiddle` directly, instead take it as a variable from the input mappings list like in all other places; or find a better way than checking the key state + if !matches!(self, Self::Drawing { .. }) && !input.keyboard.get(Key::MouseMiddle as usize) { + // Get the layer the user is hovering over + let click = document.click(input); + let not_selected_click = click.filter(|&hovered_layer| !document.network_interface.selected_nodes().selected_layers_contains(hovered_layer, document.metadata())); + if let Some(layer) = not_selected_click { + overlay_context.outline(document.metadata().layer_outline(layer), document.metadata().transform_to_viewport(layer)); + + // Measure with Alt held down + // TODO: Don't use `Key::Alt` directly, instead take it as a variable from the input mappings list like in all other places + if !matches!(self, Self::ResizingBounds { .. }) && input.keyboard.get(Key::Alt as usize) { + // Get all selected layers and compute their viewport-aligned AABB + let selected_bounds_viewport = document + .network_interface + .selected_nodes() + .selected_visible_and_unlocked_layers(&document.network_interface) + .filter(|layer| !document.network_interface.is_artboard(&layer.to_node(), &[])) + .filter_map(|layer| { + // Get the layer's bounding box in its local space + let local_bounds = document.metadata().bounding_box_with_transform(layer, DAffine2::IDENTITY)?; + // Transform the bounds directly to viewport space + let viewport_quad = document.metadata().transform_to_viewport(layer) * Quad::from_box(local_bounds); + // Convert the quad to an AABB in viewport space + Some(Rect::from_box(viewport_quad.bounding_box())) + }) + .reduce(Rect::combine_bounds); + + // Get the hovered layer's viewport-aligned AABB + let hovered_bounds_viewport = document.metadata().bounding_box_with_transform(layer, DAffine2::IDENTITY).map(|bounds| { + let viewport_quad = document.metadata().transform_to_viewport(layer) * Quad::from_box(bounds); + Rect::from_box(viewport_quad.bounding_box()) + }); + + // Use the viewport-aligned AABBs for measurement + if let (Some(selected_bounds), Some(hovered_bounds)) = (selected_bounds_viewport, hovered_bounds_viewport) { + // Since we're already in viewport space, use identity transform + measure::overlay(selected_bounds, hovered_bounds, DAffine2::IDENTITY, DAffine2::IDENTITY, &mut overlay_context); + } + } + } + } + if let Some(bounds) = bounds { let bounding_box_manager = tool_data.bounding_box_manager.get_or_insert(BoundingBoxManager::default()); bounding_box_manager.bounds = bounds; bounding_box_manager.transform = transform; bounding_box_manager.transform_tampered = transform_tampered; - bounding_box_manager.render_overlays(&mut overlay_context); } else { tool_data.bounding_box_manager.take(); @@ -722,29 +765,6 @@ impl Fsm for SelectToolFsmState { (SelectionShapeType::Lasso, _) => overlay_context.polygon(polygon, fill_color), } } - // Only highlight layers if the viewport is not being panned (middle mouse button is pressed) - // TODO: Don't use `Key::Mmb` directly, instead take it as a variable from the input mappings list like in all other places - else if !input.keyboard.get(Key::MouseMiddle as usize) { - // Get the layer the user is hovering over - let click = document.click(input); - let not_selected_click = click.filter(|&hovered_layer| !document.network_interface.selected_nodes().selected_layers_contains(hovered_layer, document.metadata())); - if let Some(layer) = not_selected_click { - overlay_context.outline(document.metadata().layer_outline(layer), document.metadata().transform_to_viewport(layer)); - - // Measure with Alt held down - // TODO: Don't use `Key::Alt` directly, instead take it as a variable from the input mappings list like in all other places - if !matches!(self, Self::ResizingBounds { .. }) && input.keyboard.get(Key::Alt as usize) { - let hovered_bounds = document - .metadata() - .bounding_box_with_transform(layer, transform.inverse() * document.metadata().transform_to_viewport(layer)); - - if let [Some(selected_bounds), Some(hovered_bounds)] = [bounds, hovered_bounds].map(|rect| rect.map(Rect::from_box)) { - measure::overlay(selected_bounds, hovered_bounds, transform, document.metadata().document_to_viewport, &mut overlay_context); - } - } - } - } - self } (_, SelectToolMessage::EditLayer) => {