tawasuyu/llimphi
Sovereign retained-mode GPU UI framework in Rust (wgpu+vello+taffy+parley), Elm loop, 40+ widgets.
⎇ 38d3dddaeabd5148cb21e7575830cecdd1937d66
llimphi-layout/examples/layout_panels.rs
History
Who touched what
raw
feat: llimphi standalone — framework UI soberano extraído del monorepo
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}