1use std::sync::Arc;
31use winit::{
32 application::ApplicationHandler,
33 event::WindowEvent,
34 event_loop::{ActiveEventLoop, ControlFlow, EventLoop},
35 window::{Window, WindowId},
36};
37
38pub struct NativeRenderer {
41 window: Arc<Window>,
42 gpu: Option<cvkg_render_gpu::SurtrRenderer>,
43}
44
45#[derive(Debug)]
47enum AppEvent {
48 AccessibilityAction(accesskit::ActionRequest),
49}
50
51impl NativeRenderer {
52 fn new(window: Arc<Window>) -> Self {
54 Self { window, gpu: None }
55 }
56
57 pub fn run<V: cvkg_core::View + 'static>(view: V) {
60 let event_loop = EventLoop::<AppEvent>::with_user_event()
61 .build()
62 .expect("Failed to create event loop");
63 event_loop.set_control_flow(ControlFlow::Poll);
64
65 let mut app = App {
66 view,
67 renderer: None,
68 accesskit_adapter: None,
69 vdom: Some(cvkg_vdom::VDom::new()),
70 asset_manager: std::sync::Arc::new(NativeAssetManager::new()),
71 cursor_pos: [0.0, 0.0],
72 proxy: event_loop.create_proxy(),
73 };
74
75 event_loop.run_app(&mut app).expect("Event loop error");
76 }
77}
78
79struct App<V: cvkg_core::View> {
80 view: V,
81 renderer: Option<NativeRenderer>,
82 accesskit_adapter: Option<accesskit_winit::Adapter>,
83 vdom: Option<cvkg_vdom::VDom>,
84 asset_manager: std::sync::Arc<NativeAssetManager>,
85 cursor_pos: [f32; 2],
86 proxy: winit::event_loop::EventLoopProxy<AppEvent>,
87}
88
89impl<V: cvkg_core::View + 'static> ApplicationHandler<AppEvent> for App<V> {
90 fn resumed(&mut self, event_loop: &ActiveEventLoop) {
91 let window_attrs = Window::default_attributes()
92 .with_title("CVKG Forge")
93 .with_inner_size(winit::dpi::LogicalSize::new(1280.0, 720.0));
94
95 let window = Arc::new(
96 event_loop
97 .create_window(window_attrs)
98 .expect("Failed to create window"),
99 );
100
101 let adapter = accesskit_winit::Adapter::with_direct_handlers(
103 event_loop,
104 &window,
105 ShieldWall {
106 proxy: self.proxy.clone(),
107 },
108 ShieldWall {
109 proxy: self.proxy.clone(),
110 },
111 ShieldWall {
112 proxy: self.proxy.clone(),
113 },
114 );
115 self.accesskit_adapter = Some(adapter);
116
117 let mut renderer = NativeRenderer::new(window.clone());
118
119 let rt = tokio::runtime::Runtime::new().unwrap();
121 renderer.gpu = Some(rt.block_on(cvkg_render_gpu::SurtrRenderer::forge(window.clone())));
122
123 self.renderer = Some(renderer);
124
125 cvkg_core::env::insert::<cvkg_core::AssetKey>(self.asset_manager.clone());
127 }
128
129 fn window_event(&mut self, event_loop: &ActiveEventLoop, _id: WindowId, event: WindowEvent) {
130 let renderer = if let Some(r) = &mut self.renderer {
131 r
132 } else {
133 return;
134 };
135
136 match event {
137 WindowEvent::CloseRequested => event_loop.exit(),
138 WindowEvent::Resized(physical_size) => {
139 if let Some(gpu) = &mut renderer.gpu {
140 gpu.resize(
141 physical_size.width,
142 physical_size.height,
143 renderer.window.scale_factor() as f32,
144 );
145 }
146 renderer.window.request_redraw();
147 }
148 WindowEvent::RedrawRequested => {
149 if let Some(gpu) = &mut renderer.gpu {
150 let encoder = gpu.begin_frame();
151
152 let size = renderer.window.inner_size();
153 let scale = renderer.window.scale_factor();
154 let logical_size = size.to_logical::<f32>(scale);
155
156 let rect = cvkg_core::Rect {
157 x: 0.0,
158 y: 0.0,
159 width: logical_size.width,
160 height: logical_size.height,
161 };
162
163 self.view.render(gpu, rect);
165
166 let new_vdom = cvkg_vdom::VDom::build(&self.view, rect);
168 if let Some(prev_vdom) = &mut self.vdom {
169 let patches = prev_vdom.diff(&new_vdom);
170
171 if let Some(adapter) = &mut self.accesskit_adapter {
173 let mut nodes = Vec::new();
174 for patch in &patches {
175 match patch {
176 cvkg_vdom::VDomPatch::Create(node)
177 | cvkg_vdom::VDomPatch::Replace { node, .. } => {
178 nodes.push((
179 accesskit::NodeId(node.id.0 as u64),
180 node.to_accesskit_node(),
181 ));
182 }
183 cvkg_vdom::VDomPatch::Update { id, .. } => {
184 if let Some(node) = new_vdom.nodes.get(id) {
185 nodes.push((
186 accesskit::NodeId(node.id.0 as u64),
187 node.to_accesskit_node(),
188 ));
189 }
190 }
191 _ => {}
192 }
193 }
194
195 if !nodes.is_empty() {
196 adapter.update_if_active(|| accesskit::TreeUpdate {
197 nodes,
198 tree: None,
199 focus: accesskit::NodeId(1),
200 });
201 }
202 }
203
204 prev_vdom.apply_patches(patches);
206 } else {
207 self.vdom = Some(new_vdom);
208 }
209
210 gpu.end_frame(encoder);
211 }
212 }
213 WindowEvent::CursorMoved { position, .. } => {
214 let scale = renderer.window.scale_factor();
215 let logical = position.to_logical::<f32>(scale);
216 self.cursor_pos = [logical.x, logical.y];
217 if let Some(vdom) = &self.vdom {
218 vdom.dispatch_event(cvkg_core::Event::PointerMove {
219 x: self.cursor_pos[0],
220 y: self.cursor_pos[1],
221 });
222 }
223 }
224 WindowEvent::MouseInput { state, .. } => {
225 if let Some(vdom) = &self.vdom {
226 let event = match state {
227 winit::event::ElementState::Pressed => {
228 let id = vdom.hit_test(self.cursor_pos[0], self.cursor_pos[1]);
229 println!("Native: MousePress at {:?}, hit={:?}", self.cursor_pos, id);
230 cvkg_core::Event::PointerDown {
231 x: self.cursor_pos[0],
232 y: self.cursor_pos[1],
233 }
234 }
235 winit::event::ElementState::Released => cvkg_core::Event::PointerUp {
236 x: self.cursor_pos[0],
237 y: self.cursor_pos[1],
238 },
239 };
240 vdom.dispatch_event(event);
241 }
242 }
243 WindowEvent::KeyboardInput { event, .. } => {
244 if let Some(vdom) = &self.vdom {
245 if let winit::keyboard::PhysicalKey::Code(code) = event.physical_key {
246 let key_str = format!("{:?}", code);
247 let cvkg_event = if event.state == winit::event::ElementState::Pressed {
248 cvkg_core::Event::KeyDown { key: key_str }
249 } else {
250 cvkg_core::Event::KeyUp { key: key_str }
251 };
252 vdom.dispatch_event(cvkg_event);
253 }
254
255 if event.state == winit::event::ElementState::Pressed {
257 if let Some(text) = event.text {
258 for c in text.chars() {
259 vdom.dispatch_event(cvkg_core::Event::KeyDown {
260 key: c.to_string(),
261 });
262 }
263 }
264 }
265 }
266 }
267 _ => (),
268 }
269 }
270
271 fn user_event(&mut self, _event_loop: &ActiveEventLoop, event: AppEvent) {
272 match event {
273 AppEvent::AccessibilityAction(request) => {
274 if let Some(vdom) = &self.vdom {
275 let node_id = cvkg_vdom::NodeId(request.target.0 as usize);
276 if let Some(node) = vdom.nodes.get(&node_id) {
277 match request.action {
278 accesskit::Action::Click => {
279 let event = cvkg_core::Event::PointerClick {
281 x: node.layout.x + node.layout.width / 2.0,
282 y: node.layout.y + node.layout.height / 2.0,
283 };
284 vdom.dispatch_event(event);
285 }
286 accesskit::Action::Focus => {
287 if let Ok(mut focus) = vdom.focused_node.lock() {
288 *focus = Some(node_id);
289 }
290 }
291 _ => (),
292 }
293 }
294 }
295 }
296 }
297 }
298
299 fn about_to_wait(&mut self, _event_loop: &ActiveEventLoop) {
300 if let Some(renderer) = &self.renderer {
301 renderer.window.request_redraw();
302 }
303 }
304}
305
306impl cvkg_core::Renderer for NativeRenderer {
307 fn fill_rect(&mut self, rect: cvkg_core::Rect, color: [f32; 4]) {
308 if let Some(gpu) = &mut self.gpu {
309 gpu.fill_rect(rect, color);
310 }
311 }
312 fn fill_rounded_rect(&mut self, rect: cvkg_core::Rect, radius: f32, color: [f32; 4]) {
313 if let Some(gpu) = &mut self.gpu {
314 gpu.fill_rounded_rect(rect, radius, color);
315 }
316 }
317 fn fill_ellipse(&mut self, rect: cvkg_core::Rect, color: [f32; 4]) {
318 if let Some(gpu) = &mut self.gpu {
319 gpu.fill_ellipse(rect, color);
320 }
321 }
322 fn stroke_rect(&mut self, rect: cvkg_core::Rect, color: [f32; 4], stroke_width: f32) {
323 if let Some(gpu) = &mut self.gpu {
324 gpu.stroke_rect(rect, color, stroke_width);
325 }
326 }
327 fn stroke_rounded_rect(
328 &mut self,
329 rect: cvkg_core::Rect,
330 radius: f32,
331 color: [f32; 4],
332 stroke_width: f32,
333 ) {
334 if let Some(gpu) = &mut self.gpu {
335 gpu.stroke_rounded_rect(rect, radius, color, stroke_width);
336 }
337 }
338 fn stroke_ellipse(&mut self, rect: cvkg_core::Rect, color: [f32; 4], stroke_width: f32) {
339 if let Some(gpu) = &mut self.gpu {
340 gpu.stroke_ellipse(rect, color, stroke_width);
341 }
342 }
343 fn draw_line(
344 &mut self,
345 x1: f32,
346 y1: f32,
347 x2: f32,
348 y2: f32,
349 color: [f32; 4],
350 stroke_width: f32,
351 ) {
352 if let Some(gpu) = &mut self.gpu {
353 gpu.draw_line(x1, y1, x2, y2, color, stroke_width);
354 }
355 }
356 fn draw_text(&mut self, text: &str, x: f32, y: f32, size: f32, color: [f32; 4]) {
357 if let Some(gpu) = &mut self.gpu {
358 gpu.draw_text(text, x, y, size, color);
359 }
360 }
361 fn measure_text(&mut self, text: &str, size: f32) -> (f32, f32) {
362 if let Some(gpu) = &mut self.gpu {
363 gpu.measure_text(text, size)
364 } else {
365 (0.0, 0.0)
366 }
367 }
368 fn draw_texture(&mut self, texture_id: u32, rect: cvkg_core::Rect) {
369 if let Some(gpu) = &mut self.gpu {
370 gpu.draw_texture(texture_id, rect);
371 }
372 }
373 fn draw_image(&mut self, image_name: &str, rect: cvkg_core::Rect) {
374 if let Some(gpu) = &mut self.gpu {
375 gpu.draw_image(image_name, rect);
376 }
377 }
378 fn load_image(&mut self, name: &str, data: &[u8]) {
379 if let Some(gpu) = &mut self.gpu {
380 gpu.load_image(name, data);
381 }
382 }
383 fn push_clip_rect(&mut self, rect: cvkg_core::Rect) {
384 if let Some(gpu) = &mut self.gpu {
385 gpu.push_clip_rect(rect);
386 }
387 }
388 fn pop_clip_rect(&mut self) {
389 if let Some(gpu) = &mut self.gpu {
390 gpu.pop_clip_rect();
391 }
392 }
393 fn push_opacity(&mut self, opacity: f32) {
394 if let Some(gpu) = &mut self.gpu {
395 gpu.push_opacity(opacity);
396 }
397 }
398 fn pop_opacity(&mut self) {
399 if let Some(gpu) = &mut self.gpu {
400 gpu.pop_opacity();
401 }
402 }
403 fn bifrost(&mut self, rect: cvkg_core::Rect, blur: f32, saturation: f32, opacity: f32) {
404 if let Some(gpu) = &mut self.gpu {
405 gpu.bifrost(rect, blur, saturation, opacity);
406 }
407 }
408 fn push_mjolnir_slice(&mut self, angle: f32, offset: f32) {
409 if let Some(gpu) = &mut self.gpu {
410 gpu.push_mjolnir_slice(angle, offset);
411 }
412 }
413 fn pop_mjolnir_slice(&mut self) {
414 if let Some(gpu) = &mut self.gpu {
415 gpu.pop_mjolnir_slice();
416 }
417 }
418 fn register_shared_element(&mut self, id: &str, rect: cvkg_core::Rect) {
419 if let Some(gpu) = &mut self.gpu {
420 gpu.register_shared_element(id, rect);
421 }
422 }
423}
424
425
426
427struct ShieldWall {
430 proxy: winit::event_loop::EventLoopProxy<AppEvent>,
431}
432
433impl accesskit::ActionHandler for ShieldWall {
434 fn do_action(&mut self, request: accesskit::ActionRequest) {
435 let _ = self.proxy.send_event(AppEvent::AccessibilityAction(request));
436 }
437}
438
439impl accesskit::ActivationHandler for ShieldWall {
440 fn request_initial_tree(&mut self) -> Option<accesskit::TreeUpdate> {
441 let mut root = accesskit::Node::new(accesskit::Role::Window);
442 root.set_label("CVKG Application");
443
444 let root_id = accesskit::NodeId(1);
445 Some(accesskit::TreeUpdate {
446 nodes: vec![(root_id, root)],
447 tree: Some(accesskit::Tree::new(root_id)),
448 focus: root_id,
449 })
450 }
451}
452
453impl accesskit::DeactivationHandler for ShieldWall {
454 fn deactivate_accessibility(&mut self) {}
455}
456
457pub struct NativeAssetManager {
459 cache: std::sync::Arc<
460 std::sync::RwLock<
461 std::collections::HashMap<String, cvkg_core::AssetState<std::sync::Arc<Vec<u8>>>>,
462 >,
463 >,
464}
465
466impl NativeAssetManager {
467 pub fn new() -> Self {
468 Self {
469 cache: std::sync::Arc::new(std::sync::RwLock::new(std::collections::HashMap::new())),
470 }
471 }
472}
473
474impl cvkg_core::AssetManager for NativeAssetManager {
475 fn load_image(&self, url: &str) -> cvkg_core::AssetState<std::sync::Arc<Vec<u8>>> {
476 {
477 let cache = self.cache.read().unwrap();
478 if let Some(state) = cache.get(url) {
479 return state.clone();
480 }
481 }
482
483 match std::fs::read(url) {
485 Ok(data) => {
486 let state = cvkg_core::AssetState::Ready(std::sync::Arc::new(data));
487 let mut cache = self.cache.write().unwrap();
488 cache.insert(url.to_string(), state.clone());
489 state
490 }
491 Err(e) => {
492 let state = cvkg_core::AssetState::Error(e.to_string());
493 let mut cache = self.cache.write().unwrap();
494 cache.insert(url.to_string(), state.clone());
495 state
496 }
497 }
498 }
499
500 fn preload_image(&self, _url: &str) {
501 }
503}