tawasuyu

tawasuyu/brahman

Rust 519TOML 202Markdown 57JSON 197424 Sutichaykuna

⎇ 53dbdf0f1deea10d6fc567ea2b43e1e75d6d60cd crates/apps/yahweh-shell/src/managed_tree.rs Rust · 287 siq'ikuna · 9.1 KB Ñawpa willay Pi imata llamkarqan ch'uya

chore: monorepo inicial con arje + minga + yahweh absorbidos 53dbdf0f1 · Sergio ·

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}