⎇ 53dbdf0f1deea10d6fc567ea2b43e1e75d6d60cd
crates/apps/yahweh-shell/src/managed_tree.rs
Ñawpa willay
Pi imata llamkarqan
ch'uya
chore: monorepo inicial con arje + minga + yahweh absorbidos
1//! `ManagedTree` — wrapper de `TreeView` que aporta su propio modelo de
2//! datos + estado de expansión. En Fase 3 sirve como stub data-driven (los
3//! datasets se eligen vía param `dataset` del JSON). En Fase 4 este patrón
4//! se concretiza en `FileExplorer` (TreeView + FsProvider) y
5//! `DatabaseExplorer` (TreeView + SqliteProvider).
6//!
7//! La entidad es completamente Render-able y se entrega al LayoutHost como
8//! un AnyView. Los TreeView events (RowClicked, ChevronToggled, …) se
9//! traducen acá en cambios al estado de expansión y luego se re-emiten
10//! como `ManagedTreeEvent` para que el host (LayoutHost o un App-bus
11//! futuro) los consuma.
12
13use std::collections::HashSet;
14
15use gpui::{
16 Context, Entity, EventEmitter, IntoElement, Render, SharedString, Window, div, prelude::*,
17};
18
19use yahweh_widget_tree::{RowId, RowKind, TreeEvent, TreeRow, TreeView};
20
21// =====================================================================
22// Datasets stub (Fase 3). En Fase 4 los reemplazan los providers reales.
23// =====================================================================
24
25#[derive(Clone)]
26pub struct DemoNode {
27 pub id: &'static str,
28 pub label: &'static str,
29 pub icon: &'static str,
30 pub children: Vec<DemoNode>,
31}
32
33impl DemoNode {
34 fn leaf(id: &'static str, label: &'static str, icon: &'static str) -> Self {
35 Self { id, label, icon, children: vec![] }
36 }
37 fn branch(id: &'static str, label: &'static str, children: Vec<Self>) -> Self {
38 Self { id, label, icon: "📁", children }
39 }
40}
41
42/// Resuelve el dataset por `key` (proveniente del param `dataset` del JSON).
43/// Cualquier key desconocida cae al stub vacío para no romper el render.
44pub fn dataset_for(key: &str) -> DemoNode {
45 match key {
46 "sources" => yahweh_sources_tree(),
47 "deps" => yahweh_deps_tree(),
48 _ => DemoNode {
49 id: "unknown",
50 label: "(dataset desconocido)",
51 icon: "❓",
52 children: vec![],
53 },
54 }
55}
56
57fn yahweh_sources_tree() -> DemoNode {
58 DemoNode::branch(
59 "src-root",
60 "yahweh (src)",
61 vec![
62 DemoNode::branch(
63 "shell",
64 "shell",
65 vec![DemoNode::leaf("shell/main.rs", "main.rs", "📄")],
66 ),
67 DemoNode::branch(
68 "widgets",
69 "widgets",
70 vec![
71 DemoNode::branch(
72 "widgets/tree",
73 "tree",
74 vec![DemoNode::leaf("widgets/tree/lib.rs", "lib.rs", "📄")],
75 ),
76 DemoNode::branch(
77 "widgets/splitter",
78 "splitter",
79 vec![DemoNode::leaf("widgets/splitter/lib.rs", "lib.rs", "📄")],
80 ),
81 ],
82 ),
83 DemoNode::branch(
84 "libs",
85 "libs",
86 vec![
87 DemoNode::branch(
88 "libs/core",
89 "core",
90 vec![DemoNode::leaf("libs/core/lib.rs", "lib.rs", "📄")],
91 ),
92 DemoNode::branch(
93 "libs/theme",
94 "theme",
95 vec![DemoNode::leaf("libs/theme/lib.rs", "lib.rs", "📄")],
96 ),
97 DemoNode::branch(
98 "libs/providers",
99 "providers",
100 vec![
101 DemoNode::leaf("libs/providers/fs.rs", "fs.rs", "📄"),
102 DemoNode::leaf("libs/providers/sqlite.rs", "sqlite.rs", "📄"),
103 ],
104 ),
105 ],
106 ),
107 ],
108 )
109}
110
111fn yahweh_deps_tree() -> DemoNode {
112 fn branch(id: &'static str, label: &'static str, children: Vec<DemoNode>) -> DemoNode {
113 DemoNode { id, label, icon: "📦", children }
114 }
115 let leaf = DemoNode::leaf;
116 branch(
117 "deps-root",
118 "deps",
119 vec![
120 branch(
121 "ui",
122 "ui",
123 vec![
124 leaf("dep:gpui", "gpui 0.2.2", "🧊"),
125 leaf("dep:gpui-macros", "gpui-macros 0.2.2", "🧊"),
126 ],
127 ),
128 branch(
129 "async",
130 "async",
131 vec![
132 leaf("dep:tokio", "tokio 1.x", "🌀"),
133 leaf("dep:async-trait", "async-trait 0.1", "🌀"),
134 ],
135 ),
136 branch(
137 "data",
138 "data",
139 vec![
140 leaf("dep:serde", "serde 1", "🧬"),
141 leaf("dep:serde_json", "serde_json 1", "🧬"),
142 leaf("dep:rusqlite", "rusqlite 0.31", "🗃️"),
143 ],
144 ),
145 leaf("dep:notify", "notify 6.1", "👂"),
146 leaf("dep:uuid", "uuid 1", "🔗"),
147 ],
148 )
149}
150
151// =====================================================================
152// Eventos re-emitidos
153// =====================================================================
154
155/// Re-emitidos por ManagedTree después de procesar el evento bruto del
156/// TreeView interno. Contienen el `dataset_key` para que el host distinga
157/// entre múltiples ManagedTrees.
158///
159/// Los campos están marcados `dead_code`-ok porque en Fase 3 nadie se
160/// suscribe — el LayoutHost los va a consumir en Fase 4 vía un AppBus que
161/// reenvíe estos eventos a los viewers (TextViewer / ImageViewer / etc).
162#[derive(Clone, Debug)]
163#[allow(dead_code)]
164pub enum ManagedTreeEvent {
165 RowClicked { dataset: SharedString, id: String },
166 RowDoubleClicked { dataset: SharedString, id: String },
167 ContextMenu { dataset: SharedString, id: Option<String> },
168}
169
170// =====================================================================
171// Widget
172// =====================================================================
173
174pub struct ManagedTree {
175 view: Entity<TreeView>,
176 data: DemoNode,
177 expanded: HashSet<String>,
178 dataset_key: SharedString,
179}
180
181impl EventEmitter<ManagedTreeEvent> for ManagedTree {}
182
183impl ManagedTree {
184 pub fn new(
185 list_id: SharedString,
186 dataset_key: SharedString,
187 cx: &mut Context<Self>,
188 ) -> Self {
189 let view = cx.new(|cx| TreeView::new(list_id, cx));
190 cx.subscribe(&view, |this: &mut ManagedTree, _, ev, cx| {
191 this.on_tree_event(ev, cx);
192 })
193 .detach();
194
195 let data = dataset_for(&dataset_key);
196 let mut expanded = HashSet::new();
197 expanded.insert(data.id.to_string());
198
199 let me = Self {
200 view,
201 data,
202 expanded,
203 dataset_key,
204 };
205 me.push_rows(cx);
206 me
207 }
208
209 fn push_rows(&self, cx: &mut Context<Self>) {
210 let mut rows = Vec::new();
211 flatten(&self.data, 0, &self.expanded, &mut rows);
212 self.view.update(cx, |tree, cx| tree.set_rows(rows, cx));
213 }
214
215 fn on_tree_event(&mut self, event: &TreeEvent, cx: &mut Context<Self>) {
216 match event {
217 TreeEvent::ChevronToggled(id) => {
218 let key = id.as_str().to_string();
219 if !self.expanded.remove(&key) {
220 self.expanded.insert(key);
221 }
222 self.push_rows(cx);
223 }
224 TreeEvent::RowClicked(id) => {
225 cx.emit(ManagedTreeEvent::RowClicked {
226 dataset: self.dataset_key.clone(),
227 id: id.to_string(),
228 });
229 }
230 TreeEvent::RowDoubleClicked(id) => {
231 cx.emit(ManagedTreeEvent::RowDoubleClicked {
232 dataset: self.dataset_key.clone(),
233 id: id.to_string(),
234 });
235 }
236 TreeEvent::ContextMenuRequested { id, .. } => {
237 cx.emit(ManagedTreeEvent::ContextMenu {
238 dataset: self.dataset_key.clone(),
239 id: id.as_ref().map(|i| i.to_string()),
240 });
241 }
242 TreeEvent::ActiveChanged(_) => {}
243 }
244 }
245
246 /// Reservado para Fase 4: el LayoutHost lo va a consultar al
247 /// re-emitir eventos al bus.
248 #[allow(dead_code)]
249 pub fn dataset_key(&self) -> &str {
250 &self.dataset_key
251 }
252}
253
254impl Render for ManagedTree {
255 fn render(&mut self, _w: &mut Window, _cx: &mut Context<Self>) -> impl IntoElement {
256 div().size_full().child(self.view.clone())
257 }
258}
259
260// -------- helpers --------
261
262fn flatten(
263 node: &DemoNode,
264 depth: u32,
265 expanded: &HashSet<String>,
266 out: &mut Vec<TreeRow>,
267) {
268 let kind = if node.children.is_empty() {
269 RowKind::Leaf
270 } else {
271 RowKind::Branch
272 };
273 let is_expanded = expanded.contains(node.id);
274 out.push(TreeRow {
275 id: RowId::new(node.id),
276 label: node.label.to_string(),
277 depth,
278 kind,
279 expanded: is_expanded,
280 icon: Some(node.icon.to_string()),
281 });
282 if is_expanded {
283 for child in &node.children {
284 flatten(child, depth + 1, expanded, out);
285 }
286 }
287}