File size: 3,474 Bytes
2409829
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
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,
		}
	}
}