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