1use std::collections::HashMap;
5
6#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
12pub enum NodeKind {
13 Entity,
14 Glyph,
15 ParticleEmitter,
16 ForceField,
17 Light,
18 Camera,
19 Group,
20 Folder,
21 Prefab,
22 Script,
23 Collider,
24 Path,
25 Custom(u32),
26}
27
28impl NodeKind {
29 pub fn icon(self) -> char {
31 match self {
32 NodeKind::Entity => 'E',
33 NodeKind::Glyph => 'G',
34 NodeKind::ParticleEmitter => 'P',
35 NodeKind::ForceField => 'F',
36 NodeKind::Light => 'L',
37 NodeKind::Camera => 'C',
38 NodeKind::Group => '+',
39 NodeKind::Folder => 'D',
40 NodeKind::Prefab => '*',
41 NodeKind::Script => 'S',
42 NodeKind::Collider => 'X',
43 NodeKind::Path => '~',
44 NodeKind::Custom(_) => '?',
45 }
46 }
47
48 pub fn label(self) -> &'static str {
49 match self {
50 NodeKind::Entity => "Entity",
51 NodeKind::Glyph => "Glyph",
52 NodeKind::ParticleEmitter => "Particles",
53 NodeKind::ForceField => "Force Field",
54 NodeKind::Light => "Light",
55 NodeKind::Camera => "Camera",
56 NodeKind::Group => "Group",
57 NodeKind::Folder => "Folder",
58 NodeKind::Prefab => "Prefab",
59 NodeKind::Script => "Script",
60 NodeKind::Collider => "Collider",
61 NodeKind::Path => "Path",
62 NodeKind::Custom(_) => "Custom",
63 }
64 }
65}
66
67#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default, PartialOrd, Ord)]
73pub struct NodeId(pub u32);
74
75#[derive(Debug, Clone)]
81pub struct HierarchyNode {
82 pub id: NodeId,
83 pub name: String,
84 pub kind: NodeKind,
85 pub parent: Option<NodeId>,
86 pub children: Vec<NodeId>,
87 pub expanded: bool,
88 pub visible: bool,
89 pub locked: bool,
90 pub is_prefab_root: bool,
91 pub prefab_name: Option<String>,
92 pub depth: u32,
94 pub sibling_index: usize,
96}
97
98impl HierarchyNode {
99 pub fn new(id: NodeId, name: impl Into<String>, kind: NodeKind) -> Self {
100 Self {
101 id,
102 name: name.into(),
103 kind,
104 parent: None,
105 children: Vec::new(),
106 expanded: true,
107 visible: true,
108 locked: false,
109 is_prefab_root: false,
110 prefab_name: None,
111 depth: 0,
112 sibling_index: 0,
113 }
114 }
115
116 pub fn visibility_icon(&self) -> char {
117 if self.visible { 'O' } else { '-' }
118 }
119
120 pub fn lock_icon(&self) -> char {
121 if self.locked { '#' } else { ' ' }
122 }
123
124 pub fn expand_icon(&self) -> char {
125 if self.children.is_empty() {
126 ' '
127 } else if self.expanded {
128 'v'
129 } else {
130 '>'
131 }
132 }
133
134 pub fn render_line(&self) -> String {
136 let indent = " ".repeat(self.depth as usize);
137 format!(
138 "{}{} [{}]{} {} {}",
139 indent,
140 self.expand_icon(),
141 self.kind.icon(),
142 if self.is_prefab_root { "*" } else { "" },
143 self.name,
144 if self.locked { "#" } else { "" },
145 )
146 }
147}
148
149#[derive(Debug, Clone)]
155pub struct PrefabNode {
156 pub name: String,
157 pub root_node: NodeId,
158 pub nodes: Vec<NodeId>,
159 pub overrides: HashMap<NodeId, HashMap<String, String>>,
160}
161
162impl PrefabNode {
163 pub fn new(name: impl Into<String>, root: NodeId) -> Self {
164 Self {
165 name: name.into(),
166 root_node: root,
167 nodes: vec![root],
168 overrides: HashMap::new(),
169 }
170 }
171 pub fn add_override(&mut self, id: NodeId, prop: impl Into<String>, val: impl Into<String>) {
172 self.overrides.entry(id).or_default().insert(prop.into(), val.into());
173 }
174 pub fn get_override(&self, id: NodeId, prop: &str) -> Option<&str> {
175 self.overrides.get(&id)?.get(prop).map(|s| s.as_str())
176 }
177}
178
179#[derive(Debug, Clone)]
185pub enum HierarchyAction {
186 AddNode { id: NodeId, parent: Option<NodeId>, name: String, kind: NodeKind },
187 RemoveNode { id: NodeId, saved_node: Option<HierarchyNode>, saved_children: Vec<NodeId> },
188 RenameNode { id: NodeId, old_name: String, new_name: String },
189 Reparent { child: NodeId, old_parent: Option<NodeId>, new_parent: Option<NodeId>, old_index: usize, new_index: usize },
190 ReorderSibling { parent: Option<NodeId>, from_index: usize, to_index: usize },
191 SetVisible { id: NodeId, old: bool, new: bool },
192 SetLocked { id: NodeId, old: bool, new: bool },
193 MakePrefab { id: NodeId, prefab_name: String },
194 DuplicateSubtree { original: NodeId, new_root: NodeId, all_new_ids: Vec<NodeId> },
195 GroupNodes { group_id: NodeId, members: Vec<NodeId>, old_parents: Vec<Option<NodeId>> },
196 UngroupNodes { group_id: NodeId, members: Vec<NodeId> },
197}
198
199pub struct HierarchySerializer;
205
206impl HierarchySerializer {
207 pub fn serialize(panel: &HierarchyPanel) -> String {
209 let mut out = String::new();
210 let roots: Vec<NodeId> = panel.nodes.values()
211 .filter(|n| n.parent.is_none())
212 .map(|n| n.id)
213 .collect();
214 let mut sorted_roots: Vec<&HierarchyNode> = roots.iter()
215 .filter_map(|id| panel.nodes.get(id))
216 .collect();
217 sorted_roots.sort_by_key(|n| n.sibling_index);
218 for node in sorted_roots {
219 Self::serialize_node(panel, node.id, &mut out);
220 }
221 out
222 }
223
224 fn serialize_node(panel: &HierarchyPanel, id: NodeId, out: &mut String) {
225 if let Some(node) = panel.nodes.get(&id) {
226 let indent = " ".repeat(node.depth as usize);
227 let visible = if node.visible { 'V' } else { 'H' };
228 let locked = if node.locked { 'L' } else { 'U' };
229 out.push_str(&format!(
230 "{}[{}][{}][{}] {}\n",
231 indent,
232 node.kind.icon(),
233 visible,
234 locked,
235 node.name,
236 ));
237 let mut children: Vec<&HierarchyNode> = node.children.iter()
238 .filter_map(|cid| panel.nodes.get(cid))
239 .collect();
240 children.sort_by_key(|n| n.sibling_index);
241 for child in children {
242 Self::serialize_node(panel, child.id, out);
243 }
244 }
245 }
246
247 pub fn deserialize(text: &str) -> (Vec<(String, NodeKind, u32)>, Vec<String>) {
249 let mut result = Vec::new();
250 let mut errors = Vec::new();
251 for (line_no, line) in text.lines().enumerate() {
252 if line.trim().is_empty() { continue; }
253 let depth = (line.len() - line.trim_start().len()) / 2;
255 let trimmed = line.trim();
256 if trimmed.starts_with('[') {
258 let parts: Vec<&str> = trimmed.splitn(5, ']').collect();
259 if parts.len() >= 4 {
260 let kind_char = parts[0].trim_start_matches('[');
261 let name = parts[3].trim().trim_start_matches(' ').to_string();
262 let kind = match kind_char.chars().next().unwrap_or(' ') {
263 'E' => NodeKind::Entity,
264 'G' => NodeKind::Glyph,
265 'P' => NodeKind::ParticleEmitter,
266 'F' => NodeKind::ForceField,
267 'L' => NodeKind::Light,
268 'C' => NodeKind::Camera,
269 '+' => NodeKind::Group,
270 'D' => NodeKind::Folder,
271 '*' => NodeKind::Prefab,
272 _ => NodeKind::Entity,
273 };
274 result.push((name, kind, depth as u32));
275 } else {
276 errors.push(format!("Line {}: malformed node '{}'", line_no + 1, trimmed));
277 }
278 } else {
279 errors.push(format!("Line {}: unexpected format", line_no + 1));
280 }
281 }
282 (result, errors)
283 }
284}
285
286#[derive(Debug, Clone, Default)]
292pub struct DragDropState {
293 pub dragging: Option<NodeId>,
294 pub hover_target: Option<NodeId>,
295 pub hover_position: DropPosition,
296 pub drag_start_y: f32,
297 pub current_y: f32,
298}
299
300#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
302pub enum DropPosition {
303 #[default]
304 Before,
305 After,
306 Inside,
307}
308
309impl DragDropState {
310 pub fn start_drag(&mut self, id: NodeId, y: f32) {
311 self.dragging = Some(id);
312 self.drag_start_y = y;
313 self.current_y = y;
314 }
315
316 pub fn update(&mut self, y: f32, target: Option<NodeId>, pos: DropPosition) {
317 self.current_y = y;
318 self.hover_target = target;
319 self.hover_position = pos;
320 }
321
322 pub fn finish(&mut self) -> Option<(NodeId, Option<NodeId>, DropPosition)> {
323 let drag = self.dragging.take()?;
324 let target = self.hover_target.take();
325 let pos = self.hover_position;
326 Some((drag, target, pos))
327 }
328
329 pub fn cancel(&mut self) {
330 self.dragging = None;
331 self.hover_target = None;
332 }
333
334 pub fn is_dragging(&self) -> bool {
335 self.dragging.is_some()
336 }
337}
338
339#[derive(Debug, Clone, Default)]
345pub struct KeyboardNavState {
346 pub focused_index: usize, pub rename_mode: bool,
348 pub rename_buffer: String,
349}
350
351impl KeyboardNavState {
352 pub fn move_up(&mut self, visible_count: usize) {
353 if visible_count == 0 { return; }
354 if self.focused_index > 0 {
355 self.focused_index -= 1;
356 }
357 }
358 pub fn move_down(&mut self, visible_count: usize) {
359 if visible_count == 0 { return; }
360 if self.focused_index + 1 < visible_count {
361 self.focused_index += 1;
362 }
363 }
364 pub fn begin_rename(&mut self, current_name: &str) {
365 self.rename_mode = true;
366 self.rename_buffer = current_name.to_string();
367 }
368 pub fn finish_rename(&mut self) -> Option<String> {
369 if self.rename_mode {
370 self.rename_mode = false;
371 Some(std::mem::take(&mut self.rename_buffer))
372 } else {
373 None
374 }
375 }
376 pub fn cancel_rename(&mut self) {
377 self.rename_mode = false;
378 self.rename_buffer.clear();
379 }
380}
381
382pub struct HierarchyPanel {
388 pub nodes: HashMap<NodeId, HierarchyNode>,
389 pub roots: Vec<NodeId>,
390 pub selected: Vec<NodeId>,
391 pub prefabs: HashMap<String, PrefabNode>,
392
393 next_id: u32,
395 undo_stack: Vec<HierarchyAction>,
396 redo_stack: Vec<HierarchyAction>,
397 pub drag_drop: DragDropState,
398 pub keyboard: KeyboardNavState,
399 pub search_query: String,
400 pub filter_kind: Option<NodeKind>,
401 flat_list: Vec<NodeId>,
403 flat_dirty: bool,
404}
405
406impl HierarchyPanel {
407 pub fn new() -> Self {
408 Self {
409 nodes: HashMap::new(),
410 roots: Vec::new(),
411 selected: Vec::new(),
412 prefabs: HashMap::new(),
413 next_id: 1,
414 undo_stack: Vec::new(),
415 redo_stack: Vec::new(),
416 drag_drop: DragDropState::default(),
417 keyboard: KeyboardNavState::default(),
418 search_query: String::new(),
419 filter_kind: None,
420 flat_list: Vec::new(),
421 flat_dirty: true,
422 }
423 }
424
425 fn alloc_id(&mut self) -> NodeId {
428 let id = NodeId(self.next_id);
429 self.next_id += 1;
430 id
431 }
432
433 pub fn add_node(
437 &mut self,
438 name: impl Into<String>,
439 kind: NodeKind,
440 parent: Option<NodeId>,
441 ) -> NodeId {
442 let id = self.alloc_id();
443 let name_s: String = name.into();
444 let depth = parent
445 .and_then(|p| self.nodes.get(&p))
446 .map(|p| p.depth + 1)
447 .unwrap_or(0);
448 let sibling_index = parent
449 .and_then(|p| self.nodes.get(&p))
450 .map(|p| p.children.len())
451 .unwrap_or(self.roots.len());
452
453 let mut node = HierarchyNode::new(id, name_s.clone(), kind);
454 node.parent = parent;
455 node.depth = depth;
456 node.sibling_index = sibling_index;
457
458 self.nodes.insert(id, node);
459
460 if let Some(pid) = parent {
461 if let Some(p) = self.nodes.get_mut(&pid) {
462 p.children.push(id);
463 }
464 } else {
465 self.roots.push(id);
466 }
467
468 self.undo_stack.push(HierarchyAction::AddNode {
469 id,
470 parent,
471 name: name_s,
472 kind,
473 });
474 self.redo_stack.clear();
475 self.flat_dirty = true;
476 id
477 }
478
479 pub fn delete_recursive(&mut self, id: NodeId) {
481 if !self.nodes.contains_key(&id) { return; }
482 let mut to_remove = Vec::new();
484 self.collect_descendants(id, &mut to_remove);
485 to_remove.push(id);
486
487 let parent = self.nodes.get(&id).and_then(|n| n.parent);
489 if let Some(pid) = parent {
490 if let Some(p) = self.nodes.get_mut(&pid) {
491 p.children.retain(|&c| c != id);
492 }
493 } else {
494 self.roots.retain(|&r| r != id);
495 }
496
497 let saved = self.nodes.get(&id).cloned();
499 let saved_children = self.nodes.get(&id).map(|n| n.children.clone()).unwrap_or_default();
500
501 for rid in &to_remove {
502 self.nodes.remove(rid);
503 self.selected.retain(|&s| &s != rid);
504 }
505
506 self.undo_stack.push(HierarchyAction::RemoveNode {
507 id,
508 saved_node: saved,
509 saved_children,
510 });
511 self.redo_stack.clear();
512 self.flat_dirty = true;
513 }
514
515 fn collect_descendants(&self, id: NodeId, out: &mut Vec<NodeId>) {
516 if let Some(node) = self.nodes.get(&id) {
517 for &child in &node.children {
518 self.collect_descendants(child, out);
519 out.push(child);
520 }
521 }
522 }
523
524 pub fn rename(&mut self, id: NodeId, new_name: impl Into<String>) {
527 let new_name: String = new_name.into();
528 if let Some(node) = self.nodes.get_mut(&id) {
529 let old_name = std::mem::replace(&mut node.name, new_name.clone());
530 self.undo_stack.push(HierarchyAction::RenameNode { id, old_name, new_name });
531 self.redo_stack.clear();
532 self.flat_dirty = true;
533 }
534 }
535
536 pub fn reparent(&mut self, child: NodeId, new_parent: Option<NodeId>) {
540 if !self.nodes.contains_key(&child) { return; }
541 if let Some(np) = new_parent {
543 if np == child { return; }
544 let mut anc = Some(np);
545 while let Some(a) = anc {
546 if a == child { return; } anc = self.nodes.get(&a).and_then(|n| n.parent);
548 }
549 }
550
551 let old_parent = self.nodes[&child].parent;
552 let old_index = self.nodes[&child].sibling_index;
553
554 if let Some(op) = old_parent {
556 if let Some(p) = self.nodes.get_mut(&op) {
557 p.children.retain(|&c| c != child);
558 }
559 let siblings: Vec<NodeId> = self.nodes.get(&op)
561 .map(|p| p.children.clone())
562 .unwrap_or_default();
563 for (i, cid) in siblings.into_iter().enumerate() {
564 if let Some(cn) = self.nodes.get_mut(&cid) {
565 cn.sibling_index = i;
566 }
567 }
568 } else {
569 self.roots.retain(|&r| r != child);
570 }
571
572 let new_index = if let Some(np) = new_parent {
574 let len = self.nodes.get(&np).map(|n| n.children.len()).unwrap_or(0);
575 if let Some(p) = self.nodes.get_mut(&np) {
576 p.children.push(child);
577 }
578 len
579 } else {
580 let len = self.roots.len();
581 self.roots.push(child);
582 len
583 };
584
585 let new_depth = new_parent
587 .and_then(|p| self.nodes.get(&p))
588 .map(|p| p.depth + 1)
589 .unwrap_or(0);
590 if let Some(cn) = self.nodes.get_mut(&child) {
591 cn.parent = new_parent;
592 cn.sibling_index = new_index;
593 }
594 self.update_depths(child, new_depth);
596
597 self.undo_stack.push(HierarchyAction::Reparent {
598 child,
599 old_parent,
600 new_parent,
601 old_index,
602 new_index,
603 });
604 self.redo_stack.clear();
605 self.flat_dirty = true;
606 }
607
608 fn update_depths(&mut self, id: NodeId, depth: u32) {
609 if let Some(node) = self.nodes.get_mut(&id) {
610 node.depth = depth;
611 }
612 let children: Vec<NodeId> = self.nodes.get(&id)
613 .map(|n| n.children.clone())
614 .unwrap_or_default();
615 for child in children {
616 self.update_depths(child, depth + 1);
617 }
618 }
619
620 pub fn duplicate_subtree(&mut self, id: NodeId) -> Option<NodeId> {
624 if !self.nodes.contains_key(&id) { return None; }
625 let parent = self.nodes[&id].parent;
626 let mut id_map: HashMap<NodeId, NodeId> = HashMap::new();
627 let new_root = self.clone_node_recursive(id, parent, &mut id_map);
628 let all_new: Vec<NodeId> = id_map.values().copied().collect();
629 self.undo_stack.push(HierarchyAction::DuplicateSubtree {
630 original: id,
631 new_root,
632 all_new_ids: all_new,
633 });
634 self.redo_stack.clear();
635 self.flat_dirty = true;
636 Some(new_root)
637 }
638
639 fn clone_node_recursive(
640 &mut self,
641 src: NodeId,
642 parent: Option<NodeId>,
643 id_map: &mut HashMap<NodeId, NodeId>,
644 ) -> NodeId {
645 let new_id = self.alloc_id();
646 id_map.insert(src, new_id);
647
648 let src_node = self.nodes[&src].clone();
649 let depth = parent.and_then(|p| self.nodes.get(&p)).map(|p| p.depth + 1).unwrap_or(0);
650 let sibling_index = parent
651 .and_then(|p| self.nodes.get(&p))
652 .map(|p| p.children.len())
653 .unwrap_or(self.roots.len());
654
655 let mut new_node = HierarchyNode::new(
656 new_id,
657 format!("{} (copy)", src_node.name),
658 src_node.kind,
659 );
660 new_node.parent = parent;
661 new_node.depth = depth;
662 new_node.sibling_index = sibling_index;
663 new_node.visible = src_node.visible;
664 new_node.locked = src_node.locked;
665
666 self.nodes.insert(new_id, new_node);
667
668 if let Some(pid) = parent {
669 if let Some(p) = self.nodes.get_mut(&pid) {
670 p.children.push(new_id);
671 }
672 } else {
673 self.roots.push(new_id);
674 }
675
676 let children: Vec<NodeId> = src_node.children.clone();
677 for child in children {
678 self.clone_node_recursive(child, Some(new_id), id_map);
679 }
680 new_id
681 }
682
683 pub fn expand_all(&mut self) {
686 for node in self.nodes.values_mut() {
687 node.expanded = true;
688 }
689 self.flat_dirty = true;
690 }
691
692 pub fn collapse_all(&mut self) {
693 for node in self.nodes.values_mut() {
694 node.expanded = false;
695 }
696 self.flat_dirty = true;
697 }
698
699 pub fn expand_to(&mut self, id: NodeId) {
701 let mut current = self.nodes.get(&id).and_then(|n| n.parent);
702 while let Some(pid) = current {
703 if let Some(n) = self.nodes.get_mut(&pid) {
704 n.expanded = true;
705 current = n.parent;
706 } else {
707 break;
708 }
709 }
710 self.flat_dirty = true;
711 }
712
713 pub fn set_visible(&mut self, id: NodeId, visible: bool) {
716 if let Some(node) = self.nodes.get_mut(&id) {
717 let old = node.visible;
718 node.visible = visible;
719 self.undo_stack.push(HierarchyAction::SetVisible { id, old, new: visible });
720 self.redo_stack.clear();
721 }
722 }
723
724 pub fn toggle_visible(&mut self, id: NodeId) {
725 let vis = self.nodes.get(&id).map(|n| !n.visible).unwrap_or(false);
726 self.set_visible(id, vis);
727 }
728
729 pub fn set_locked(&mut self, id: NodeId, locked: bool) {
730 if let Some(node) = self.nodes.get_mut(&id) {
731 let old = node.locked;
732 node.locked = locked;
733 self.undo_stack.push(HierarchyAction::SetLocked { id, old, new: locked });
734 self.redo_stack.clear();
735 }
736 }
737
738 pub fn toggle_locked(&mut self, id: NodeId) {
739 let locked = self.nodes.get(&id).map(|n| !n.locked).unwrap_or(false);
740 self.set_locked(id, locked);
741 }
742
743 pub fn set_search(&mut self, query: impl Into<String>) {
746 self.search_query = query.into();
747 self.flat_dirty = true;
748 }
749
750 pub fn clear_search(&mut self) {
751 self.search_query.clear();
752 self.flat_dirty = true;
753 }
754
755 pub fn set_kind_filter(&mut self, kind: Option<NodeKind>) {
756 self.filter_kind = kind;
757 self.flat_dirty = true;
758 }
759
760 fn node_matches(&self, node: &HierarchyNode) -> bool {
761 let name_ok = self.search_query.is_empty()
762 || node.name.to_lowercase().contains(&self.search_query.to_lowercase());
763 let kind_ok = self.filter_kind.map(|k| k == node.kind).unwrap_or(true);
764 name_ok && kind_ok
765 }
766
767 pub fn filter_by_name(&self, query: &str) -> Vec<NodeId> {
768 self.nodes.values()
769 .filter(|n| n.name.to_lowercase().contains(&query.to_lowercase()))
770 .map(|n| n.id)
771 .collect()
772 }
773
774 pub fn filter_by_kind(&self, kind: NodeKind) -> Vec<NodeId> {
775 self.nodes.values()
776 .filter(|n| n.kind == kind)
777 .map(|n| n.id)
778 .collect()
779 }
780
781 pub fn select(&mut self, id: NodeId) {
784 self.selected.clear();
785 self.selected.push(id);
786 self.rebuild_flat_if_dirty();
788 if let Some(pos) = self.flat_list.iter().position(|&x| x == id) {
789 self.keyboard.focused_index = pos;
790 }
791 }
792
793 pub fn toggle_select(&mut self, id: NodeId) {
794 if let Some(pos) = self.selected.iter().position(|&s| s == id) {
795 self.selected.remove(pos);
796 } else {
797 self.selected.push(id);
798 }
799 }
800
801 pub fn range_select(&mut self, id: NodeId) {
803 self.rebuild_flat_if_dirty();
804 let anchor = self.selected.first().copied();
805 let Some(target_idx) = self.flat_list.iter().position(|&x| x == id) else { return; };
806 let anchor_idx = anchor
807 .and_then(|a| self.flat_list.iter().position(|&x| x == a))
808 .unwrap_or(target_idx);
809 let (lo, hi) = if anchor_idx <= target_idx {
810 (anchor_idx, target_idx)
811 } else {
812 (target_idx, anchor_idx)
813 };
814 self.selected = self.flat_list[lo..=hi].to_vec();
815 }
816
817 pub fn select_all(&mut self) {
818 self.selected = self.nodes.keys().copied().collect();
819 }
820
821 pub fn deselect_all(&mut self) {
822 self.selected.clear();
823 }
824
825 pub fn is_selected(&self, id: NodeId) -> bool {
826 self.selected.contains(&id)
827 }
828
829 pub fn make_prefab(&mut self, id: NodeId, prefab_name: impl Into<String>) -> String {
832 let name: String = prefab_name.into();
833 let nodes: Vec<NodeId> = {
834 let mut all = Vec::new();
835 self.collect_descendants(id, &mut all);
836 all.push(id);
837 all
838 };
839 let prefab = PrefabNode { name: name.clone(), root_node: id, nodes, overrides: HashMap::new() };
840 self.prefabs.insert(name.clone(), prefab);
841 if let Some(node) = self.nodes.get_mut(&id) {
842 node.is_prefab_root = true;
843 node.prefab_name = Some(name.clone());
844 }
845 self.undo_stack.push(HierarchyAction::MakePrefab { id, prefab_name: name.clone() });
846 self.redo_stack.clear();
847 name
848 }
849
850 pub fn instantiate_prefab(&mut self, prefab_name: &str, parent: Option<NodeId>) -> Option<NodeId> {
851 let prefab = self.prefabs.get(prefab_name)?.clone();
852 let root = prefab.root_node;
853 Some(self.clone_node_recursive(root, parent, &mut HashMap::new()))
854 }
855
856 pub fn group_selected(&mut self, group_name: impl Into<String>) -> Option<NodeId> {
860 if self.selected.is_empty() { return None; }
861 let members: Vec<NodeId> = self.selected.clone();
862
863 let common_parent = self.nodes.get(&members[0]).and_then(|n| n.parent);
865 let old_parents: Vec<Option<NodeId>> = members.iter()
866 .map(|&m| self.nodes.get(&m).and_then(|n| n.parent))
867 .collect();
868
869 let group_id = self.add_node(group_name, NodeKind::Group, common_parent);
870
871 let members_clone = members.clone();
873 for m in members_clone {
874 self.reparent(m, Some(group_id));
875 }
876
877 self.undo_stack.push(HierarchyAction::GroupNodes {
880 group_id,
881 members,
882 old_parents,
883 });
884 self.redo_stack.clear();
885 Some(group_id)
886 }
887
888 fn rebuild_flat_if_dirty(&mut self) {
891 if !self.flat_dirty { return; }
892 self.flat_list.clear();
893 let roots = self.roots.clone();
894 for root in &roots {
895 self.flatten_node(*root, &mut Vec::new());
896 }
897 self.flat_dirty = false;
898 }
899
900 fn flatten_node(&mut self, id: NodeId, accumulator: &mut Vec<NodeId>) {
901 let (matches, expanded, children) = {
902 let node = match self.nodes.get(&id) { Some(n) => n, None => return };
903 (self.node_matches(node), node.expanded, node.children.clone())
904 };
905 if matches {
906 self.flat_list.push(id);
907 }
908 if expanded {
909 for child in children {
910 self.flatten_node(child, accumulator);
911 }
912 }
913 }
914
915 pub fn visible_flat_list(&mut self) -> &[NodeId] {
917 self.rebuild_flat_if_dirty();
918 &self.flat_list
919 }
920
921 pub fn undo(&mut self) -> bool {
924 let action = match self.undo_stack.pop() {
925 Some(a) => a,
926 None => return false,
927 };
928 match &action {
929 HierarchyAction::AddNode { id, .. } => {
930 self.nodes.remove(id);
931 self.roots.retain(|&r| r != *id);
932 self.flat_dirty = true;
933 }
934 HierarchyAction::RemoveNode { id, saved_node, .. } => {
935 if let Some(saved) = saved_node.clone() {
936 let parent = saved.parent;
937 self.nodes.insert(*id, saved);
938 if let Some(pid) = parent {
939 if let Some(p) = self.nodes.get_mut(&pid) {
940 p.children.push(*id);
941 }
942 } else {
943 self.roots.push(*id);
944 }
945 self.flat_dirty = true;
946 }
947 }
948 HierarchyAction::RenameNode { id, old_name, .. } => {
949 if let Some(node) = self.nodes.get_mut(id) {
950 node.name = old_name.clone();
951 self.flat_dirty = true;
952 }
953 }
954 HierarchyAction::SetVisible { id, old, .. } => {
955 if let Some(node) = self.nodes.get_mut(id) {
956 node.visible = *old;
957 }
958 }
959 HierarchyAction::SetLocked { id, old, .. } => {
960 if let Some(node) = self.nodes.get_mut(id) {
961 node.locked = *old;
962 }
963 }
964 HierarchyAction::Reparent { child, old_parent, new_parent, old_index, .. } => {
965 if let Some(np) = new_parent {
967 if let Some(p) = self.nodes.get_mut(np) {
968 p.children.retain(|&c| c != *child);
969 }
970 } else {
971 self.roots.retain(|&r| r != *child);
972 }
973 if let Some(op) = old_parent {
974 if let Some(p) = self.nodes.get_mut(op) {
975 let idx = (*old_index).min(p.children.len());
976 p.children.insert(idx, *child);
977 }
978 } else {
979 let idx = (*old_index).min(self.roots.len());
980 self.roots.insert(idx, *child);
981 }
982 if let Some(cn) = self.nodes.get_mut(child) {
983 cn.parent = *old_parent;
984 cn.sibling_index = *old_index;
985 }
986 let depth = old_parent
987 .and_then(|p| self.nodes.get(&p))
988 .map(|p| p.depth + 1)
989 .unwrap_or(0);
990 self.update_depths(*child, depth);
991 self.flat_dirty = true;
992 }
993 HierarchyAction::DuplicateSubtree { all_new_ids, .. } => {
994 for &nid in all_new_ids {
995 if let Some(node) = self.nodes.remove(&nid) {
996 if let Some(pid) = node.parent {
997 if let Some(p) = self.nodes.get_mut(&pid) {
998 p.children.retain(|&c| c != nid);
999 }
1000 } else {
1001 self.roots.retain(|&r| r != nid);
1002 }
1003 }
1004 }
1005 self.flat_dirty = true;
1006 }
1007 _ => {}
1008 }
1009 self.redo_stack.push(action);
1010 true
1011 }
1012
1013 pub fn redo(&mut self) -> bool {
1014 let action = match self.redo_stack.pop() {
1015 Some(a) => a,
1016 None => return false,
1017 };
1018 match &action {
1019 HierarchyAction::AddNode { id, parent, name, kind } => {
1020 let depth = parent.and_then(|p| self.nodes.get(&p)).map(|p| p.depth + 1).unwrap_or(0);
1021 let sibling_index = parent
1022 .and_then(|p| self.nodes.get(&p))
1023 .map(|p| p.children.len())
1024 .unwrap_or(self.roots.len());
1025 let mut node = HierarchyNode::new(*id, name.clone(), *kind);
1026 node.parent = *parent;
1027 node.depth = depth;
1028 node.sibling_index = sibling_index;
1029 self.nodes.insert(*id, node);
1030 if let Some(pid) = parent {
1031 if let Some(p) = self.nodes.get_mut(pid) { p.children.push(*id); }
1032 } else {
1033 self.roots.push(*id);
1034 }
1035 self.flat_dirty = true;
1036 }
1037 HierarchyAction::RenameNode { id, new_name, .. } => {
1038 if let Some(n) = self.nodes.get_mut(id) {
1039 n.name = new_name.clone();
1040 self.flat_dirty = true;
1041 }
1042 }
1043 HierarchyAction::SetVisible { id, new, .. } => {
1044 if let Some(n) = self.nodes.get_mut(id) { n.visible = *new; }
1045 }
1046 HierarchyAction::SetLocked { id, new, .. } => {
1047 if let Some(n) = self.nodes.get_mut(id) { n.locked = *new; }
1048 }
1049 _ => {}
1050 }
1051 self.undo_stack.push(action);
1052 true
1053 }
1054
1055 pub fn keyboard_up(&mut self) -> Option<NodeId> {
1059 let count = self.visible_flat_list().len();
1060 self.keyboard.move_up(count);
1061 self.flat_list.get(self.keyboard.focused_index).copied()
1062 }
1063
1064 pub fn keyboard_down(&mut self) -> Option<NodeId> {
1066 let count = self.visible_flat_list().len();
1067 self.keyboard.move_down(count);
1068 self.flat_list.get(self.keyboard.focused_index).copied()
1069 }
1070
1071 pub fn keyboard_begin_rename(&mut self) {
1073 if let Some(&id) = self.flat_list.get(self.keyboard.focused_index) {
1074 if let Some(node) = self.nodes.get(&id) {
1075 let name = node.name.clone();
1076 self.keyboard.begin_rename(&name);
1077 }
1078 }
1079 }
1080
1081 pub fn keyboard_finish_rename(&mut self) {
1083 if let Some(new_name) = self.keyboard.finish_rename() {
1084 if let Some(&id) = self.flat_list.get(self.keyboard.focused_index) {
1085 self.rename(id, new_name);
1086 }
1087 }
1088 }
1089
1090 pub fn keyboard_delete(&mut self) {
1092 if let Some(&id) = self.flat_list.get(self.keyboard.focused_index).cloned().as_ref() {
1093 self.delete_recursive(id);
1094 }
1095 }
1096
1097 pub fn begin_drag(&mut self, id: NodeId, y: f32) {
1100 self.drag_drop.start_drag(id, y);
1101 }
1102
1103 pub fn update_drag(&mut self, y: f32, hover: Option<NodeId>, pos: DropPosition) {
1104 self.drag_drop.update(y, hover, pos);
1105 }
1106
1107 pub fn finish_drag(&mut self) {
1108 if let Some((dragged, Some(target), pos)) = self.drag_drop.finish() {
1109 match pos {
1110 DropPosition::Inside => self.reparent(dragged, Some(target)),
1111 DropPosition::Before | DropPosition::After => {
1112 let new_parent = self.nodes.get(&target).and_then(|n| n.parent);
1113 self.reparent(dragged, new_parent);
1114 }
1115 }
1116 } else {
1117 self.drag_drop.cancel();
1118 }
1119 }
1120
1121 pub fn render_ascii(&mut self) -> String {
1124 self.rebuild_flat_if_dirty();
1125 let flat = self.flat_list.clone();
1126 let mut out = String::new();
1127 out.push_str("┌─ Hierarchy ──────────────────────────\n");
1128 for (i, &id) in flat.iter().enumerate() {
1129 if let Some(node) = self.nodes.get(&id) {
1130 let sel = if self.is_selected(id) { ">" } else { " " };
1131 let focus = if i == self.keyboard.focused_index { "*" } else { " " };
1132 out.push_str(&format!("{}{} {}\n", focus, sel, node.render_line()));
1133 }
1134 }
1135 out.push_str("└──────────────────────────────────────\n");
1136 out
1137 }
1138
1139 pub fn node_count(&self) -> usize {
1140 self.nodes.len()
1141 }
1142}
1143
1144impl Default for HierarchyPanel {
1145 fn default() -> Self { Self::new() }
1146}
1147
1148#[cfg(test)]
1153mod tests {
1154 use super::*;
1155
1156 fn panel_with_tree() -> (HierarchyPanel, NodeId, NodeId, NodeId) {
1157 let mut p = HierarchyPanel::new();
1158 let root = p.add_node("root", NodeKind::Entity, None);
1159 let child = p.add_node("child", NodeKind::Glyph, Some(root));
1160 let leaf = p.add_node("leaf", NodeKind::Glyph, Some(child));
1161 (p, root, child, leaf)
1162 }
1163
1164 #[test]
1165 fn test_add_and_count() {
1166 let (p, _, _, _) = panel_with_tree();
1167 assert_eq!(p.node_count(), 3);
1168 }
1169
1170 #[test]
1171 fn test_delete_recursive() {
1172 let (mut p, root, child, _leaf) = panel_with_tree();
1173 p.delete_recursive(child);
1174 assert!(!p.nodes.contains_key(&child));
1175 assert!(!p.nodes.contains_key(&_leaf));
1176 assert!(p.nodes.contains_key(&root));
1177 }
1178
1179 #[test]
1180 fn test_rename_undo() {
1181 let (mut p, _root, child, _leaf) = panel_with_tree();
1182 p.rename(child, "renamed");
1183 assert_eq!(p.nodes[&child].name, "renamed");
1184 p.undo();
1185 assert_eq!(p.nodes[&child].name, "child");
1186 }
1187
1188 #[test]
1189 fn test_reparent() {
1190 let (mut p, root, _child, leaf) = panel_with_tree();
1191 p.reparent(leaf, Some(root));
1192 assert_eq!(p.nodes[&leaf].parent, Some(root));
1193 assert_eq!(p.nodes[&leaf].depth, 1);
1194 }
1195
1196 #[test]
1197 fn test_reparent_no_cycle() {
1198 let (mut p, root, child, _leaf) = panel_with_tree();
1199 let depth_before = p.nodes[&root].depth;
1200 p.reparent(root, Some(child)); assert_eq!(p.nodes[&root].depth, depth_before);
1202 }
1203
1204 #[test]
1205 fn test_duplicate_subtree() {
1206 let (mut p, _root, child, _leaf) = panel_with_tree();
1207 let count_before = p.node_count();
1208 p.duplicate_subtree(child);
1209 assert!(p.node_count() > count_before);
1210 }
1211
1212 #[test]
1213 fn test_visibility_toggle() {
1214 let (mut p, root, _, _) = panel_with_tree();
1215 assert!(p.nodes[&root].visible);
1216 p.toggle_visible(root);
1217 assert!(!p.nodes[&root].visible);
1218 p.undo();
1219 assert!(p.nodes[&root].visible);
1220 }
1221
1222 #[test]
1223 fn test_search_filter() {
1224 let (p, _, _, _) = panel_with_tree();
1225 let found = p.filter_by_name("leaf");
1226 assert_eq!(found.len(), 1);
1227 let empty = p.filter_by_name("nonexistent");
1228 assert!(empty.is_empty());
1229 }
1230
1231 #[test]
1232 fn test_kind_filter() {
1233 let (p, _, _, _) = panel_with_tree();
1234 let glyphs = p.filter_by_kind(NodeKind::Glyph);
1235 assert_eq!(glyphs.len(), 2);
1236 }
1237
1238 #[test]
1239 fn test_selection_range() {
1240 let (mut p, root, child, leaf) = panel_with_tree();
1241 p.expand_all();
1242 p.rebuild_flat_if_dirty();
1244 p.select(root);
1245 p.range_select(leaf);
1246 assert_eq!(p.selected.len(), 3);
1247 }
1248
1249 #[test]
1250 fn test_prefab_creation() {
1251 let (mut p, _root, child, _leaf) = panel_with_tree();
1252 p.make_prefab(child, "my_prefab");
1253 assert!(p.prefabs.contains_key("my_prefab"));
1254 assert!(p.nodes[&child].is_prefab_root);
1255 }
1256
1257 #[test]
1258 fn test_serializer_roundtrip() {
1259 let (p, _, _, _) = panel_with_tree();
1260 let serialized = HierarchySerializer::serialize(&p);
1261 assert!(serialized.contains("root"));
1262 assert!(serialized.contains("child"));
1263 assert!(serialized.contains("leaf"));
1264 }
1265}