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}
45
46#[derive(Debug)]
48enum AppEvent {
49 AccessibilityAction(accesskit::ActionRequest),
50}
51
52impl NativeRenderer {
53 fn new(_window: Arc<Window>, gpu: Arc<std::sync::Mutex<cvkg_render_gpu::SurtrRenderer>>, delta_time: f32) -> Self {
55 Self { gpu, delta_time }
56 }
57
58 fn get_telemetry(&self) -> cvkg_core::TelemetryData {
60 self.gpu.lock().unwrap().get_telemetry()
61 }
62
63 pub fn run<V: cvkg_core::View + 'static>(view: V) {
66 let event_loop = EventLoop::<AppEvent>::with_user_event()
67 .build()
68 .expect("Failed to create event loop");
69 event_loop.set_control_flow(ControlFlow::Poll);
70
71 let mut app = App {
72 view,
73 windows: std::collections::HashMap::new(),
74 gpu: None,
75 asset_manager: std::sync::Arc::new(NativeAssetManager::new()),
76 proxy: event_loop.create_proxy(),
77 };
78
79 event_loop.run_app(&mut app).expect("Event loop error");
80 }
81}
82
83struct WindowState {
84 window: Arc<Window>,
85 accesskit_adapter: Option<accesskit_winit::Adapter>,
86 vdom: Option<cvkg_vdom::VDom>,
87 cursor_pos: [f32; 2],
88 last_redraw_start: std::time::Instant,
90}
91
92struct App<V: cvkg_core::View> {
93 view: V,
94 windows: std::collections::HashMap<WindowId, WindowState>,
95 gpu: Option<Arc<std::sync::Mutex<cvkg_render_gpu::SurtrRenderer>>>,
96 asset_manager: std::sync::Arc<NativeAssetManager>,
97 proxy: winit::event_loop::EventLoopProxy<AppEvent>,
98}
99
100impl<V: cvkg_core::View + 'static> ApplicationHandler<AppEvent> for App<V> {
101 fn resumed(&mut self, event_loop: &ActiveEventLoop) {
102 if self.gpu.is_none() {
103 let window_attrs = Window::default_attributes()
104 .with_title("CVKG Forge")
105 .with_inner_size(winit::dpi::LogicalSize::new(1280.0, 720.0));
106
107 let window = Arc::new(
108 event_loop
109 .create_window(window_attrs)
110 .expect("Failed to create window"),
111 );
112 window.set_ime_allowed(true);
113
114 let adapter = accesskit_winit::Adapter::with_direct_handlers(
115 event_loop,
116 &window,
117 ShieldWall { proxy: self.proxy.clone() },
118 ShieldWall { proxy: self.proxy.clone() },
119 ShieldWall { proxy: self.proxy.clone() },
120 );
121
122 let rt = tokio::runtime::Runtime::new().unwrap();
123 let gpu = rt.block_on(cvkg_render_gpu::SurtrRenderer::forge(window.clone()));
124 let gpu = Arc::new(std::sync::Mutex::new(gpu));
125 self.gpu = Some(gpu);
126
127 self.windows.insert(window.id(), WindowState {
128 window,
129 accesskit_adapter: Some(adapter),
130 vdom: Some(cvkg_vdom::VDom::new()),
131 cursor_pos: [0.0, 0.0],
132 last_redraw_start: std::time::Instant::now(),
133 });
134
135 cvkg_core::env::insert::<cvkg_core::AssetKey>(self.asset_manager.clone());
136 }
137 }
138
139 fn window_event(&mut self, event_loop: &ActiveEventLoop, id: WindowId, event: WindowEvent) {
140 let gpu_arc = if let Some(g) = &self.gpu { g.clone() } else { return };
141 let state = if let Some(s) = self.windows.get_mut(&id) { s } else { return };
142
143 match event {
144 WindowEvent::CloseRequested => {
145 self.windows.remove(&id);
146 if self.windows.is_empty() {
147 event_loop.exit();
148 }
149 }
150 WindowEvent::Resized(physical_size) => {
151 gpu_arc.lock().unwrap().resize(
152 id,
153 physical_size.width,
154 physical_size.height,
155 state.window.scale_factor() as f32,
156 );
157 state.window.request_redraw();
158 }
159 WindowEvent::RedrawRequested => {
160 let size = state.window.inner_size();
161 let scale = state.window.scale_factor();
162 let logical_size = size.to_logical::<f32>(scale);
163
164 let rect = cvkg_core::Rect {
165 x: 0.0,
166 y: 0.0,
167 width: logical_size.width,
168 height: logical_size.height,
169 };
170
171 let redraw_start = std::time::Instant::now();
173
174 let layout_start = std::time::Instant::now();
176 let new_vdom = cvkg_vdom::VDom::build(&self.view, rect);
177 let layout_end = std::time::Instant::now();
178
179 let state_flush_start = std::time::Instant::now();
181 if let Some(prev_vdom) = &mut state.vdom {
182 let patches = prev_vdom.diff(&new_vdom);
183 if let Some(adapter) = &mut state.accesskit_adapter {
184 let mut nodes = Vec::new();
185 for patch in &patches {
186 match patch {
187 cvkg_vdom::VDomPatch::Create(node)
188 | cvkg_vdom::VDomPatch::Replace { node, .. } => {
189 nodes.push((accesskit::NodeId(node.id.0 as u64), node.to_accesskit_node()));
190 }
191 cvkg_vdom::VDomPatch::Update { id, .. } => {
192 if let Some(node) = new_vdom.nodes.get(id) {
193 nodes.push((accesskit::NodeId(node.id.0 as u64), node.to_accesskit_node()));
194 }
195 }
196 _ => {}
197 }
198 }
199 if !nodes.is_empty() {
200 adapter.update_if_active(|| accesskit::TreeUpdate {
201 nodes,
202 tree: None,
203 focus: accesskit::NodeId(1),
204 });
205 }
206 }
207 prev_vdom.apply_patches(patches);
208 } else {
209 state.vdom = Some(new_vdom);
210 }
211 let state_flush_end = std::time::Instant::now();
212
213 let draw_start = std::time::Instant::now();
215 let delta_time = redraw_start.duration_since(state.last_redraw_start).as_secs_f32();
216 let mut gpu = gpu_arc.lock().unwrap();
217 let encoder = gpu.begin_frame(id);
218 let mut renderer = NativeRenderer::new(state.window.clone(), gpu_arc.clone(), delta_time);
219 self.view.render(&mut renderer, rect);
220 let draw_end = std::time::Instant::now();
221
222 let gpu_submit_start = std::time::Instant::now();
224 gpu.end_frame(encoder);
225 let gpu_submit_end = std::time::Instant::now();
226
227 let mut telemetry = gpu.telemetry.clone();
229 telemetry.input_time_ms = redraw_start.duration_since(state.last_redraw_start).as_secs_f32() * 1000.0;
231 telemetry.layout_time_ms = layout_end.duration_since(layout_start).as_secs_f32() * 1000.0;
232 telemetry.state_flush_time_ms = state_flush_end.duration_since(state_flush_start).as_secs_f32() * 1000.0;
233 telemetry.draw_time_ms = draw_end.duration_since(draw_start).as_secs_f32() * 1000.0;
234 telemetry.gpu_submit_time_ms = gpu_submit_end.duration_since(gpu_submit_start).as_secs_f32() * 1000.0;
235
236 telemetry.frame_time_ms = gpu_submit_end.duration_since(redraw_start).as_secs_f32() * 1000.0;
238
239 gpu.telemetry = telemetry;
240 state.last_redraw_start = gpu_submit_end;
241 }
242 WindowEvent::CursorMoved { position, .. } => {
243 let scale = state.window.scale_factor();
244 let logical = position.to_logical::<f32>(scale);
245 state.cursor_pos = [logical.x, logical.y];
246 if let Some(vdom) = &state.vdom {
247 vdom.dispatch_event(cvkg_core::Event::PointerMove {
248 x: state.cursor_pos[0],
249 y: state.cursor_pos[1],
250 });
251 }
252 }
253 WindowEvent::MouseInput { state: mouse_state, .. } => {
254 if let Some(vdom) = &state.vdom {
255 let event = match mouse_state {
256 winit::event::ElementState::Pressed => {
257 cvkg_core::Event::PointerDown {
258 x: state.cursor_pos[0],
259 y: state.cursor_pos[1],
260 }
261 }
262 winit::event::ElementState::Released => cvkg_core::Event::PointerUp {
263 x: state.cursor_pos[0],
264 y: state.cursor_pos[1],
265 },
266 };
267 vdom.dispatch_event(event);
268 }
269 }
270 WindowEvent::KeyboardInput { event, .. } => {
271 if let Some(vdom) = &state.vdom {
272 if let winit::keyboard::PhysicalKey::Code(code) = event.physical_key {
273 let key_str = format!("{:?}", code);
274 let cvkg_event = if event.state == winit::event::ElementState::Pressed {
275 cvkg_core::Event::KeyDown { key: key_str }
276 } else {
277 cvkg_core::Event::KeyUp { key: key_str }
278 };
279 vdom.dispatch_event(cvkg_event);
280 }
281 }
282 }
283 WindowEvent::Ime(ime_event) => {
284 if let Some(vdom) = &state.vdom {
285 match ime_event {
286 winit::event::Ime::Commit(string) => {
287 vdom.dispatch_event(cvkg_core::Event::Ime(string));
288 }
289 _ => {}
290 }
291 }
292 }
293 _ => {}
294 }
295 }
296
297 fn user_event(&mut self, _event_loop: &ActiveEventLoop, event: AppEvent) {
298 match event {
299 AppEvent::AccessibilityAction(request) => {
300 let node_id = cvkg_vdom::NodeId(request.target.0 as u64);
301 if let Some(state) = self.windows.values_mut().next() {
303 if let Some(vdom) = &state.vdom {
304 if let Some(node) = vdom.nodes.get(&node_id) {
305 match request.action {
306 accesskit::Action::Click => {
307 let event = cvkg_core::Event::PointerClick {
308 x: node.layout.x + node.layout.width / 2.0,
309 y: node.layout.y + node.layout.height / 2.0,
310 };
311 vdom.dispatch_event(event);
312 }
313 _ => ()
314 }
315 }
316 }
317 }
318 }
319 }
320 }
321
322 fn about_to_wait(&mut self, _event_loop: &ActiveEventLoop) {
323 for state in self.windows.values() {
324 state.window.request_redraw();
325 }
326 }
327}
328
329impl cvkg_core::Renderer for NativeRenderer {
330 fn delta_time(&self) -> f32 {
331 self.delta_time
332 }
333
334 fn fill_rect(&mut self, rect: cvkg_core::Rect, color: [f32; 4]) {
335 self.gpu.lock().unwrap().fill_rect(rect, color);
336 }
337 fn fill_rounded_rect(&mut self, rect: cvkg_core::Rect, radius: f32, color: [f32; 4]) {
338 self.gpu.lock().unwrap().fill_rounded_rect(rect, radius, color);
339 }
340 fn fill_ellipse(&mut self, rect: cvkg_core::Rect, color: [f32; 4]) {
341 self.gpu.lock().unwrap().fill_ellipse(rect, color);
342 }
343 fn stroke_rect(&mut self, rect: cvkg_core::Rect, color: [f32; 4], stroke_width: f32) {
344 self.gpu.lock().unwrap().stroke_rect(rect, color, stroke_width);
345 }
346 fn stroke_rounded_rect(
347 &mut self,
348 rect: cvkg_core::Rect,
349 radius: f32,
350 color: [f32; 4],
351 stroke_width: f32,
352 ) {
353 self.gpu.lock().unwrap().stroke_rounded_rect(rect, radius, color, stroke_width);
354 }
355 fn stroke_ellipse(&mut self, rect: cvkg_core::Rect, color: [f32; 4], stroke_width: f32) {
356 self.gpu.lock().unwrap().stroke_ellipse(rect, color, stroke_width);
357 }
358 fn draw_line(
359 &mut self,
360 x1: f32,
361 y1: f32,
362 x2: f32,
363 y2: f32,
364 color: [f32; 4],
365 stroke_width: f32,
366 ) {
367 self.gpu.lock().unwrap().draw_line(x1, y1, x2, y2, color, stroke_width);
368 }
369 fn draw_text(&mut self, text: &str, x: f32, y: f32, size: f32, color: [f32; 4]) {
370 self.gpu.lock().unwrap().draw_text(text, x, y, size, color);
371 }
372 fn measure_text(&mut self, text: &str, size: f32) -> (f32, f32) {
373 self.gpu.lock().unwrap().measure_text(text, size)
374 }
375 fn draw_texture(&mut self, texture_id: u32, rect: cvkg_core::Rect) {
376 self.gpu.lock().unwrap().draw_texture(texture_id, rect);
377 }
378 fn draw_image(&mut self, image_name: &str, rect: cvkg_core::Rect) {
379 self.gpu.lock().unwrap().draw_image(image_name, rect);
380 }
381 fn load_image(&mut self, name: &str, data: &[u8]) {
382 self.gpu.lock().unwrap().load_image(name, data);
383 }
384 fn push_clip_rect(&mut self, rect: cvkg_core::Rect) {
385 self.gpu.lock().unwrap().push_clip_rect(rect);
386 }
387 fn pop_clip_rect(&mut self) {
388 self.gpu.lock().unwrap().pop_clip_rect();
389 }
390 fn push_opacity(&mut self, opacity: f32) {
391 self.gpu.lock().unwrap().push_opacity(opacity);
392 }
393 fn pop_opacity(&mut self) {
394 self.gpu.lock().unwrap().pop_opacity();
395 }
396 fn bifrost(&mut self, rect: cvkg_core::Rect, blur: f32, saturation: f32, opacity: f32) {
397 self.gpu.lock().unwrap().bifrost(rect, blur, saturation, opacity);
398 }
399 fn push_mjolnir_slice(&mut self, angle: f32, offset: f32) {
400 self.gpu.lock().unwrap().push_mjolnir_slice(angle, offset);
401 }
402 fn pop_mjolnir_slice(&mut self) {
403 self.gpu.lock().unwrap().pop_mjolnir_slice();
404 }
405 fn register_shared_element(&mut self, id: &str, rect: cvkg_core::Rect) {
406 self.gpu.lock().unwrap().register_shared_element(id, rect);
407 }
408 fn set_z_index(&mut self, z: f32) {
409 self.gpu.lock().unwrap().set_z_index(z);
410 }
411 fn get_z_index(&self) -> f32 {
412 self.gpu.lock().unwrap().get_z_index()
413 }
414 fn load_svg(&mut self, name: &str, svg_data: &[u8]) {
415 self.gpu.lock().unwrap().load_svg(name, svg_data);
416 }
417 fn draw_svg(&mut self, name: &str, rect: cvkg_core::Rect) {
418 self.gpu.lock().unwrap().draw_svg(name, rect, None, 0);
419 }
420 fn get_telemetry(&self) -> cvkg_core::TelemetryData {
421 self.gpu.lock().unwrap().telemetry.clone()
422 }
423}
424
425struct ShieldWall {
428 proxy: winit::event_loop::EventLoopProxy<AppEvent>,
429}
430
431impl accesskit::ActionHandler for ShieldWall {
432 fn do_action(&mut self, request: accesskit::ActionRequest) {
433 let _ = self
434 .proxy
435 .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 {
463 cache: std::sync::Arc<
464 arc_swap::ArcSwap<
465 std::collections::HashMap<String, cvkg_core::AssetState<std::sync::Arc<Vec<u8>>>>,
466 >,
467 >,
468}
469
470impl Default for NativeAssetManager {
471 fn default() -> Self {
472 Self::new()
473 }
474}
475
476impl NativeAssetManager {
477 pub fn new() -> Self {
479 Self {
480 cache: std::sync::Arc::new(arc_swap::ArcSwap::from_pointee(
481 std::collections::HashMap::new(),
482 )),
483 }
484 }
485}
486
487impl cvkg_core::AssetManager for NativeAssetManager {
488 fn load_image(&self, url: &str) -> cvkg_core::AssetState<std::sync::Arc<Vec<u8>>> {
494 if let Some(state) = self.cache.load().get(url) {
496 return state.clone();
497 }
498
499 let result = match std::fs::read(url) {
501 Ok(data) => cvkg_core::AssetState::Ready(std::sync::Arc::new(data)),
502 Err(e) => cvkg_core::AssetState::Error(e.to_string()),
503 };
504 let result_clone = result.clone();
505 let key = url.to_string();
506 self.cache.rcu(move |map| {
507 let mut m = (**map).clone();
508 m.insert(key.clone(), result_clone.clone());
509 m
510 });
511 result
512 }
513
514 fn preload_image(&self, _url: &str) {
515 }
517}