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::{GpuFramePtrGuard, GPU_FRAME_PTR, 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!("[Native] Skipping PointerClick (is_dragging=true)");
737                                }
738                                state.is_dragging = false;
739                                state.active_pointer_target = None;
740                                state.active_pointer_target_type = None;
741                                state.active_pointer_target_key = None;
742                                state.active_pointer_pos = None;
743                            }
744                        }
745                        state.window.request_redraw();
746                    } else {
747                        tracing::warn!("[Native] Mouse input received but state.vdom is None!");
748                    }
749                }
750                WindowEvent::MouseWheel { delta, .. } => {
751                    if let Some(vdom) = &state.vdom {
752                        let (dx, dy) = match delta {
753                            winit::event::MouseScrollDelta::LineDelta(x, y) => (x * 10.0, y * 10.0),
754                            winit::event::MouseScrollDelta::PixelDelta(pos) => {
755                                (pos.x as f32, pos.y as f32)
756                            }
757                        };
758                        vdom.dispatch_event(cvkg_core::Event::PointerWheel {
759                            x: state.cursor_pos[0],
760                            y: state.cursor_pos[1],
761                            delta_x: dx,
762                            delta_y: dy,
763                            pointer_precision: 0.0,
764                        });
765                        state.window.request_redraw();
766                    }
767                }
768                WindowEvent::Touch(touch) => {
769                    state.last_touch_time = Some(std::time::Instant::now());
770                    if let Some(vdom) = &state.vdom {
771                        let scale = state.window.scale_factor();
772                        let logical = touch.location.to_logical::<f32>(scale);
773                        let x = logical.x;
774                        let y = logical.y;
775                        let touch_btn = 0;
776
777                        match touch.phase {
778                            winit::event::TouchPhase::Started => {
779                                tracing::info!("[Native] Dispatching PointerDown (Touch) to VDOM");
780                                state.drag_start_pos = [x, y];
781                                state.is_dragging = false;
782                                state.drag_button = touch_btn;
783                                state.active_pointer_pos = Some([x, y]);
784                                state.active_pointer_precision = 150.0;
785                                state.active_pointer_target =
786                                    vdom.hit_test(x, y, 150.0).map(|(id, _)| id);
787                                if let Some(target_id) = state.active_pointer_target {
788                                    if let Some(node) = vdom.nodes.get(&target_id) {
789                                        state.active_pointer_target_type =
790                                            Some(node.component_type.clone());
791                                        state.active_pointer_target_key = node.key.clone();
792                                    }
793                                }
794                                vdom.dispatch_event(cvkg_core::Event::PointerDown {
795                                    x,
796                                    y,
797                                    button: touch_btn,
798                                    proximity_field: 0.0,
799                                    tilt: None,
800                                    azimuth: None,
801                                    pressure: Some(
802                                        touch.force.map(|f| f.normalized() as f32).unwrap_or(0.5),
803                                    ),
804                                    barrel_rotation: None,
805                                    pointer_precision: 150.0,
806                                });
807                            }
808                            winit::event::TouchPhase::Moved => {
809                                if !state.is_dragging {
810                                    let ddx = x - state.drag_start_pos[0];
811                                    let ddy = y - state.drag_start_pos[1];
812                                    let dist_sq = ddx * ddx + ddy * ddy;
813                                    if dist_sq > state.drag_threshold * state.drag_threshold {
814                                        state.is_dragging = true;
815                                    }
816                                }
817                                vdom.dispatch_event(cvkg_core::Event::PointerMove {
818                                    x,
819                                    y,
820                                    proximity_field: 0.0,
821                                    tilt: None,
822                                    azimuth: None,
823                                    pressure: Some(
824                                        touch.force.map(|f| f.normalized() as f32).unwrap_or(0.5),
825                                    ),
826                                    barrel_rotation: None,
827                                    pointer_precision: 150.0,
828                                });
829                            }
830                            winit::event::TouchPhase::Ended => {
831                                let fallback_target = state
832                                    .active_pointer_pos
833                                    .and_then(|pos| {
834                                        vdom.hit_test(
835                                            pos[0],
836                                            pos[1],
837                                            state.active_pointer_precision,
838                                        )
839                                        .map(|(id, _)| id)
840                                    })
841                                    .or_else(|| {
842                                        vdom.hit_test(x, y, state.active_pointer_precision)
843                                            .map(|(id, _)| id)
844                                    });
845                                let target = state
846                                    .active_pointer_target
847                                    .filter(|target| {
848                                        vdom.nodes.get(target).map_or(false, |node| {
849                                            Some(&node.component_type)
850                                                == state.active_pointer_target_type.as_ref()
851                                                && node.key == state.active_pointer_target_key
852                                        })
853                                    })
854                                    .or(fallback_target);
855                                let pointer_up = cvkg_core::Event::PointerUp {
856                                    x,
857                                    y,
858                                    button: touch_btn,
859                                    tilt: None,
860                                    azimuth: None,
861                                    pressure: Some(0.0),
862                                    barrel_rotation: None,
863                                    pointer_precision: 150.0,
864                                };
865                                let pointer_click = cvkg_core::Event::PointerClick {
866                                    x,
867                                    y,
868                                    button: touch_btn,
869                                    tilt: None,
870                                    azimuth: None,
871                                    pressure: Some(0.0),
872                                    barrel_rotation: None,
873                                    pointer_precision: 150.0,
874                                };
875                                if let Some(target) = target {
876                                    vdom.dispatch_event_to_target(target, pointer_up);
877                                } else {
878                                    vdom.dispatch_event(pointer_up);
879                                }
880                                if !state.is_dragging {
881                                    if let Some(target) = target {
882                                        tracing::info!(
883                                            "[Native] Dispatching PointerClick to VDOM (target={:?})",
884                                            target
885                                        );
886                                        vdom.dispatch_event_to_target(target, pointer_click);
887                                    } else {
888                                        tracing::info!(
889                                            "[Native] Dispatching PointerClick to VDOM (no target, bubbling)"
890                                        );
891                                        vdom.dispatch_event(pointer_click);
892                                    }
893                                } else {
894                                    tracing::info!("[Native] Skipping PointerClick (is_dragging=true)");
895                                }
896                                state.is_dragging = false;
897                                state.active_pointer_target = None;
898                                state.active_pointer_target_type = None;
899                                state.active_pointer_target_key = None;
900                                state.active_pointer_pos = None;
901                            }
902                            winit::event::TouchPhase::Cancelled => {
903                                vdom.dispatch_event(cvkg_core::Event::PointerUp {
904                                    x,
905                                    y,
906                                    button: touch_btn,
907                                    tilt: None,
908                                    azimuth: None,
909                                    pressure: Some(0.0),
910                                    barrel_rotation: None,
911                                    pointer_precision: 150.0,
912                                });
913                                state.active_pointer_target = None;
914                                state.active_pointer_pos = None;
915                            }
916                        }
917                        state.window.request_redraw();
918                    }
919                }
920                WindowEvent::PinchGesture { delta, .. } => {
921                    if let Some(vdom) = &state.vdom {
922                        let scale = 1.0 + delta as f32;
923                        let velocity = delta as f32;
924                        vdom.dispatch_event(cvkg_core::Event::GesturePinch {
925                            center: state.cursor_pos,
926                            scale,
927                            velocity,
928                            phase: cvkg_core::TouchPhase::Moved,
929                        });
930                    }
931                    if let Some(audio) = &self.audio_engine {
932                        audio.play_sound("nav_tick", 0.3);
933                    }
934                    self.haptic_engine
935                        .visual_tick((delta.abs() as f32 * 5.0).min(1.0));
936                    state.window.request_redraw();
937                }
938                WindowEvent::RotationGesture { delta, .. } => {
939                    if let Some(vdom) = &state.vdom {
940                        let angle = delta;
941                        vdom.dispatch_event(cvkg_core::Event::GestureSwipe {
942                            direction: [angle.cos(), angle.sin()],
943                            velocity: delta.abs(),
944                            phase: cvkg_core::TouchPhase::Moved,
945                        });
946                    }
947                    state.window.request_redraw();
948                }
949                WindowEvent::KeyboardInput { event, .. } => {
950                    if event.state == winit::event::ElementState::Pressed {
951                        if let winit::keyboard::PhysicalKey::Code(code) = event.physical_key {
952                            let is_cmd = if cfg!(target_os = "macos") {
953                                self.modifiers.super_key()
954                            } else {
955                                self.modifiers.control_key()
956                            };
957                            let is_shift = self.modifiers.shift_key();
958
959                            if is_cmd {
960                                match code {
961                                    winit::keyboard::KeyCode::KeyZ => {
962                                        if is_shift {
963                                            tracing::info!("[Native] Shortcut: Redo (Cmd+Shift+Z)");
964                                            let mut redo_action = None;
965                                            update_system_state(|s| {
966                                                let mut s = s.clone();
967                                                redo_action = s.undo_manager.redo();
968                                                s
969                                            });
970                                            if let Some(action) = redo_action {
971                                                action();
972                                            }
973                                            state.window.request_redraw();
974                                        } else {
975                                            tracing::info!("[Native] Shortcut: Undo (Cmd+Z)");
976                                            let mut undo_action = None;
977                                            update_system_state(|s| {
978                                                let mut s = s.clone();
979                                                undo_action = s.undo_manager.undo();
980                                                s
981                                            });
982                                            if let Some(action) = undo_action {
983                                                action();
984                                            }
985                                            state.window.request_redraw();
986                                        }
987                                    }
988                                    winit::keyboard::KeyCode::KeyY
989                                        if !cfg!(target_os = "macos") =>
990                                    {
991                                        tracing::info!("[Native] Shortcut: Redo (Ctrl+Y)");
992                                        let mut redo_action = None;
993                                        update_system_state(|s| {
994                                            let mut s = s.clone();
995                                            redo_action = s.undo_manager.redo();
996                                            s
997                                        });
998                                        if let Some(action) = redo_action {
999                                            action();
1000                                        }
1001                                        state.window.request_redraw();
1002                                    }
1003                                    winit::keyboard::KeyCode::KeyN => {
1004                                        tracing::info!("[Native] Shortcut: New Window (Cmd+N)");
1005                                        create_new_window = true;
1006                                    }
1007                                    winit::keyboard::KeyCode::KeyO => {
1008                                        tracing::info!("[Native] Shortcut: Open File (Cmd+O)");
1009                                        if let Some(vdom) = &state.vdom {
1010                                            vdom.dispatch_event(cvkg_core::Event::KeyDown {
1011                                                key: "cmd+o".to_string(),
1012                                                modifiers: cvkg_core::KeyModifiers::default(),
1013                                            });
1014                                        }
1015                                        state.window.request_redraw();
1016                                    }
1017                                    winit::keyboard::KeyCode::KeyS => {
1018                                        tracing::info!("[Native] Shortcut: Save (Cmd+S)");
1019                                        if let Some(vdom) = &state.vdom {
1020                                            vdom.dispatch_event(cvkg_core::Event::KeyDown {
1021                                                key: "cmd+s".to_string(),
1022                                                modifiers: cvkg_core::KeyModifiers::default(),
1023                                            });
1024                                        }
1025                                        state.window.request_redraw();
1026                                    }
1027                                    winit::keyboard::KeyCode::KeyW => {
1028                                        tracing::info!("[Native] Shortcut: Close Window (Cmd+W)");
1029                                        close_window = true;
1030                                    }
1031                                    winit::keyboard::KeyCode::KeyQ => {
1032                                        tracing::info!("[Native] Shortcut: Quit (Cmd+Q)");
1033                                        quit_all = true;
1034                                    }
1035                                    winit::keyboard::KeyCode::KeyC => {
1036                                        tracing::info!("[Native] Shortcut: Copy (Cmd+C)");
1037                                        if let Some(vdom) = &state.vdom {
1038                                            vdom.dispatch_event(cvkg_core::Event::Copy);
1039                                        }
1040                                        state.window.request_redraw();
1041                                    }
1042                                    winit::keyboard::KeyCode::KeyV => {
1043                                        tracing::info!("[Native] Shortcut: Paste (Cmd+V)");
1044                                        let text = arboard::Clipboard::new()
1045                                            .ok()
1046                                            .and_then(|mut cb| cb.get_text().ok())
1047                                            .unwrap_or_default();
1048                                        if let Some(vdom) = &state.vdom {
1049                                            vdom.dispatch_event(cvkg_core::Event::Paste(text));
1050                                        }
1051                                        state.window.request_redraw();
1052                                    }
1053                                    winit::keyboard::KeyCode::KeyX => {
1054                                        tracing::info!("[Native] Shortcut: Cut (Cmd+X)");
1055                                        if let Some(vdom) = &state.vdom {
1056                                            vdom.dispatch_event(cvkg_core::Event::Cut);
1057                                        }
1058                                        state.window.request_redraw();
1059                                    }
1060                                    winit::keyboard::KeyCode::F11 => {
1061                                        let is_fullscreen = state.window.fullscreen().is_some();
1062                                        if is_fullscreen {
1063                                            state.window.set_fullscreen(None);
1064                                            tracing::info!("[Native] Fullscreen OFF");
1065                                        } else {
1066                                            if let Some(monitor) = state.window.current_monitor() {
1067                                                if let Some(mode) = monitor.video_modes().next() {
1068                                                    let w = mode.size().width;
1069                                                    let h = mode.size().height;
1070                                                    let rr = mode.refresh_rate_millihertz();
1071                                                    state.window.set_fullscreen(Some(
1072                                                        winit::window::Fullscreen::Exclusive(mode),
1073                                                    ));
1074                                                    tracing::info!(
1075                                                        "[Native] Fullscreen ON (exclusive: {}x{}@{:?}Hz)",
1076                                                        w,
1077                                                        h,
1078                                                        rr
1079                                                    );
1080                                                }
1081                                            } else {
1082                                                state.window.set_fullscreen(Some(
1083                                                    winit::window::Fullscreen::Borderless(None),
1084                                                ));
1085                                                tracing::info!("[Native] Fullscreen ON (borderless)");
1086                                            }
1087                                        }
1088                                        state.window.request_redraw();
1089                                    }
1090                                    winit::keyboard::KeyCode::KeyA => {
1091                                        tracing::info!("[Native] Shortcut: Select All (Cmd+A)");
1092                                        if let Some(vdom) = &state.vdom {
1093                                            vdom.dispatch_event(cvkg_core::Event::KeyDown {
1094                                                key: "cmd+a".to_string(),
1095                                                modifiers: cvkg_core::KeyModifiers::default(),
1096                                            });
1097                                        }
1098                                        state.window.request_redraw();
1099                                    }
1100                                    winit::keyboard::KeyCode::KeyF => {
1101                                        tracing::info!("[Native] Shortcut: Find (Cmd+F)");
1102                                        if let Some(vdom) = &state.vdom {
1103                                            vdom.dispatch_event(cvkg_core::Event::KeyDown {
1104                                                key: "cmd+f".to_string(),
1105                                                modifiers: cvkg_core::KeyModifiers::default(),
1106                                            });
1107                                        }
1108                                        state.window.request_redraw();
1109                                    }
1110                                    winit::keyboard::KeyCode::Tab => {
1111                                        if is_shift {
1112                                            if let Some(id) = state.focus_manager.focus_prev() {
1113                                                if let Ok(node_id) = id.as_str().parse::<u64>() {
1114                                                    state.focused_node_id =
1115                                                        Some(cvkg_core::KvasirId(node_id));
1116                                                    tracing::info!(
1117                                                        "[Native] Focus previous: {:?}",
1118                                                        node_id
1119                                                    );
1120                                                }
1121                                            }
1122                                        } else {
1123                                            if let Some(id) = state.focus_manager.focus_next() {
1124                                                if let Ok(node_id) = id.as_str().parse::<u64>() {
1125                                                    state.focused_node_id =
1126                                                        Some(cvkg_core::KvasirId(node_id));
1127                                                    tracing::info!(
1128                                                        "[Native] Focus next: {:?}",
1129                                                        node_id
1130                                                    );
1131                                                }
1132                                            }
1133                                        }
1134                                        state.window.request_redraw();
1135                                    }
1136                                    _ => {}
1137                                }
1138                            }
1139                        }
1140                    }
1141
1142                    if let Some(vdom) = &state.vdom
1143                        && let Some(cvkg_event) = convert_keyboard_event(event, &self.modifiers)
1144                    {
1145                        vdom.dispatch_event(cvkg_event);
1146                        state.window.request_redraw();
1147                    }
1148                }
1149                WindowEvent::Ime(ime_event) => {
1150                    if let Some(vdom) = &state.vdom
1151                        && let Some(cvkg_event) = convert_ime_event(ime_event)
1152                    {
1153                        vdom.dispatch_event(cvkg_event);
1154                        state.window.request_redraw();
1155                    }
1156                }
1157                WindowEvent::ModifiersChanged(new_modifiers) => {
1158                    self.modifiers = new_modifiers.state();
1159                    let shift = self.modifiers.shift_key();
1160                    let ctrl = self.modifiers.control_key();
1161                    let alt = self.modifiers.alt_key();
1162                    let logo = self.modifiers.super_key();
1163                    update_system_state(|st| {
1164                        let mut new_st = st.clone();
1165                        new_st.modifiers_shift = shift;
1166                        new_st.modifiers_ctrl = ctrl;
1167                        new_st.modifiers_alt = alt;
1168                        new_st.modifiers_logo = logo;
1169                        new_st
1170                    });
1171                }
1172                WindowEvent::ScaleFactorChanged { .. } => {
1173                    if let Some(ctx) = self.window_manager.windows.get(&id) {
1174                        ctx.window.request_redraw();
1175                    }
1176                }
1177                _ => {}
1178            }
1179        }
1180
1181        if close_window {
1182            self.window_manager.close_window(id);
1183        }
1184        if quit_all {
1185            for wid in self.window_manager.window_order().to_vec() {
1186                self.window_manager.close_window(wid);
1187            }
1188        }
1189        if self.window_manager.windows.is_empty() {
1190            event_loop.exit();
1191        }
1192        if bring_to_front {
1193            self.window_manager.bring_to_front(id);
1194        }
1195        if create_new_window {
1196            self.window_manager.create_window(
1197                event_loop,
1198                &self.gpu,
1199                self.proxy.clone(),
1200                WindowConfig {
1201                    title: "New CVKG Window".to_string(),
1202                    size: (800.0, 600.0),
1203                    ..Default::default()
1204                },
1205                false,
1206                &self.view,
1207            );
1208        }
1209    }
1210
1211    fn user_event(&mut self, event_loop: &ActiveEventLoop, event: AppEvent) {
1212        match event {
1213            AppEvent::AccessibilityAction(request) => {
1214                let node_id = cvkg_core::KvasirId(request.target_node.0);
1215                let target_state = self.window_manager.windows.values_mut().find(|s| {
1216                    s.vdom
1217                        .as_ref()
1218                        .map_or(false, |v| v.nodes.contains_key(&node_id))
1219                });
1220
1221                if let Some(state) = target_state
1222                    && let Some(vdom) = &state.vdom
1223                    && let Some(node) = vdom.nodes.get(&node_id)
1224                    && request.action == accesskit::Action::Click
1225                {
1226                    let event = cvkg_core::Event::PointerClick {
1227                        x: node.layout.x + node.layout.width / 2.0,
1228                        y: node.layout.y + node.layout.height / 2.0,
1229                        button: 0,
1230                        tilt: None,
1231                        azimuth: None,
1232                        pressure: Some(1.0),
1233                        barrel_rotation: None,
1234                        pointer_precision: 0.0,
1235                    };
1236                    vdom.dispatch_event(event);
1237                }
1238            }
1239            AppEvent::AccessibilityInitialTreeRequested(winit_id) => {
1240                if let Some(state) = self.window_manager.windows.get_mut(&winit_id) {
1241                    if let Some(vdom) = &state.vdom {
1242                        let root_id = vdom.root.map(|id| id.0).unwrap_or(1);
1243                        let mut nodes = Vec::new();
1244                        for (id, node) in &vdom.nodes {
1245                            nodes.push((accesskit::NodeId(id.0), node.to_accesskit_node()));
1246                        }
1247                        let tree = accesskit::Tree::new(accesskit::NodeId(root_id));
1248                        if let Some(adapter) = &mut state.accesskit_adapter {
1249                            adapter.update_if_active(|| accesskit::TreeUpdate {
1250                                nodes,
1251                                tree: Some(tree),
1252                                focus: accesskit::NodeId(root_id),
1253                                tree_id: accesskit::TreeId::ROOT,
1254                            });
1255                        }
1256                    }
1257                }
1258            }
1259            AppEvent::CloseWindow(winit_id) => {
1260                self.window_manager.close_window(winit_id);
1261                if self.window_manager.windows.is_empty() {
1262                    event_loop.exit();
1263                }
1264            }
1265            AppEvent::SetTitle(winit_id, title) => {
1266                if let Some(data) = self.window_manager.windows.get(&winit_id) {
1267                    data.window.set_title(&title);
1268                }
1269            }
1270            AppEvent::SetSize(winit_id, width, height) => {
1271                if let Some(data) = self.window_manager.windows.get(&winit_id) {
1272                    let _ = data
1273                        .window
1274                        .request_inner_size(winit::dpi::LogicalSize::new(width, height));
1275                }
1276            }
1277            AppEvent::SetVisible(winit_id, visible) => {
1278                if let Some(data) = self.window_manager.windows.get(&winit_id) {
1279                    data.window.set_visible(visible);
1280                }
1281            }
1282            AppEvent::BringToFront(winit_id) => {
1283                self.window_manager.bring_to_front(winit_id);
1284            }
1285        }
1286    }
1287
1288    fn about_to_wait(&mut self, event_loop: &ActiveEventLoop) {
1289        self.rage = (self.rage - 0.02).max(0.0);
1290
1291        let now = std::time::Instant::now();
1292        let target_interval = std::time::Duration::from_micros(8_333);
1293
1294        if now.duration_since(self.last_frame_time) >= target_interval {
1295            self.last_frame_time = now;
1296            let needs_redraw = self.view.changed();
1297            if needs_redraw {
1298                for window_state in self.window_manager.windows.values() {
1299                    window_state.window.request_redraw();
1300                }
1301            }
1302            event_loop.set_control_flow(ControlFlow::WaitUntil(now + target_interval));
1303        } else {
1304            event_loop.set_control_flow(ControlFlow::WaitUntil(
1305                self.last_frame_time + target_interval,
1306            ));
1307        }
1308    }
1309}
1310
1311pub struct ShieldWall {
1312    pub(crate) proxy: EventLoopProxy<AppEvent>,
1313}
1314
1315impl accesskit::ActionHandler for ShieldWall {
1316    fn do_action(&mut self, request: accesskit::ActionRequest) {
1317        let _ = self
1318            .proxy
1319            .send_event(AppEvent::AccessibilityAction(request));
1320    }
1321}
1322
1323impl accesskit::ActivationHandler for ShieldWall {
1324    fn request_initial_tree(&mut self) -> Option<accesskit::TreeUpdate> {
1325        let mut root = accesskit::Node::new(accesskit::Role::Window);
1326        root.set_label("CVKG Application");
1327
1328        let root_id = accesskit::NodeId(1);
1329        Some(accesskit::TreeUpdate {
1330            nodes: vec![(root_id, root)],
1331            tree: Some(accesskit::Tree::new(root_id)),
1332            focus: root_id,
1333            tree_id: accesskit::TreeId::ROOT,
1334        })
1335    }
1336}
1337
1338impl accesskit::DeactivationHandler for ShieldWall {
1339    fn deactivate_accessibility(&mut self) {}
1340}