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