1#![allow(
28 unused_imports,
29 clippy::single_component_path_imports,
30 dead_code,
31 clippy::items_after_test_module,
32 clippy::field_reassign_with_default,
33 clippy::collapsible_if,
34 clippy::unnecessary_map_or
35)]
36
37use cvkg_core::{FrameRenderer, Renderer};
43use image;
44use std::sync::Arc;
46use winit::{
47 application::ApplicationHandler,
48 event::{DeviceEvent, DeviceId, WindowEvent},
49 event_loop::{ActiveEventLoop, ControlFlow, EventLoop},
50 window::{Window, WindowId},
51};
52
53#[derive(Debug, Clone, Copy, PartialEq, Eq)]
58pub enum WindowState {
59 Normal,
61 Minimized,
63 Fullscreen,
65 SplitView,
67 Occluded,
69 Hidden,
71}
72
73pub struct WindowStateDetector {
87 state: WindowState,
88 is_key: bool,
89 is_main: bool,
90}
91
92impl WindowStateDetector {
93 pub fn new() -> Self {
95 Self {
96 state: WindowState::Normal,
97 is_key: false,
98 is_main: false,
99 }
100 }
101
102 pub fn state(&self) -> WindowState {
104 self.state
105 }
106
107 pub fn is_key(&self) -> bool {
109 self.is_key
110 }
111
112 pub fn is_main(&self) -> bool {
114 self.is_main
115 }
116
117 pub fn update_from_event(&mut self, event: &WindowEvent) -> Option<WindowState> {
133 let old_state = self.state;
134 match event {
135 WindowEvent::Occluded(true) => {
136 self.state = WindowState::Occluded;
137 }
138 WindowEvent::Focused(focused) => {
139 self.is_key = *focused;
140 if !focused && self.state != WindowState::Minimized {
141 self.state = WindowState::Normal;
142 }
143 }
144 _ => {}
145 };
146 if self.state != old_state {
147 Some(self.state)
148 } else {
149 None
150 }
151 }
152
153 pub fn update_from_window(&mut self, window: &winit::window::Window) -> Option<WindowState> {
160 let old_state = self.state;
161 if window.is_minimized().unwrap_or(false) {
162 self.state = WindowState::Minimized;
163 } else if window.fullscreen().is_some() {
164 self.state = WindowState::Fullscreen;
165 } else if self.state == WindowState::Minimized || self.state == WindowState::Fullscreen {
166 self.state = WindowState::Normal;
168 }
169 if self.state != old_state {
170 Some(self.state)
171 } else {
172 None
173 }
174 }
175
176 pub fn should_render(&self) -> bool {
181 !matches!(
182 self.state,
183 WindowState::Occluded | WindowState::Minimized | WindowState::Hidden
184 )
185 }
186
187 pub fn control_flow(&self) -> ControlFlow {
192 if self.should_render() {
193 ControlFlow::Poll
194 } else {
195 ControlFlow::Wait
196 }
197 }
198}
199
200impl Default for WindowStateDetector {
201 fn default() -> Self {
202 Self::new()
203 }
204}
205
206pub struct ResizeHitTest {
213 window_size: winit::dpi::PhysicalSize<u32>,
215 corner_radius: f32,
217 expansion: f32,
219}
220
221impl ResizeHitTest {
222 pub fn new(
230 window_size: winit::dpi::PhysicalSize<u32>,
231 corner_radius: f32,
232 expansion: f32,
233 ) -> Self {
234 Self {
235 window_size,
236 corner_radius,
237 expansion,
238 }
239 }
240
241 pub fn hit_test(&self, pos: winit::dpi::PhysicalSize<u32>, corner_radius: f32) -> bool {
248 let r = corner_radius + self.expansion;
249 let w = self.window_size.width as f32;
250 let h = self.window_size.height as f32;
251 let px = pos.width as f32;
252 let py = pos.height as f32;
253
254 if px <= r && py <= r {
256 return true;
257 }
258
259 if px >= w - r && py <= r {
261 return true;
262 }
263
264 if px <= r && py >= h - r {
266 return true;
267 }
268
269 if px >= w - r && py >= h - r {
271 return true;
272 }
273
274 false
275 }
276}
277
278#[derive(Debug, Clone, Copy, PartialEq)]
282pub struct SafeAreaInsets {
283 pub top: f32,
285 pub bottom: f32,
287 pub left: f32,
289 pub right: f32,
291}
292
293impl SafeAreaInsets {
294 pub fn zero() -> Self {
296 Self {
297 top: 0.0,
298 bottom: 0.0,
299 left: 0.0,
300 right: 0.0,
301 }
302 }
303
304 pub fn for_window_state(state: WindowState) -> Self {
312 if state == WindowState::Fullscreen {
313 return Self::zero();
314 }
315 #[cfg(target_os = "macos")]
316 let top = 24.0;
317 #[cfg(not(target_os = "macos"))]
318 let top = 0.0;
319 Self {
320 top,
321 bottom: 0.0,
322 left: 0.0,
323 right: 0.0,
324 }
325 }
326}
327
328pub struct NativeRenderer {
331 gpu: Arc<std::sync::Mutex<cvkg_render_gpu::SurtrRenderer>>,
332 delta_time: f32,
333 elapsed_time: f32,
334 berserker_mode: cvkg_core::BerserkerMode,
335 rage: f32,
336 window: Arc<Window>,
337}
338
339#[derive(Debug)]
342pub enum AppEvent {
343 AccessibilityAction(accesskit::ActionRequest),
345 CloseWindow(winit::window::WindowId),
347 SetTitle(winit::window::WindowId, String),
349 SetSize(winit::window::WindowId, f32, f32),
351 SetVisible(winit::window::WindowId, bool),
353 BringToFront(winit::window::WindowId),
355}
356
357impl NativeRenderer {
358 fn new(
360 window: Arc<Window>,
361 gpu: Arc<std::sync::Mutex<cvkg_render_gpu::SurtrRenderer>>,
362 delta_time: f32,
363 elapsed_time: f32,
364 berserker_mode: cvkg_core::BerserkerMode,
365 rage: f32,
366 ) -> Self {
367 Self {
368 gpu,
369 delta_time,
370 elapsed_time,
371 berserker_mode,
372 rage,
373 window,
374 }
375 }
376
377 pub fn run<V: cvkg_core::View + 'static>(view: V) {
380 let event_loop = EventLoop::<AppEvent>::with_user_event()
381 .build()
382 .expect("Failed to create event loop");
383 event_loop.set_control_flow(ControlFlow::Wait);
384
385 let mut app = App {
386 view,
387 window_manager: WindowManager::new(),
388 gpu: None,
389 asset_manager: std::sync::Arc::new(NativeAssetManager::new()),
390 proxy: event_loop.create_proxy(),
391 start_time: std::time::Instant::now(),
392 last_frame_time: std::time::Instant::now(),
393 berserker_mode: cvkg_core::BerserkerMode::Normal,
394 rage: 0.0,
395 state_detector: WindowStateDetector::new(),
396 modifiers: winit::keyboard::ModifiersState::default(),
397 audio_engine: None,
398 haptic_engine: Arc::new(VisualHapticEngine::new()),
399 };
400
401 event_loop.run_app(&mut app).expect("Event loop error");
402 }
403}
404
405struct NativeWindowWrapper {
408 winit_id: winit::window::WindowId,
409 window: Arc<winit::window::Window>,
410 proxy: winit::event_loop::EventLoopProxy<AppEvent>,
411 is_key: Arc<std::sync::atomic::AtomicBool>,
412 is_main: bool,
413}
414
415impl cvkg_core::Window for NativeWindowWrapper {
416 fn close(&self) {
418 let _ = self.proxy.send_event(AppEvent::CloseWindow(self.winit_id));
419 }
420
421 fn set_title(&self, title: &str) {
423 let _ = self
424 .proxy
425 .send_event(AppEvent::SetTitle(self.winit_id, title.to_string()));
426 }
427
428 fn set_size(&self, width: f32, height: f32) {
430 let _ = self
431 .proxy
432 .send_event(AppEvent::SetSize(self.winit_id, width, height));
433 }
434
435 fn is_key(&self) -> bool {
437 self.is_key.load(std::sync::atomic::Ordering::SeqCst)
438 }
439
440 fn is_main(&self) -> bool {
442 self.is_main
443 }
444
445 fn is_visible(&self) -> bool {
447 self.window.is_visible().unwrap_or(false)
448 }
449
450 fn set_visible(&self, visible: bool) {
452 let _ = self
453 .proxy
454 .send_event(AppEvent::SetVisible(self.winit_id, visible));
455 }
456
457 fn bring_to_front(&self) {
459 let _ = self.proxy.send_event(AppEvent::BringToFront(self.winit_id));
460 }
461}
462
463pub struct WindowManager {
465 pub windows: std::collections::HashMap<winit::window::WindowId, WindowData>,
467 pub window_stack: Vec<winit::window::WindowId>,
469 pub winit_to_core: std::collections::HashMap<winit::window::WindowId, cvkg_core::WindowId>,
471 pub core_to_winit: std::collections::HashMap<cvkg_core::WindowId, winit::window::WindowId>,
473 pub next_core_id: u64,
475}
476
477impl Default for WindowManager {
478 fn default() -> Self {
479 Self::new()
480 }
481}
482
483impl WindowManager {
484 pub fn new() -> Self {
486 Self {
487 windows: std::collections::HashMap::new(),
488 window_stack: Vec::new(),
489 winit_to_core: std::collections::HashMap::new(),
490 core_to_winit: std::collections::HashMap::new(),
491 next_core_id: 1,
492 }
493 }
494
495 pub fn create_window(
497 &mut self,
498 event_loop: &ActiveEventLoop,
499 gpu: &Option<Arc<std::sync::Mutex<cvkg_render_gpu::SurtrRenderer>>>,
500 proxy: winit::event_loop::EventLoopProxy<AppEvent>,
501 config: cvkg_core::WindowConfig,
502 is_main: bool,
503 view: &impl cvkg_core::View,
504 ) -> cvkg_core::WindowHandle {
505 let mut window_attrs = Window::default_attributes()
506 .with_title(&config.title)
507 .with_visible(true)
508 .with_transparent(config.transparent)
509 .with_decorations(config.decorations)
510 .with_inner_size(winit::dpi::LogicalSize::new(config.size.0, config.size.1));
511
512 if let Some(min) = config.min_size {
513 window_attrs =
514 window_attrs.with_min_inner_size(winit::dpi::LogicalSize::new(min.0, min.1));
515 }
516 if let Some(max) = config.max_size {
517 window_attrs =
518 window_attrs.with_max_inner_size(winit::dpi::LogicalSize::new(max.0, max.1));
519 }
520
521 let winit_level = match config.level {
522 cvkg_core::WindowLevel::Normal => winit::window::WindowLevel::Normal,
523 cvkg_core::WindowLevel::AlwaysOnTop => winit::window::WindowLevel::AlwaysOnTop,
524 cvkg_core::WindowLevel::PopUpMenu => winit::window::WindowLevel::AlwaysOnTop,
525 };
526 window_attrs = window_attrs.with_window_level(winit_level);
527
528 let window = Arc::new(
529 event_loop
530 .create_window(window_attrs)
531 .expect("Failed to create window"),
532 );
533
534 let winit_id = window.id();
535 let core_id = cvkg_core::WindowId(self.next_core_id);
536 self.next_core_id += 1;
537
538 let is_key_focused = Arc::new(std::sync::atomic::AtomicBool::new(true));
539
540 let wrapper = Arc::new(NativeWindowWrapper {
541 winit_id,
542 window: window.clone(),
543 proxy: proxy.clone(),
544 is_key: is_key_focused.clone(),
545 is_main,
546 });
547
548 let handle = cvkg_core::WindowHandle::new(core_id, wrapper);
549
550 let vdom = cvkg_vdom::VDom::build(
551 view,
552 cvkg_core::Rect::new(0.0, 0.0, config.size.0, config.size.1),
553 );
554
555 let data = WindowData {
556 window: window.clone(),
557 accesskit_adapter: None,
558 vdom: Some(vdom),
559 cursor_pos: [0.0, 0.0],
560 last_redraw_start: std::time::Instant::now(),
561 frame_history: std::collections::VecDeque::with_capacity(60),
562 frame_count: 0,
563 last_pos: None,
564 is_dragging: false,
565 drag_start_pos: [0.0, 0.0],
566 drag_button: 0,
567 drag_threshold: 5.0,
568 is_key_focused,
569 is_main,
570 core_id,
571 window_handle: handle.clone(),
572 };
573
574 self.windows.insert(winit_id, data);
575 self.window_stack.push(winit_id);
576 self.winit_to_core.insert(winit_id, core_id);
577 self.core_to_winit.insert(core_id, winit_id);
578
579 if let Some(gpu_mutex) = gpu {
580 gpu_mutex.lock().unwrap().register_window(window.clone());
581 }
582
583 handle
584 }
585
586 pub fn close_window(&mut self, winit_id: winit::window::WindowId) {
588 self.windows.remove(&winit_id);
589 self.window_stack.retain(|id| *id != winit_id);
590 if let Some(core_id) = self.winit_to_core.remove(&winit_id) {
591 self.core_to_winit.remove(&core_id);
592 }
593 }
594
595 pub fn bring_to_front(&mut self, winit_id: winit::window::WindowId) {
597 self.window_stack.retain(|id| *id != winit_id);
598 self.window_stack.push(winit_id);
599 if let Some(data) = self.windows.get(&winit_id) {
600 data.window.focus_window();
601 }
602 }
603
604 pub fn window(&self, winit_id: winit::window::WindowId) -> Option<&WindowData> {
606 self.windows.get(&winit_id)
607 }
608
609 pub fn window_mut(&mut self, winit_id: winit::window::WindowId) -> Option<&mut WindowData> {
611 self.windows.get_mut(&winit_id)
612 }
613
614 pub fn window_order(&self) -> &[winit::window::WindowId] {
616 &self.window_stack
617 }
618}
619
620pub struct WindowData {
621 window: Arc<Window>,
622 accesskit_adapter: Option<accesskit_winit::Adapter>,
623 vdom: Option<cvkg_vdom::VDom>,
624 cursor_pos: [f32; 2],
625 last_redraw_start: std::time::Instant,
627 frame_history: std::collections::VecDeque<f32>,
629 frame_count: u64,
631 last_pos: Option<[i32; 2]>,
633 is_dragging: bool,
636 drag_start_pos: [f32; 2],
638 drag_button: u32,
640 drag_threshold: f32,
642
643 is_key_focused: Arc<std::sync::atomic::AtomicBool>,
645 is_main: bool,
646 core_id: cvkg_core::WindowId,
647 window_handle: cvkg_core::WindowHandle,
648}
649
650struct App<V: cvkg_core::View> {
651 view: V,
652 window_manager: WindowManager,
653 gpu: Option<Arc<std::sync::Mutex<cvkg_render_gpu::SurtrRenderer>>>,
654 #[allow(dead_code)]
655 asset_manager: std::sync::Arc<NativeAssetManager>,
656 proxy: winit::event_loop::EventLoopProxy<AppEvent>,
657 start_time: std::time::Instant,
658 last_frame_time: std::time::Instant,
659 berserker_mode: cvkg_core::BerserkerMode,
660 rage: f32,
661 state_detector: WindowStateDetector,
663 modifiers: winit::keyboard::ModifiersState,
665 audio_engine: Option<Arc<dyn cvkg_core::AudioEngine>>,
667 haptic_engine: Arc<dyn cvkg_core::HapticEngine>,
669}
670
671impl<V: cvkg_core::View + 'static> ApplicationHandler<AppEvent> for App<V> {
672 fn resumed(&mut self, event_loop: &ActiveEventLoop) {
673 if self.gpu.is_none() {
674 let a11y_prefs = cvkg_core::AccessibilityPreferences::detect_from_system();
676 cvkg_core::set_accessibility_preferences(a11y_prefs);
677 if a11y_prefs.reduce_motion
678 || a11y_prefs.reduce_transparency
679 || a11y_prefs.increase_contrast
680 {
681 log::info!(
682 "[Native] Accessibility prefs: motion={} transparency={} contrast={}",
683 a11y_prefs.reduce_motion,
684 a11y_prefs.reduce_transparency,
685 a11y_prefs.increase_contrast
686 );
687 }
688
689 let system_theme = cvkg_core::detect_system_theme();
691 log::info!("[Native] System theme detected: {:?}", system_theme);
692
693 self.audio_engine =
695 RodioAudioEngine::new().map(|e| Arc::new(e) as Arc<dyn cvkg_core::AudioEngine>);
696
697 self.haptic_engine = Arc::new(VisualHapticEngine::new());
699
700 log::info!("[Native] App instance (resumed): {:p}", self);
701
702 let config = cvkg_core::WindowConfig {
703 title: "CVKG Berserker".to_string(),
704 size: (1280.0, 720.0),
705 min_size: None,
706 max_size: None,
707 resizable: true,
708 transparent: false,
709 decorations: true,
710 level: cvkg_core::WindowLevel::Normal,
711 };
712
713 let handle = self.window_manager.create_window(
714 event_loop,
715 &self.gpu,
716 self.proxy.clone(),
717 config,
718 true, &self.view,
720 );
721
722 let winit_id = self
723 .window_manager
724 .core_to_winit
725 .get(&handle.id)
726 .copied()
727 .expect("Failed to get winit_id");
728 let window = self
729 .window_manager
730 .windows
731 .get(&winit_id)
732 .unwrap()
733 .window
734 .clone();
735
736 let gpu = pollster::block_on(cvkg_render_gpu::SurtrRenderer::forge(window.clone()));
738 self.gpu = Some(Arc::new(std::sync::Mutex::new(gpu)));
739
740 if let Some(gpu_mutex) = &self.gpu {
742 gpu_mutex
743 .lock()
744 .expect("Failed to lock GPU mutex")
745 .register_window(window.clone());
746 }
747
748 log::info!("[Native] Initialization complete.");
749 window.request_redraw();
750 }
751 }
752
753 fn new_events(&mut self, _event_loop: &ActiveEventLoop, cause: winit::event::StartCause) {
754 if matches!(cause, winit::event::StartCause::Poll) {
755 } else {
757 log::debug!("[Native] Event Loop Wake: {:?}", cause);
758 }
759 }
760
761 fn device_event(
762 &mut self,
763 _event_loop: &ActiveEventLoop,
764 _device_id: winit::event::DeviceId,
765 event: winit::event::DeviceEvent,
766 ) {
767 if matches!(event, winit::event::DeviceEvent::MouseMotion { .. }) {
768 } else {
770 log::info!("[Native] DEVICE EVENT: {:?}", event);
771 }
772 }
773
774 fn window_event(&mut self, event_loop: &ActiveEventLoop, id: WindowId, event: WindowEvent) {
775 if !matches!(event, WindowEvent::RedrawRequested)
776 && !matches!(event, WindowEvent::CursorMoved { .. })
777 {
778 log::info!(
779 "[Native] App instance: {:p} | WINDOW EVENT: {:?}",
780 self,
781 event
782 );
783 }
784
785 let gpu_arc = if let Some(g) = &self.gpu {
786 g.clone()
787 } else {
788 log::warn!("[Native] DROPPING EVENT: GPU not initialized yet");
789 return;
790 };
791
792 let mut close_window = false;
793 let mut bring_to_front = false;
794 let mut create_new_window = false;
795 let mut quit_all = false;
797
798 {
799 let state = if let Some(s) = self.window_manager.windows.get_mut(&id) {
800 s
801 } else {
802 return;
803 };
804
805 match event {
806 WindowEvent::Moved(pos) => {
807 let dx = state.last_pos.map_or(0, |last| pos.x - last[0]);
808 let dy = state.last_pos.map_or(0, |last| pos.y - last[1]);
809 let speed = ((dx.pow(2) + dy.pow(2)) as f32).sqrt();
810
811 if speed > 0.1 {
812 self.rage = (self.rage + 0.2).min(1.0);
814 log::info!("[Native] Kinetic Injection! Rage: {}", self.rage);
815 }
816
817 state.last_pos = Some([pos.x, pos.y]);
818 state.window.request_redraw();
819 }
820 WindowEvent::DroppedFile(path) => {
821 if let Some(vdom) = &state.vdom {
822 vdom.dispatch_event(cvkg_core::Event::FileDrop {
823 path: path.to_string_lossy().into_owned(),
824 });
825 }
826 }
827 WindowEvent::CloseRequested => {
828 let close_action = cvkg_core::WindowCloseAction::Allow;
829 match close_action {
830 cvkg_core::WindowCloseAction::Allow
831 | cvkg_core::WindowCloseAction::Confirm => {
832 close_window = true;
833 }
834 cvkg_core::WindowCloseAction::Deny => {
835 log::info!("[Native] Close request denied for window {:?}", id);
836 }
837 }
838 }
839 WindowEvent::Resized(physical_size) => {
840 gpu_arc
841 .lock()
842 .expect("GPU mutex poisoned during resize")
843 .resize(
844 id,
845 physical_size.width,
846 physical_size.height,
847 state.window.scale_factor() as f32,
848 );
849 state.window.request_redraw();
850 }
851 WindowEvent::Focused(focused) => {
852 log::info!("[Native] Window focus changed: {}", focused);
853 state
854 .is_key_focused
855 .store(focused, std::sync::atomic::Ordering::SeqCst);
856 if focused {
857 bring_to_front = true;
858 }
859 }
860 WindowEvent::RedrawRequested => {
861 if state.frame_count % 60 == 0 {
862 log::info!("[Native] RedrawRequested (frame {})", state.frame_count);
863 }
864 let size = state.window.inner_size();
865 let scale = state.window.scale_factor();
866 let logical_size = size.to_logical::<f32>(scale);
867
868 let rect = cvkg_core::Rect {
869 x: 0.0,
870 y: 0.0,
871 width: logical_size.width,
872 height: logical_size.height,
873 };
874
875 let redraw_start = std::time::Instant::now();
878 let last_redraw_start = state.last_redraw_start;
879 state.last_redraw_start = redraw_start;
882
883 let layout_start = std::time::Instant::now();
885 let new_vdom = cvkg_vdom::VDom::build(&self.view, rect);
886 let layout_end = std::time::Instant::now();
887
888 let state_flush_start = std::time::Instant::now();
890 if let Some(prev_vdom) = &mut state.vdom {
891 let patches = prev_vdom.diff(&new_vdom);
892 let mut nodes = Vec::new();
893 for patch in &patches {
894 if let cvkg_vdom::VDomPatch::Create(node)
895 | cvkg_vdom::VDomPatch::Replace { node, .. } = patch
896 {
897 nodes
898 .push((accesskit::NodeId(node.id.0), node.to_accesskit_node()));
899 } else if let cvkg_vdom::VDomPatch::Update { id, .. } = patch
900 && let Some(node) = new_vdom.nodes.get(id)
901 {
902 nodes
903 .push((accesskit::NodeId(node.id.0), node.to_accesskit_node()));
904 }
905 }
906 if !nodes.is_empty() {
907 if let Some(adapter) = &mut state.accesskit_adapter {
908 adapter.update_if_active(|| accesskit::TreeUpdate {
909 nodes,
910 tree: None,
911 focus: accesskit::NodeId(1),
912 });
913 }
914 }
915 prev_vdom.apply_patches(patches);
916 } else {
917 state.vdom = Some(new_vdom);
918 }
919 let state_flush_end = std::time::Instant::now();
920
921 let draw_start = std::time::Instant::now();
923 let delta_time = redraw_start.duration_since(last_redraw_start).as_secs_f32();
924 let elapsed_time = redraw_start.duration_since(self.start_time).as_secs_f32();
925 let mut gpu = gpu_arc
926 .lock()
927 .expect("GPU mutex poisoned during frame begin");
928 let encoder = gpu.begin_frame(id);
929 let mut renderer = NativeRenderer::new(
930 state.window.clone(),
931 gpu_arc.clone(),
932 delta_time,
933 elapsed_time,
934 self.berserker_mode,
935 self.rage,
936 );
937 drop(gpu);
941 self.view.render(&mut renderer, rect);
942 let draw_end = std::time::Instant::now();
943
944 let gpu_submit_start = std::time::Instant::now();
946 let mut gpu = gpu_arc
947 .lock()
948 .expect("GPU mutex poisoned during frame submit");
949 gpu.render_frame();
950 gpu.end_frame(encoder);
951 let gpu_submit_end = std::time::Instant::now();
952
953 let mut telemetry = cvkg_core::TelemetryData::default();
958 telemetry.input_time_ms =
959 redraw_start.duration_since(last_redraw_start).as_secs_f32() * 1000.0;
960 telemetry.layout_time_ms =
961 layout_end.duration_since(layout_start).as_secs_f32() * 1000.0;
962 telemetry.state_flush_time_ms = state_flush_end
963 .duration_since(state_flush_start)
964 .as_secs_f32()
965 * 1000.0;
966 telemetry.draw_time_ms =
967 draw_end.duration_since(draw_start).as_secs_f32() * 1000.0;
968 telemetry.gpu_submit_time_ms = gpu_submit_end
969 .duration_since(gpu_submit_start)
970 .as_secs_f32()
971 * 1000.0;
972
973 let frame_time_ms =
975 gpu_submit_end.duration_since(redraw_start).as_secs_f32() * 1000.0;
976 telemetry.frame_time_ms = frame_time_ms;
977
978 state.frame_history.push_back(frame_time_ms);
980 if state.frame_history.len() > 100 {
981 state.frame_history.pop_front();
982 }
983
984 let mut sorted_frames: Vec<f32> = state.frame_history.iter().copied().collect();
985 sorted_frames
986 .sort_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal));
987
988 if !sorted_frames.is_empty() {
989 let p99_idx = (sorted_frames.len() as f32 * 0.99).floor() as usize;
990 telemetry.p99_frame_time_ms =
991 sorted_frames[p99_idx.min(sorted_frames.len() - 1)];
992
993 let avg = sorted_frames.iter().sum::<f32>() / sorted_frames.len() as f32;
995 let variance = sorted_frames.iter().map(|f| (f - avg).powi(2)).sum::<f32>()
996 / sorted_frames.len() as f32;
997 telemetry.frame_jitter_ms = variance.sqrt();
998 }
999
1000 telemetry.hardware_stall_detected = telemetry.frame_jitter_ms > 20.0;
1006
1007 state.frame_count += 1;
1008
1009 telemetry.berserker_rage = self.rage;
1010 gpu.telemetry = telemetry;
1011 }
1012 WindowEvent::CursorEntered { .. } => {
1013 log::info!("[Native] Cursor ENTERED window");
1014 if let Some(vdom) = &state.vdom {
1015 vdom.dispatch_event(cvkg_core::Event::PointerEnter);
1016 }
1017 state.window.request_redraw();
1018 }
1019 WindowEvent::CursorLeft { .. } => {
1020 log::info!("[Native] Cursor LEFT window");
1021 if let Some(vdom) = &state.vdom {
1022 vdom.dispatch_event(cvkg_core::Event::PointerLeave);
1023 }
1024 state.window.request_redraw();
1025 }
1026 WindowEvent::CursorMoved { position, .. } => {
1027 let scale = state.window.scale_factor();
1028 let logical = position.to_logical::<f32>(scale);
1029 log::info!(
1030 "[Native] Cursor Moved: Physical={:?} Logical={:?} Scale={}",
1031 position,
1032 logical,
1033 scale
1034 );
1035 state.cursor_pos = [logical.x, logical.y];
1036 if let Some(vdom) = &state.vdom {
1037 vdom.dispatch_event(cvkg_core::Event::PointerMove {
1038 x: state.cursor_pos[0],
1039 y: state.cursor_pos[1],
1040 proximity_field: 0.0,
1041 tilt: None,
1042 azimuth: None,
1043 pressure: Some(1.0),
1044 barrel_rotation: None,
1045 });
1046 }
1047 state.window.request_redraw();
1048 }
1049 WindowEvent::MouseInput {
1050 state: mouse_state,
1051 button,
1052 ..
1053 } => {
1054 log::info!(
1055 "[Native] MOUSE INPUT: {:?} button={:?} pos={:?}",
1056 mouse_state,
1057 button,
1058 state.cursor_pos
1059 );
1060 if let Some(vdom) = &state.vdom {
1061 let btn_id = match button {
1062 winit::event::MouseButton::Left => 0,
1063 winit::event::MouseButton::Right => 2,
1064 winit::event::MouseButton::Middle => 1,
1065 winit::event::MouseButton::Back => 3,
1066 winit::event::MouseButton::Forward => 4,
1067 winit::event::MouseButton::Other(id) => id as u32,
1068 };
1069
1070 match mouse_state {
1071 winit::event::ElementState::Pressed => {
1072 log::info!("[Native] Dispatching PointerDown to VDOM");
1073 vdom.dispatch_event(cvkg_core::Event::PointerDown {
1074 x: state.cursor_pos[0],
1075 y: state.cursor_pos[1],
1076 button: btn_id,
1077 proximity_field: 0.0,
1078 tilt: None,
1079 azimuth: None,
1080 pressure: Some(1.0),
1081 barrel_rotation: None,
1082 });
1083 }
1084 winit::event::ElementState::Released => {
1085 log::info!("[Native] Dispatching PointerUp to VDOM");
1086 vdom.dispatch_event(cvkg_core::Event::PointerUp {
1087 x: state.cursor_pos[0],
1088 y: state.cursor_pos[1],
1089 button: btn_id,
1090 tilt: None,
1091 azimuth: None,
1092 pressure: Some(0.0),
1093 barrel_rotation: None,
1094 });
1095 }
1096 }
1097 state.window.request_redraw();
1098 } else {
1099 log::warn!("[Native] Mouse input received but state.vdom is None!");
1100 }
1101 }
1102 WindowEvent::MouseWheel { delta, .. } => {
1103 if let Some(vdom) = &state.vdom {
1104 let (dx, dy) = match delta {
1105 winit::event::MouseScrollDelta::LineDelta(x, y) => (x * 10.0, y * 10.0),
1106 winit::event::MouseScrollDelta::PixelDelta(pos) => {
1107 (pos.x as f32, pos.y as f32)
1108 }
1109 };
1110 vdom.dispatch_event(cvkg_core::Event::PointerWheel {
1111 x: state.cursor_pos[0],
1112 y: state.cursor_pos[1],
1113 delta_x: dx,
1114 delta_y: dy,
1115 });
1116 state.window.request_redraw();
1117 }
1118 }
1119 WindowEvent::PinchGesture { delta, .. } => {
1123 if let Some(vdom) = &state.vdom {
1124 let scale = 1.0 + delta as f32;
1125 let velocity = delta as f32;
1126 vdom.dispatch_event(cvkg_core::Event::GesturePinch {
1127 center: state.cursor_pos,
1128 scale,
1129 velocity,
1130 phase: cvkg_core::TouchPhase::Moved,
1131 });
1132 }
1133 if let Some(audio) = &self.audio_engine {
1135 audio.play_sound("nav_tick", 0.3);
1136 }
1137 self.haptic_engine
1138 .visual_tick((delta.abs() as f32 * 5.0).min(1.0));
1139 state.window.request_redraw();
1140 }
1141 WindowEvent::RotationGesture { delta, .. } => {
1142 if let Some(vdom) = &state.vdom {
1143 let angle = delta;
1144 vdom.dispatch_event(cvkg_core::Event::GestureSwipe {
1145 direction: [angle.cos(), angle.sin()],
1146 velocity: delta.abs(),
1147 phase: cvkg_core::TouchPhase::Moved,
1148 });
1149 }
1150 state.window.request_redraw();
1151 }
1152 WindowEvent::KeyboardInput { event, .. } => {
1153 if event.state == winit::event::ElementState::Pressed {
1154 if let winit::keyboard::PhysicalKey::Code(code) = event.physical_key {
1155 let is_cmd = if cfg!(target_os = "macos") {
1159 self.modifiers.super_key()
1160 } else {
1161 self.modifiers.control_key()
1162 };
1163 let is_shift = self.modifiers.shift_key();
1164
1165 if is_cmd {
1166 match code {
1167 winit::keyboard::KeyCode::KeyZ => {
1169 if is_shift {
1170 log::info!("[Native] Shortcut: Redo (Cmd+Shift+Z)");
1171 let mut redo_action = None;
1172 cvkg_core::update_system_state(|s| {
1173 let mut s = s.clone();
1174 redo_action = s.undo_manager.redo();
1175 s
1176 });
1177 if let Some(action) = redo_action {
1178 action();
1179 }
1180 state.window.request_redraw();
1181 } else {
1182 log::info!("[Native] Shortcut: Undo (Cmd+Z)");
1183 let mut undo_action = None;
1184 cvkg_core::update_system_state(|s| {
1185 let mut s = s.clone();
1186 undo_action = s.undo_manager.undo();
1187 s
1188 });
1189 if let Some(action) = undo_action {
1190 action();
1191 }
1192 state.window.request_redraw();
1193 }
1194 }
1195 winit::keyboard::KeyCode::KeyY
1197 if !cfg!(target_os = "macos") =>
1198 {
1199 log::info!("[Native] Shortcut: Redo (Ctrl+Y)");
1200 let mut redo_action = None;
1201 cvkg_core::update_system_state(|s| {
1202 let mut s = s.clone();
1203 redo_action = s.undo_manager.redo();
1204 s
1205 });
1206 if let Some(action) = redo_action {
1207 action();
1208 }
1209 state.window.request_redraw();
1210 }
1211 winit::keyboard::KeyCode::KeyN => {
1213 log::info!("[Native] Shortcut: New Window (Cmd+N)");
1214 create_new_window = true;
1215 }
1216 winit::keyboard::KeyCode::KeyO => {
1217 log::info!("[Native] Shortcut: Open File (Cmd+O)");
1218 if let Some(vdom) = &state.vdom {
1219 vdom.dispatch_event(cvkg_core::Event::KeyDown {
1220 key: "cmd+o".to_string(),
1221 });
1222 }
1223 state.window.request_redraw();
1224 }
1225 winit::keyboard::KeyCode::KeyS => {
1226 log::info!("[Native] Shortcut: Save (Cmd+S)");
1227 if let Some(vdom) = &state.vdom {
1228 vdom.dispatch_event(cvkg_core::Event::KeyDown {
1229 key: "cmd+s".to_string(),
1230 });
1231 }
1232 state.window.request_redraw();
1233 }
1234 winit::keyboard::KeyCode::KeyW => {
1235 log::info!("[Native] Shortcut: Close Window (Cmd+W)");
1236 close_window = true;
1237 }
1238 winit::keyboard::KeyCode::KeyQ => {
1239 log::info!("[Native] Shortcut: Quit (Cmd+Q)");
1240 quit_all = true;
1242 }
1243 winit::keyboard::KeyCode::KeyC => {
1245 log::info!("[Native] Shortcut: Copy (Cmd+C)");
1246 if let Some(vdom) = &state.vdom {
1247 vdom.dispatch_event(cvkg_core::Event::Copy);
1248 }
1249 state.window.request_redraw();
1250 }
1251 winit::keyboard::KeyCode::KeyV => {
1252 log::info!("[Native] Shortcut: Paste (Cmd+V)");
1253 let text = arboard::Clipboard::new()
1256 .ok()
1257 .and_then(|mut cb| cb.get_text().ok())
1258 .unwrap_or_default();
1259 if let Some(vdom) = &state.vdom {
1260 vdom.dispatch_event(cvkg_core::Event::Paste(text));
1261 }
1262 state.window.request_redraw();
1263 }
1264 winit::keyboard::KeyCode::KeyX => {
1265 log::info!("[Native] Shortcut: Cut (Cmd+X)");
1266 if let Some(vdom) = &state.vdom {
1267 vdom.dispatch_event(cvkg_core::Event::Cut);
1268 }
1269 state.window.request_redraw();
1270 }
1271 winit::keyboard::KeyCode::KeyA => {
1273 log::info!("[Native] Shortcut: Select All (Cmd+A)");
1274 if let Some(vdom) = &state.vdom {
1275 vdom.dispatch_event(cvkg_core::Event::KeyDown {
1276 key: "cmd+a".to_string(),
1277 });
1278 }
1279 state.window.request_redraw();
1280 }
1281 winit::keyboard::KeyCode::KeyF => {
1282 log::info!("[Native] Shortcut: Find (Cmd+F)");
1283 if let Some(vdom) = &state.vdom {
1284 vdom.dispatch_event(cvkg_core::Event::KeyDown {
1285 key: "cmd+f".to_string(),
1286 });
1287 }
1288 state.window.request_redraw();
1289 }
1290 _ => {}
1291 }
1292 }
1293 }
1294 }
1295
1296 if let Some(vdom) = &state.vdom
1297 && let Some(cvkg_event) = convert_keyboard_event(event)
1298 {
1299 vdom.dispatch_event(cvkg_event);
1300 state.window.request_redraw();
1301 }
1302 }
1303
1304 WindowEvent::Ime(ime_event) => {
1305 if let Some(vdom) = &state.vdom
1306 && let Some(cvkg_event) = convert_ime_event(ime_event)
1307 {
1308 vdom.dispatch_event(cvkg_event);
1309 state.window.request_redraw();
1310 }
1311 }
1312 WindowEvent::ModifiersChanged(new_modifiers) => {
1313 self.modifiers = new_modifiers.state();
1314 let shift = self.modifiers.shift_key();
1315 let ctrl = self.modifiers.control_key();
1316 let alt = self.modifiers.alt_key();
1317 let logo = self.modifiers.super_key();
1318 cvkg_core::update_system_state(|st| {
1319 let mut new_st = st.clone();
1320 new_st.modifiers_shift = shift;
1321 new_st.modifiers_ctrl = ctrl;
1322 new_st.modifiers_alt = alt;
1323 new_st.modifiers_logo = logo;
1324 new_st
1325 });
1326 }
1327 _ => {}
1328 }
1329 } if close_window {
1332 self.window_manager.close_window(id);
1333 }
1334 if quit_all {
1335 for wid in self.window_manager.window_order().to_vec() {
1337 self.window_manager.close_window(wid);
1338 }
1339 }
1340 if self.window_manager.windows.is_empty() {
1342 event_loop.exit();
1343 }
1344 if bring_to_front {
1345 self.window_manager.bring_to_front(id);
1346 }
1347 if create_new_window {
1348 self.window_manager.create_window(
1349 event_loop,
1350 &self.gpu,
1351 self.proxy.clone(),
1352 cvkg_core::WindowConfig {
1353 title: "New CVKG Window".to_string(),
1354 size: (800.0, 600.0),
1355 ..Default::default()
1356 },
1357 false, &self.view,
1359 );
1360 }
1361 }
1362
1363 fn user_event(&mut self, event_loop: &ActiveEventLoop, event: AppEvent) {
1364 match event {
1365 AppEvent::AccessibilityAction(request) => {
1366 let node_id = cvkg_vdom::NodeId(request.target.0);
1367 let target_state = self.window_manager.windows.values_mut().find(|s| {
1368 s.vdom
1369 .as_ref()
1370 .map_or(false, |v| v.nodes.contains_key(&node_id))
1371 });
1372
1373 if let Some(state) = target_state
1374 && let Some(vdom) = &state.vdom
1375 && let Some(node) = vdom.nodes.get(&node_id)
1376 && request.action == accesskit::Action::Click
1377 {
1378 let event = cvkg_core::Event::PointerClick {
1379 x: node.layout.x + node.layout.width / 2.0,
1380 y: node.layout.y + node.layout.height / 2.0,
1381 button: 0, tilt: None,
1383 azimuth: None,
1384 pressure: Some(1.0),
1385 barrel_rotation: None,
1386 };
1387 vdom.dispatch_event(event);
1388 }
1389 }
1390 AppEvent::CloseWindow(winit_id) => {
1391 self.window_manager.close_window(winit_id);
1392 if self.window_manager.windows.is_empty() {
1393 event_loop.exit();
1394 }
1395 }
1396 AppEvent::SetTitle(winit_id, title) => {
1397 if let Some(data) = self.window_manager.windows.get(&winit_id) {
1398 data.window.set_title(&title);
1399 }
1400 }
1401 AppEvent::SetSize(winit_id, width, height) => {
1402 if let Some(data) = self.window_manager.windows.get(&winit_id) {
1403 let _ = data
1404 .window
1405 .request_inner_size(winit::dpi::LogicalSize::new(width, height));
1406 }
1407 }
1408 AppEvent::SetVisible(winit_id, visible) => {
1409 if let Some(data) = self.window_manager.windows.get(&winit_id) {
1410 data.window.set_visible(visible);
1411 }
1412 }
1413 AppEvent::BringToFront(winit_id) => {
1414 self.window_manager.bring_to_front(winit_id);
1415 }
1416 }
1417 }
1418
1419 fn about_to_wait(&mut self, event_loop: &ActiveEventLoop) {
1420 self.rage = (self.rage - 0.02).max(0.0);
1422
1423 let now = std::time::Instant::now();
1425 let target_interval = std::time::Duration::from_millis(16);
1426
1427 if now.duration_since(self.last_frame_time) >= target_interval {
1428 if self.rage > 0.01 {
1429 log::debug!("[Native] Heartbeat ticking (rage: {})", self.rage);
1431 }
1432 self.last_frame_time = now;
1433 for window_state in self.window_manager.windows.values() {
1434 window_state.window.request_redraw();
1435 }
1436 event_loop.set_control_flow(winit::event_loop::ControlFlow::WaitUntil(
1437 now + target_interval,
1438 ));
1439 } else {
1440 event_loop.set_control_flow(winit::event_loop::ControlFlow::WaitUntil(
1441 self.last_frame_time + target_interval,
1442 ));
1443 }
1444 }
1445}
1446
1447impl cvkg_core::ElapsedTime for NativeRenderer {
1448 fn delta_time(&self) -> f32 {
1449 self.delta_time
1450 }
1451
1452 fn elapsed_time(&self) -> f32 {
1453 self.elapsed_time
1454 }
1455}
1456
1457impl cvkg_core::Renderer for NativeRenderer {
1458 fn fill_rect(&mut self, rect: cvkg_core::Rect, color: [f32; 4]) {
1459 self.gpu
1460 .lock()
1461 .expect("GPU mutex poisoned: fill_rect")
1462 .fill_rect(rect, color);
1463 }
1464 fn fill_rounded_rect(&mut self, rect: cvkg_core::Rect, radius: f32, color: [f32; 4]) {
1465 self.gpu
1466 .lock()
1467 .expect("GPU mutex poisoned: fill_rounded_rect")
1468 .fill_rounded_rect(rect, radius, color);
1469 }
1470 fn fill_ellipse(&mut self, rect: cvkg_core::Rect, color: [f32; 4]) {
1471 self.gpu
1472 .lock()
1473 .expect("GPU mutex poisoned: fill_ellipse")
1474 .fill_ellipse(rect, color);
1475 }
1476 fn stroke_rect(&mut self, rect: cvkg_core::Rect, color: [f32; 4], stroke_width: f32) {
1477 self.gpu
1478 .lock()
1479 .expect("GPU mutex poisoned: stroke_rect")
1480 .stroke_rect(rect, color, stroke_width);
1481 }
1482 fn stroke_rounded_rect(
1483 &mut self,
1484 rect: cvkg_core::Rect,
1485 radius: f32,
1486 color: [f32; 4],
1487 stroke_width: f32,
1488 ) {
1489 self.gpu
1490 .lock()
1491 .expect("GPU mutex poisoned: stroke_rounded_rect")
1492 .stroke_rounded_rect(rect, radius, color, stroke_width);
1493 }
1494 fn stroke_ellipse(&mut self, rect: cvkg_core::Rect, color: [f32; 4], stroke_width: f32) {
1495 self.gpu
1496 .lock()
1497 .expect("GPU mutex poisoned: stroke_ellipse")
1498 .stroke_ellipse(rect, color, stroke_width);
1499 }
1500 fn draw_line(
1501 &mut self,
1502 x1: f32,
1503 y1: f32,
1504 x2: f32,
1505 y2: f32,
1506 color: [f32; 4],
1507 stroke_width: f32,
1508 ) {
1509 self.gpu
1510 .lock()
1511 .expect("GPU mutex poisoned: draw_line")
1512 .draw_line(x1, y1, x2, y2, color, stroke_width);
1513 }
1514 fn draw_text(&mut self, text: &str, x: f32, y: f32, size: f32, color: [f32; 4]) {
1515 self.gpu
1516 .lock()
1517 .expect("GPU mutex poisoned: draw_text")
1518 .draw_text(text, x, y, size, color);
1519 }
1520 fn measure_text(&mut self, text: &str, size: f32) -> (f32, f32) {
1521 self.gpu
1522 .lock()
1523 .expect("GPU mutex poisoned: measure_text")
1524 .measure_text(text, size)
1525 }
1526 fn draw_linear_gradient(
1527 &mut self,
1528 rect: cvkg_core::Rect,
1529 start_color: [f32; 4],
1530 end_color: [f32; 4],
1531 angle: f32,
1532 ) {
1533 self.gpu
1534 .lock()
1535 .expect("GPU mutex poisoned: draw_linear_gradient")
1536 .draw_linear_gradient(rect, start_color, end_color, angle);
1537 }
1538 fn draw_radial_gradient(
1539 &mut self,
1540 rect: cvkg_core::Rect,
1541 inner_color: [f32; 4],
1542 outer_color: [f32; 4],
1543 ) {
1544 self.gpu
1545 .lock()
1546 .expect("GPU mutex poisoned: draw_radial_gradient")
1547 .draw_radial_gradient(rect, inner_color, outer_color);
1548 }
1549 fn draw_texture(&mut self, texture_id: u32, rect: cvkg_core::Rect) {
1550 self.gpu
1551 .lock()
1552 .expect("GPU mutex poisoned: draw_texture")
1553 .draw_texture(texture_id, rect);
1554 }
1555 fn draw_image(&mut self, image_name: &str, rect: cvkg_core::Rect) {
1556 self.gpu
1557 .lock()
1558 .expect("GPU mutex poisoned: draw_image")
1559 .draw_image(image_name, rect);
1560 }
1561 fn load_image(&mut self, name: &str, data: &[u8]) {
1562 self.gpu
1563 .lock()
1564 .expect("GPU mutex poisoned: load_image")
1565 .load_image(name, data);
1566 }
1567 fn push_clip_rect(&mut self, rect: cvkg_core::Rect) {
1568 self.gpu
1569 .lock()
1570 .expect("GPU mutex poisoned: push_clip_rect")
1571 .push_clip_rect(rect);
1572 }
1573 fn pop_clip_rect(&mut self) {
1574 self.gpu
1575 .lock()
1576 .expect("GPU mutex poisoned: pop_clip_rect")
1577 .pop_clip_rect();
1578 }
1579 fn push_opacity(&mut self, opacity: f32) {
1580 self.gpu
1581 .lock()
1582 .expect("GPU mutex poisoned: push_opacity")
1583 .push_opacity(opacity);
1584 }
1585 fn draw_3d_cube(&mut self, rect: cvkg_core::Rect, color: [f32; 4], rotation: [f32; 3]) {
1586 self.gpu
1587 .lock()
1588 .expect("GPU mutex poisoned: draw_3d_cube")
1589 .draw_3d_cube(rect, color, rotation);
1590 }
1591 fn pop_opacity(&mut self) {
1592 self.gpu
1593 .lock()
1594 .expect("GPU mutex poisoned: pop_opacity")
1595 .pop_opacity();
1596 }
1597 fn bifrost(&mut self, rect: cvkg_core::Rect, blur: f32, saturation: f32, opacity: f32) {
1598 self.gpu
1599 .lock()
1600 .expect("GPU mutex poisoned: bifrost")
1601 .bifrost(rect, blur, saturation, opacity);
1602 }
1603 fn push_mjolnir_slice(&mut self, angle: f32, offset: f32) {
1604 self.gpu
1605 .lock()
1606 .expect("GPU mutex poisoned: push_mjolnir_slice")
1607 .push_mjolnir_slice(angle, offset);
1608 }
1609 fn pop_mjolnir_slice(&mut self) {
1610 self.gpu
1611 .lock()
1612 .expect("GPU mutex poisoned: pop_mjolnir_slice")
1613 .pop_mjolnir_slice();
1614 }
1615 fn mjolnir_shatter(&mut self, rect: cvkg_core::Rect, pieces: u32, force: f32, color: [f32; 4]) {
1616 self.gpu
1617 .lock()
1618 .expect("GPU mutex poisoned: mjolnir_shatter")
1619 .mjolnir_shatter(rect, pieces, force, color);
1620 }
1621 fn mjolnir_fluid_shatter(
1622 &mut self,
1623 rect: cvkg_core::Rect,
1624 pieces: u32,
1625 force: f32,
1626 color: [f32; 4],
1627 ) {
1628 self.gpu
1629 .lock()
1630 .expect("GPU mutex poisoned: mjolnir_fluid_shatter")
1631 .mjolnir_fluid_shatter(rect, pieces, force, color);
1632 }
1633 fn draw_mjolnir_bolt(&mut self, from: [f32; 2], to: [f32; 2], color: [f32; 4]) {
1634 self.gpu
1635 .lock()
1636 .expect("GPU mutex poisoned: draw_mjolnir_bolt")
1637 .draw_mjolnir_bolt(from, to, color);
1638 }
1639 fn gungnir(&mut self, rect: cvkg_core::Rect, color: [f32; 4], radius: f32, intensity: f32) {
1640 self.gpu
1641 .lock()
1642 .expect("GPU mutex poisoned: gungnir")
1643 .gungnir(rect, color, radius, intensity);
1644 }
1645 fn mani_glow(&mut self, rect: cvkg_core::Rect, color: [f32; 4], radius: f32) {
1646 self.gpu
1647 .lock()
1648 .expect("GPU mutex poisoned: mani_glow")
1649 .mani_glow(rect, color, radius);
1650 }
1651 fn register_handler(
1652 &mut self,
1653 event_type: &str,
1654 handler: std::sync::Arc<dyn Fn(cvkg_core::Event) + Send + Sync>,
1655 ) {
1656 self.gpu
1657 .lock()
1658 .expect("GPU mutex poisoned: register_handler")
1659 .register_handler(event_type, handler);
1660 }
1661 fn push_vnode(&mut self, rect: cvkg_core::Rect, name: &'static str) {
1662 self.gpu
1663 .lock()
1664 .expect("GPU mutex poisoned: push_vnode")
1665 .push_vnode(rect, name);
1666 }
1667 fn pop_vnode(&mut self) {
1668 self.gpu
1669 .lock()
1670 .expect("GPU mutex poisoned: pop_vnode")
1671 .pop_vnode();
1672 }
1673 fn set_z_index(&mut self, z: f32) {
1677 self.gpu
1678 .lock()
1679 .expect("GPU mutex poisoned: set_z_index")
1680 .set_z_index(z);
1681 }
1682 fn get_z_index(&self) -> f32 {
1683 self.gpu
1684 .lock()
1685 .expect("GPU mutex poisoned: get_z_index")
1686 .get_z_index()
1687 }
1688 fn register_shared_element(&mut self, id: &str, rect: cvkg_core::Rect) {
1689 self.gpu
1690 .lock()
1691 .expect("GPU mutex poisoned: register_shared_element")
1692 .register_shared_element(id, rect);
1693 }
1694 fn load_svg(&mut self, name: &str, svg_data: &[u8]) {
1695 self.gpu
1696 .lock()
1697 .expect("GPU mutex poisoned: load_svg")
1698 .load_svg(name, svg_data);
1699 }
1700 fn draw_svg(&mut self, name: &str, rect: cvkg_core::Rect) {
1701 self.gpu
1702 .lock()
1703 .expect("GPU mutex poisoned: draw_svg")
1704 .draw_svg(name, rect, None, 0);
1705 }
1706 fn get_telemetry(&self) -> cvkg_core::TelemetryData {
1707 self.gpu
1708 .lock()
1709 .expect("GPU mutex poisoned: get_telemetry")
1710 .telemetry
1711 .clone()
1712 }
1713 fn prewarm_vram(&mut self, assets: Vec<(String, Vec<u8>)>) {
1714 self.gpu
1715 .lock()
1716 .expect("GPU mutex poisoned: prewarm_vram")
1717 .prewarm_vram(assets);
1718 }
1719 fn push_transform(&mut self, translation: [f32; 2], scale: [f32; 2], rotation: f32) {
1720 self.gpu
1721 .lock()
1722 .expect("GPU mutex poisoned: push_transform")
1723 .push_transform(translation, scale, rotation);
1724 }
1725 fn pop_transform(&mut self) {
1726 self.gpu
1727 .lock()
1728 .expect("GPU mutex poisoned: pop_transform")
1729 .pop_transform();
1730 }
1731
1732 fn set_berserker_mode(&mut self, state: cvkg_core::BerserkerMode) {
1733 self.berserker_mode = state;
1734
1735 if state == cvkg_core::BerserkerMode::GodMode {
1740 log::info!("ENTERING GOD MODE: Activating Berserker Determinism (High Priority)");
1741 #[cfg(target_os = "linux")]
1742 unsafe {
1743 let _ = libc::setpriority(libc::PRIO_PROCESS, 0, -10);
1744 }
1745 } else {
1746 #[cfg(target_os = "linux")]
1747 unsafe {
1748 let _ = libc::setpriority(libc::PRIO_PROCESS, 0, 0);
1749 }
1750 }
1751
1752 self.gpu
1753 .lock()
1754 .expect("GPU mutex poisoned: set_berserker_mode")
1755 .set_berserker_mode(state);
1756 }
1757
1758 fn set_rage(&mut self, rage: f32) {
1759 self.rage = rage;
1760 self.gpu
1761 .lock()
1762 .expect("GPU mutex poisoned: set_rage")
1763 .set_rage(rage);
1764 }
1765
1766 fn memoize(&mut self, id: u64, data_hash: u64, render_fn: &dyn Fn(&mut dyn Renderer)) {
1767 self.gpu
1768 .lock()
1769 .expect("GPU mutex poisoned: memoize")
1770 .memoize(id, data_hash, render_fn);
1771 }
1772 fn request_redraw(&mut self) {
1773 self.window.request_redraw();
1774 }
1775
1776 fn capture_png(&mut self) -> Vec<u8> {
1786 log::info!("CAPTURING_FRAME: Initiating GPU readback...");
1787 let gpu = self.gpu.lock().expect("GPU mutex poisoned: capture_png");
1791 pollster::block_on(gpu.capture_frame()).unwrap_or_else(|e| {
1792 log::error!("GPU frame capture failed: {}", e);
1793 Vec::new() })
1795 }
1796
1797 fn print(&mut self) {
1798 log::info!("PRINT_BRIDGE: Spooling mission status to native printer...");
1799 println!("[BRIDGE] PRINTER_READY // SPOOLING_DATA...");
1802 }
1803}
1804
1805fn convert_keyboard_event(event: winit::event::KeyEvent) -> Option<cvkg_core::Event> {
1810 if let winit::keyboard::PhysicalKey::Code(code) = event.physical_key {
1811 let key_str = format!("{:?}", code);
1812 if event.state == winit::event::ElementState::Pressed {
1813 Some(cvkg_core::Event::KeyDown { key: key_str })
1814 } else {
1815 Some(cvkg_core::Event::KeyUp { key: key_str })
1816 }
1817 } else {
1818 None
1819 }
1820}
1821
1822fn convert_ime_event(event: winit::event::Ime) -> Option<cvkg_core::Event> {
1823 if let winit::event::Ime::Commit(string) = event {
1824 Some(cvkg_core::Event::Ime(string))
1825 } else {
1826 None
1827 }
1828}
1829
1830fn convert_mouse_event(
1831 state: winit::event::ElementState,
1832 position: [f32; 2],
1833 button: u32,
1834) -> cvkg_core::Event {
1835 match state {
1836 winit::event::ElementState::Pressed => cvkg_core::Event::PointerDown {
1837 x: position[0],
1838 y: position[1],
1839 button,
1840 proximity_field: 0.0,
1841 tilt: None,
1842 azimuth: None,
1843 pressure: Some(1.0),
1844 barrel_rotation: None,
1845 },
1846 winit::event::ElementState::Released => cvkg_core::Event::PointerUp {
1847 x: position[0],
1848 y: position[1],
1849 button,
1850 tilt: None,
1851 azimuth: None,
1852 pressure: Some(0.0),
1853 barrel_rotation: None,
1854 },
1855 }
1856}
1857
1858struct ShieldWall {
1861 proxy: winit::event_loop::EventLoopProxy<AppEvent>,
1862}
1863
1864impl accesskit::ActionHandler for ShieldWall {
1865 fn do_action(&mut self, request: accesskit::ActionRequest) {
1866 let _ = self
1867 .proxy
1868 .send_event(AppEvent::AccessibilityAction(request));
1869 }
1870}
1871
1872impl accesskit::ActivationHandler for ShieldWall {
1873 fn request_initial_tree(&mut self) -> Option<accesskit::TreeUpdate> {
1874 let mut root = accesskit::Node::new(accesskit::Role::Window);
1875 root.set_label("CVKG Application");
1876
1877 let root_id = accesskit::NodeId(1);
1878 Some(accesskit::TreeUpdate {
1879 nodes: vec![(root_id, root)],
1880 tree: Some(accesskit::Tree::new(root_id)),
1881 focus: root_id,
1882 })
1883 }
1884}
1885
1886impl accesskit::DeactivationHandler for ShieldWall {
1887 fn deactivate_accessibility(&mut self) {}
1888}
1889
1890type AssetCacheMap =
1891 std::collections::HashMap<String, cvkg_core::AssetState<std::sync::Arc<Vec<u8>>>>;
1892
1893pub struct NativeAssetManager {
1899 cache: std::sync::Arc<arc_swap::ArcSwap<AssetCacheMap>>,
1900}
1901
1902impl Default for NativeAssetManager {
1903 fn default() -> Self {
1904 Self::new()
1905 }
1906}
1907
1908impl NativeAssetManager {
1909 pub fn new() -> Self {
1911 Self {
1912 cache: std::sync::Arc::new(arc_swap::ArcSwap::from_pointee(
1913 std::collections::HashMap::new(),
1914 )),
1915 }
1916 }
1917}
1918
1919impl cvkg_core::AssetManager for NativeAssetManager {
1920 fn load_image(&self, url: &str) -> cvkg_core::AssetState<std::sync::Arc<Vec<u8>>> {
1935 if let Some(state) = self.cache.load().get(url) {
1937 return state.clone();
1938 }
1939
1940 let cache = self.cache.clone();
1941 let key = url.to_string();
1942
1943 let mut we_inserted = false;
1948 self.cache.rcu(|map| {
1949 if map.contains_key(&key) {
1950 (**map).clone()
1952 } else {
1953 we_inserted = true;
1954 let mut m = (**map).clone();
1955 m.insert(key.clone(), cvkg_core::AssetState::Loading);
1956 m
1957 }
1958 });
1959
1960 if we_inserted {
1963 let cache_inner = cache.clone();
1964 let key_inner = key.clone();
1965
1966 std::thread::spawn(move || {
1967 log::debug!("[Native] Asynchronously loading asset: {}", key_inner);
1968 let result = match std::fs::read(&key_inner) {
1969 Ok(data) => cvkg_core::AssetState::Ready(std::sync::Arc::new(data)),
1970 Err(e) => cvkg_core::AssetState::Error(e.to_string()),
1971 };
1972
1973 cache_inner.rcu(move |map| {
1974 let mut m = (**map).clone();
1975 m.insert(key_inner.clone(), result.clone());
1976 m
1977 });
1978 });
1979 }
1980
1981 cvkg_core::AssetState::Loading
1982 }
1983
1984 fn preload_image(&self, url: &str) {
1991 if self.cache.load().contains_key(url) {
1993 return;
1994 }
1995
1996 let cache = self.cache.clone();
1997 let key = url.to_string();
1998
1999 let mut we_inserted = false;
2000 self.cache.rcu(|map| {
2001 if map.contains_key(&key) {
2002 (**map).clone()
2003 } else {
2004 we_inserted = true;
2005 let mut m = (**map).clone();
2006 m.insert(key.clone(), cvkg_core::AssetState::Loading);
2007 m
2008 }
2009 });
2010
2011 if we_inserted {
2012 std::thread::spawn(move || {
2013 log::debug!("[Native] Preloading asset: {}", key);
2014 let result = match std::fs::read(&key) {
2015 Ok(data) => cvkg_core::AssetState::Ready(std::sync::Arc::new(data)),
2016 Err(e) => cvkg_core::AssetState::Error(e.to_string()),
2017 };
2018
2019 cache.rcu(move |map| {
2020 let mut m = (**map).clone();
2021 m.insert(key.clone(), result.clone());
2022 m
2023 });
2024 });
2025 }
2026 }
2027}
2028
2029#[cfg(test)]
2030mod tests {
2031 use super::*;
2032 use cvkg_core::AssetManager;
2033 use std::io::Write;
2034
2035 #[test]
2040 fn test_native_asset_manager_loading() {
2041 let manager = NativeAssetManager::new();
2042 let temp_path = std::env::temp_dir().join("cvkg_test_asset_loading.png");
2043 let temp_file_path = temp_path.to_str().expect("temp path must be valid UTF-8");
2044 let test_data = b"fake-image-data";
2045
2046 let mut file = std::fs::File::create(temp_file_path).unwrap();
2048 file.write_all(test_data).unwrap();
2049 drop(file);
2050
2051 let mut state = manager.load_image(temp_file_path);
2053
2054 let start = std::time::Instant::now();
2056 while matches!(state, cvkg_core::AssetState::Loading) && start.elapsed().as_secs() < 5 {
2057 std::thread::sleep(std::time::Duration::from_millis(10));
2058 state = manager.load_image(temp_file_path);
2059 }
2060
2061 if let cvkg_core::AssetState::Ready(data) = state {
2062 assert_eq!(&*data, test_data);
2063 } else {
2064 let _ = std::fs::remove_file(temp_file_path);
2065 panic!("Expected Ready state, got {:?}", state);
2066 }
2067
2068 let state2 = manager.load_image(temp_file_path);
2070 if let cvkg_core::AssetState::Ready(data) = state2 {
2071 assert_eq!(&*data, test_data);
2072 } else {
2073 let _ = std::fs::remove_file(temp_file_path);
2074 panic!("Expected Ready state (cached), got {:?}", state2);
2075 }
2076
2077 let _ = std::fs::remove_file(temp_file_path);
2078 }
2079
2080 #[test]
2081 fn test_native_asset_manager_error() {
2082 let manager = NativeAssetManager::new();
2083 let path = "non_existent_file_cvkg_test.png";
2084 let mut state = manager.load_image(path);
2085
2086 let start = std::time::Instant::now();
2087 while matches!(state, cvkg_core::AssetState::Loading) && start.elapsed().as_secs() < 5 {
2088 std::thread::sleep(std::time::Duration::from_millis(10));
2089 state = manager.load_image(path);
2090 }
2091
2092 if let cvkg_core::AssetState::Error(_) = state {
2093 } else {
2095 panic!("Expected Error state, got {:?}", state);
2096 }
2097 }
2098
2099 #[test]
2100 fn test_event_conversion() {
2101 let event = convert_mouse_event(winit::event::ElementState::Pressed, [10.0, 20.0], 0);
2103 if let cvkg_core::Event::PointerDown { x, y, button, .. } = event {
2104 assert_eq!(x, 10.0);
2105 assert_eq!(y, 20.0);
2106 assert_eq!(button, 0);
2107 } else {
2108 panic!("Expected PointerDown");
2109 }
2110
2111 let event = convert_ime_event(winit::event::Ime::Commit("hello".to_string()));
2113 if let Some(cvkg_core::Event::Ime(s)) = event {
2114 assert_eq!(s, "hello");
2115 } else {
2116 panic!("Expected Ime event");
2117 }
2118 }
2119}
2120
2121fn load_icon() -> Option<winit::window::Icon> {
2125 let base = std::env::current_dir().unwrap_or_else(|e| {
2129 log::warn!(
2130 "[Native] Failed to get current directory for icon search: {}",
2131 e
2132 );
2133 std::path::PathBuf::new()
2134 });
2135
2136 let mut candidates = vec![
2137 base.join("icon.png"),
2138 base.join("crates/ulfhednar/icons/icon.png"),
2139 base.join("ulfhednar/icons/icon.png"),
2140 base.join("crates/ulfhednar/assets/icon.png"),
2141 base.join("ulfhednar/assets/icon.png"),
2142 base.join("assets/icon.png"),
2143 ];
2144
2145 if let Ok(exe_path) = std::env::current_exe()
2147 && let Some(exe_dir) = exe_path.parent()
2148 {
2149 candidates.push(exe_dir.join("icons/icon.png"));
2150 candidates.push(exe_dir.join("assets/icon.png"));
2151 candidates.push(exe_dir.join("icon.png"));
2152 if let Some(parent) = exe_dir.parent() {
2153 candidates.push(parent.join("icons/icon.png"));
2154 candidates.push(parent.join("assets/icon.png"));
2155 candidates.push(parent.join("icon.png"));
2156 }
2157 }
2158
2159 for path in candidates {
2160 if !path.exists() {
2161 log::debug!("[Native] Icon candidate not found: {:?}", path);
2162 continue;
2163 }
2164
2165 match image::open(&path) {
2166 Ok(img) => {
2167 let rgba = img.to_rgba8();
2168 let (width, height) = rgba.dimensions();
2169 match winit::window::Icon::from_rgba(rgba.into_raw(), width, height) {
2170 Ok(icon) => {
2171 log::info!("[Native] Successfully loaded app icon from: {:?}", path);
2172 return Some(icon);
2173 }
2174 Err(e) => {
2175 log::warn!("[Native] Icon format error at {:?}: {}", path, e);
2176 }
2177 }
2178 }
2179 Err(e) => {
2180 log::warn!("[Native] Failed to open icon image at {:?}: {}", path, e);
2181 }
2182 }
2183 }
2184
2185 log::warn!(
2186 "[Native] Failed to find icon.png in any search path (CWD: {:?})",
2187 base
2188 );
2189 None
2190}
2191
2192pub struct RodioAudioEngine {
2200 _stream: rodio::OutputStream,
2201}
2202
2203unsafe impl Send for RodioAudioEngine {}
2207unsafe impl Sync for RodioAudioEngine {}
2208
2209impl RodioAudioEngine {
2210 pub fn new() -> Option<Self> {
2212 match rodio::OutputStreamBuilder::open_default_stream() {
2213 Ok(stream) => {
2214 log::info!("[Native] Audio engine initialized (rodio)");
2215 Some(Self { _stream: stream })
2216 }
2217 Err(e) => {
2218 log::warn!("[Native] Audio init failed (no sound): {}", e);
2219 None
2220 }
2221 }
2222 }
2223}
2224
2225impl cvkg_core::AudioEngine for RodioAudioEngine {
2226 fn play_sound(&self, name: &str, volume: f32) {
2227 let data: &[u8] = match name {
2228 "nav_tick" => cvkg_core::sounds::NAVIGATION_TICK,
2229 "success_chime" => cvkg_core::sounds::SUCCESS_CHIME,
2230 "warning_tone" => cvkg_core::sounds::WARNING_TONE,
2231 _ => {
2232 log::warn!("[Native] Unknown sound: {}", name);
2233 return;
2234 }
2235 };
2236 self.play_buffer(data, volume);
2237 }
2238
2239 fn play_buffer(&self, data: &[u8], _volume: f32) {
2240 use std::io::Cursor;
2241 let cursor = Cursor::new(data.to_vec());
2242 let mixer = self._stream.mixer();
2243 match rodio::play(mixer, cursor) {
2244 Ok(_sink) => {}
2245 Err(e) => log::warn!("[Native] Audio play failed: {}", e),
2246 }
2247 }
2248
2249 fn play_spatial(&self, name: &str, _position: [f32; 3], volume: f32) {
2250 self.play_sound(name, volume);
2252 }
2253}
2254
2255pub struct VisualHapticEngine {
2258 last_impact: std::sync::Mutex<std::time::Instant>,
2259}
2260
2261impl Default for VisualHapticEngine {
2262 fn default() -> Self {
2263 Self::new()
2264 }
2265}
2266
2267impl VisualHapticEngine {
2268 pub fn new() -> Self {
2269 Self {
2270 last_impact: std::sync::Mutex::new(std::time::Instant::now()),
2271 }
2272 }
2273}
2274
2275impl cvkg_core::HapticEngine for VisualHapticEngine {
2276 fn impact(&self, intensity: cvkg_core::HapticIntensity) {
2277 let _ = intensity;
2278 *self.last_impact.lock().unwrap() = std::time::Instant::now();
2279 }
2280 fn selection(&self) {
2281 self.impact(cvkg_core::HapticIntensity::Light);
2282 }
2283 fn success(&self) {
2284 self.impact(cvkg_core::HapticIntensity::Medium);
2285 }
2286 fn warning(&self) {
2287 self.impact(cvkg_core::HapticIntensity::Medium);
2288 }
2289 fn error(&self) {
2290 self.impact(cvkg_core::HapticIntensity::Heavy);
2291 }
2292 fn visual_tick(&self, _intensity: f32) {
2293 *self.last_impact.lock().unwrap() = std::time::Instant::now();
2294 }
2295}