Fix quick measuring of skewed and rotated layers by using the viewport space AABB (#2396)
* Make sure that quick measure overlays are based on AABBs drawn in the viewport local space * Draw overlays to visualise AABBs of selected and hovered shapes * use pre-existing functions to render dashed lines * Redraw selected bounds using existing BoundingBoxManager * remove unused variables * Render transform cage after overlay is drawn * Bring overlay and transform cage render calls above(before) other gizmos * Add line length tolerance and render single line for singal edge alignment with one axis overlap * Comments --------- Co-authored-by: Keavon Chambers <keavon@keavon.com>
This commit is contained in:
parent
2bcfe5ea0c
commit
927d7dd9b2
|
|
@ -1,7 +1,6 @@
|
||||||
use crate::consts::COLOR_OVERLAY_BLUE;
|
use crate::consts::COLOR_OVERLAY_BLUE;
|
||||||
use crate::messages::portfolio::document::overlays::utility_types::{OverlayContext, Pivot};
|
use crate::messages::portfolio::document::overlays::utility_types::{OverlayContext, Pivot};
|
||||||
use crate::messages::tool::tool_messages::tool_prelude::*;
|
use crate::messages::tool::tool_messages::tool_prelude::*;
|
||||||
|
|
||||||
use graphene_std::renderer::Rect;
|
use graphene_std::renderer::Rect;
|
||||||
|
|
||||||
/// Draws a dashed line between two points transformed by the given affine transformation.
|
/// 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('.')
|
.trim_end_matches('.')
|
||||||
.to_string();
|
.to_string();
|
||||||
|
|
||||||
const TEXT_PADDING: f64 = 5.;
|
const TOLERANCE: f64 = 0.01;
|
||||||
// Calculate midpoint of the line
|
if transform_to_document.transform_vector2(line_end - line_start).length() >= TOLERANCE {
|
||||||
let midpoint = (min_viewport + max_viewport) / 2.;
|
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
|
// Adjust text position based on line orientation and flags
|
||||||
// Determine 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) {
|
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, true, _) => (Pivot::End, Pivot::Middle), // Vertical line, text on the left
|
||||||
(true, false, _) => (Pivot::Start, Pivot::Middle), // Vertical line, text on the right
|
(true, false, _) => (Pivot::Start, Pivot::Middle), // Vertical line, text on the right
|
||||||
(false, _, true) => (Pivot::Middle, Pivot::End), // Horizontal line, text on top
|
(false, _, true) => (Pivot::Middle, Pivot::End), // Horizontal line, text on top
|
||||||
(false, _, false) => (Pivot::Middle, Pivot::Start), // Horizontal line, text on bottom
|
(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]);
|
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.
|
/// 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 {
|
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.
|
/// 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 {
|
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.
|
/// 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 (selected_min, selected_max) = (selected_bounds.min(), selected_bounds.max());
|
||||||
let (hovered_min, hovered_max) = (hovered_bounds.min(), hovered_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_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);
|
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_bottom = selected_bounds.center().y > hovered_bounds.center().y;
|
||||||
let selected_on_right = selected_bounds.center().x > hovered_bounds.center().x;
|
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 selected_center = selected_bounds.center();
|
||||||
let hovered_center = hovered_bounds.center();
|
let hovered_center = hovered_bounds.center();
|
||||||
|
|
||||||
let overlap_y = does_overlap_y(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);
|
let overlap_x = does_overlap_x(selected_bounds, hovered_bounds) || does_overlap_x(hovered_bounds, selected_bounds);
|
||||||
|
|
||||||
if overlap_y {
|
if overlap_y {
|
||||||
let selected_facing_edge = if hovered_max.y < selected_min.y { selected_min.y } else { selected_max.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.
|
/// 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) {
|
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 (selected_min, selected_max) = (selected_bounds.min(), selected_bounds.max());
|
||||||
let (hovered_min, hovered_max) = (hovered_bounds.min(), hovered_bounds.max());
|
let (hovered_min, hovered_max) = (hovered_bounds.min(), hovered_bounds.max());
|
||||||
|
|
||||||
// Determine axis overlaps
|
// Determine axis overlaps
|
||||||
let overlap_x = selected_min.x <= hovered_max.x && selected_max.x >= hovered_min.x;
|
let overlap_y = does_overlap_y(selected_bounds, hovered_bounds) || does_overlap_y(hovered_bounds, selected_bounds);
|
||||||
let overlap_y = selected_min.y <= hovered_max.y && selected_max.y >= hovered_min.y;
|
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) {
|
let overlap_axes = match (overlap_x, overlap_y) {
|
||||||
(true, true) => 2,
|
(true, true) => 2,
|
||||||
(true, false) | (false, true) => 1,
|
(true, false) | (false, true) => 1,
|
||||||
|
|
|
||||||
|
|
@ -544,13 +544,56 @@ impl Fsm for SelectToolFsmState {
|
||||||
})
|
})
|
||||||
.reduce(graphene_core::renderer::Quad::combine_bounds);
|
.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 {
|
if let Some(bounds) = bounds {
|
||||||
let bounding_box_manager = tool_data.bounding_box_manager.get_or_insert(BoundingBoxManager::default());
|
let bounding_box_manager = tool_data.bounding_box_manager.get_or_insert(BoundingBoxManager::default());
|
||||||
|
|
||||||
bounding_box_manager.bounds = bounds;
|
bounding_box_manager.bounds = bounds;
|
||||||
bounding_box_manager.transform = transform;
|
bounding_box_manager.transform = transform;
|
||||||
bounding_box_manager.transform_tampered = transform_tampered;
|
bounding_box_manager.transform_tampered = transform_tampered;
|
||||||
|
|
||||||
bounding_box_manager.render_overlays(&mut overlay_context);
|
bounding_box_manager.render_overlays(&mut overlay_context);
|
||||||
} else {
|
} else {
|
||||||
tool_data.bounding_box_manager.take();
|
tool_data.bounding_box_manager.take();
|
||||||
|
|
@ -722,29 +765,6 @@ impl Fsm for SelectToolFsmState {
|
||||||
(SelectionShapeType::Lasso, _) => overlay_context.polygon(polygon, fill_color),
|
(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
|
self
|
||||||
}
|
}
|
||||||
(_, SelectToolMessage::EditLayer) => {
|
(_, SelectToolMessage::EditLayer) => {
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue