⎇ fa8dfd6ed33f76d296e8428ca68a0d3d527c295b
crates/apps/nahual-shell/src/layout_model.rs
History
Who touched what
raw
refactor(monorepo): reorganización lógica + renames + SDDs + split CHANGELOG
1//! `LayoutModel` — fuente de verdad mutable del árbol de layout.
2//!
3//! Distinguimos dos clases de cambio:
4//!
5//! - [`LayoutModelEvent::StructureChanged`]: cambios que requieren rebuild
6//! del árbol de entidades (kind, children, params relevantes). Estos
7//! también invocan `cx.notify()` para que los `cx.observe` (StatusPanel,
8//! etc.) refresquen.
9//! - [`LayoutModelEvent::FlexChanged`]: actualización de `flex` de un
10//! nodo (típicamente proviene de un drag de divisor). NO requiere
11//! rebuild — el SplitContainer ya tiene el flex aplicado en su Vec; solo
12//! nos importa para persistir. Por eso no llamamos `cx.notify()`: solo
13//! emitimos el evento, así los `cx.observe` (que rebuilden) se mantienen
14//! silenciosos durante el drag.
15//!
16//! El `Persister` (ver `shell/persister.rs`) se subscribe vía
17//! `cx.subscribe` y reacciona a los dos.
18
19use gpui::{Context, EventEmitter};
20
21use nahual_core::{LayerConfig, NodeId};
22
23#[derive(Clone, Debug)]
24pub enum LayoutModelEvent {
25 /// Estructural — kind / children / params. Triggerea rebuild en
26 /// `LayoutHost` y persist.
27 StructureChanged,
28 /// Solo flex de un nodo. Triggerea persist; NO rebuild.
29 FlexChanged,
30}
31
32pub struct LayoutModel {
33 tree: LayerConfig,
34}
35
36impl EventEmitter<LayoutModelEvent> for LayoutModel {}
37
38impl LayoutModel {
39 pub fn new(tree: LayerConfig) -> Self {
40 Self { tree }
41 }
42
43 pub fn tree(&self) -> &LayerConfig {
44 &self.tree
45 }
46
47 /// Reemplazo completo del árbol — para hot-reload del JSON.
48 pub fn replace_tree(&mut self, tree: LayerConfig, cx: &mut Context<Self>) {
49 self.tree = tree;
50 cx.emit(LayoutModelEvent::StructureChanged);
51 cx.notify();
52 }
53
54 /// Cambia el `kind` del nodo cuyo id JSON coincide con `target_id`.
55 pub fn set_kind(
56 &mut self,
57 target_id: &NodeId,
58 new_kind: &str,
59 cx: &mut Context<Self>,
60 ) {
61 let changed = mutate_node(&mut self.tree, target_id, &mut |node| {
62 if node.kind != new_kind {
63 node.kind = new_kind.to_string();
64 true
65 } else {
66 false
67 }
68 });
69 if changed {
70 cx.emit(LayoutModelEvent::StructureChanged);
71 cx.notify();
72 }
73 }
74
75 /// Setea el flex de un nodo. Solo emite `FlexChanged` (no notify) —
76 /// usado al final de un drag de divisor para persistir sin
77 /// triggerear rebuild.
78 pub fn set_flex(&mut self, target_id: &NodeId, flex: f32, cx: &mut Context<Self>) {
79 let new_val = Some(flex as f64);
80 let changed = mutate_node(&mut self.tree, target_id, &mut |node| {
81 if node.flex != new_val {
82 node.flex = new_val;
83 true
84 } else {
85 false
86 }
87 });
88 if changed {
89 cx.emit(LayoutModelEvent::FlexChanged);
90 }
91 }
92
93 /// Intercambia dos children del nodo `parent_id`. Triggerea
94 /// `StructureChanged` (rebuild + persist), porque cambia el orden de
95 /// instanciación. Si los índices son iguales o están out-of-bounds,
96 /// es no-op.
97 pub fn swap_children(
98 &mut self,
99 parent_id: &NodeId,
100 idx_a: usize,
101 idx_b: usize,
102 cx: &mut Context<Self>,
103 ) {
104 if idx_a == idx_b {
105 return;
106 }
107 let mut did_swap = false;
108 mutate_node(&mut self.tree, parent_id, &mut |node| {
109 if idx_a < node.children.len() && idx_b < node.children.len() {
110 node.children.swap(idx_a, idx_b);
111 did_swap = true;
112 true
113 } else {
114 false
115 }
116 });
117 if did_swap {
118 cx.emit(LayoutModelEvent::StructureChanged);
119 cx.notify();
120 }
121 }
122}
123
124fn mutate_node(
125 node: &mut LayerConfig,
126 target: &NodeId,
127 f: &mut impl FnMut(&mut LayerConfig) -> bool,
128) -> bool {
129 if let Some(id) = &node.id {
130 if id == target.as_str() {
131 return f(node);
132 }
133 }
134 for child in node.children.iter_mut() {
135 if mutate_node(child, target, f) {
136 return true;
137 }
138 }
139 false
140}