Add the compass rose translation gizmo to the transform cage (#2277)

* Rotate pivot and squares to orient along quad

* Add compass rose UI

* Add compass rose functionality

* Refactor code and polish things

* Fix UI

* Fix crash

* More polish

* Rework arrow to use different selection method

* Adjust for rotated layer and show when within cage

* Don't show when other modes are possible

* Fix glitchy compass

* fixes

* fixes

* WIP separate pivot and compass rose (not compiling)

* Complete file moving fixes

* Code review

---------

Co-authored-by: Keavon Chambers <keavon@keavon.com>
This commit is contained in:
mTvare
2025-02-14 22:29:32 -08:00
committed by GitHub
co-authored by Keavon Chambers
parent e44c460cf8
commit 70b4beab49
8 changed files with 343 additions and 40 deletions
@@ -0,0 +1,85 @@
use crate::consts::{COMPASS_ROSE_ARROW_CLICK_TARGET_ANGLE, COMPASS_ROSE_HOVER_RING_DIAMETER, COMPASS_ROSE_RING_INNER_DIAMETER};
use crate::messages::prelude::DocumentMessageHandler;
use glam::{DAffine2, DVec2};
use std::f64::consts::FRAC_PI_2;
#[derive(Clone, Default, Debug)]
pub struct CompassRose {
compass_center: DVec2,
}
impl CompassRose {
pub fn refresh_transform(&mut self, document: &DocumentMessageHandler) {
let [min, max] = document.selected_visible_and_unlock_layers_bounding_box_viewport().unwrap_or([DVec2::ZERO, DVec2::ONE]);
self.compass_center = (DAffine2::from_translation(min) * DAffine2::from_scale(max - min)).transform_point2(DVec2::splat(0.5));
}
pub fn compass_rose_position(&self) -> DVec2 {
self.compass_center
}
pub fn compass_rose_state(&self, mouse: DVec2, angle: f64) -> CompassRoseState {
const COMPASS_ROSE_RING_INNER_RADIUS_SQUARED: f64 = (COMPASS_ROSE_RING_INNER_DIAMETER / 2.) * (COMPASS_ROSE_RING_INNER_DIAMETER / 2.);
const COMPASS_ROSE_HOVER_RING_RADIUS_SQUARED: f64 = (COMPASS_ROSE_HOVER_RING_DIAMETER / 2.) * (COMPASS_ROSE_HOVER_RING_DIAMETER / 2.);
let compass_distance_squared = mouse.distance_squared(self.compass_center);
if !(COMPASS_ROSE_RING_INNER_RADIUS_SQUARED..COMPASS_ROSE_HOVER_RING_RADIUS_SQUARED).contains(&compass_distance_squared) {
return CompassRoseState::None;
}
let angle = (mouse - self.compass_center).angle_to(DVec2::from_angle(angle)).abs();
let resolved_angle = (FRAC_PI_2 - angle).abs();
let angular_width = COMPASS_ROSE_ARROW_CLICK_TARGET_ANGLE.to_radians();
if resolved_angle < angular_width {
CompassRoseState::AxisY
} else if resolved_angle > (FRAC_PI_2 - angular_width) {
CompassRoseState::AxisX
} else {
CompassRoseState::Ring
}
}
}
#[derive(Clone, Debug, PartialEq)]
pub enum CompassRoseState {
Ring,
AxisX,
AxisY,
None,
}
impl CompassRoseState {
pub fn can_grab(&self) -> bool {
matches!(self, Self::Ring | Self::AxisX | Self::AxisY)
}
pub fn is_ring(&self) -> bool {
matches!(self, Self::Ring)
}
pub fn axis_type(&self) -> Option<Axis> {
match self {
CompassRoseState::AxisX => Some(Axis::X),
CompassRoseState::AxisY => Some(Axis::Y),
CompassRoseState::Ring => Some(Axis::None),
_ => None,
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Default)]
pub enum Axis {
#[default]
None,
X,
Y,
}
impl Axis {
pub fn is_constraint(&self) -> bool {
matches!(self, Self::X | Self::Y)
}
}
@@ -1,5 +1,6 @@
pub mod auto_panning;
pub mod color_selector;
pub mod compass_rose;
pub mod graph_modification_utils;
pub mod measure;
pub mod pivot;
@@ -83,10 +83,10 @@ impl Pivot {
}
}
pub fn update_pivot(&mut self, document: &DocumentMessageHandler, overlay_context: &mut OverlayContext) {
pub fn update_pivot(&mut self, document: &DocumentMessageHandler, overlay_context: &mut OverlayContext, angle: f64) {
self.recalculate_pivot(document);
if let Some(pivot) = self.pivot {
overlay_context.pivot(pivot);
overlay_context.pivot(pivot, angle);
}
}
@@ -1,6 +1,6 @@
use crate::consts::{
BOUNDS_ROTATE_THRESHOLD, BOUNDS_SELECT_THRESHOLD, MAXIMUM_ALT_SCALE_FACTOR, MIN_LENGTH_FOR_CORNERS_VISIBILITY, MIN_LENGTH_FOR_MIDPOINT_VISIBILITY, MIN_LENGTH_FOR_RESIZE_TO_INCLUDE_INTERIOR,
SELECTION_DRAG_ANGLE,
BOUNDS_ROTATE_THRESHOLD, BOUNDS_SELECT_THRESHOLD, COLOR_OVERLAY_WHITE, MAXIMUM_ALT_SCALE_FACTOR, MIN_LENGTH_FOR_CORNERS_VISIBILITY, MIN_LENGTH_FOR_MIDPOINT_VISIBILITY,
MIN_LENGTH_FOR_RESIZE_TO_INCLUDE_INTERIOR, SELECTION_DRAG_ANGLE,
};
use crate::messages::frontend::utility_types::MouseCursorIcon;
use crate::messages::portfolio::document::overlays::utility_types::OverlayContext;
@@ -379,7 +379,10 @@ impl BoundingBoxManager {
// Draw the bounding box rectangle
overlay_context.quad(quad, None);
let mut draw_handle = |point: DVec2| overlay_context.square(point, Some(6.), None, None);
let mut draw_handle = |point: DVec2| {
let quad = DAffine2::from_angle_translation((quad.top_left() - quad.top_right()).to_angle(), point) * Quad::from_box([DVec2::splat(-3.), DVec2::splat(3.)]);
overlay_context.quad(quad, Some(COLOR_OVERLAY_WHITE));
};
// Draw the horizontal midpoint drag handles
if matches!(category, HandleDisplayCategory::Full | HandleDisplayCategory::Narrow | HandleDisplayCategory::ReducedLandscape) {