1use alloc::{string::String, vec::Vec};
2
3use scenix_core::NodeId;
4use scenix_math::Vec3;
5
6#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
8#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
9pub struct LayerMask(u32);
10
11impl LayerMask {
12 pub const NONE: Self = Self(0);
14 pub const ALL: Self = Self(u32::MAX);
16
17 #[inline]
19 pub const fn from_bits(bits: u32) -> Self {
20 Self(bits)
21 }
22
23 #[inline]
25 pub const fn bits(self) -> u32 {
26 self.0
27 }
28
29 #[inline]
31 pub const fn intersects(self, other: Self) -> bool {
32 self.0 & other.0 != 0
33 }
34
35 #[inline]
37 pub const fn matches_node(self, node_layers: u32) -> bool {
38 self.0 & node_layers != 0
39 }
40
41 #[inline]
43 pub fn insert(&mut self, other: Self) {
44 self.0 |= other.0;
45 }
46
47 #[inline]
49 pub fn remove(&mut self, other: Self) {
50 self.0 &= !other.0;
51 }
52}
53
54#[derive(Clone, Copy, Debug, PartialEq, Eq)]
56#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
57pub struct LayerPolicy {
58 pub selectable: LayerMask,
60 pub draggable: LayerMask,
62 pub transformable: LayerMask,
64}
65
66impl Default for LayerPolicy {
67 fn default() -> Self {
68 Self {
69 selectable: LayerMask::ALL,
70 draggable: LayerMask::ALL,
71 transformable: LayerMask::ALL,
72 }
73 }
74}
75
76#[derive(Clone, Debug, PartialEq, Eq)]
78#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
79pub struct NodeEditorMetadata {
80 pub selectable: bool,
82 pub draggable: bool,
84 pub transformable: bool,
86 pub locked: bool,
88 pub visible_in_inspector: bool,
90 pub label: Option<String>,
92 pub tags: Vec<String>,
94}
95
96impl Default for NodeEditorMetadata {
97 fn default() -> Self {
98 Self {
99 selectable: true,
100 draggable: true,
101 transformable: true,
102 locked: false,
103 visible_in_inspector: true,
104 label: None,
105 tags: Vec::new(),
106 }
107 }
108}
109
110#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
112#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
113pub enum SelectionMode {
114 #[default]
116 Replace,
117 Add,
119 Toggle,
121 Remove,
123}
124
125#[derive(Clone, Debug, Default, PartialEq, Eq)]
127#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
128pub struct SelectionState {
129 pub hovered: Option<NodeId>,
131 pub active: Option<NodeId>,
133 selected: Vec<NodeId>,
134}
135
136impl SelectionState {
137 #[inline]
139 pub fn selected(&self) -> &[NodeId] {
140 &self.selected
141 }
142
143 #[inline]
145 pub fn contains(&self, id: NodeId) -> bool {
146 self.selected.binary_search(&id).is_ok()
147 }
148}
149
150#[derive(Clone, Debug, Default, PartialEq, Eq)]
152#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
153pub struct SelectionChange {
154 pub added: Vec<NodeId>,
156 pub removed: Vec<NodeId>,
158}
159
160#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
162#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
163pub enum TransformMode {
164 #[default]
166 Translate,
167 Rotate,
169 Scale,
171}
172
173#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
175#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
176pub enum TransformSpace {
177 #[default]
179 World,
180 Local,
182}
183
184#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
186#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
187pub enum TransformConstraint {
188 #[default]
190 Free,
191 X,
193 Y,
195 Z,
197 XY,
199 XZ,
201 YZ,
203}
204
205impl TransformConstraint {
206 pub const fn component_mask(self) -> Vec3 {
208 match self {
209 Self::Free => Vec3::ONE,
210 Self::X => Vec3::X,
211 Self::Y => Vec3::Y,
212 Self::Z => Vec3::Z,
213 Self::XY => Vec3::new(1.0, 1.0, 0.0),
214 Self::XZ => Vec3::new(1.0, 0.0, 1.0),
215 Self::YZ => Vec3::new(0.0, 1.0, 1.0),
216 }
217 }
218
219 pub const fn axis(self) -> Option<Vec3> {
221 match self {
222 Self::X => Some(Vec3::X),
223 Self::Y => Some(Vec3::Y),
224 Self::Z => Some(Vec3::Z),
225 _ => None,
226 }
227 }
228}
229
230#[derive(Clone, Copy, Debug, Default, PartialEq)]
232#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
233pub struct SnapSettings {
234 pub translation: Vec3,
236 pub rotation_radians: f32,
238 pub scale: Vec3,
240}
241
242impl SnapSettings {
243 pub fn snap_translation(self, delta: Vec3) -> Vec3 {
245 snap_vec3(delta, self.translation)
246 }
247
248 pub fn snap_rotation(self, radians: f32) -> f32 {
250 snap_scalar(radians, self.rotation_radians)
251 }
252
253 pub fn snap_scale(self, delta: Vec3) -> Vec3 {
255 snap_vec3(delta, self.scale)
256 }
257}
258
259fn snap_scalar(value: f32, increment: f32) -> f32 {
260 if increment.is_finite() && increment.abs() > 1.0e-6 {
261 let increment = increment.abs();
262 let scaled = value / increment;
263 if !scaled.is_finite() {
264 return value;
265 }
266 let rounded = if scaled >= 0.0 {
267 (scaled + 0.5) as i64
268 } else {
269 (scaled - 0.5) as i64
270 };
271 rounded as f32 * increment
272 } else {
273 value
274 }
275}
276
277fn snap_vec3(value: Vec3, increments: Vec3) -> Vec3 {
278 Vec3::new(
279 snap_scalar(value.x, increments.x),
280 snap_scalar(value.y, increments.y),
281 snap_scalar(value.z, increments.z),
282 )
283}
284
285pub(crate) fn apply_selection(
286 state: &mut SelectionState,
287 id: NodeId,
288 mode: SelectionMode,
289) -> SelectionChange {
290 let previous = state.selected.clone();
291 match mode {
292 SelectionMode::Replace => {
293 state.selected.clear();
294 state.selected.push(id);
295 }
296 SelectionMode::Add => {
297 if let Err(index) = state.selected.binary_search(&id) {
298 state.selected.insert(index, id);
299 }
300 }
301 SelectionMode::Toggle => match state.selected.binary_search(&id) {
302 Ok(index) => {
303 state.selected.remove(index);
304 }
305 Err(index) => state.selected.insert(index, id),
306 },
307 SelectionMode::Remove => {
308 if let Ok(index) = state.selected.binary_search(&id) {
309 state.selected.remove(index);
310 }
311 }
312 }
313 selection_diff(&previous, &state.selected)
314}
315
316pub(crate) fn replace_selection(
317 state: &mut SelectionState,
318 mut selected: Vec<NodeId>,
319) -> SelectionChange {
320 selected.sort_unstable();
321 selected.dedup();
322 let previous = core::mem::replace(&mut state.selected, selected);
323 selection_diff(&previous, &state.selected)
324}
325
326fn selection_diff(previous: &[NodeId], current: &[NodeId]) -> SelectionChange {
327 SelectionChange {
328 added: current
329 .iter()
330 .copied()
331 .filter(|id| previous.binary_search(id).is_err())
332 .collect(),
333 removed: previous
334 .iter()
335 .copied()
336 .filter(|id| current.binary_search(id).is_err())
337 .collect(),
338 }
339}
340
341#[cfg(test)]
342mod tests {
343 use super::*;
344
345 #[test]
346 fn snap_settings_quantize_relative_deltas() {
347 let snap = SnapSettings {
348 translation: Vec3::new(0.5, 1.0, 0.0),
349 rotation_radians: 0.25,
350 scale: Vec3::new(0.1, 0.0, 0.5),
351 };
352 assert_eq!(
353 snap.snap_translation(Vec3::new(0.74, 1.6, 0.3)),
354 Vec3::new(0.5, 2.0, 0.3)
355 );
356 assert_eq!(snap.snap_rotation(0.38), 0.5);
357 }
358}