1use std::sync::Arc;
31use winit::{
32 application::ApplicationHandler,
33 event::WindowEvent,
34 event_loop::{ActiveEventLoop, ControlFlow, EventLoop},
35 window::{Window, WindowId},
36};
37
38
39pub struct NativeRenderer {
42 gpu: Arc<std::sync::Mutex<cvkg_render_gpu::SurtrRenderer>>,
43 delta_time: f32,
44 elapsed_time: f32,
45}
46
47#[derive(Debug)]
49enum AppEvent {
50 AccessibilityAction(accesskit::ActionRequest),
51}
52
53impl NativeRenderer {
54 fn new(_window: Arc<Window>, gpu: Arc<std::sync::Mutex<cvkg_render_gpu::SurtrRenderer>>, delta_time: f32, elapsed_time: f32) -> Self {
56 Self { gpu, delta_time, elapsed_time }
57 }
58
59
60 pub fn run<V: cvkg_core::View + 'static>(view: V) {
63 let event_loop = EventLoop::<AppEvent>::with_user_event()
64 .build()
65 .expect("Failed to create event loop");
66 event_loop.set_control_flow(ControlFlow::Poll);
67
68 let mut app = App {
69 view,
70 windows: std::collections::HashMap::new(),
71 gpu: None,
72 asset_manager: std::sync::Arc::new(NativeAssetManager::new()),
73 proxy: event_loop.create_proxy(),
74 start_time: std::time::Instant::now(),
75 };
76
77 event_loop.run_app(&mut app).expect("Event loop error");
78 }
79}
80
81struct WindowState {
82 window: Arc<Window>,
83 accesskit_adapter: Option<accesskit_winit::Adapter>,
84 vdom: Option<cvkg_vdom::VDom>,
85 cursor_pos: [f32; 2],
86 last_redraw_start: std::time::Instant,
88}
89
90struct App<V: cvkg_core::View> {
91 view: V,
92 windows: std::collections::HashMap<WindowId, WindowState>,
93 gpu: Option<Arc<std::sync::Mutex<cvkg_render_gpu::SurtrRenderer>>>,
94 asset_manager: std::sync::Arc<NativeAssetManager>,
95 proxy: winit::event_loop::EventLoopProxy<AppEvent>,
96 start_time: std::time::Instant,
97}
98
99impl<V: cvkg_core::View + 'static> ApplicationHandler<AppEvent> for App<V> {
100 fn resumed(&mut self, event_loop: &ActiveEventLoop) {
101 if self.gpu.is_none() {
102 let window_attrs = Window::default_attributes()
103 .with_title("CVKG Forge")
104 .with_inner_size(winit::dpi::LogicalSize::new(1280.0, 720.0));
105
106 let window = Arc::new(
107 event_loop
108 .create_window(window_attrs)
109 .expect("Failed to create window"),
110 );
111 window.set_ime_allowed(true);
112
113 let adapter = accesskit_winit::Adapter::with_direct_handlers(
114 event_loop,
115 &window,
116 ShieldWall { proxy: self.proxy.clone() },
117 ShieldWall { proxy: self.proxy.clone() },
118 ShieldWall { proxy: self.proxy.clone() },
119 );
120
121 let rt = tokio::runtime::Runtime::new().unwrap();
122 let gpu = rt.block_on(cvkg_render_gpu::SurtrRenderer::forge(window.clone()));
123 let gpu = Arc::new(std::sync::Mutex::new(gpu));
124 self.gpu = Some(gpu);
125
126 self.windows.insert(window.id(), WindowState {
127 window,
128 accesskit_adapter: Some(adapter),
129 vdom: Some(cvkg_vdom::VDom::new()),
130 cursor_pos: [0.0, 0.0],
131 last_redraw_start: std::time::Instant::now(),
132 });
133
134 cvkg_core::env::insert::<cvkg_core::AssetKey>(self.asset_manager.clone());
135 }
136 }
137
138 fn window_event(&mut self, event_loop: &ActiveEventLoop, id: WindowId, event: WindowEvent) {
139 let gpu_arc = if let Some(g) = &self.gpu { g.clone() } else { return };
140 let state = if let Some(s) = self.windows.get_mut(&id) { s } else { return };
141
142 match event {
143 WindowEvent::CloseRequested => {
144 self.windows.remove(&id);
145 if self.windows.is_empty() {
146 event_loop.exit();
147 }
148 }
149 WindowEvent::Resized(physical_size) => {
150 gpu_arc.lock().unwrap().resize(
151 id,
152 physical_size.width,
153 physical_size.height,
154 state.window.scale_factor() as f32,
155 );
156 state.window.request_redraw();
157 }
158 WindowEvent::RedrawRequested => {
159 let size = state.window.inner_size();
160 let scale = state.window.scale_factor();
161 let logical_size = size.to_logical::<f32>(scale);
162
163 let rect = cvkg_core::Rect {
164 x: 0.0,
165 y: 0.0,
166 width: logical_size.width,
167 height: logical_size.height,
168 };
169
170 let redraw_start = std::time::Instant::now();
172
173 let layout_start = std::time::Instant::now();
175 let new_vdom = cvkg_vdom::VDom::build(&self.view, rect);
176 let layout_end = std::time::Instant::now();
177
178 let state_flush_start = std::time::Instant::now();
180 if let Some(prev_vdom) = &mut state.vdom {
181 let patches = prev_vdom.diff(&new_vdom);
182 if let Some(adapter) = &mut state.accesskit_adapter {
183 let mut nodes = Vec::new();
184 for patch in &patches {
185 if let cvkg_vdom::VDomPatch::Create(node) | cvkg_vdom::VDomPatch::Replace { node, .. } = patch {
186 nodes.push((accesskit::NodeId(node.id.0), node.to_accesskit_node()));
187 } else if let cvkg_vdom::VDomPatch::Update { id, .. } = patch
188 && let Some(node) = new_vdom.nodes.get(id) {
189 nodes.push((accesskit::NodeId(node.id.0), node.to_accesskit_node()));
190 }
191 }
192 if !nodes.is_empty() {
193 adapter.update_if_active(|| accesskit::TreeUpdate {
194 nodes,
195 tree: None,
196 focus: accesskit::NodeId(1),
197 });
198 }
199 }
200 prev_vdom.apply_patches(patches);
201 } else {
202 state.vdom = Some(new_vdom);
203 }
204 let state_flush_end = std::time::Instant::now();
205
206 let draw_start = std::time::Instant::now();
208 let delta_time = redraw_start.duration_since(state.last_redraw_start).as_secs_f32();
209 let elapsed_time = redraw_start.duration_since(self.start_time).as_secs_f32();
210 let mut gpu = gpu_arc.lock().unwrap();
211 let encoder = gpu.begin_frame(id);
212 let mut renderer = NativeRenderer::new(state.window.clone(), gpu_arc.clone(), delta_time, elapsed_time);
213 self.view.render(&mut renderer, rect);
214 let draw_end = std::time::Instant::now();
215
216 let gpu_submit_start = std::time::Instant::now();
218 gpu.end_frame(encoder);
219 let gpu_submit_end = std::time::Instant::now();
220
221 let mut telemetry = gpu.telemetry.clone();
223 telemetry.input_time_ms = redraw_start.duration_since(state.last_redraw_start).as_secs_f32() * 1000.0;
225 telemetry.layout_time_ms = layout_end.duration_since(layout_start).as_secs_f32() * 1000.0;
226 telemetry.state_flush_time_ms = state_flush_end.duration_since(state_flush_start).as_secs_f32() * 1000.0;
227 telemetry.draw_time_ms = draw_end.duration_since(draw_start).as_secs_f32() * 1000.0;
228 telemetry.gpu_submit_time_ms = gpu_submit_end.duration_since(gpu_submit_start).as_secs_f32() * 1000.0;
229
230 telemetry.frame_time_ms = gpu_submit_end.duration_since(redraw_start).as_secs_f32() * 1000.0;
232
233 gpu.telemetry = telemetry;
234 state.last_redraw_start = gpu_submit_end;
235 }
236 WindowEvent::CursorMoved { position, .. } => {
237 let scale = state.window.scale_factor();
238 let logical = position.to_logical::<f32>(scale);
239 state.cursor_pos = [logical.x, logical.y];
240 if let Some(vdom) = &state.vdom {
241 vdom.dispatch_event(cvkg_core::Event::PointerMove {
242 x: state.cursor_pos[0],
243 y: state.cursor_pos[1],
244 });
245 }
246 }
247 WindowEvent::MouseInput { state: mouse_state, .. } => {
248 if let Some(vdom) = &state.vdom {
249 let event = convert_mouse_event(mouse_state, state.cursor_pos);
250 vdom.dispatch_event(event);
251 }
252 }
253 WindowEvent::KeyboardInput { event, .. } => {
254 if let Some(vdom) = &state.vdom
255 && let Some(cvkg_event) = convert_keyboard_event(event) {
256 vdom.dispatch_event(cvkg_event);
257 }
258 }
259 WindowEvent::Ime(ime_event) => {
260 if let Some(vdom) = &state.vdom
261 && let Some(cvkg_event) = convert_ime_event(ime_event) {
262 vdom.dispatch_event(cvkg_event);
263 }
264 }
265 _ => {}
266 }
267 }
268
269 fn user_event(&mut self, _event_loop: &ActiveEventLoop, event: AppEvent) {
270 let AppEvent::AccessibilityAction(request) = event;
271 let node_id = cvkg_vdom::NodeId(request.target.0);
272 if let Some(state) = self.windows.values_mut().next()
274 && let Some(vdom) = &state.vdom
275 && let Some(node) = vdom.nodes.get(&node_id)
276 && request.action == accesskit::Action::Click {
277 let event = cvkg_core::Event::PointerClick {
278 x: node.layout.x + node.layout.width / 2.0,
279 y: node.layout.y + node.layout.height / 2.0,
280 };
281 vdom.dispatch_event(event);
282 }
283 }
284
285 fn about_to_wait(&mut self, _event_loop: &ActiveEventLoop) {
286 for state in self.windows.values() {
287 state.window.request_redraw();
288 }
289 }
290}
291
292impl cvkg_core::ElapsedTime for NativeRenderer {
293 fn delta_time(&self) -> f32 {
294 self.delta_time
295 }
296
297 fn elapsed_time(&self) -> f32 {
298 self.elapsed_time
299 }
300}
301
302impl cvkg_core::Renderer for NativeRenderer {
303
304 fn fill_rect(&mut self, rect: cvkg_core::Rect, color: [f32; 4]) {
305 self.gpu.lock().unwrap().fill_rect(rect, color);
306 }
307 fn fill_rounded_rect(&mut self, rect: cvkg_core::Rect, radius: f32, color: [f32; 4]) {
308 self.gpu.lock().unwrap().fill_rounded_rect(rect, radius, color);
309 }
310 fn fill_ellipse(&mut self, rect: cvkg_core::Rect, color: [f32; 4]) {
311 self.gpu.lock().unwrap().fill_ellipse(rect, color);
312 }
313 fn stroke_rect(&mut self, rect: cvkg_core::Rect, color: [f32; 4], stroke_width: f32) {
314 self.gpu.lock().unwrap().stroke_rect(rect, color, stroke_width);
315 }
316 fn stroke_rounded_rect(
317 &mut self,
318 rect: cvkg_core::Rect,
319 radius: f32,
320 color: [f32; 4],
321 stroke_width: f32,
322 ) {
323 self.gpu.lock().unwrap().stroke_rounded_rect(rect, radius, color, stroke_width);
324 }
325 fn stroke_ellipse(&mut self, rect: cvkg_core::Rect, color: [f32; 4], stroke_width: f32) {
326 self.gpu.lock().unwrap().stroke_ellipse(rect, color, stroke_width);
327 }
328 fn draw_line(
329 &mut self,
330 x1: f32,
331 y1: f32,
332 x2: f32,
333 y2: f32,
334 color: [f32; 4],
335 stroke_width: f32,
336 ) {
337 self.gpu.lock().unwrap().draw_line(x1, y1, x2, y2, color, stroke_width);
338 }
339 fn draw_text(&mut self, text: &str, x: f32, y: f32, size: f32, color: [f32; 4]) {
340 self.gpu.lock().unwrap().draw_text(text, x, y, size, color);
341 }
342 fn measure_text(&mut self, text: &str, size: f32) -> (f32, f32) {
343 self.gpu.lock().unwrap().measure_text(text, size)
344 }
345 fn draw_texture(&mut self, texture_id: u32, rect: cvkg_core::Rect) {
346 self.gpu.lock().unwrap().draw_texture(texture_id, rect);
347 }
348 fn draw_image(&mut self, image_name: &str, rect: cvkg_core::Rect) {
349 self.gpu.lock().unwrap().draw_image(image_name, rect);
350 }
351 fn load_image(&mut self, name: &str, data: &[u8]) {
352 self.gpu.lock().unwrap().load_image(name, data);
353 }
354 fn push_clip_rect(&mut self, rect: cvkg_core::Rect) {
355 self.gpu.lock().unwrap().push_clip_rect(rect);
356 }
357 fn pop_clip_rect(&mut self) {
358 self.gpu.lock().unwrap().pop_clip_rect();
359 }
360 fn push_opacity(&mut self, opacity: f32) {
361 self.gpu.lock().unwrap().push_opacity(opacity);
362 }
363 fn pop_opacity(&mut self) {
364 self.gpu.lock().unwrap().pop_opacity();
365 }
366 fn bifrost(&mut self, rect: cvkg_core::Rect, blur: f32, saturation: f32, opacity: f32) {
367 self.gpu.lock().unwrap().bifrost(rect, blur, saturation, opacity);
368 }
369 fn push_mjolnir_slice(&mut self, angle: f32, offset: f32) {
370 self.gpu.lock().unwrap().push_mjolnir_slice(angle, offset);
371 }
372 fn pop_mjolnir_slice(&mut self) {
373 self.gpu.lock().unwrap().pop_mjolnir_slice();
374 }
375 fn mjolnir_shatter(&mut self, rect: cvkg_core::Rect, pieces: u32, force: f32, color: [f32; 4]) {
376 self.gpu.lock().unwrap().mjolnir_shatter(rect, pieces, force, color);
377 }
378 fn mjolnir_fluid_shatter(&mut self, rect: cvkg_core::Rect, pieces: u32, force: f32, color: [f32; 4]) {
379 self.gpu.lock().unwrap().mjolnir_fluid_shatter(rect, pieces, force, color);
380 }
381 fn draw_mjolnir_bolt(&mut self, from: [f32; 2], to: [f32; 2], color: [f32; 4]) {
382 self.gpu.lock().unwrap().draw_mjolnir_bolt(from, to, color);
383 }
384 fn register_shared_element(&mut self, id: &str, rect: cvkg_core::Rect) {
385 self.gpu.lock().unwrap().register_shared_element(id, rect);
386 }
387 fn set_z_index(&mut self, z: f32) {
388 self.gpu.lock().unwrap().set_z_index(z);
389 }
390 fn get_z_index(&self) -> f32 {
391 self.gpu.lock().unwrap().get_z_index()
392 }
393 fn load_svg(&mut self, name: &str, svg_data: &[u8]) {
394 self.gpu.lock().unwrap().load_svg(name, svg_data);
395 }
396 fn draw_svg(&mut self, name: &str, rect: cvkg_core::Rect) {
397 self.gpu.lock().unwrap().draw_svg(name, rect, None, 0);
398 }
399 fn get_telemetry(&self) -> cvkg_core::TelemetryData {
400 self.gpu.lock().unwrap().telemetry.clone()
401 }
402
403 fn push_transform(&mut self, translation: [f32; 2], scale: [f32; 2], rotation: f32) {
404 self.gpu.lock().unwrap().push_transform(translation, scale, rotation);
405 }
406
407 fn pop_transform(&mut self) {
408 self.gpu.lock().unwrap().pop_transform();
409 }
410}
411
412fn convert_mouse_event(state: winit::event::ElementState, pos: [f32; 2]) -> cvkg_core::Event {
415 match state {
416 winit::event::ElementState::Pressed => cvkg_core::Event::PointerDown { x: pos[0], y: pos[1] },
417 winit::event::ElementState::Released => cvkg_core::Event::PointerUp { x: pos[0], y: pos[1] },
418 }
419}
420
421fn convert_keyboard_event(event: winit::event::KeyEvent) -> Option<cvkg_core::Event> {
422 if let winit::keyboard::PhysicalKey::Code(code) = event.physical_key {
423 let key_str = format!("{:?}", code);
424 if event.state == winit::event::ElementState::Pressed {
425 Some(cvkg_core::Event::KeyDown { key: key_str })
426 } else {
427 Some(cvkg_core::Event::KeyUp { key: key_str })
428 }
429 } else {
430 None
431 }
432}
433
434fn convert_ime_event(event: winit::event::Ime) -> Option<cvkg_core::Event> {
435 if let winit::event::Ime::Commit(string) = event {
436 Some(cvkg_core::Event::Ime(string))
437 } else {
438 None
439 }
440}
441
442struct ShieldWall {
445 proxy: winit::event_loop::EventLoopProxy<AppEvent>,
446}
447
448impl accesskit::ActionHandler for ShieldWall {
449 fn do_action(&mut self, request: accesskit::ActionRequest) {
450 let _ = self
451 .proxy
452 .send_event(AppEvent::AccessibilityAction(request));
453 }
454}
455
456impl accesskit::ActivationHandler for ShieldWall {
457 fn request_initial_tree(&mut self) -> Option<accesskit::TreeUpdate> {
458 let mut root = accesskit::Node::new(accesskit::Role::Window);
459 root.set_label("CVKG Application");
460
461 let root_id = accesskit::NodeId(1);
462 Some(accesskit::TreeUpdate {
463 nodes: vec![(root_id, root)],
464 tree: Some(accesskit::Tree::new(root_id)),
465 focus: root_id,
466 })
467 }
468}
469
470impl accesskit::DeactivationHandler for ShieldWall {
471 fn deactivate_accessibility(&mut self) {}
472}
473
474pub struct NativeAssetManager {
480 cache: std::sync::Arc<
481 arc_swap::ArcSwap<
482 std::collections::HashMap<String, cvkg_core::AssetState<std::sync::Arc<Vec<u8>>>>,
483 >,
484 >,
485}
486
487impl Default for NativeAssetManager {
488 fn default() -> Self {
489 Self::new()
490 }
491}
492
493impl NativeAssetManager {
494 pub fn new() -> Self {
496 Self {
497 cache: std::sync::Arc::new(arc_swap::ArcSwap::from_pointee(
498 std::collections::HashMap::new(),
499 )),
500 }
501 }
502}
503
504impl cvkg_core::AssetManager for NativeAssetManager {
505 fn load_image(&self, url: &str) -> cvkg_core::AssetState<std::sync::Arc<Vec<u8>>> {
511 if let Some(state) = self.cache.load().get(url) {
513 return state.clone();
514 }
515
516 let result = match std::fs::read(url) {
518 Ok(data) => cvkg_core::AssetState::Ready(std::sync::Arc::new(data)),
519 Err(e) => cvkg_core::AssetState::Error(e.to_string()),
520 };
521 let result_clone = result.clone();
522 let key = url.to_string();
523 self.cache.rcu(move |map| {
524 let mut m = (**map).clone();
525 m.insert(key.clone(), result_clone.clone());
526 m
527 });
528 result
529 }
530
531 fn preload_image(&self, _url: &str) {
532 }
534}
535
536#[cfg(test)]
537mod tests {
538 use super::*;
539 use cvkg_core::AssetManager;
540 use std::io::Write;
541
542 #[test]
543 fn test_native_asset_manager_loading() {
544 let manager = NativeAssetManager::new();
545 let temp_file_path = "test_asset.png";
546 let test_data = b"fake-image-data";
547
548 let mut file = std::fs::File::create(temp_file_path).unwrap();
550 file.write_all(test_data).unwrap();
551
552 let state = manager.load_image(temp_file_path);
554 if let cvkg_core::AssetState::Ready(data) = state {
555 assert_eq!(&*data, test_data);
556 } else {
557 panic!("Expected Ready state");
558 }
559
560 let state2 = manager.load_image(temp_file_path);
562 if let cvkg_core::AssetState::Ready(data) = state2 {
563 assert_eq!(&*data, test_data);
564 } else {
565 panic!("Expected Ready state (cached)");
566 }
567
568 let _ = std::fs::remove_file(temp_file_path);
570 }
571
572 #[test]
573 fn test_native_asset_manager_error() {
574 let manager = NativeAssetManager::new();
575 let state = manager.load_image("non_existent_file.png");
576 if let cvkg_core::AssetState::Error(_) = state {
577 } else {
579 panic!("Expected Error state");
580 }
581 }
582
583 #[test]
584 fn test_event_conversion() {
585 let event = convert_mouse_event(winit::event::ElementState::Pressed, [10.0, 20.0]);
587 if let cvkg_core::Event::PointerDown { x, y } = event {
588 assert_eq!(x, 10.0);
589 assert_eq!(y, 20.0);
590 } else {
591 panic!("Expected PointerDown");
592 }
593
594 let event = convert_ime_event(winit::event::Ime::Commit("hello".to_string()));
596 if let Some(cvkg_core::Event::Ime(s)) = event {
597 assert_eq!(s, "hello");
598 } else {
599 panic!("Expected Ime event");
600 }
601 }
602}