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 = match mouse_state {
250 winit::event::ElementState::Pressed => {
251 cvkg_core::Event::PointerDown {
252 x: state.cursor_pos[0],
253 y: state.cursor_pos[1],
254 }
255 }
256 winit::event::ElementState::Released => cvkg_core::Event::PointerUp {
257 x: state.cursor_pos[0],
258 y: state.cursor_pos[1],
259 },
260 };
261 vdom.dispatch_event(event);
262 }
263 }
264 WindowEvent::KeyboardInput { event, .. } => {
265 if let Some(vdom) = &state.vdom
266 && let winit::keyboard::PhysicalKey::Code(code) = event.physical_key {
267 let key_str = format!("{:?}", code);
268 let cvkg_event = if event.state == winit::event::ElementState::Pressed {
269 cvkg_core::Event::KeyDown { key: key_str }
270 } else {
271 cvkg_core::Event::KeyUp { key: key_str }
272 };
273 vdom.dispatch_event(cvkg_event);
274 }
275 }
276 WindowEvent::Ime(ime_event) => {
277 if let Some(vdom) = &state.vdom
278 && let winit::event::Ime::Commit(string) = ime_event {
279 vdom.dispatch_event(cvkg_core::Event::Ime(string));
280 }
281 }
282 _ => {}
283 }
284 }
285
286 fn user_event(&mut self, _event_loop: &ActiveEventLoop, event: AppEvent) {
287 let AppEvent::AccessibilityAction(request) = event;
288 let node_id = cvkg_vdom::NodeId(request.target.0);
289 if let Some(state) = self.windows.values_mut().next()
291 && let Some(vdom) = &state.vdom
292 && let Some(node) = vdom.nodes.get(&node_id)
293 && request.action == accesskit::Action::Click {
294 let event = cvkg_core::Event::PointerClick {
295 x: node.layout.x + node.layout.width / 2.0,
296 y: node.layout.y + node.layout.height / 2.0,
297 };
298 vdom.dispatch_event(event);
299 }
300 }
301
302 fn about_to_wait(&mut self, _event_loop: &ActiveEventLoop) {
303 for state in self.windows.values() {
304 state.window.request_redraw();
305 }
306 }
307}
308
309impl cvkg_core::ElapsedTime for NativeRenderer {
310 fn delta_time(&self) -> f32 {
311 self.delta_time
312 }
313
314 fn elapsed_time(&self) -> f32 {
315 self.elapsed_time
316 }
317}
318
319impl cvkg_core::Renderer for NativeRenderer {
320
321 fn fill_rect(&mut self, rect: cvkg_core::Rect, color: [f32; 4]) {
322 self.gpu.lock().unwrap().fill_rect(rect, color);
323 }
324 fn fill_rounded_rect(&mut self, rect: cvkg_core::Rect, radius: f32, color: [f32; 4]) {
325 self.gpu.lock().unwrap().fill_rounded_rect(rect, radius, color);
326 }
327 fn fill_ellipse(&mut self, rect: cvkg_core::Rect, color: [f32; 4]) {
328 self.gpu.lock().unwrap().fill_ellipse(rect, color);
329 }
330 fn stroke_rect(&mut self, rect: cvkg_core::Rect, color: [f32; 4], stroke_width: f32) {
331 self.gpu.lock().unwrap().stroke_rect(rect, color, stroke_width);
332 }
333 fn stroke_rounded_rect(
334 &mut self,
335 rect: cvkg_core::Rect,
336 radius: f32,
337 color: [f32; 4],
338 stroke_width: f32,
339 ) {
340 self.gpu.lock().unwrap().stroke_rounded_rect(rect, radius, color, stroke_width);
341 }
342 fn stroke_ellipse(&mut self, rect: cvkg_core::Rect, color: [f32; 4], stroke_width: f32) {
343 self.gpu.lock().unwrap().stroke_ellipse(rect, color, stroke_width);
344 }
345 fn draw_line(
346 &mut self,
347 x1: f32,
348 y1: f32,
349 x2: f32,
350 y2: f32,
351 color: [f32; 4],
352 stroke_width: f32,
353 ) {
354 self.gpu.lock().unwrap().draw_line(x1, y1, x2, y2, color, stroke_width);
355 }
356 fn draw_text(&mut self, text: &str, x: f32, y: f32, size: f32, color: [f32; 4]) {
357 self.gpu.lock().unwrap().draw_text(text, x, y, size, color);
358 }
359 fn measure_text(&mut self, text: &str, size: f32) -> (f32, f32) {
360 self.gpu.lock().unwrap().measure_text(text, size)
361 }
362 fn draw_texture(&mut self, texture_id: u32, rect: cvkg_core::Rect) {
363 self.gpu.lock().unwrap().draw_texture(texture_id, rect);
364 }
365 fn draw_image(&mut self, image_name: &str, rect: cvkg_core::Rect) {
366 self.gpu.lock().unwrap().draw_image(image_name, rect);
367 }
368 fn load_image(&mut self, name: &str, data: &[u8]) {
369 self.gpu.lock().unwrap().load_image(name, data);
370 }
371 fn push_clip_rect(&mut self, rect: cvkg_core::Rect) {
372 self.gpu.lock().unwrap().push_clip_rect(rect);
373 }
374 fn pop_clip_rect(&mut self) {
375 self.gpu.lock().unwrap().pop_clip_rect();
376 }
377 fn push_opacity(&mut self, opacity: f32) {
378 self.gpu.lock().unwrap().push_opacity(opacity);
379 }
380 fn pop_opacity(&mut self) {
381 self.gpu.lock().unwrap().pop_opacity();
382 }
383 fn bifrost(&mut self, rect: cvkg_core::Rect, blur: f32, saturation: f32, opacity: f32) {
384 self.gpu.lock().unwrap().bifrost(rect, blur, saturation, opacity);
385 }
386 fn push_mjolnir_slice(&mut self, angle: f32, offset: f32) {
387 self.gpu.lock().unwrap().push_mjolnir_slice(angle, offset);
388 }
389 fn pop_mjolnir_slice(&mut self) {
390 self.gpu.lock().unwrap().pop_mjolnir_slice();
391 }
392 fn mjolnir_shatter(&mut self, rect: cvkg_core::Rect, pieces: u32, force: f32, color: [f32; 4]) {
393 self.gpu.lock().unwrap().mjolnir_shatter(rect, pieces, force, color);
394 }
395 fn mjolnir_fluid_shatter(&mut self, rect: cvkg_core::Rect, pieces: u32, force: f32, color: [f32; 4]) {
396 self.gpu.lock().unwrap().mjolnir_fluid_shatter(rect, pieces, force, color);
397 }
398 fn draw_mjolnir_bolt(&mut self, from: [f32; 2], to: [f32; 2], color: [f32; 4]) {
399 self.gpu.lock().unwrap().draw_mjolnir_bolt(from, to, color);
400 }
401 fn register_shared_element(&mut self, id: &str, rect: cvkg_core::Rect) {
402 self.gpu.lock().unwrap().register_shared_element(id, rect);
403 }
404 fn set_z_index(&mut self, z: f32) {
405 self.gpu.lock().unwrap().set_z_index(z);
406 }
407 fn get_z_index(&self) -> f32 {
408 self.gpu.lock().unwrap().get_z_index()
409 }
410 fn load_svg(&mut self, name: &str, svg_data: &[u8]) {
411 self.gpu.lock().unwrap().load_svg(name, svg_data);
412 }
413 fn draw_svg(&mut self, name: &str, rect: cvkg_core::Rect) {
414 self.gpu.lock().unwrap().draw_svg(name, rect, None, 0);
415 }
416 fn get_telemetry(&self) -> cvkg_core::TelemetryData {
417 self.gpu.lock().unwrap().telemetry.clone()
418 }
419
420 fn push_transform(&mut self, translation: [f32; 2], scale: [f32; 2], rotation: f32) {
421 self.gpu.lock().unwrap().push_transform(translation, scale, rotation);
422 }
423
424 fn pop_transform(&mut self) {
425 self.gpu.lock().unwrap().pop_transform();
426 }
427}
428
429struct ShieldWall {
432 proxy: winit::event_loop::EventLoopProxy<AppEvent>,
433}
434
435impl accesskit::ActionHandler for ShieldWall {
436 fn do_action(&mut self, request: accesskit::ActionRequest) {
437 let _ = self
438 .proxy
439 .send_event(AppEvent::AccessibilityAction(request));
440 }
441}
442
443impl accesskit::ActivationHandler for ShieldWall {
444 fn request_initial_tree(&mut self) -> Option<accesskit::TreeUpdate> {
445 let mut root = accesskit::Node::new(accesskit::Role::Window);
446 root.set_label("CVKG Application");
447
448 let root_id = accesskit::NodeId(1);
449 Some(accesskit::TreeUpdate {
450 nodes: vec![(root_id, root)],
451 tree: Some(accesskit::Tree::new(root_id)),
452 focus: root_id,
453 })
454 }
455}
456
457impl accesskit::DeactivationHandler for ShieldWall {
458 fn deactivate_accessibility(&mut self) {}
459}
460
461pub struct NativeAssetManager {
467 cache: std::sync::Arc<
468 arc_swap::ArcSwap<
469 std::collections::HashMap<String, cvkg_core::AssetState<std::sync::Arc<Vec<u8>>>>,
470 >,
471 >,
472}
473
474impl Default for NativeAssetManager {
475 fn default() -> Self {
476 Self::new()
477 }
478}
479
480impl NativeAssetManager {
481 pub fn new() -> Self {
483 Self {
484 cache: std::sync::Arc::new(arc_swap::ArcSwap::from_pointee(
485 std::collections::HashMap::new(),
486 )),
487 }
488 }
489}
490
491impl cvkg_core::AssetManager for NativeAssetManager {
492 fn load_image(&self, url: &str) -> cvkg_core::AssetState<std::sync::Arc<Vec<u8>>> {
498 if let Some(state) = self.cache.load().get(url) {
500 return state.clone();
501 }
502
503 let result = match std::fs::read(url) {
505 Ok(data) => cvkg_core::AssetState::Ready(std::sync::Arc::new(data)),
506 Err(e) => cvkg_core::AssetState::Error(e.to_string()),
507 };
508 let result_clone = result.clone();
509 let key = url.to_string();
510 self.cache.rcu(move |map| {
511 let mut m = (**map).clone();
512 m.insert(key.clone(), result_clone.clone());
513 m
514 });
515 result
516 }
517
518 fn preload_image(&self, _url: &str) {
519 }
521}