Skip to main content

cvkg_render_native/
lib.rs

1//! # CVKG Agentic Development Guidelines (v1.2)
2//!
3//! All AI agents contributing to this crate MUST follow ALL seven rules:
4//!
5//! ── Karpathy Guidelines (1–4) ────────────────────────────────────────────
6//! 1. THINK FIRST     — State assumptions. Surface ambiguity. Push back on complexity.
7//! 2. STAY SIMPLE     — Minimum code. No speculative features. No unasked-for abstractions.
8//! 3. BE SURGICAL     — Touch only what's required. Own your orphans. Don't improve neighbors.
9//! 4. VERIFY GOALS    — Turn tasks into checkable criteria. Loop until they pass. Never commit broken.
10//!
11//! ── CVKG Extended Protocols (5–7) ────────────────────────────────────────
12//! 5. TRIPLE-PASS     — Read the target, its surrounding context, and its full call graph
13//                      at least THREE TIMES before making any edit or revision.
14//! 6. COMMENT ALL     — Every major pub fn, unsafe block, and non-trivial algorithm in
15//                      every .rs/.ts/.h/.wgsl file MUST have a descriptive doc comment.
16//                      Comments describe WHY and WHAT CONTRACT, not HOW mechanically.
17//! 7. MONITOR LOOPS   — Check every tool call / command for progress every 30 seconds.
18//                      After 3 consecutive identical failures, stop, write BLOCKED.md,
19//                      and move to unblocked work. Never silently accept a broken state.
20//!
21//! Sources:
22//   Karpathy: https://github.com/multica-ai/andrej-karpathy-skills
23//   CVKG Extended: Section 2 of the CVKG Design Specification
24
25//! Platform-native widget delegation using winit and AccessKit
26//!
27//! This crate provides platform-specific rendering backends for native desktop targets
28//  using winit for window/event handling and AccessKit for accessibility tree integration.
29
30use std::sync::Arc;
31use winit::{
32    application::ApplicationHandler,
33    event::WindowEvent,
34    event_loop::{ActiveEventLoop, ControlFlow, EventLoop},
35    window::{Window, WindowId},
36};
37
38/// Native renderer backend implementing the Renderer trait.
39/// It wraps a SurtrRenderer for high-performance GPU drawing.
40pub struct NativeRenderer {
41    window: Arc<Window>,
42    gpu: Option<cvkg_render_gpu::SurtrRenderer>,
43}
44
45impl NativeRenderer {
46    /// Create a new NativeRenderer (internal use by App)
47    fn new(window: Arc<Window>) -> Self {
48        Self { window, gpu: None }
49    }
50
51    /// Start the CVKG native application with the given view.
52    /// This is the main entry point for desktop applications.
53    pub fn run<V: cvkg_core::View + 'static>(view: V) {
54        let event_loop = EventLoop::new().expect("Failed to create event loop");
55        event_loop.set_control_flow(ControlFlow::Poll);
56
57        let mut app = App {
58            view,
59            renderer: None,
60            accesskit_adapter: None,
61            vdom: Some(cvkg_vdom::VDom::new()),
62            asset_manager: std::sync::Arc::new(NativeAssetManager::new()),
63            cursor_pos: [0.0, 0.0],
64        };
65
66        event_loop.run_app(&mut app).expect("Event loop error");
67    }
68}
69
70struct App<V: cvkg_core::View> {
71    view: V,
72    renderer: Option<NativeRenderer>,
73    accesskit_adapter: Option<accesskit_winit::Adapter>,
74    vdom: Option<cvkg_vdom::VDom>,
75    asset_manager: std::sync::Arc<NativeAssetManager>,
76    cursor_pos: [f32; 2],
77}
78
79impl<V: cvkg_core::View + 'static> ApplicationHandler for App<V> {
80    fn resumed(&mut self, event_loop: &ActiveEventLoop) {
81        let window_attrs = Window::default_attributes()
82            .with_title("CVKG Forge")
83            .with_inner_size(winit::dpi::LogicalSize::new(1280.0, 720.0));
84
85        let window = Arc::new(
86            event_loop
87                .create_window(window_attrs)
88                .expect("Failed to create window"),
89        );
90
91        // Initialize AccessKit adapter
92        let adapter = accesskit_winit::Adapter::with_direct_handlers(
93            event_loop, &window, ShieldWall, ShieldWall, ShieldWall,
94        );
95        self.accesskit_adapter = Some(adapter);
96
97        let mut renderer = NativeRenderer::new(window.clone());
98
99        // Use a Runtime to block on the async forge process
100        let rt = tokio::runtime::Runtime::new().unwrap();
101        renderer.gpu = Some(rt.block_on(cvkg_render_gpu::SurtrRenderer::forge(window.clone())));
102
103        self.renderer = Some(renderer);
104
105        // Register AssetManager in the environment
106        cvkg_core::env::insert::<cvkg_core::AssetKey>(self.asset_manager.clone());
107    }
108
109    fn window_event(&mut self, event_loop: &ActiveEventLoop, _id: WindowId, event: WindowEvent) {
110        let renderer = if let Some(r) = &mut self.renderer {
111            r
112        } else {
113            return;
114        };
115
116        match event {
117            WindowEvent::CloseRequested => event_loop.exit(),
118            WindowEvent::Resized(physical_size) => {
119                if let Some(gpu) = &mut renderer.gpu {
120                    gpu.resize(physical_size.width, physical_size.height);
121                }
122                renderer.window.request_redraw();
123            }
124            WindowEvent::RedrawRequested => {
125                if let Some(gpu) = &mut renderer.gpu {
126                    let encoder = gpu.begin_frame();
127
128                    let size = renderer.window.inner_size();
129                    let rect = cvkg_core::Rect {
130                        x: 0.0,
131                        y: 0.0,
132                        width: size.width as f32,
133                        height: size.height as f32,
134                    };
135
136                    // Render the view tree
137                    self.view.render(gpu, rect);
138
139                    // Update VDOM and Accessibility Tree
140                    let new_vdom = cvkg_vdom::VDom::build(&self.view, rect);
141                    if let (Some(prev_vdom), Some(adapter)) =
142                        (&mut self.vdom, &mut self.accesskit_adapter)
143                    {
144                        let patches = new_vdom.diff(prev_vdom);
145
146                        // Generate AccessKit updates from patches
147                        let mut nodes = Vec::new();
148                        for patch in patches {
149                            match patch {
150                                cvkg_vdom::VDomPatch::Create(node)
151                                | cvkg_vdom::VDomPatch::Replace { node, .. } => {
152                                    nodes.push((
153                                        accesskit::NodeId(node.id.0 as u64),
154                                        node.to_accesskit_node(),
155                                    ));
156                                }
157                                _ => {}
158                            }
159                        }
160
161                        if !nodes.is_empty() {
162                            adapter.update_if_active(|| accesskit::TreeUpdate {
163                                nodes,
164                                tree: None,
165                                focus: accesskit::NodeId(1),
166                            });
167                        }
168                    }
169                    self.vdom = Some(new_vdom);
170
171                    gpu.end_frame(encoder);
172                }
173            }
174            WindowEvent::CursorMoved { position, .. } => {
175                let scale = renderer.window.scale_factor();
176                let logical = position.to_logical::<f32>(scale);
177                self.cursor_pos = [logical.x, logical.y];
178                if let Some(vdom) = &self.vdom {
179                    vdom.dispatch_event(cvkg_core::Event::PointerMove {
180                        x: self.cursor_pos[0],
181                        y: self.cursor_pos[1],
182                    });
183                }
184            }
185            WindowEvent::MouseInput { state, .. } => {
186                if let Some(vdom) = &self.vdom {
187                    let event = match state {
188                        winit::event::ElementState::Pressed => cvkg_core::Event::PointerDown {
189                            x: self.cursor_pos[0],
190                            y: self.cursor_pos[1],
191                        },
192                        winit::event::ElementState::Released => cvkg_core::Event::PointerUp {
193                            x: self.cursor_pos[0],
194                            y: self.cursor_pos[1],
195                        },
196                    };
197                    vdom.dispatch_event(event);
198                }
199            }
200            WindowEvent::KeyboardInput { event, .. } => {
201                if let Some(vdom) = &self.vdom {
202                    if let winit::keyboard::PhysicalKey::Code(code) = event.physical_key {
203                        let key_str = format!("{:?}", code);
204                        let cvkg_event = if event.state == winit::event::ElementState::Pressed {
205                            cvkg_core::Event::KeyDown { key: key_str }
206                        } else {
207                            cvkg_core::Event::KeyUp { key: key_str }
208                        };
209                        vdom.dispatch_event(cvkg_event);
210                    }
211
212                    // Also handle text input (IME / character events)
213                    if event.state == winit::event::ElementState::Pressed {
214                        if let Some(text) = event.text {
215                            for c in text.chars() {
216                                vdom.dispatch_event(cvkg_core::Event::KeyDown {
217                                    key: c.to_string(),
218                                });
219                            }
220                        }
221                    }
222                }
223            }
224            _ => (),
225        }
226    }
227
228    fn about_to_wait(&mut self, _event_loop: &ActiveEventLoop) {
229        if let Some(renderer) = &self.renderer {
230            renderer.window.request_redraw();
231        }
232    }
233}
234
235impl cvkg_core::Renderer for NativeRenderer {
236    fn fill_rect(&mut self, rect: cvkg_core::Rect, color: [f32; 4]) {
237        if let Some(gpu) = &mut self.gpu {
238            gpu.fill_rect(rect, color);
239        }
240    }
241    fn fill_rounded_rect(&mut self, rect: cvkg_core::Rect, radius: f32, color: [f32; 4]) {
242        if let Some(gpu) = &mut self.gpu {
243            gpu.fill_rounded_rect(rect, radius, color);
244        }
245    }
246    fn fill_ellipse(&mut self, rect: cvkg_core::Rect, color: [f32; 4]) {
247        if let Some(gpu) = &mut self.gpu {
248            gpu.fill_ellipse(rect, color);
249        }
250    }
251    fn stroke_rect(&mut self, rect: cvkg_core::Rect, color: [f32; 4], stroke_width: f32) {
252        if let Some(gpu) = &mut self.gpu {
253            gpu.stroke_rect(rect, color, stroke_width);
254        }
255    }
256    fn stroke_rounded_rect(
257        &mut self,
258        rect: cvkg_core::Rect,
259        radius: f32,
260        color: [f32; 4],
261        stroke_width: f32,
262    ) {
263        if let Some(gpu) = &mut self.gpu {
264            gpu.stroke_rounded_rect(rect, radius, color, stroke_width);
265        }
266    }
267    fn stroke_ellipse(&mut self, rect: cvkg_core::Rect, color: [f32; 4], stroke_width: f32) {
268        if let Some(gpu) = &mut self.gpu {
269            gpu.stroke_ellipse(rect, color, stroke_width);
270        }
271    }
272    fn draw_line(
273        &mut self,
274        x1: f32,
275        y1: f32,
276        x2: f32,
277        y2: f32,
278        color: [f32; 4],
279        stroke_width: f32,
280    ) {
281        if let Some(gpu) = &mut self.gpu {
282            gpu.draw_line(x1, y1, x2, y2, color, stroke_width);
283        }
284    }
285    fn draw_text(&mut self, text: &str, x: f32, y: f32, size: f32, color: [f32; 4]) {
286        if let Some(gpu) = &mut self.gpu {
287            gpu.draw_text(text, x, y, size, color);
288        }
289    }
290    fn measure_text(&mut self, text: &str, size: f32) -> (f32, f32) {
291        if let Some(gpu) = &mut self.gpu {
292            gpu.measure_text(text, size)
293        } else {
294            (0.0, 0.0)
295        }
296    }
297    fn draw_texture(&mut self, texture_id: u32, rect: cvkg_core::Rect) {
298        if let Some(gpu) = &mut self.gpu {
299            gpu.draw_texture(texture_id, rect);
300        }
301    }
302    fn draw_image(&mut self, image_name: &str, rect: cvkg_core::Rect) {
303        if let Some(gpu) = &mut self.gpu {
304            gpu.draw_image(image_name, rect);
305        }
306    }
307    fn load_image(&mut self, name: &str, data: &[u8]) {
308        if let Some(gpu) = &mut self.gpu {
309            gpu.load_image(name, data);
310        }
311    }
312    fn push_clip_rect(&mut self, rect: cvkg_core::Rect) {
313        if let Some(gpu) = &mut self.gpu {
314            gpu.push_clip_rect(rect);
315        }
316    }
317    fn pop_clip_rect(&mut self) {
318        if let Some(gpu) = &mut self.gpu {
319            gpu.pop_clip_rect();
320        }
321    }
322    fn push_opacity(&mut self, opacity: f32) {
323        if let Some(gpu) = &mut self.gpu {
324            gpu.push_opacity(opacity);
325        }
326    }
327    fn pop_opacity(&mut self) {
328        if let Some(gpu) = &mut self.gpu {
329            gpu.pop_opacity();
330        }
331    }
332    fn bifrost(&mut self, rect: cvkg_core::Rect, blur: f32, saturation: f32, opacity: f32) {
333        if let Some(gpu) = &mut self.gpu {
334            gpu.bifrost(rect, blur, saturation, opacity);
335        }
336    }
337    fn push_mjolnir_slice(&mut self, angle: f32, offset: f32) {
338        if let Some(gpu) = &mut self.gpu {
339            gpu.push_mjolnir_slice(angle, offset);
340        }
341    }
342    fn pop_mjolnir_slice(&mut self) {
343        if let Some(gpu) = &mut self.gpu {
344            gpu.pop_mjolnir_slice();
345        }
346    }
347    fn register_shared_element(&mut self, id: &str, rect: cvkg_core::Rect) {
348        if let Some(gpu) = &mut self.gpu {
349            gpu.register_shared_element(id, rect);
350        }
351    }
352}
353
354
355
356// Platform-specific implementations for macOS, Windows, and Linux are handled by winit and AccessKit.
357
358struct ShieldWall;
359
360impl accesskit::ActionHandler for ShieldWall {
361    fn do_action(&mut self, _request: accesskit::ActionRequest) {}
362}
363
364impl accesskit::ActivationHandler for ShieldWall {
365    fn request_initial_tree(&mut self) -> Option<accesskit::TreeUpdate> {
366        let mut root = accesskit::Node::new(accesskit::Role::Window);
367        root.set_label("CVKG Application");
368
369        let root_id = accesskit::NodeId(1);
370        Some(accesskit::TreeUpdate {
371            nodes: vec![(root_id, root)],
372            tree: Some(accesskit::Tree::new(root_id)),
373            focus: root_id,
374        })
375    }
376}
377
378impl accesskit::DeactivationHandler for ShieldWall {
379    fn deactivate_accessibility(&mut self) {}
380}
381
382/// A concrete AssetManager for native desktop targets that loads from the local filesystem.
383pub struct NativeAssetManager {
384    cache: std::sync::Arc<
385        std::sync::RwLock<
386            std::collections::HashMap<String, cvkg_core::AssetState<std::sync::Arc<Vec<u8>>>>,
387        >,
388    >,
389}
390
391impl NativeAssetManager {
392    pub fn new() -> Self {
393        Self {
394            cache: std::sync::Arc::new(std::sync::RwLock::new(std::collections::HashMap::new())),
395        }
396    }
397}
398
399impl cvkg_core::AssetManager for NativeAssetManager {
400    fn load_image(&self, url: &str) -> cvkg_core::AssetState<std::sync::Arc<Vec<u8>>> {
401        {
402            let cache = self.cache.read().unwrap();
403            if let Some(state) = cache.get(url) {
404                return state.clone();
405            }
406        }
407
408        // Real filesystem I/O (simplistic implementation for now)
409        match std::fs::read(url) {
410            Ok(data) => {
411                let state = cvkg_core::AssetState::Ready(std::sync::Arc::new(data));
412                let mut cache = self.cache.write().unwrap();
413                cache.insert(url.to_string(), state.clone());
414                state
415            }
416            Err(e) => {
417                let state = cvkg_core::AssetState::Error(e.to_string());
418                let mut cache = self.cache.write().unwrap();
419                cache.insert(url.to_string(), state.clone());
420                state
421            }
422        }
423    }
424
425    fn preload_image(&self, _url: &str) {
426        // Implementation for async preloading could go here
427    }
428}