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

⎇ dba855e446ca2bd4e7d146bdc168fb232cad15a4 llimphi-layout/examples/layout_panels.rs Rust · 250 siq'ikuna · 7.1 KB Ñawpa willay Pi imata llamkarqan ch'uya

feat: llimphi standalone — framework UI soberano extraído del monorepo e65e9cc62 · Sergio ·

1//! Fase 3 de Llimphi: 3 paneles (sidebar + header/body/footer) que se
2//! reorganizan al redimensionar la ventana. Pintados por vello a través
3//! de llimphi-raster.
4//!
5//! Corre con: `cargo run -p llimphi-layout --example layout_panels --release`.
6
7use std::sync::Arc;
8
9use llimphi_hal::winit::application::ApplicationHandler;
10use llimphi_hal::winit::dpi::LogicalSize;
11use llimphi_hal::winit::event::WindowEvent;
12use llimphi_hal::winit::event_loop::{ActiveEventLoop, ControlFlow, EventLoop};
13use llimphi_hal::winit::window::{Window, WindowAttributes, WindowId};
14use llimphi_hal::{Hal, Surface, WinitSurface};
15use llimphi_layout::{
16    taffy::{prelude::*, Style},
17    ComputedLayout, LayoutTree, Rect,
18};
19use llimphi_raster::kurbo::{Affine, RoundedRect};
20use llimphi_raster::peniko::{color::palette, Color, Fill};
21use llimphi_raster::{vello, Renderer};
22
23struct Panels {
24    sidebar: NodeId,
25    header: NodeId,
26    body: NodeId,
27    footer: NodeId,
28    root: NodeId,
29}
30
31struct State {
32    window: Arc<Window>,
33    hal: Hal,
34    surface: WinitSurface,
35    renderer: Renderer,
36    scene: vello::Scene,
37    layout: LayoutTree,
38    panels: Panels,
39}
40
41struct App {
42    state: Option<State>,
43}
44
45fn build_tree(layout: &mut LayoutTree) -> Panels {
46    let sidebar = layout
47        .leaf(Style {
48            size: Size {
49                width: length(220.0_f32),
50                height: percent(1.0_f32),
51            },
52            ..Default::default()
53        })
54        .unwrap();
55
56    let header = layout
57        .leaf(Style {
58            size: Size {
59                width: percent(1.0_f32),
60                height: length(64.0_f32),
61            },
62            ..Default::default()
63        })
64        .unwrap();
65
66    let body = layout
67        .leaf(Style {
68            size: Size {
69                width: percent(1.0_f32),
70                height: Dimension::auto(),
71            },
72            flex_grow: 1.0,
73            ..Default::default()
74        })
75        .unwrap();
76
77    let footer = layout
78        .leaf(Style {
79            size: Size {
80                width: percent(1.0_f32),
81                height: length(40.0_f32),
82            },
83            ..Default::default()
84        })
85        .unwrap();
86
87    let content = layout
88        .node(
89            Style {
90                flex_direction: FlexDirection::Column,
91                flex_grow: 1.0,
92                size: Size {
93                    width: Dimension::auto(),
94                    height: percent(1.0_f32),
95                },
96                gap: Size {
97                    width: length(0.0_f32),
98                    height: length(8.0_f32),
99                },
100                padding: Rect_(length(8.0_f32)),
101                ..Default::default()
102            },
103            &[header, body, footer],
104        )
105        .unwrap();
106
107    let root = layout
108        .node(
109            Style {
110                flex_direction: FlexDirection::Row,
111                size: Size {
112                    width: percent(1.0_f32),
113                    height: percent(1.0_f32),
114                },
115                ..Default::default()
116            },
117            &[sidebar, content],
118        )
119        .unwrap();
120
121    Panels {
122        sidebar,
123        header,
124        body,
125        footer,
126        root,
127    }
128}
129
130/// Helper para pasar el mismo length a todos los lados de un Rect.
131#[allow(non_snake_case)]
132fn Rect_(v: LengthPercentage) -> taffy::Rect<LengthPercentage> {
133    taffy::Rect {
134        left: v,
135        right: v,
136        top: v,
137        bottom: v,
138    }
139}
140
141fn paint(scene: &mut vello::Scene, computed: &ComputedLayout, panels: &Panels) {
142    fn rect(scene: &mut vello::Scene, r: Rect, color: Color, radius: f64) {
143        let rr = RoundedRect::new(
144            r.x as f64,
145            r.y as f64,
146            (r.x + r.w) as f64,
147            (r.y + r.h) as f64,
148            radius,
149        );
150        scene.fill(Fill::NonZero, Affine::IDENTITY, color, None, &rr);
151    }
152
153    if let Some(r) = computed.get(panels.sidebar) {
154        rect(scene, r, Color::from_rgba8(36, 44, 60, 255), 0.0);
155    }
156    if let Some(r) = computed.get(panels.header) {
157        rect(scene, r, Color::from_rgba8(60, 80, 110, 255), 8.0);
158    }
159    if let Some(r) = computed.get(panels.body) {
160        rect(scene, r, Color::from_rgba8(80, 110, 150, 255), 8.0);
161    }
162    if let Some(r) = computed.get(panels.footer) {
163        rect(scene, r, Color::from_rgba8(60, 80, 110, 255), 8.0);
164    }
165}
166
167impl ApplicationHandler for App {
168    fn resumed(&mut self, event_loop: &ActiveEventLoop) {
169        if self.state.is_some() {
170            return;
171        }
172        let window = event_loop
173            .create_window(
174                WindowAttributes::default()
175                    .with_title("llimphi · layout_panels")
176                    .with_inner_size(LogicalSize::new(960u32, 540u32)),
177            )
178            .expect("create window");
179        let window = Arc::new(window);
180        let hal = pollster::block_on(Hal::new(None)).expect("hal");
181        let surface = WinitSurface::new(&hal, window.clone()).expect("surface");
182        let renderer = Renderer::new(&hal).expect("renderer");
183        let mut layout = LayoutTree::new();
184        let panels = build_tree(&mut layout);
185        window.request_redraw();
186        self.state = Some(State {
187            window,
188            hal,
189            surface,
190            renderer,
191            scene: vello::Scene::new(),
192            layout,
193            panels,
194        });
195    }
196
197    fn window_event(
198        &mut self,
199        event_loop: &ActiveEventLoop,
200        _id: WindowId,
201        event: WindowEvent,
202    ) {
203        let Some(state) = self.state.as_mut() else {
204            return;
205        };
206        match event {
207            WindowEvent::CloseRequested => event_loop.exit(),
208            WindowEvent::Resized(size) => {
209                state.surface.resize(size.width, size.height);
210                state.window.request_redraw();
211            }
212            WindowEvent::RedrawRequested => {
213                let frame = match state.surface.acquire() {
214                    Ok(f) => f,
215                    Err(_) => {
216                        let (w, h) = state.surface.size();
217                        state.surface.resize(w, h);
218                        state.window.request_redraw();
219                        return;
220                    }
221                };
222                let (w, h) = frame.size();
223                let computed = state
224                    .layout
225                    .compute(state.panels.root, (w as f32, h as f32))
226                    .expect("compute layout");
227                state.scene.reset();
228                paint(&mut state.scene, &computed, &state.panels);
229                if let Err(e) = state.renderer.render(
230                    &state.hal,
231                    &state.scene,
232                    &frame,
233                    palette::css::BLACK,
234                ) {
235                    eprintln!("render error: {e}");
236                }
237                state.surface.present(frame, &state.hal);
238                state.window.request_redraw();
239            }
240            _ => {}
241        }
242    }
243}
244
245fn main() {
246    let event_loop = EventLoop::new().expect("event loop");
247    event_loop.set_control_flow(ControlFlow::Poll);
248    let mut app = App { state: None };
249    event_loop.run_app(&mut app).expect("run app");
250}