Skip to main content

render_node/
render_node.rs

1//! Fase 2 de Llimphi: un nodo (círculo + halo) renderizado por vello con AA
2//! perfecto sobre el swapchain de llimphi-hal.
3//!
4//! Corre con: `cargo run -p llimphi-raster --example render_node --release`.
5
6use std::sync::Arc;
7use std::time::Instant;
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_raster::kurbo::{Affine, Circle, Stroke};
16use llimphi_raster::peniko::{color::palette, Color, Fill};
17use llimphi_raster::{vello, Renderer};
18
19struct State {
20    window: Arc<Window>,
21    hal: Hal,
22    surface: WinitSurface,
23    renderer: Renderer,
24    scene: vello::Scene,
25}
26
27struct App {
28    state: Option<State>,
29    started: Instant,
30}
31
32impl ApplicationHandler for App {
33    fn resumed(&mut self, event_loop: &ActiveEventLoop) {
34        if self.state.is_some() {
35            return;
36        }
37        let window = event_loop
38            .create_window(
39                WindowAttributes::default()
40                    .with_title("llimphi · render_node")
41                    .with_inner_size(LogicalSize::new(960u32, 540u32)),
42            )
43            .expect("create window");
44        let window = Arc::new(window);
45        let hal = pollster::block_on(Hal::new(None)).expect("hal");
46        let surface = WinitSurface::new(&hal, window.clone()).expect("surface");
47        let renderer = Renderer::new(&hal).expect("renderer");
48        window.request_redraw();
49        self.state = Some(State {
50            window,
51            hal,
52            surface,
53            renderer,
54            scene: vello::Scene::new(),
55        });
56    }
57
58    fn window_event(
59        &mut self,
60        event_loop: &ActiveEventLoop,
61        _id: WindowId,
62        event: WindowEvent,
63    ) {
64        let Some(state) = self.state.as_mut() else {
65            return;
66        };
67        match event {
68            WindowEvent::CloseRequested => event_loop.exit(),
69            WindowEvent::Resized(size) => {
70                state.surface.resize(size.width, size.height);
71                state.window.request_redraw();
72            }
73            WindowEvent::RedrawRequested => {
74                let frame = match state.surface.acquire() {
75                    Ok(f) => f,
76                    Err(_) => {
77                        let (w, h) = state.surface.size();
78                        state.surface.resize(w, h);
79                        state.window.request_redraw();
80                        return;
81                    }
82                };
83                let (w, h) = frame.size();
84                state.scene.reset();
85                build_node(&mut state.scene, w as f64, h as f64, self.started.elapsed().as_secs_f64());
86                if let Err(e) = state.renderer.render(
87                    &state.hal,
88                    &state.scene,
89                    &frame,
90                    palette::css::BLACK,
91                ) {
92                    eprintln!("render error: {e}");
93                }
94                state.surface.present(frame, &state.hal);
95                state.window.request_redraw();
96            }
97            _ => {}
98        }
99    }
100}
101
102/// Pinta un nodo centrado (círculo lleno + halo) que respira con `t`.
103fn build_node(scene: &mut vello::Scene, w: f64, h: f64, t: f64) {
104    let cx = w * 0.5;
105    let cy = h * 0.5;
106    let pulse = 1.0 + 0.06 * (t * 1.6).sin();
107    let r = (h.min(w) * 0.18) * pulse;
108
109    // Halo
110    scene.stroke(
111        &Stroke::new(2.0),
112        Affine::IDENTITY,
113        Color::from_rgba8(60, 120, 200, 180),
114        None,
115        &Circle::new((cx, cy), r * 1.35),
116    );
117    // Cuerpo
118    scene.fill(
119        Fill::NonZero,
120        Affine::IDENTITY,
121        Color::from_rgba8(90, 160, 230, 255),
122        None,
123        &Circle::new((cx, cy), r),
124    );
125    // Borde
126    scene.stroke(
127        &Stroke::new(3.0),
128        Affine::IDENTITY,
129        Color::from_rgba8(20, 50, 100, 255),
130        None,
131        &Circle::new((cx, cy), r),
132    );
133}
134
135fn main() {
136    let event_loop = EventLoop::new().expect("event loop");
137    event_loop.set_control_flow(ControlFlow::Poll);
138    let mut app = App {
139        state: None,
140        started: Instant::now(),
141    };
142    event_loop.run_app(&mut app).expect("run app");
143}