gioser

tawasuyu/llimphi

Sovereign retained-mode GPU UI framework in Rust (wgpu+vello+taffy+parley), Elm loop, 40+ widgets.

Rust 660Markdown 302TOML 157RON 312382 Sutichaykuna

⎇ main llimphi-workspace/examples/workspace_demo.rs Rust · 212 siq'ikuna · 5.8 KB Ñawpa willay Pi imata llamkarqan ch'uya

chore: refresco desde el monorepo — 27 crates nuevos (anim, lottie, svg, mesh, image, layer, test, wire-view, widgets rag-sidebar/router/lazy-list/rive-button) y sync de los 95 existentes 93253cc22 · Sergio ·

1//! Demo del chasis `llimphi-workspace`.
2//!
3//! Mismo resultado que `panes_demo` pero la app ya no reimplementa la
4//! máquina de estados: guarda un `Workspace` + un mapa de paneles, y deja
5//! que el chasis maneje split/cerrar/foco/resize y el chrome. Esto es el
6//! molde que después adopta cada app de tawasuyu.
7//!
8//! Correr:  `cargo run -p llimphi-workspace --example workspace_demo --release`
9
10use std::collections::HashMap;
11
12use llimphi_ui::llimphi_layout::taffy::{
13    prelude::{length, FlexDirection, Size, Style},
14    Rect,
15};
16use llimphi_ui::{App, Handle, View};
17use llimphi_theme::Theme;
18use llimphi_workspace::{workspace_view, Axis, PaneId, Workspace, WorkspacePalette, WsEffect, WsMsg};
19
20struct Demo;
21
22#[derive(Clone)]
23enum Msg {
24    Ws(WsMsg),
25    Panel(PaneId, PanelMsg),
26}
27
28#[derive(Clone)]
29enum PanelMsg {
30    Inc,
31    Dec,
32    AddNote,
33}
34
35enum Kind {
36    Counter(i64),
37    Notes(Vec<String>),
38}
39
40struct Model {
41    ws: Workspace,
42    panes: HashMap<PaneId, Kind>,
43    theme: Theme,
44}
45
46impl App for Demo {
47    type Model = Model;
48    type Msg = Msg;
49
50    fn title() -> &'static str {
51        "workspace — chasis tmux de tawasuyu"
52    }
53
54    fn init(_: &Handle<Msg>) -> Model {
55        let mut ws = Workspace::new(); // panel 0
56        let mut panes = HashMap::new();
57        panes.insert(0, Kind::Counter(0));
58        let id = ws.split(Axis::Horizontal);
59        panes.insert(id, Kind::Notes(vec!["arrastra el divisor del medio →".into()]));
60        ws.focus(0);
61        Model {
62            ws,
63            panes,
64            theme: Theme::dark(),
65        }
66    }
67
68    fn update(mut model: Model, msg: Msg, _: &Handle<Msg>) -> Model {
69        match msg {
70            Msg::Ws(m) => match model.ws.apply(m) {
71                WsEffect::Created(id) => {
72                    // Alternamos tipo para ilustrar paneles heterogéneos.
73                    let kind = if id % 2 == 0 {
74                        Kind::Counter(0)
75                    } else {
76                        Kind::Notes(vec![])
77                    };
78                    model.panes.insert(id, kind);
79                }
80                WsEffect::Closed(id) => {
81                    model.panes.remove(&id);
82                }
83                WsEffect::None => {}
84            },
85            Msg::Panel(id, pm) => {
86                if let Some(kind) = model.panes.get_mut(&id) {
87                    match (kind, pm) {
88                        (Kind::Counter(n), PanelMsg::Inc) => *n += 1,
89                        (Kind::Counter(n), PanelMsg::Dec) => *n -= 1,
90                        (Kind::Notes(v), PanelMsg::AddNote) => {
91                            let n = v.len() + 1;
92                            v.push(format!("nota #{n}"));
93                        }
94                        _ => {}
95                    }
96                }
97            }
98        }
99        model
100    }
101
102    fn view(model: &Model) -> View<Msg> {
103        let palette = WorkspacePalette::from_theme(&model.theme);
104        let panes = &model.panes;
105        let theme = &model.theme;
106        workspace_view(
107            &model.ws,
108            &palette,
109            move |id| render_pane(panes, theme, id),
110            Msg::Ws,
111        )
112    }
113}
114
115fn render_pane(panes: &HashMap<PaneId, Kind>, t: &Theme, id: PaneId) -> View<Msg> {
116    let Some(kind) = panes.get(&id) else {
117        return label("(vacío)".to_string(), 14.0, t.fg_muted);
118    };
119    let body = match kind {
120        Kind::Counter(n) => col(
121            8.0,
122            vec![
123                label(format!("{n}"), 44.0, t.accent),
124                row(
125                    8.0,
126                    vec![
127                        button("−", Msg::Panel(id, PanelMsg::Dec), t),
128                        button("+", Msg::Panel(id, PanelMsg::Inc), t),
129                    ],
130                ),
131            ],
132        ),
133        Kind::Notes(v) => {
134            let mut lines: Vec<View<Msg>> = v
135                .iter()
136                .map(|s| label(format!("• {s}"), 14.0, t.fg_text))
137                .collect();
138            lines.push(button("+ nota", Msg::Panel(id, PanelMsg::AddNote), t));
139            col(6.0, lines)
140        }
141    };
142    View::new(Style {
143        flex_direction: FlexDirection::Column,
144        gap: Size {
145            width: length(10.0),
146            height: length(10.0),
147        },
148        padding: uniform(12.0),
149        flex_grow: 1.0,
150        ..Default::default()
151    })
152    .children(vec![label(format!("panel #{id}"), 13.0, t.fg_muted), body])
153}
154
155fn button(text: &str, msg: Msg, t: &Theme) -> View<Msg> {
156    View::new(Style {
157        padding: Rect {
158            left: length(12.0),
159            right: length(12.0),
160            top: length(6.0),
161            bottom: length(6.0),
162        },
163        flex_shrink: 0.0,
164        ..Default::default()
165    })
166    .fill(t.bg_button)
167    .hover_fill(t.bg_button_hover)
168    .radius(6.0)
169    .on_click(msg)
170    .children(vec![label(text.to_string(), 14.0, t.fg_text)])
171}
172
173fn col(gap: f32, children: Vec<View<Msg>>) -> View<Msg> {
174    View::new(Style {
175        flex_direction: FlexDirection::Column,
176        gap: Size {
177            width: length(gap),
178            height: length(gap),
179        },
180        ..Default::default()
181    })
182    .children(children)
183}
184
185fn row(gap: f32, children: Vec<View<Msg>>) -> View<Msg> {
186    View::new(Style {
187        flex_direction: FlexDirection::Row,
188        gap: Size {
189            width: length(gap),
190            height: length(gap),
191        },
192        ..Default::default()
193    })
194    .children(children)
195}
196
197fn label(text: String, size: f32, color: llimphi_ui::llimphi_raster::peniko::Color) -> View<Msg> {
198    View::new(Style::default()).text(text, size, color)
199}
200
201fn uniform(px: f32) -> Rect<llimphi_ui::llimphi_layout::taffy::prelude::LengthPercentage> {
202    Rect {
203        left: length(px),
204        right: length(px),
205        top: length(px),
206        bottom: length(px),
207    }
208}
209
210fn main() {
211    llimphi_ui::run::<Demo>();
212}