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