openfree's picture
Deploy from GitHub repository
2409829 verified
use crate::consts::{COMPASS_ROSE_ARROW_CLICK_TARGET_ANGLE, COMPASS_ROSE_HOVER_RING_DIAMETER, COMPASS_ROSE_RING_INNER_DIAMETER};
use crate::messages::portfolio::document::utility_types::document_metadata::LayerNodeIdentifier;
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 {
fn get_layer_pivot_transform(layer: LayerNodeIdentifier, document: &DocumentMessageHandler) -> DAffine2 {
let [min, max] = document.metadata().nonzero_bounding_box(layer);
let bounds_transform = DAffine2::from_translation(min) * DAffine2::from_scale(max - min);
let layer_transform = document.metadata().transform_to_viewport(layer);
layer_transform * bounds_transform
}
pub fn refresh_position(&mut self, document: &DocumentMessageHandler) {
let selected_nodes = document.network_interface.selected_nodes();
let mut layers = selected_nodes.selected_visible_and_unlocked_layers(&document.network_interface);
let Some(first) = layers.next() else { return };
let count = layers.count() + 1;
let transform = if count == 1 {
Self::get_layer_pivot_transform(first, document)
} else {
let [min, max] = document.selected_visible_and_unlock_layers_bounding_box_viewport().unwrap_or([DVec2::ZERO, DVec2::ONE]);
DAffine2::from_translation(min) * DAffine2::from_scale(max - min)
};
self.compass_center = transform.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)
}
}
impl From<Axis> for DVec2 {
fn from(value: Axis) -> Self {
match value {
Axis::X => DVec2::X,
Axis::Y => DVec2::Y,
Axis::None => DVec2::ZERO,
}
}
}