Skip to main content

cvkg_render_native/
main_loop.rs

1use std::sync::Arc;
2use winit::application::ApplicationHandler;
3use winit::event::{DeviceEvent, DeviceId, WindowEvent};
4use winit::event_loop::{ActiveEventLoop, ControlFlow, EventLoopProxy};
5use winit::window::{Window, WindowId};
6
7use crate::asset_manager::NativeAssetManager;
8use crate::audio::{RodioAudioEngine, VisualHapticEngine};
9use crate::events::{convert_ime_event, convert_keyboard_event};
10use crate::renderer::{GPU_FRAME_PTR, GpuFramePtrGuard, NativeRenderer};
11use crate::window::{SafeAreaInsets, WindowManager, WindowState, WindowStateDetector};
12use cvkg_core::{
13    AccessibilityPreferences, ColorTheme, FocusableId, FrameBudgetTracker, FrameRenderer,
14    RenderIntensityMode, Renderer, TelemetryData, View, WindowConfig, detect_system_theme,
15    set_accessibility_preferences, update_system_state,
16};
17
18/// Custom events for the native application event loop, handling accessibility
19/// callbacks and routing window lifecycle control events from background threads.
20#[derive(Debug)]
21pub enum AppEvent {
22    /// Action request from the accessibility subsystem.
23    AccessibilityAction(accesskit::ActionRequest),
24    /// Request to close a specific window.
25    CloseWindow(WindowId),
26    /// Request to set the title bar string of a window.
27    SetTitle(WindowId, String),
28    /// Request to resize a window.
29    SetSize(WindowId, f32, f32),
30    /// Request to change visibility of a window.
31    SetVisible(WindowId, bool),
32    /// Request to bring a window to the front and focus it.
33    BringToFront(WindowId),
34    /// Initial accessibility tree requested by screen reader.
35    AccessibilityInitialTreeRequested(WindowId),
36}
37
38impl From<accesskit_winit::Event> for AppEvent {
39    fn from(event: accesskit_winit::Event) -> Self {
40        match event.window_event {
41            accesskit_winit::WindowEvent::ActionRequested(req) => {
42                AppEvent::AccessibilityAction(req)
43            }
44            accesskit_winit::WindowEvent::InitialTreeRequested => {
45                AppEvent::AccessibilityInitialTreeRequested(event.window_id)
46            }
47            _ => AppEvent::AccessibilityAction(accesskit::ActionRequest {
48                action: accesskit::Action::Focus,
49                target_node: accesskit::NodeId(0),
50                target_tree: accesskit::TreeId::ROOT,
51                data: None,
52            }),
53        }
54    }
55}
56
57pub struct App<V: View> {
58    pub(crate) view: V,
59    pub(crate) window_manager: WindowManager,
60    pub(crate) gpu: Option<Arc<std::sync::Mutex<cvkg_render_gpu::GpuRenderer>>>,
61    #[allow(dead_code)]
62    pub(crate) asset_manager: std::sync::Arc<NativeAssetManager>,
63    pub(crate) proxy: EventLoopProxy<AppEvent>,
64    pub(crate) start_time: std::time::Instant,
65    pub(crate) last_frame_time: std::time::Instant,
66    pub(crate) berserker_mode: RenderIntensityMode,
67    pub(crate) rage: f32,
68    pub(crate) state_detector: WindowStateDetector,
69    pub(crate) frame_budget: FrameBudgetTracker,
70    pub(crate) modifiers: winit::keyboard::ModifiersState,
71    pub(crate) audio_engine: Option<Arc<dyn cvkg_core::AudioEngine>>,
72    pub(crate) haptic_engine: Arc<dyn cvkg_core::HapticEngine>,
73    pub(crate) pending_prewarm: Option<Vec<(String, Vec<u8>)>>,
74}
75
76impl<V: View + 'static> ApplicationHandler<AppEvent> for App<V> {
77    fn resumed(&mut self, event_loop: &ActiveEventLoop) {
78        if self.gpu.is_none() {
79            let a11y_prefs = AccessibilityPreferences::detect_from_system();
80            set_accessibility_preferences(a11y_prefs);
81            if a11y_prefs.reduce_motion
82                || a11y_prefs.reduce_transparency
83                || a11y_prefs.increase_contrast
84            {
85                tracing::info!(
86                    "[Native] Accessibility prefs: motion={} transparency={} contrast={}",
87                    a11y_prefs.reduce_motion,
88                    a11y_prefs.reduce_transparency,
89                    a11y_prefs.increase_contrast
90                );
91            }
92
93            let system_theme = detect_system_theme();
94            tracing::info!("[Native] System theme detected: {:?}", system_theme);
95
96            self.audio_engine =
97                RodioAudioEngine::new().map(|e| Arc::new(e) as Arc<dyn cvkg_core::AudioEngine>);
98
99            self.haptic_engine = Arc::new(VisualHapticEngine::new());
100
101            tracing::info!("[Native] App instance (resumed): {:p}", self);
102
103            let config = WindowConfig {
104                title: "CVKG Gallery".to_string(),
105                size: (1280.0, 720.0),
106                min_size: None,
107                max_size: None,
108                resizable: true,
109                transparent: true,
110                decorations: true,
111                level: cvkg_core::WindowLevel::Normal,
112            };
113
114            let handle = self.window_manager.create_window(
115                event_loop,
116                &self.gpu,
117                self.proxy.clone(),
118                config,
119                true, // is_main
120                &self.view,
121            );
122
123            let winit_id = self
124                .window_manager
125                .core_to_winit
126                .get(&handle.id)
127                .copied()
128                .unwrap_or_else(|| {
129                    tracing::error!("[Native] winit_id not found for window handle: window may have been destroyed");
130                    std::process::exit(1);
131                });
132            let window = self
133                .window_manager
134                .windows
135                .get(&winit_id)
136                .unwrap()
137                .window
138                .clone();
139
140            let mut gpu = pollster::block_on(cvkg_render_gpu::GpuRenderer::forge(window.clone()));
141
142            static PREFETCH_LABELS: &[(&str, f32)] = &[
143                ("File", 13.0),
144                ("Edit", 13.0),
145                ("View", 13.0),
146                ("Window", 13.0),
147                ("Help", 13.0),
148                ("Gallery", 14.0),
149                ("Rage", 12.0),
150                ("FPS", 12.0),
151                ("Frame", 12.0),
152                ("Draw", 12.0),
153                ("Layout", 12.0),
154                ("Submit", 12.0),
155                ("Browser", 12.0),
156                ("Chat", 12.0),
157                ("Code", 12.0),
158                ("Terminal", 12.0),
159            ];
160            gpu.prewarm_text_cache(PREFETCH_LABELS);
161
162            self.gpu = Some(Arc::new(std::sync::Mutex::new(gpu)));
163
164            tracing::info!("[Native] Initialization complete.");
165            window.request_redraw();
166        }
167    }
168
169    fn new_events(&mut self, _event_loop: &ActiveEventLoop, cause: winit::event::StartCause) {
170        if !matches!(cause, winit::event::StartCause::Poll) {
171            tracing::trace!("[Native] Event Loop Wake: {:?}", cause);
172        }
173    }
174
175    fn device_event(
176        &mut self,
177        _event_loop: &ActiveEventLoop,
178        _device_id: DeviceId,
179        event: DeviceEvent,
180    ) {
181        if !matches!(event, DeviceEvent::MouseMotion { .. }) {
182            tracing::trace!("[Native] DEVICE EVENT: {:?}", event);
183        }
184    }
185
186    fn window_event(&mut self, event_loop: &ActiveEventLoop, id: WindowId, event: WindowEvent) {
187        if !matches!(event, WindowEvent::RedrawRequested)
188            && !matches!(event, WindowEvent::CursorMoved { .. })
189        {
190            tracing::info!(
191                "[Native] App instance: {:p} | WINDOW EVENT: {:?}",
192                self,
193                event
194            );
195        }
196
197        let gpu_arc = if let Some(g) = &self.gpu {
198            g.clone()
199        } else {
200            tracing::warn!("[Native] DROPPING EVENT: GPU not initialized yet");
201            return;
202        };
203
204        let mut close_window = false;
205        let mut bring_to_front = false;
206        let mut create_new_window = false;
207        let mut quit_all = false;
208
209        {
210            let state = if let Some(s) = self.window_manager.windows.get_mut(&id) {
211                s
212            } else {
213                return;
214            };
215
216            match event {
217                WindowEvent::Moved(pos) => {
218                    let dx = state.last_pos.map_or(0, |last| pos.x - last[0]);
219                    let dy = state.last_pos.map_or(0, |last| pos.y - last[1]);
220                    let speed = ((dx.pow(2) + dy.pow(2)) as f32).sqrt();
221
222                    if speed > 0.1 {
223                        self.rage = (self.rage + 0.2).min(1.0);
224                        tracing::info!("[Native] Kinetic Injection! Rage: {}", self.rage);
225                    }
226
227                    state.last_pos = Some([pos.x, pos.y]);
228                    state.window.request_redraw();
229                }
230                WindowEvent::DroppedFile(path) => {
231                    if let Some(vdom) = &state.vdom {
232                        vdom.dispatch_event(cvkg_core::Event::FileDrop {
233                            x: state.cursor_pos[0],
234                            y: state.cursor_pos[1],
235                            path: path.to_string_lossy().into_owned(),
236                        });
237                    }
238                }
239                WindowEvent::CloseRequested => {
240                    close_window = true;
241                }
242                WindowEvent::Resized(physical_size) => {
243                    gpu_arc.lock().unwrap_or_else(|p| p.into_inner()).resize(
244                        id,
245                        physical_size.width,
246                        physical_size.height,
247                        state.window.scale_factor() as f32,
248                    );
249                    state.window.request_redraw();
250                }
251                WindowEvent::Focused(focused) => {
252                    tracing::info!("[Native] Window focus changed: {}", focused);
253                    state
254                        .is_key_focused
255                        .store(focused, std::sync::atomic::Ordering::SeqCst);
256                    if focused {
257                        bring_to_front = true;
258                    }
259                }
260                WindowEvent::RedrawRequested => {
261                    if state.frame_count % 60 == 0 {
262                        tracing::info!("[Native] RedrawRequested (frame {})", state.frame_count);
263                    }
264                    let size = state.window.inner_size();
265                    let scale = state.window.scale_factor();
266                    let logical_size = size.to_logical::<f32>(scale);
267
268                    let rect = cvkg_core::Rect {
269                        x: 0.0,
270                        y: 0.0,
271                        width: logical_size.width,
272                        height: logical_size.height,
273                    };
274
275                    let redraw_start = std::time::Instant::now();
276                    let last_redraw_start = state.last_redraw_start;
277                    state.last_redraw_start = redraw_start;
278                    self.frame_budget.new_frame();
279
280                    let layout_start = std::time::Instant::now();
281                    let view_changed = self.view.changed();
282
283                    let bounds_changed = state.last_bounds.map_or(true, |b| b != rect);
284                    let new_vdom: Option<cvkg_vdom::VDom> = if view_changed || bounds_changed {
285                        state.last_bounds = Some(rect);
286                        let vdom_start = std::time::Instant::now();
287                        let vdom = cvkg_vdom::VDom::build(&self.view, rect);
288                        let vdom_elapsed = vdom_start.elapsed();
289                        if vdom_elapsed > std::time::Duration::from_millis(1) {
290                            tracing::warn!(
291                                "[Native] VDom::build took {:?} ({} nodes)",
292                                vdom_elapsed,
293                                vdom.nodes.len()
294                            );
295                        }
296                        Some(vdom)
297                    } else {
298                        None
299                    };
300
301                    if state.needs_cursor_update {
302                        if let Some(vdom) = &state.vdom {
303                            vdom.dispatch_event(cvkg_core::Event::PointerMove {
304                                x: state.cursor_pos[0],
305                                y: state.cursor_pos[1],
306                                proximity_field: 0.0,
307                                tilt: None,
308                                azimuth: None,
309                                pressure: Some(1.0),
310                                barrel_rotation: None,
311                                pointer_precision: 0.0,
312                            });
313                        }
314                        state.needs_cursor_update = false;
315                    }
316                    let layout_end = std::time::Instant::now();
317                    self.frame_budget.subsystem_finish(1);
318
319                    let state_flush_start = std::time::Instant::now();
320                    #[allow(unused_assignments)]
321                    let mut diff_patches = None;
322                    match (new_vdom, &mut state.vdom) {
323                        (Some(new_vdom), Some(prev_vdom)) => {
324                            let diff_start = std::time::Instant::now();
325                            let patches = prev_vdom.diff(&new_vdom);
326                            let diff_elapsed = diff_start.elapsed();
327                            if diff_elapsed > std::time::Duration::from_millis(1) {
328                                tracing::warn!(
329                                    "[Native] VDom::diff took {:?} ({} patches)",
330                                    diff_elapsed,
331                                    patches.len()
332                                );
333                            }
334                            diff_patches = Some(patches);
335                            // ponytail: if diff returned None/empty, skip patching (no UI change this frame)
336                            let patches = diff_patches.as_deref().unwrap_or_default();
337                            let mut nodes = Vec::new();
338                            for patch in patches {
339                                if let cvkg_vdom::VDomPatch::Create(node)
340                                | cvkg_vdom::VDomPatch::Replace { node, .. } = patch
341                                {
342                                    nodes.push((
343                                        accesskit::NodeId(node.id.0),
344                                        node.to_accesskit_node(),
345                                    ));
346                                } else if let cvkg_vdom::VDomPatch::Update { id, .. } = patch
347                                    && let Some(node) = new_vdom.nodes.get(id)
348                                {
349                                    nodes.push((
350                                        accesskit::NodeId(node.id.0),
351                                        node.to_accesskit_node(),
352                                    ));
353                                } else if let cvkg_vdom::VDomPatch::Remove(id) = patch {
354                                    state
355                                        .focus_manager
356                                        .unregister(&FocusableId::from(id.0.to_string()));
357                                }
358                            }
359                            let focused_id = state
360                                .focused_node_id
361                                .map(|id| accesskit::NodeId(id.0))
362                                .unwrap_or(accesskit::NodeId(1));
363                            for patch in diff_patches.as_deref().unwrap_or_default() {
364                                if let cvkg_vdom::VDomPatch::Create(node)
365                                | cvkg_vdom::VDomPatch::Replace { node, .. } = patch
366                                {
367                                    if node.is_focusable() {
368                                        state.focus_manager.register(node.id.0.to_string());
369                                    }
370                                }
371                            }
372                            if !nodes.is_empty() {
373                                if let Some(adapter) = &mut state.accesskit_adapter {
374                                    adapter.update_if_active(|| accesskit::TreeUpdate {
375                                        nodes,
376                                        tree: None,
377                                        focus: focused_id,
378                                        tree_id: accesskit::TreeId::ROOT,
379                                    });
380                                }
381                            }
382                            prev_vdom.apply_patches(diff_patches.unwrap_or_default());
383                            state.vdom = Some(new_vdom);
384                        }
385                        (Some(new_vdom), None) => {
386                            state.vdom = Some(new_vdom);
387                        }
388                        (None, _) => {}
389                    }
390                    let state_flush_end = std::time::Instant::now();
391                    self.frame_budget.subsystem_finish(0);
392
393                    let delta_time = redraw_start.duration_since(last_redraw_start).as_secs_f32();
394                    let elapsed_time = redraw_start.duration_since(self.start_time).as_secs_f32();
395
396                    let safe_area = SafeAreaInsets::for_window_state(self.state_detector.state());
397                    let content_rect = cvkg_core::Rect {
398                        x: safe_area.left,
399                        y: safe_area.top,
400                        width: rect.width - safe_area.left - safe_area.right,
401                        height: rect.height - safe_area.top - safe_area.bottom,
402                    };
403                    let layout_deadline =
404                        std::time::Instant::now() + self.frame_budget.allocations()[1].time_slice;
405                    cvkg_core::LayoutCache::set_layout_budget_deadline(Some(layout_deadline));
406
407                    let mut renderer = NativeRenderer::new(
408                        state.window.clone(),
409                        gpu_arc.clone(),
410                        delta_time,
411                        elapsed_time,
412                        self.berserker_mode,
413                        self.rage,
414                    );
415
416                    let cpu_draw_start = std::time::Instant::now();
417                    let mut gpu = gpu_arc.lock().unwrap_or_else(|p| p.into_inner());
418                    let gpu_lock_time = cpu_draw_start.elapsed().as_secs_f32() * 1000.0;
419
420                    gpu.update_mouse(state.cursor_pos, state.cursor_velocity);
421
422                    if let Some(assets) = self.pending_prewarm.take() {
423                        tracing::info!(
424                            "[Native] Pre-warming {} assets on first frame",
425                            assets.len()
426                        );
427                        gpu.prewarm_vram(assets);
428                    }
429
430                    let encoder = gpu.begin_frame(id);
431                    let begin_frame_time =
432                        cpu_draw_start.elapsed().as_secs_f32() * 1000.0 - gpu_lock_time;
433
434                    {
435                        let raw: *mut cvkg_render_gpu::GpuRenderer = &mut *gpu;
436                        // SAFETY: `gpu` MutexGuard outlives this guard (scope ends after render)
437                        let _guard = unsafe { GpuFramePtrGuard::set(raw) };
438                        let render_start = std::time::Instant::now();
439                        self.view.render(&mut renderer, content_rect);
440                        let render_time = render_start.elapsed().as_secs_f32() * 1000.0;
441                        // _guard drops here, clearing GPU_FRAME_PTR even on panic
442                        if render_time > 5.0 {
443                            tracing::warn!(
444                                "[Native] view.render() took {:.2}ms (gpu_lock={:.2}ms, begin_frame={:.2}ms)",
445                                render_time,
446                                gpu_lock_time,
447                                begin_frame_time
448                            );
449                        }
450                    }
451                    let cpu_draw_end = std::time::Instant::now();
452                    cvkg_core::LayoutCache::clear_layout_budget_deadline();
453
454                    self.frame_budget.subsystem_finish(2);
455
456                    let gpu_render_start = std::time::Instant::now();
457                    gpu.render_frame();
458                    let gpu_render_end = std::time::Instant::now();
459
460                    gpu.end_frame(encoder);
461                    let gpu_submit_end = std::time::Instant::now();
462
463                    if state.frame_count % 60 == 0 {
464                        let cpu_draw = cpu_draw_end.duration_since(cpu_draw_start);
465                        let gpu_render = gpu_render_end.duration_since(gpu_render_start);
466                        let gpu_submit = gpu_submit_end.duration_since(gpu_render_end);
467                        let total = gpu_submit_end.duration_since(redraw_start);
468                        tracing::info!(
469                            "[Native] Frame breakdown: cpu_draw={:?} gpu_render={:?} gpu_submit(end_frame)={:?} total={:?}",
470                            cpu_draw,
471                            gpu_render,
472                            gpu_submit,
473                            total
474                        );
475                    }
476
477                    let mut telemetry = TelemetryData::default();
478                    telemetry.input_time_ms =
479                        redraw_start.duration_since(last_redraw_start).as_secs_f32() * 1000.0;
480                    telemetry.layout_time_ms =
481                        layout_end.duration_since(layout_start).as_secs_f32() * 1000.0;
482                    telemetry.state_flush_time_ms = state_flush_end
483                        .duration_since(state_flush_start)
484                        .as_secs_f32()
485                        * 1000.0;
486                    telemetry.draw_time_ms =
487                        cpu_draw_end.duration_since(cpu_draw_start).as_secs_f32() * 1000.0;
488                    telemetry.gpu_submit_time_ms =
489                        gpu_submit_end.duration_since(cpu_draw_end).as_secs_f32() * 1000.0;
490
491                    let frame_time_ms =
492                        gpu_submit_end.duration_since(redraw_start).as_secs_f32() * 1000.0;
493                    telemetry.frame_time_ms = frame_time_ms;
494                    telemetry.frame_budget_ms = self.frame_budget.total().as_secs_f32() * 1000.0;
495                    telemetry.frame_budget_remaining_ms =
496                        telemetry.frame_budget_ms - telemetry.frame_time_ms;
497                    telemetry.layout_budget_remaining_ms = self
498                        .frame_budget
499                        .allocations()
500                        .get(1)
501                        .map(|alloc| {
502                            alloc.time_slice.as_secs_f32() * 1000.0 - telemetry.layout_time_ms
503                        })
504                        .unwrap_or(0.0);
505                    telemetry.frame_over_budget = !self.frame_budget.frame_within_budget()
506                        || telemetry.frame_budget_remaining_ms < 0.0;
507                    telemetry.layout_over_budget = !self.frame_budget.is_within_budget(1)
508                        || telemetry.layout_budget_remaining_ms < 0.0;
509
510                    // Record frame budget telemetry
511                    if telemetry.frame_over_budget {
512                        tracing::warn!(
513                            "[Telemetry] Frame budget exceeded by {:.2}ms (frame={:.2}ms budget={:.2}ms)",
514                            telemetry.frame_time_ms - telemetry.frame_budget_ms,
515                            telemetry.frame_time_ms,
516                            telemetry.frame_budget_ms
517                        );
518                    }
519
520                    tracing::info!(
521                        "[Native] Frame timings: layout={:.2}ms state={:.2}ms draw={:.2}ms submit={:.2}ms total={:.2}ms",
522                        telemetry.layout_time_ms,
523                        telemetry.state_flush_time_ms,
524                        telemetry.draw_time_ms,
525                        telemetry.gpu_submit_time_ms,
526                        telemetry.frame_time_ms
527                    );
528
529                    state.frame_history.push_back(frame_time_ms);
530                    if state.frame_history.len() > 100 {
531                        state.frame_history.pop_front();
532                    }
533
534                    let mut sorted_frames: Vec<f32> = state.frame_history.iter().copied().collect();
535                    sorted_frames
536                        .sort_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal));
537
538                    if !sorted_frames.is_empty() {
539                        let p99_idx = (sorted_frames.len() as f32 * 0.99).floor() as usize;
540                        telemetry.p99_frame_time_ms =
541                            sorted_frames[p99_idx.min(sorted_frames.len() - 1)];
542
543                        let avg = sorted_frames.iter().sum::<f32>() / sorted_frames.len() as f32;
544                        let variance = sorted_frames.iter().map(|f| (f - avg).powi(2)).sum::<f32>()
545                            / sorted_frames.len() as f32;
546                        telemetry.frame_jitter_ms = variance.sqrt();
547                    }
548
549                    telemetry.hardware_stall_detected = telemetry.frame_jitter_ms > 20.0;
550                    if telemetry.frame_over_budget {
551                        tracing::warn!(
552                            "[Native] Frame budget exceeded by {:.2}ms (layout remaining {:.2}ms)",
553                            -telemetry.frame_budget_remaining_ms,
554                            telemetry.layout_budget_remaining_ms
555                        );
556                    }
557
558                    state.frame_count += 1;
559
560                    telemetry.berserker_rage = self.rage;
561                    gpu.telemetry = telemetry;
562
563                    state.window.request_redraw();
564                }
565                WindowEvent::CursorEntered { .. } => {
566                    tracing::info!("[Native] Cursor ENTERED window");
567                    if let Some(vdom) = &state.vdom {
568                        vdom.dispatch_event(cvkg_core::Event::PointerEnter);
569                    }
570                    state.window.request_redraw();
571                }
572                WindowEvent::CursorLeft { .. } => {
573                    tracing::info!("[Native] Cursor LEFT window");
574                    if let Some(vdom) = &state.vdom {
575                        vdom.dispatch_event(cvkg_core::Event::PointerLeave);
576                    }
577                    state.window.request_redraw();
578                }
579                WindowEvent::CursorMoved { position, .. } => {
580                    let scale = state.window.scale_factor();
581                    let logical = position.to_logical::<f32>(scale);
582                    let elapsed = state.last_redraw_start.elapsed().as_secs_f32().max(0.001);
583                    let dx = logical.x - state.cursor_pos[0];
584                    let dy = logical.y - state.cursor_pos[1];
585                    state.cursor_velocity = [dx / elapsed, dy / elapsed];
586                    state.cursor_pos = [logical.x, logical.y];
587                    if !state.is_dragging {
588                        let ddx = state.cursor_pos[0] - state.drag_start_pos[0];
589                        let ddy = state.cursor_pos[1] - state.drag_start_pos[1];
590                        let dist_sq = ddx * ddx + ddy * ddy;
591                        if dist_sq > state.drag_threshold * state.drag_threshold {
592                            state.is_dragging = true;
593                        }
594                    }
595                    state.needs_cursor_update = true;
596                    if state.frame_count == 0 {
597                        state.window.request_redraw();
598                    }
599                }
600                WindowEvent::MouseInput {
601                    state: mouse_state,
602                    button,
603                    ..
604                } => {
605                    tracing::info!(
606                        "[Native] MOUSE INPUT: {:?} button={:?} pos={:?}",
607                        mouse_state,
608                        button,
609                        state.cursor_pos
610                    );
611                    if let Some(touch_time) = state.last_touch_time {
612                        if touch_time.elapsed().as_millis() < 500 {
613                            tracing::info!("[Native] Ignoring MouseInput (synthesized from Touch)");
614                            return;
615                        }
616                    }
617                    if let Some(vdom) = &state.vdom {
618                        let btn_id = match button {
619                            winit::event::MouseButton::Left => 0,
620                            winit::event::MouseButton::Right => 2,
621                            winit::event::MouseButton::Middle => 1,
622                            winit::event::MouseButton::Back => 3,
623                            winit::event::MouseButton::Forward => 4,
624                            winit::event::MouseButton::Other(id) => id as u32,
625                        };
626
627                        match mouse_state {
628                            winit::event::ElementState::Pressed => {
629                                state.drag_start_pos = state.cursor_pos;
630                                state.is_dragging = false;
631                                state.drag_button = btn_id;
632                                state.active_pointer_pos = Some(state.cursor_pos);
633                                state.active_pointer_precision = 0.0;
634                                state.active_pointer_target = vdom
635                                    .hit_test(state.cursor_pos[0], state.cursor_pos[1], 0.0)
636                                    .map(|(id, _)| id);
637                                if let Some(target_id) = state.active_pointer_target {
638                                    if let Some(node) = vdom.nodes.get(&target_id) {
639                                        state.active_pointer_target_type =
640                                            Some(node.component_type.clone());
641                                        state.active_pointer_target_key = node.key.clone();
642                                    }
643                                }
644                                tracing::info!("[Native] Dispatching PointerDown to VDOM");
645                                vdom.dispatch_event(cvkg_core::Event::PointerDown {
646                                    x: state.cursor_pos[0],
647                                    y: state.cursor_pos[1],
648                                    button: btn_id,
649                                    proximity_field: 0.0,
650                                    tilt: None,
651                                    azimuth: None,
652                                    pressure: Some(1.0),
653                                    barrel_rotation: None,
654                                    pointer_precision: 0.0,
655                                });
656                            }
657                            winit::event::ElementState::Released => {
658                                tracing::info!("[Native] Dispatching PointerUp to VDOM");
659                                let fallback_target = state
660                                    .active_pointer_pos
661                                    .and_then(|pos| {
662                                        vdom.hit_test(
663                                            pos[0],
664                                            pos[1],
665                                            state.active_pointer_precision,
666                                        )
667                                        .map(|(id, _)| id)
668                                    })
669                                    .or_else(|| {
670                                        vdom.hit_test(
671                                            state.cursor_pos[0],
672                                            state.cursor_pos[1],
673                                            state.active_pointer_precision,
674                                        )
675                                        .map(|(id, _)| id)
676                                    });
677                                let target = state
678                                    .active_pointer_target
679                                    .filter(|target| {
680                                        if state.active_pointer_target_key.is_none() {
681                                            tracing::debug!("[Native] Target verification: key is None, skipping cache");
682                                            return false;
683                                        }
684                                        let verified = vdom.nodes.get(target).map_or(false, |node| {
685                                            let type_match = Some(&node.component_type) == state.active_pointer_target_type.as_ref();
686                                            let key_match = node.key == state.active_pointer_target_key;
687                                            tracing::debug!("[Native] Target verify: id={:?} type={} key={:?} type_match={} key_match={}",
688                                                target, node.component_type, node.key, type_match, key_match);
689                                            type_match && key_match
690                                        });
691                                        if !verified {
692                                            tracing::debug!("[Native] Target verification failed for {:?}, using fallback", target);
693                                        }
694                                        verified
695                                    })
696                                    .or(fallback_target);
697                                let pointer_up = cvkg_core::Event::PointerUp {
698                                    x: state.cursor_pos[0],
699                                    y: state.cursor_pos[1],
700                                    button: btn_id,
701                                    tilt: None,
702                                    azimuth: None,
703                                    pressure: Some(0.0),
704                                    barrel_rotation: None,
705                                    pointer_precision: 0.0,
706                                };
707                                let pointer_click = cvkg_core::Event::PointerClick {
708                                    x: state.cursor_pos[0],
709                                    y: state.cursor_pos[1],
710                                    button: btn_id,
711                                    tilt: None,
712                                    azimuth: None,
713                                    pressure: Some(0.0),
714                                    barrel_rotation: None,
715                                    pointer_precision: 0.0,
716                                };
717                                if let Some(target) = target {
718                                    vdom.dispatch_event_to_target(target, pointer_up);
719                                } else {
720                                    vdom.dispatch_event(pointer_up);
721                                }
722                                if !state.is_dragging {
723                                    if let Some(target) = target {
724                                        tracing::info!(
725                                            "[Native] Dispatching PointerClick to VDOM (target={:?})",
726                                            target
727                                        );
728                                        vdom.dispatch_event_to_target(target, pointer_click);
729                                    } else {
730                                        tracing::info!(
731                                            "[Native] Dispatching PointerClick to VDOM (no target, bubbling)"
732                                        );
733                                        vdom.dispatch_event(pointer_click);
734                                    }
735                                } else {
736                                    tracing::info!(
737                                        "[Native] Skipping PointerClick (is_dragging=true)"
738                                    );
739                                }
740                                state.is_dragging = false;
741                                state.active_pointer_target = None;
742                                state.active_pointer_target_type = None;
743                                state.active_pointer_target_key = None;
744                                state.active_pointer_pos = None;
745                            }
746                        }
747                        state.window.request_redraw();
748                    } else {
749                        tracing::warn!("[Native] Mouse input received but state.vdom is None!");
750                    }
751                }
752                WindowEvent::MouseWheel { delta, .. } => {
753                    if let Some(vdom) = &state.vdom {
754                        let (dx, dy) = match delta {
755                            winit::event::MouseScrollDelta::LineDelta(x, y) => (x * 10.0, y * 10.0),
756                            winit::event::MouseScrollDelta::PixelDelta(pos) => {
757                                (pos.x as f32, pos.y as f32)
758                            }
759                        };
760                        vdom.dispatch_event(cvkg_core::Event::PointerWheel {
761                            x: state.cursor_pos[0],
762                            y: state.cursor_pos[1],
763                            delta_x: dx,
764                            delta_y: dy,
765                            pointer_precision: 0.0,
766                        });
767                        state.window.request_redraw();
768                    }
769                }
770                WindowEvent::Touch(touch) => {
771                    state.last_touch_time = Some(std::time::Instant::now());
772                    if let Some(vdom) = &state.vdom {
773                        let scale = state.window.scale_factor();
774                        let logical = touch.location.to_logical::<f32>(scale);
775                        let x = logical.x;
776                        let y = logical.y;
777                        let touch_btn = 0;
778
779                        match touch.phase {
780                            winit::event::TouchPhase::Started => {
781                                tracing::info!("[Native] Dispatching PointerDown (Touch) to VDOM");
782                                state.drag_start_pos = [x, y];
783                                state.is_dragging = false;
784                                state.drag_button = touch_btn;
785                                state.active_pointer_pos = Some([x, y]);
786                                state.active_pointer_precision = 150.0;
787                                state.active_pointer_target =
788                                    vdom.hit_test(x, y, 150.0).map(|(id, _)| id);
789                                if let Some(target_id) = state.active_pointer_target {
790                                    if let Some(node) = vdom.nodes.get(&target_id) {
791                                        state.active_pointer_target_type =
792                                            Some(node.component_type.clone());
793                                        state.active_pointer_target_key = node.key.clone();
794                                    }
795                                }
796                                vdom.dispatch_event(cvkg_core::Event::PointerDown {
797                                    x,
798                                    y,
799                                    button: touch_btn,
800                                    proximity_field: 0.0,
801                                    tilt: None,
802                                    azimuth: None,
803                                    pressure: Some(
804                                        touch.force.map(|f| f.normalized() as f32).unwrap_or(0.5),
805                                    ),
806                                    barrel_rotation: None,
807                                    pointer_precision: 150.0,
808                                });
809                            }
810                            winit::event::TouchPhase::Moved => {
811                                if !state.is_dragging {
812                                    let ddx = x - state.drag_start_pos[0];
813                                    let ddy = y - state.drag_start_pos[1];
814                                    let dist_sq = ddx * ddx + ddy * ddy;
815                                    if dist_sq > state.drag_threshold * state.drag_threshold {
816                                        state.is_dragging = true;
817                                    }
818                                }
819                                vdom.dispatch_event(cvkg_core::Event::PointerMove {
820                                    x,
821                                    y,
822                                    proximity_field: 0.0,
823                                    tilt: None,
824                                    azimuth: None,
825                                    pressure: Some(
826                                        touch.force.map(|f| f.normalized() as f32).unwrap_or(0.5),
827                                    ),
828                                    barrel_rotation: None,
829                                    pointer_precision: 150.0,
830                                });
831                            }
832                            winit::event::TouchPhase::Ended => {
833                                let fallback_target = state
834                                    .active_pointer_pos
835                                    .and_then(|pos| {
836                                        vdom.hit_test(
837                                            pos[0],
838                                            pos[1],
839                                            state.active_pointer_precision,
840                                        )
841                                        .map(|(id, _)| id)
842                                    })
843                                    .or_else(|| {
844                                        vdom.hit_test(x, y, state.active_pointer_precision)
845                                            .map(|(id, _)| id)
846                                    });
847                                let target = state
848                                    .active_pointer_target
849                                    .filter(|target| {
850                                        vdom.nodes.get(target).map_or(false, |node| {
851                                            Some(&node.component_type)
852                                                == state.active_pointer_target_type.as_ref()
853                                                && node.key == state.active_pointer_target_key
854                                        })
855                                    })
856                                    .or(fallback_target);
857                                let pointer_up = cvkg_core::Event::PointerUp {
858                                    x,
859                                    y,
860                                    button: touch_btn,
861                                    tilt: None,
862                                    azimuth: None,
863                                    pressure: Some(0.0),
864                                    barrel_rotation: None,
865                                    pointer_precision: 150.0,
866                                };
867                                let pointer_click = cvkg_core::Event::PointerClick {
868                                    x,
869                                    y,
870                                    button: touch_btn,
871                                    tilt: None,
872                                    azimuth: None,
873                                    pressure: Some(0.0),
874                                    barrel_rotation: None,
875                                    pointer_precision: 150.0,
876                                };
877                                if let Some(target) = target {
878                                    vdom.dispatch_event_to_target(target, pointer_up);
879                                } else {
880                                    vdom.dispatch_event(pointer_up);
881                                }
882                                if !state.is_dragging {
883                                    if let Some(target) = target {
884                                        tracing::info!(
885                                            "[Native] Dispatching PointerClick to VDOM (target={:?})",
886                                            target
887                                        );
888                                        vdom.dispatch_event_to_target(target, pointer_click);
889                                    } else {
890                                        tracing::info!(
891                                            "[Native] Dispatching PointerClick to VDOM (no target, bubbling)"
892                                        );
893                                        vdom.dispatch_event(pointer_click);
894                                    }
895                                } else {
896                                    tracing::info!(
897                                        "[Native] Skipping PointerClick (is_dragging=true)"
898                                    );
899                                }
900                                state.is_dragging = false;
901                                state.active_pointer_target = None;
902                                state.active_pointer_target_type = None;
903                                state.active_pointer_target_key = None;
904                                state.active_pointer_pos = None;
905                            }
906                            winit::event::TouchPhase::Cancelled => {
907                                vdom.dispatch_event(cvkg_core::Event::PointerUp {
908                                    x,
909                                    y,
910                                    button: touch_btn,
911                                    tilt: None,
912                                    azimuth: None,
913                                    pressure: Some(0.0),
914                                    barrel_rotation: None,
915                                    pointer_precision: 150.0,
916                                });
917                                state.active_pointer_target = None;
918                                state.active_pointer_pos = None;
919                            }
920                        }
921                        state.window.request_redraw();
922                    }
923                }
924                WindowEvent::PinchGesture { delta, .. } => {
925                    if let Some(vdom) = &state.vdom {
926                        let scale = 1.0 + delta as f32;
927                        let velocity = delta as f32;
928                        vdom.dispatch_event(cvkg_core::Event::GesturePinch {
929                            center: state.cursor_pos,
930                            scale,
931                            velocity,
932                            phase: cvkg_core::TouchPhase::Moved,
933                        });
934                    }
935                    if let Some(audio) = &self.audio_engine {
936                        audio.play_sound("nav_tick", 0.3);
937                    }
938                    self.haptic_engine
939                        .visual_tick((delta.abs() as f32 * 5.0).min(1.0));
940                    state.window.request_redraw();
941                }
942                WindowEvent::RotationGesture { delta, .. } => {
943                    if let Some(vdom) = &state.vdom {
944                        let angle = delta;
945                        vdom.dispatch_event(cvkg_core::Event::GestureSwipe {
946                            direction: [angle.cos(), angle.sin()],
947                            velocity: delta.abs(),
948                            phase: cvkg_core::TouchPhase::Moved,
949                        });
950                    }
951                    state.window.request_redraw();
952                }
953                WindowEvent::KeyboardInput { event, .. } => {
954                    if event.state == winit::event::ElementState::Pressed {
955                        if let winit::keyboard::PhysicalKey::Code(code) = event.physical_key {
956                            let is_cmd = if cfg!(target_os = "macos") {
957                                self.modifiers.super_key()
958                            } else {
959                                self.modifiers.control_key()
960                            };
961                            let is_shift = self.modifiers.shift_key();
962
963                            if is_cmd {
964                                match code {
965                                    winit::keyboard::KeyCode::KeyZ => {
966                                        if is_shift {
967                                            tracing::info!("[Native] Shortcut: Redo (Cmd+Shift+Z)");
968                                            let mut redo_action = None;
969                                            update_system_state(|s| {
970                                                let mut s = s.clone();
971                                                redo_action = s.undo_manager.redo();
972                                                s
973                                            });
974                                            if let Some(action) = redo_action {
975                                                action();
976                                            }
977                                            state.window.request_redraw();
978                                        } else {
979                                            tracing::info!("[Native] Shortcut: Undo (Cmd+Z)");
980                                            let mut undo_action = None;
981                                            update_system_state(|s| {
982                                                let mut s = s.clone();
983                                                undo_action = s.undo_manager.undo();
984                                                s
985                                            });
986                                            if let Some(action) = undo_action {
987                                                action();
988                                            }
989                                            state.window.request_redraw();
990                                        }
991                                    }
992                                    winit::keyboard::KeyCode::KeyY
993                                        if !cfg!(target_os = "macos") =>
994                                    {
995                                        tracing::info!("[Native] Shortcut: Redo (Ctrl+Y)");
996                                        let mut redo_action = None;
997                                        update_system_state(|s| {
998                                            let mut s = s.clone();
999                                            redo_action = s.undo_manager.redo();
1000                                            s
1001                                        });
1002                                        if let Some(action) = redo_action {
1003                                            action();
1004                                        }
1005                                        state.window.request_redraw();
1006                                    }
1007                                    winit::keyboard::KeyCode::KeyN => {
1008                                        tracing::info!("[Native] Shortcut: New Window (Cmd+N)");
1009                                        create_new_window = true;
1010                                    }
1011                                    winit::keyboard::KeyCode::KeyO => {
1012                                        tracing::info!("[Native] Shortcut: Open File (Cmd+O)");
1013                                        if let Some(vdom) = &state.vdom {
1014                                            vdom.dispatch_event(cvkg_core::Event::KeyDown {
1015                                                key: "cmd+o".to_string(),
1016                                                modifiers: cvkg_core::KeyModifiers::default(),
1017                                            });
1018                                        }
1019                                        state.window.request_redraw();
1020                                    }
1021                                    winit::keyboard::KeyCode::KeyS => {
1022                                        tracing::info!("[Native] Shortcut: Save (Cmd+S)");
1023                                        if let Some(vdom) = &state.vdom {
1024                                            vdom.dispatch_event(cvkg_core::Event::KeyDown {
1025                                                key: "cmd+s".to_string(),
1026                                                modifiers: cvkg_core::KeyModifiers::default(),
1027                                            });
1028                                        }
1029                                        state.window.request_redraw();
1030                                    }
1031                                    winit::keyboard::KeyCode::KeyW => {
1032                                        tracing::info!("[Native] Shortcut: Close Window (Cmd+W)");
1033                                        close_window = true;
1034                                    }
1035                                    winit::keyboard::KeyCode::KeyQ => {
1036                                        tracing::info!("[Native] Shortcut: Quit (Cmd+Q)");
1037                                        quit_all = true;
1038                                    }
1039                                    winit::keyboard::KeyCode::KeyC => {
1040                                        tracing::info!("[Native] Shortcut: Copy (Cmd+C)");
1041                                        if let Some(vdom) = &state.vdom {
1042                                            vdom.dispatch_event(cvkg_core::Event::Copy);
1043                                        }
1044                                        state.window.request_redraw();
1045                                    }
1046                                    winit::keyboard::KeyCode::KeyV => {
1047                                        tracing::info!("[Native] Shortcut: Paste (Cmd+V)");
1048                                        let text = arboard::Clipboard::new()
1049                                            .ok()
1050                                            .and_then(|mut cb| cb.get_text().ok())
1051                                            .unwrap_or_default();
1052                                        if let Some(vdom) = &state.vdom {
1053                                            vdom.dispatch_event(cvkg_core::Event::Paste(text));
1054                                        }
1055                                        state.window.request_redraw();
1056                                    }
1057                                    winit::keyboard::KeyCode::KeyX => {
1058                                        tracing::info!("[Native] Shortcut: Cut (Cmd+X)");
1059                                        if let Some(vdom) = &state.vdom {
1060                                            vdom.dispatch_event(cvkg_core::Event::Cut);
1061                                        }
1062                                        state.window.request_redraw();
1063                                    }
1064                                    winit::keyboard::KeyCode::F11 => {
1065                                        let is_fullscreen = state.window.fullscreen().is_some();
1066                                        if is_fullscreen {
1067                                            state.window.set_fullscreen(None);
1068                                            tracing::info!("[Native] Fullscreen OFF");
1069                                        } else {
1070                                            if let Some(monitor) = state.window.current_monitor() {
1071                                                if let Some(mode) = monitor.video_modes().next() {
1072                                                    let w = mode.size().width;
1073                                                    let h = mode.size().height;
1074                                                    let rr = mode.refresh_rate_millihertz();
1075                                                    state.window.set_fullscreen(Some(
1076                                                        winit::window::Fullscreen::Exclusive(mode),
1077                                                    ));
1078                                                    tracing::info!(
1079                                                        "[Native] Fullscreen ON (exclusive: {}x{}@{:?}Hz)",
1080                                                        w,
1081                                                        h,
1082                                                        rr
1083                                                    );
1084                                                }
1085                                            } else {
1086                                                state.window.set_fullscreen(Some(
1087                                                    winit::window::Fullscreen::Borderless(None),
1088                                                ));
1089                                                tracing::info!(
1090                                                    "[Native] Fullscreen ON (borderless)"
1091                                                );
1092                                            }
1093                                        }
1094                                        state.window.request_redraw();
1095                                    }
1096                                    winit::keyboard::KeyCode::KeyA => {
1097                                        tracing::info!("[Native] Shortcut: Select All (Cmd+A)");
1098                                        if let Some(vdom) = &state.vdom {
1099                                            vdom.dispatch_event(cvkg_core::Event::KeyDown {
1100                                                key: "cmd+a".to_string(),
1101                                                modifiers: cvkg_core::KeyModifiers::default(),
1102                                            });
1103                                        }
1104                                        state.window.request_redraw();
1105                                    }
1106                                    winit::keyboard::KeyCode::KeyF => {
1107                                        tracing::info!("[Native] Shortcut: Find (Cmd+F)");
1108                                        if let Some(vdom) = &state.vdom {
1109                                            vdom.dispatch_event(cvkg_core::Event::KeyDown {
1110                                                key: "cmd+f".to_string(),
1111                                                modifiers: cvkg_core::KeyModifiers::default(),
1112                                            });
1113                                        }
1114                                        state.window.request_redraw();
1115                                    }
1116                                    winit::keyboard::KeyCode::Tab => {
1117                                        if is_shift {
1118                                            if let Some(id) = state.focus_manager.focus_prev() {
1119                                                if let Ok(node_id) = id.as_str().parse::<u64>() {
1120                                                    state.focused_node_id =
1121                                                        Some(cvkg_core::KvasirId(node_id));
1122                                                    tracing::info!(
1123                                                        "[Native] Focus previous: {:?}",
1124                                                        node_id
1125                                                    );
1126                                                }
1127                                            }
1128                                        } else {
1129                                            if let Some(id) = state.focus_manager.focus_next() {
1130                                                if let Ok(node_id) = id.as_str().parse::<u64>() {
1131                                                    state.focused_node_id =
1132                                                        Some(cvkg_core::KvasirId(node_id));
1133                                                    tracing::info!(
1134                                                        "[Native] Focus next: {:?}",
1135                                                        node_id
1136                                                    );
1137                                                }
1138                                            }
1139                                        }
1140                                        state.window.request_redraw();
1141                                    }
1142                                    _ => {}
1143                                }
1144                            }
1145                        }
1146                    }
1147
1148                    if let Some(vdom) = &state.vdom
1149                        && let Some(cvkg_event) = convert_keyboard_event(event, &self.modifiers)
1150                    {
1151                        vdom.dispatch_event(cvkg_event);
1152                        state.window.request_redraw();
1153                    }
1154                }
1155                WindowEvent::Ime(ime_event) => {
1156                    if let Some(vdom) = &state.vdom
1157                        && let Some(cvkg_event) = convert_ime_event(ime_event)
1158                    {
1159                        vdom.dispatch_event(cvkg_event);
1160                        state.window.request_redraw();
1161                    }
1162                }
1163                WindowEvent::ModifiersChanged(new_modifiers) => {
1164                    self.modifiers = new_modifiers.state();
1165                    let shift = self.modifiers.shift_key();
1166                    let ctrl = self.modifiers.control_key();
1167                    let alt = self.modifiers.alt_key();
1168                    let logo = self.modifiers.super_key();
1169                    update_system_state(|st| {
1170                        let mut new_st = st.clone();
1171                        new_st.modifiers_shift = shift;
1172                        new_st.modifiers_ctrl = ctrl;
1173                        new_st.modifiers_alt = alt;
1174                        new_st.modifiers_logo = logo;
1175                        new_st
1176                    });
1177                }
1178                WindowEvent::ScaleFactorChanged { .. } => {
1179                    if let Some(ctx) = self.window_manager.windows.get(&id) {
1180                        ctx.window.request_redraw();
1181                    }
1182                }
1183                _ => {}
1184            }
1185        }
1186
1187        if close_window {
1188            self.window_manager.close_window(id);
1189        }
1190        if quit_all {
1191            for wid in self.window_manager.window_order().to_vec() {
1192                self.window_manager.close_window(wid);
1193            }
1194        }
1195        if self.window_manager.windows.is_empty() {
1196            event_loop.exit();
1197        }
1198        if bring_to_front {
1199            self.window_manager.bring_to_front(id);
1200        }
1201        if create_new_window {
1202            self.window_manager.create_window(
1203                event_loop,
1204                &self.gpu,
1205                self.proxy.clone(),
1206                WindowConfig {
1207                    title: "New CVKG Window".to_string(),
1208                    size: (800.0, 600.0),
1209                    ..Default::default()
1210                },
1211                false,
1212                &self.view,
1213            );
1214        }
1215    }
1216
1217    fn user_event(&mut self, event_loop: &ActiveEventLoop, event: AppEvent) {
1218        match event {
1219            AppEvent::AccessibilityAction(request) => {
1220                let node_id = cvkg_core::KvasirId(request.target_node.0);
1221                let target_state = self.window_manager.windows.values_mut().find(|s| {
1222                    s.vdom
1223                        .as_ref()
1224                        .map_or(false, |v| v.nodes.contains_key(&node_id))
1225                });
1226
1227                if let Some(state) = target_state
1228                    && let Some(vdom) = &state.vdom
1229                    && let Some(node) = vdom.nodes.get(&node_id)
1230                    && request.action == accesskit::Action::Click
1231                {
1232                    let event = cvkg_core::Event::PointerClick {
1233                        x: node.layout.x + node.layout.width / 2.0,
1234                        y: node.layout.y + node.layout.height / 2.0,
1235                        button: 0,
1236                        tilt: None,
1237                        azimuth: None,
1238                        pressure: Some(1.0),
1239                        barrel_rotation: None,
1240                        pointer_precision: 0.0,
1241                    };
1242                    vdom.dispatch_event(event);
1243                }
1244            }
1245            AppEvent::AccessibilityInitialTreeRequested(winit_id) => {
1246                if let Some(state) = self.window_manager.windows.get_mut(&winit_id) {
1247                    if let Some(vdom) = &state.vdom {
1248                        let root_id = vdom.root.map(|id| id.0).unwrap_or(1);
1249                        let mut nodes = Vec::new();
1250                        for (id, node) in &vdom.nodes {
1251                            nodes.push((accesskit::NodeId(id.0), node.to_accesskit_node()));
1252                        }
1253                        let tree = accesskit::Tree::new(accesskit::NodeId(root_id));
1254                        if let Some(adapter) = &mut state.accesskit_adapter {
1255                            adapter.update_if_active(|| accesskit::TreeUpdate {
1256                                nodes,
1257                                tree: Some(tree),
1258                                focus: accesskit::NodeId(root_id),
1259                                tree_id: accesskit::TreeId::ROOT,
1260                            });
1261                        }
1262                    }
1263                }
1264            }
1265            AppEvent::CloseWindow(winit_id) => {
1266                self.window_manager.close_window(winit_id);
1267                if self.window_manager.windows.is_empty() {
1268                    event_loop.exit();
1269                }
1270            }
1271            AppEvent::SetTitle(winit_id, title) => {
1272                if let Some(data) = self.window_manager.windows.get(&winit_id) {
1273                    data.window.set_title(&title);
1274                }
1275            }
1276            AppEvent::SetSize(winit_id, width, height) => {
1277                if let Some(data) = self.window_manager.windows.get(&winit_id) {
1278                    let _ = data
1279                        .window
1280                        .request_inner_size(winit::dpi::LogicalSize::new(width, height));
1281                }
1282            }
1283            AppEvent::SetVisible(winit_id, visible) => {
1284                if let Some(data) = self.window_manager.windows.get(&winit_id) {
1285                    data.window.set_visible(visible);
1286                }
1287            }
1288            AppEvent::BringToFront(winit_id) => {
1289                self.window_manager.bring_to_front(winit_id);
1290            }
1291        }
1292    }
1293
1294    fn about_to_wait(&mut self, event_loop: &ActiveEventLoop) {
1295        self.rage = (self.rage - 0.02).max(0.0);
1296
1297        let now = std::time::Instant::now();
1298        let target_interval = std::time::Duration::from_micros(8_333);
1299
1300        if now.duration_since(self.last_frame_time) >= target_interval {
1301            self.last_frame_time = now;
1302            let needs_redraw = self.view.changed();
1303            if needs_redraw {
1304                for window_state in self.window_manager.windows.values() {
1305                    window_state.window.request_redraw();
1306                }
1307            }
1308            event_loop.set_control_flow(ControlFlow::WaitUntil(now + target_interval));
1309        } else {
1310            event_loop.set_control_flow(ControlFlow::WaitUntil(
1311                self.last_frame_time + target_interval,
1312            ));
1313        }
1314    }
1315}
1316
1317pub struct ShieldWall {
1318    pub(crate) proxy: EventLoopProxy<AppEvent>,
1319}
1320
1321impl accesskit::ActionHandler for ShieldWall {
1322    fn do_action(&mut self, request: accesskit::ActionRequest) {
1323        let _ = self
1324            .proxy
1325            .send_event(AppEvent::AccessibilityAction(request));
1326    }
1327}
1328
1329impl accesskit::ActivationHandler for ShieldWall {
1330    fn request_initial_tree(&mut self) -> Option<accesskit::TreeUpdate> {
1331        let mut root = accesskit::Node::new(accesskit::Role::Window);
1332        root.set_label("CVKG Application");
1333
1334        let root_id = accesskit::NodeId(1);
1335        Some(accesskit::TreeUpdate {
1336            nodes: vec![(root_id, root)],
1337            tree: Some(accesskit::Tree::new(root_id)),
1338            focus: root_id,
1339            tree_id: accesskit::TreeId::ROOT,
1340        })
1341    }
1342}
1343
1344impl accesskit::DeactivationHandler for ShieldWall {
1345    fn deactivate_accessibility(&mut self) {}
1346}