Skip to main content

cvkg_render_native/
main_loop.rs

1use std::sync::Arc;
2use winit::application::ApplicationHandler;
3use winit::event::{DeviceEvent, DeviceId, WindowEvent};
4use winit::event_loop::{ActiveEventLoop, ControlFlow, EventLoopProxy};
5use winit::window::{Window, WindowId};
6
7use crate::asset_manager::NativeAssetManager;
8use crate::audio::{RodioAudioEngine, VisualHapticEngine};
9use crate::events::{convert_ime_event, convert_keyboard_event};
10use crate::renderer::{GPU_FRAME_PTR, 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                log::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            log::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            log::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                    log::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            log::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            log::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            log::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            log::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            log::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                        log::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                    log::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                        log::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                             log::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                                log::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                        log::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                        GPU_FRAME_PTR.with(|ptr| ptr.set(raw));
437                        let render_start = std::time::Instant::now();
438                        self.view.render(&mut renderer, content_rect);
439                        let render_time = render_start.elapsed().as_secs_f32() * 1000.0;
440                        GPU_FRAME_PTR.with(|ptr| ptr.set(std::ptr::null_mut()));
441                        if render_time > 5.0 {
442                            log::warn!(
443                                "[Native] view.render() took {:.2}ms (gpu_lock={:.2}ms, begin_frame={:.2}ms)",
444                                render_time,
445                                gpu_lock_time,
446                                begin_frame_time
447                            );
448                        }
449                    }
450                    let cpu_draw_end = std::time::Instant::now();
451                    cvkg_core::LayoutCache::clear_layout_budget_deadline();
452
453                    self.frame_budget.subsystem_finish(2);
454
455                    let gpu_render_start = std::time::Instant::now();
456                    gpu.render_frame();
457                    let gpu_render_end = std::time::Instant::now();
458
459                    gpu.end_frame(encoder);
460                    let gpu_submit_end = std::time::Instant::now();
461
462                    if state.frame_count % 60 == 0 {
463                        let cpu_draw = cpu_draw_end.duration_since(cpu_draw_start);
464                        let gpu_render = gpu_render_end.duration_since(gpu_render_start);
465                        let gpu_submit = gpu_submit_end.duration_since(gpu_render_end);
466                        let total = gpu_submit_end.duration_since(redraw_start);
467                        log::info!(
468                            "[Native] Frame breakdown: cpu_draw={:?} gpu_render={:?} gpu_submit(end_frame)={:?} total={:?}",
469                            cpu_draw,
470                            gpu_render,
471                            gpu_submit,
472                            total
473                        );
474                    }
475
476                    let mut telemetry = TelemetryData::default();
477                    telemetry.input_time_ms =
478                        redraw_start.duration_since(last_redraw_start).as_secs_f32() * 1000.0;
479                    telemetry.layout_time_ms =
480                        layout_end.duration_since(layout_start).as_secs_f32() * 1000.0;
481                    telemetry.state_flush_time_ms = state_flush_end
482                        .duration_since(state_flush_start)
483                        .as_secs_f32()
484                        * 1000.0;
485                    telemetry.draw_time_ms =
486                        cpu_draw_end.duration_since(cpu_draw_start).as_secs_f32() * 1000.0;
487                    telemetry.gpu_submit_time_ms =
488                        gpu_submit_end.duration_since(cpu_draw_end).as_secs_f32() * 1000.0;
489
490                    let frame_time_ms =
491                        gpu_submit_end.duration_since(redraw_start).as_secs_f32() * 1000.0;
492                    telemetry.frame_time_ms = frame_time_ms;
493                    telemetry.frame_budget_ms = self.frame_budget.total().as_secs_f32() * 1000.0;
494                    telemetry.frame_budget_remaining_ms =
495                        telemetry.frame_budget_ms - telemetry.frame_time_ms;
496                    telemetry.layout_budget_remaining_ms = self
497                        .frame_budget
498                        .allocations()
499                        .get(1)
500                        .map(|alloc| {
501                            alloc.time_slice.as_secs_f32() * 1000.0 - telemetry.layout_time_ms
502                        })
503                        .unwrap_or(0.0);
504                    telemetry.frame_over_budget = !self.frame_budget.frame_within_budget()
505                        || telemetry.frame_budget_remaining_ms < 0.0;
506                    telemetry.layout_over_budget = !self.frame_budget.is_within_budget(1)
507                        || telemetry.layout_budget_remaining_ms < 0.0;
508
509                    log::info!(
510                        "[Native] Frame timings: layout={:.2}ms state={:.2}ms draw={:.2}ms submit={:.2}ms total={:.2}ms",
511                        telemetry.layout_time_ms,
512                        telemetry.state_flush_time_ms,
513                        telemetry.draw_time_ms,
514                        telemetry.gpu_submit_time_ms,
515                        telemetry.frame_time_ms
516                    );
517
518                    state.frame_history.push_back(frame_time_ms);
519                    if state.frame_history.len() > 100 {
520                        state.frame_history.pop_front();
521                    }
522
523                    let mut sorted_frames: Vec<f32> = state.frame_history.iter().copied().collect();
524                    sorted_frames
525                        .sort_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal));
526
527                    if !sorted_frames.is_empty() {
528                        let p99_idx = (sorted_frames.len() as f32 * 0.99).floor() as usize;
529                        telemetry.p99_frame_time_ms =
530                            sorted_frames[p99_idx.min(sorted_frames.len() - 1)];
531
532                        let avg = sorted_frames.iter().sum::<f32>() / sorted_frames.len() as f32;
533                        let variance = sorted_frames.iter().map(|f| (f - avg).powi(2)).sum::<f32>()
534                            / sorted_frames.len() as f32;
535                        telemetry.frame_jitter_ms = variance.sqrt();
536                    }
537
538                    telemetry.hardware_stall_detected = telemetry.frame_jitter_ms > 20.0;
539                    if telemetry.frame_over_budget {
540                        log::warn!(
541                            "[Native] Frame budget exceeded by {:.2}ms (layout remaining {:.2}ms)",
542                            -telemetry.frame_budget_remaining_ms,
543                            telemetry.layout_budget_remaining_ms
544                        );
545                    }
546
547                    state.frame_count += 1;
548
549                    telemetry.berserker_rage = self.rage;
550                    gpu.telemetry = telemetry;
551
552                    state.window.request_redraw();
553                }
554                WindowEvent::CursorEntered { .. } => {
555                    log::info!("[Native] Cursor ENTERED window");
556                    if let Some(vdom) = &state.vdom {
557                        vdom.dispatch_event(cvkg_core::Event::PointerEnter);
558                    }
559                    state.window.request_redraw();
560                }
561                WindowEvent::CursorLeft { .. } => {
562                    log::info!("[Native] Cursor LEFT window");
563                    if let Some(vdom) = &state.vdom {
564                        vdom.dispatch_event(cvkg_core::Event::PointerLeave);
565                    }
566                    state.window.request_redraw();
567                }
568                WindowEvent::CursorMoved { position, .. } => {
569                    let scale = state.window.scale_factor();
570                    let logical = position.to_logical::<f32>(scale);
571                    let elapsed = state.last_redraw_start.elapsed().as_secs_f32().max(0.001);
572                    let dx = logical.x - state.cursor_pos[0];
573                    let dy = logical.y - state.cursor_pos[1];
574                    state.cursor_velocity = [dx / elapsed, dy / elapsed];
575                    state.cursor_pos = [logical.x, logical.y];
576                    if !state.is_dragging {
577                        let ddx = state.cursor_pos[0] - state.drag_start_pos[0];
578                        let ddy = state.cursor_pos[1] - state.drag_start_pos[1];
579                        let dist_sq = ddx * ddx + ddy * ddy;
580                        if dist_sq > state.drag_threshold * state.drag_threshold {
581                            state.is_dragging = true;
582                        }
583                    }
584                    state.needs_cursor_update = true;
585                    if state.frame_count == 0 {
586                        state.window.request_redraw();
587                    }
588                }
589                WindowEvent::MouseInput {
590                    state: mouse_state,
591                    button,
592                    ..
593                } => {
594                    log::info!(
595                        "[Native] MOUSE INPUT: {:?} button={:?} pos={:?}",
596                        mouse_state,
597                        button,
598                        state.cursor_pos
599                    );
600                    if let Some(touch_time) = state.last_touch_time {
601                        if touch_time.elapsed().as_millis() < 500 {
602                            log::info!("[Native] Ignoring MouseInput (synthesized from Touch)");
603                            return;
604                        }
605                    }
606                    if let Some(vdom) = &state.vdom {
607                        let btn_id = match button {
608                            winit::event::MouseButton::Left => 0,
609                            winit::event::MouseButton::Right => 2,
610                            winit::event::MouseButton::Middle => 1,
611                            winit::event::MouseButton::Back => 3,
612                            winit::event::MouseButton::Forward => 4,
613                            winit::event::MouseButton::Other(id) => id as u32,
614                        };
615
616                        match mouse_state {
617                            winit::event::ElementState::Pressed => {
618                                state.drag_start_pos = state.cursor_pos;
619                                state.is_dragging = false;
620                                state.drag_button = btn_id;
621                                state.active_pointer_pos = Some(state.cursor_pos);
622                                state.active_pointer_precision = 0.0;
623                                state.active_pointer_target = vdom
624                                    .hit_test(state.cursor_pos[0], state.cursor_pos[1], 0.0)
625                                    .map(|(id, _)| id);
626                                if let Some(target_id) = state.active_pointer_target {
627                                    if let Some(node) = vdom.nodes.get(&target_id) {
628                                        state.active_pointer_target_type =
629                                            Some(node.component_type.clone());
630                                        state.active_pointer_target_key = node.key.clone();
631                                    }
632                                }
633                                log::info!("[Native] Dispatching PointerDown to VDOM");
634                                vdom.dispatch_event(cvkg_core::Event::PointerDown {
635                                    x: state.cursor_pos[0],
636                                    y: state.cursor_pos[1],
637                                    button: btn_id,
638                                    proximity_field: 0.0,
639                                    tilt: None,
640                                    azimuth: None,
641                                    pressure: Some(1.0),
642                                    barrel_rotation: None,
643                                    pointer_precision: 0.0,
644                                });
645                            }
646                            winit::event::ElementState::Released => {
647                                log::info!("[Native] Dispatching PointerUp to VDOM");
648                                let fallback_target = state
649                                    .active_pointer_pos
650                                    .and_then(|pos| {
651                                        vdom.hit_test(
652                                            pos[0],
653                                            pos[1],
654                                            state.active_pointer_precision,
655                                        )
656                                        .map(|(id, _)| id)
657                                    })
658                                    .or_else(|| {
659                                        vdom.hit_test(
660                                            state.cursor_pos[0],
661                                            state.cursor_pos[1],
662                                            state.active_pointer_precision,
663                                        )
664                                        .map(|(id, _)| id)
665                                    });
666                                let target = state
667                                    .active_pointer_target
668                                    .filter(|target| {
669                                        if state.active_pointer_target_key.is_none() {
670                                            log::debug!("[Native] Target verification: key is None, skipping cache");
671                                            return false;
672                                        }
673                                        let verified = vdom.nodes.get(target).map_or(false, |node| {
674                                            let type_match = Some(&node.component_type) == state.active_pointer_target_type.as_ref();
675                                            let key_match = node.key == state.active_pointer_target_key;
676                                            log::debug!("[Native] Target verify: id={:?} type={} key={:?} type_match={} key_match={}",
677                                                target, node.component_type, node.key, type_match, key_match);
678                                            type_match && key_match
679                                        });
680                                        if !verified {
681                                            log::debug!("[Native] Target verification failed for {:?}, using fallback", target);
682                                        }
683                                        verified
684                                    })
685                                    .or(fallback_target);
686                                let pointer_up = cvkg_core::Event::PointerUp {
687                                    x: state.cursor_pos[0],
688                                    y: state.cursor_pos[1],
689                                    button: btn_id,
690                                    tilt: None,
691                                    azimuth: None,
692                                    pressure: Some(0.0),
693                                    barrel_rotation: None,
694                                    pointer_precision: 0.0,
695                                };
696                                let pointer_click = cvkg_core::Event::PointerClick {
697                                    x: state.cursor_pos[0],
698                                    y: state.cursor_pos[1],
699                                    button: btn_id,
700                                    tilt: None,
701                                    azimuth: None,
702                                    pressure: Some(0.0),
703                                    barrel_rotation: None,
704                                    pointer_precision: 0.0,
705                                };
706                                if let Some(target) = target {
707                                    vdom.dispatch_event_to_target(target, pointer_up);
708                                } else {
709                                    vdom.dispatch_event(pointer_up);
710                                }
711                                if !state.is_dragging {
712                                    if let Some(target) = target {
713                                        log::info!(
714                                            "[Native] Dispatching PointerClick to VDOM (target={:?})",
715                                            target
716                                        );
717                                        vdom.dispatch_event_to_target(target, pointer_click);
718                                    } else {
719                                        log::info!(
720                                            "[Native] Dispatching PointerClick to VDOM (no target, bubbling)"
721                                        );
722                                        vdom.dispatch_event(pointer_click);
723                                    }
724                                } else {
725                                    log::info!("[Native] Skipping PointerClick (is_dragging=true)");
726                                }
727                                state.is_dragging = false;
728                                state.active_pointer_target = None;
729                                state.active_pointer_target_type = None;
730                                state.active_pointer_target_key = None;
731                                state.active_pointer_pos = None;
732                            }
733                        }
734                        state.window.request_redraw();
735                    } else {
736                        log::warn!("[Native] Mouse input received but state.vdom is None!");
737                    }
738                }
739                WindowEvent::MouseWheel { delta, .. } => {
740                    if let Some(vdom) = &state.vdom {
741                        let (dx, dy) = match delta {
742                            winit::event::MouseScrollDelta::LineDelta(x, y) => (x * 10.0, y * 10.0),
743                            winit::event::MouseScrollDelta::PixelDelta(pos) => {
744                                (pos.x as f32, pos.y as f32)
745                            }
746                        };
747                        vdom.dispatch_event(cvkg_core::Event::PointerWheel {
748                            x: state.cursor_pos[0],
749                            y: state.cursor_pos[1],
750                            delta_x: dx,
751                            delta_y: dy,
752                            pointer_precision: 0.0,
753                        });
754                        state.window.request_redraw();
755                    }
756                }
757                WindowEvent::Touch(touch) => {
758                    state.last_touch_time = Some(std::time::Instant::now());
759                    if let Some(vdom) = &state.vdom {
760                        let scale = state.window.scale_factor();
761                        let logical = touch.location.to_logical::<f32>(scale);
762                        let x = logical.x;
763                        let y = logical.y;
764                        let touch_btn = 0;
765
766                        match touch.phase {
767                            winit::event::TouchPhase::Started => {
768                                log::info!("[Native] Dispatching PointerDown (Touch) to VDOM");
769                                state.drag_start_pos = [x, y];
770                                state.is_dragging = false;
771                                state.drag_button = touch_btn;
772                                state.active_pointer_pos = Some([x, y]);
773                                state.active_pointer_precision = 150.0;
774                                state.active_pointer_target =
775                                    vdom.hit_test(x, y, 150.0).map(|(id, _)| id);
776                                if let Some(target_id) = state.active_pointer_target {
777                                    if let Some(node) = vdom.nodes.get(&target_id) {
778                                        state.active_pointer_target_type =
779                                            Some(node.component_type.clone());
780                                        state.active_pointer_target_key = node.key.clone();
781                                    }
782                                }
783                                vdom.dispatch_event(cvkg_core::Event::PointerDown {
784                                    x,
785                                    y,
786                                    button: touch_btn,
787                                    proximity_field: 0.0,
788                                    tilt: None,
789                                    azimuth: None,
790                                    pressure: Some(
791                                        touch.force.map(|f| f.normalized() as f32).unwrap_or(0.5),
792                                    ),
793                                    barrel_rotation: None,
794                                    pointer_precision: 150.0,
795                                });
796                            }
797                            winit::event::TouchPhase::Moved => {
798                                if !state.is_dragging {
799                                    let ddx = x - state.drag_start_pos[0];
800                                    let ddy = y - state.drag_start_pos[1];
801                                    let dist_sq = ddx * ddx + ddy * ddy;
802                                    if dist_sq > state.drag_threshold * state.drag_threshold {
803                                        state.is_dragging = true;
804                                    }
805                                }
806                                vdom.dispatch_event(cvkg_core::Event::PointerMove {
807                                    x,
808                                    y,
809                                    proximity_field: 0.0,
810                                    tilt: None,
811                                    azimuth: None,
812                                    pressure: Some(
813                                        touch.force.map(|f| f.normalized() as f32).unwrap_or(0.5),
814                                    ),
815                                    barrel_rotation: None,
816                                    pointer_precision: 150.0,
817                                });
818                            }
819                            winit::event::TouchPhase::Ended => {
820                                let fallback_target = state
821                                    .active_pointer_pos
822                                    .and_then(|pos| {
823                                        vdom.hit_test(
824                                            pos[0],
825                                            pos[1],
826                                            state.active_pointer_precision,
827                                        )
828                                        .map(|(id, _)| id)
829                                    })
830                                    .or_else(|| {
831                                        vdom.hit_test(x, y, state.active_pointer_precision)
832                                            .map(|(id, _)| id)
833                                    });
834                                let target = state
835                                    .active_pointer_target
836                                    .filter(|target| {
837                                        vdom.nodes.get(target).map_or(false, |node| {
838                                            Some(&node.component_type)
839                                                == state.active_pointer_target_type.as_ref()
840                                                && node.key == state.active_pointer_target_key
841                                        })
842                                    })
843                                    .or(fallback_target);
844                                let pointer_up = cvkg_core::Event::PointerUp {
845                                    x,
846                                    y,
847                                    button: touch_btn,
848                                    tilt: None,
849                                    azimuth: None,
850                                    pressure: Some(0.0),
851                                    barrel_rotation: None,
852                                    pointer_precision: 150.0,
853                                };
854                                let pointer_click = cvkg_core::Event::PointerClick {
855                                    x,
856                                    y,
857                                    button: touch_btn,
858                                    tilt: None,
859                                    azimuth: None,
860                                    pressure: Some(0.0),
861                                    barrel_rotation: None,
862                                    pointer_precision: 150.0,
863                                };
864                                if let Some(target) = target {
865                                    vdom.dispatch_event_to_target(target, pointer_up);
866                                } else {
867                                    vdom.dispatch_event(pointer_up);
868                                }
869                                if !state.is_dragging {
870                                    if let Some(target) = target {
871                                        log::info!(
872                                            "[Native] Dispatching PointerClick to VDOM (target={:?})",
873                                            target
874                                        );
875                                        vdom.dispatch_event_to_target(target, pointer_click);
876                                    } else {
877                                        log::info!(
878                                            "[Native] Dispatching PointerClick to VDOM (no target, bubbling)"
879                                        );
880                                        vdom.dispatch_event(pointer_click);
881                                    }
882                                } else {
883                                    log::info!("[Native] Skipping PointerClick (is_dragging=true)");
884                                }
885                                state.is_dragging = false;
886                                state.active_pointer_target = None;
887                                state.active_pointer_target_type = None;
888                                state.active_pointer_target_key = None;
889                                state.active_pointer_pos = None;
890                            }
891                            winit::event::TouchPhase::Cancelled => {
892                                vdom.dispatch_event(cvkg_core::Event::PointerUp {
893                                    x,
894                                    y,
895                                    button: touch_btn,
896                                    tilt: None,
897                                    azimuth: None,
898                                    pressure: Some(0.0),
899                                    barrel_rotation: None,
900                                    pointer_precision: 150.0,
901                                });
902                                state.active_pointer_target = None;
903                                state.active_pointer_pos = None;
904                            }
905                        }
906                        state.window.request_redraw();
907                    }
908                }
909                WindowEvent::PinchGesture { delta, .. } => {
910                    if let Some(vdom) = &state.vdom {
911                        let scale = 1.0 + delta as f32;
912                        let velocity = delta as f32;
913                        vdom.dispatch_event(cvkg_core::Event::GesturePinch {
914                            center: state.cursor_pos,
915                            scale,
916                            velocity,
917                            phase: cvkg_core::TouchPhase::Moved,
918                        });
919                    }
920                    if let Some(audio) = &self.audio_engine {
921                        audio.play_sound("nav_tick", 0.3);
922                    }
923                    self.haptic_engine
924                        .visual_tick((delta.abs() as f32 * 5.0).min(1.0));
925                    state.window.request_redraw();
926                }
927                WindowEvent::RotationGesture { delta, .. } => {
928                    if let Some(vdom) = &state.vdom {
929                        let angle = delta;
930                        vdom.dispatch_event(cvkg_core::Event::GestureSwipe {
931                            direction: [angle.cos(), angle.sin()],
932                            velocity: delta.abs(),
933                            phase: cvkg_core::TouchPhase::Moved,
934                        });
935                    }
936                    state.window.request_redraw();
937                }
938                WindowEvent::KeyboardInput { event, .. } => {
939                    if event.state == winit::event::ElementState::Pressed {
940                        if let winit::keyboard::PhysicalKey::Code(code) = event.physical_key {
941                            let is_cmd = if cfg!(target_os = "macos") {
942                                self.modifiers.super_key()
943                            } else {
944                                self.modifiers.control_key()
945                            };
946                            let is_shift = self.modifiers.shift_key();
947
948                            if is_cmd {
949                                match code {
950                                    winit::keyboard::KeyCode::KeyZ => {
951                                        if is_shift {
952                                            log::info!("[Native] Shortcut: Redo (Cmd+Shift+Z)");
953                                            let mut redo_action = None;
954                                            update_system_state(|s| {
955                                                let mut s = s.clone();
956                                                redo_action = s.undo_manager.redo();
957                                                s
958                                            });
959                                            if let Some(action) = redo_action {
960                                                action();
961                                            }
962                                            state.window.request_redraw();
963                                        } else {
964                                            log::info!("[Native] Shortcut: Undo (Cmd+Z)");
965                                            let mut undo_action = None;
966                                            update_system_state(|s| {
967                                                let mut s = s.clone();
968                                                undo_action = s.undo_manager.undo();
969                                                s
970                                            });
971                                            if let Some(action) = undo_action {
972                                                action();
973                                            }
974                                            state.window.request_redraw();
975                                        }
976                                    }
977                                    winit::keyboard::KeyCode::KeyY
978                                        if !cfg!(target_os = "macos") =>
979                                    {
980                                        log::info!("[Native] Shortcut: Redo (Ctrl+Y)");
981                                        let mut redo_action = None;
982                                        update_system_state(|s| {
983                                            let mut s = s.clone();
984                                            redo_action = s.undo_manager.redo();
985                                            s
986                                        });
987                                        if let Some(action) = redo_action {
988                                            action();
989                                        }
990                                        state.window.request_redraw();
991                                    }
992                                    winit::keyboard::KeyCode::KeyN => {
993                                        log::info!("[Native] Shortcut: New Window (Cmd+N)");
994                                        create_new_window = true;
995                                    }
996                                    winit::keyboard::KeyCode::KeyO => {
997                                        log::info!("[Native] Shortcut: Open File (Cmd+O)");
998                                        if let Some(vdom) = &state.vdom {
999                                            vdom.dispatch_event(cvkg_core::Event::KeyDown {
1000                                                key: "cmd+o".to_string(),
1001                                                modifiers: cvkg_core::KeyModifiers::default(),
1002                                            });
1003                                        }
1004                                        state.window.request_redraw();
1005                                    }
1006                                    winit::keyboard::KeyCode::KeyS => {
1007                                        log::info!("[Native] Shortcut: Save (Cmd+S)");
1008                                        if let Some(vdom) = &state.vdom {
1009                                            vdom.dispatch_event(cvkg_core::Event::KeyDown {
1010                                                key: "cmd+s".to_string(),
1011                                                modifiers: cvkg_core::KeyModifiers::default(),
1012                                            });
1013                                        }
1014                                        state.window.request_redraw();
1015                                    }
1016                                    winit::keyboard::KeyCode::KeyW => {
1017                                        log::info!("[Native] Shortcut: Close Window (Cmd+W)");
1018                                        close_window = true;
1019                                    }
1020                                    winit::keyboard::KeyCode::KeyQ => {
1021                                        log::info!("[Native] Shortcut: Quit (Cmd+Q)");
1022                                        quit_all = true;
1023                                    }
1024                                    winit::keyboard::KeyCode::KeyC => {
1025                                        log::info!("[Native] Shortcut: Copy (Cmd+C)");
1026                                        if let Some(vdom) = &state.vdom {
1027                                            vdom.dispatch_event(cvkg_core::Event::Copy);
1028                                        }
1029                                        state.window.request_redraw();
1030                                    }
1031                                    winit::keyboard::KeyCode::KeyV => {
1032                                        log::info!("[Native] Shortcut: Paste (Cmd+V)");
1033                                        let text = arboard::Clipboard::new()
1034                                            .ok()
1035                                            .and_then(|mut cb| cb.get_text().ok())
1036                                            .unwrap_or_default();
1037                                        if let Some(vdom) = &state.vdom {
1038                                            vdom.dispatch_event(cvkg_core::Event::Paste(text));
1039                                        }
1040                                        state.window.request_redraw();
1041                                    }
1042                                    winit::keyboard::KeyCode::KeyX => {
1043                                        log::info!("[Native] Shortcut: Cut (Cmd+X)");
1044                                        if let Some(vdom) = &state.vdom {
1045                                            vdom.dispatch_event(cvkg_core::Event::Cut);
1046                                        }
1047                                        state.window.request_redraw();
1048                                    }
1049                                    winit::keyboard::KeyCode::F11 => {
1050                                        let is_fullscreen = state.window.fullscreen().is_some();
1051                                        if is_fullscreen {
1052                                            state.window.set_fullscreen(None);
1053                                            log::info!("[Native] Fullscreen OFF");
1054                                        } else {
1055                                            if let Some(monitor) = state.window.current_monitor() {
1056                                                if let Some(mode) = monitor.video_modes().next() {
1057                                                    let w = mode.size().width;
1058                                                    let h = mode.size().height;
1059                                                    let rr = mode.refresh_rate_millihertz();
1060                                                    state.window.set_fullscreen(Some(
1061                                                        winit::window::Fullscreen::Exclusive(mode),
1062                                                    ));
1063                                                    log::info!(
1064                                                        "[Native] Fullscreen ON (exclusive: {}x{}@{:?}Hz)",
1065                                                        w,
1066                                                        h,
1067                                                        rr
1068                                                    );
1069                                                }
1070                                            } else {
1071                                                state.window.set_fullscreen(Some(
1072                                                    winit::window::Fullscreen::Borderless(None),
1073                                                ));
1074                                                log::info!("[Native] Fullscreen ON (borderless)");
1075                                            }
1076                                        }
1077                                        state.window.request_redraw();
1078                                    }
1079                                    winit::keyboard::KeyCode::KeyA => {
1080                                        log::info!("[Native] Shortcut: Select All (Cmd+A)");
1081                                        if let Some(vdom) = &state.vdom {
1082                                            vdom.dispatch_event(cvkg_core::Event::KeyDown {
1083                                                key: "cmd+a".to_string(),
1084                                                modifiers: cvkg_core::KeyModifiers::default(),
1085                                            });
1086                                        }
1087                                        state.window.request_redraw();
1088                                    }
1089                                    winit::keyboard::KeyCode::KeyF => {
1090                                        log::info!("[Native] Shortcut: Find (Cmd+F)");
1091                                        if let Some(vdom) = &state.vdom {
1092                                            vdom.dispatch_event(cvkg_core::Event::KeyDown {
1093                                                key: "cmd+f".to_string(),
1094                                                modifiers: cvkg_core::KeyModifiers::default(),
1095                                            });
1096                                        }
1097                                        state.window.request_redraw();
1098                                    }
1099                                    winit::keyboard::KeyCode::Tab => {
1100                                        if is_shift {
1101                                            if let Some(id) = state.focus_manager.focus_prev() {
1102                                                if let Ok(node_id) = id.as_str().parse::<u64>() {
1103                                                    state.focused_node_id =
1104                                                        Some(cvkg_core::KvasirId(node_id));
1105                                                    log::info!(
1106                                                        "[Native] Focus previous: {:?}",
1107                                                        node_id
1108                                                    );
1109                                                }
1110                                            }
1111                                        } else {
1112                                            if let Some(id) = state.focus_manager.focus_next() {
1113                                                if let Ok(node_id) = id.as_str().parse::<u64>() {
1114                                                    state.focused_node_id =
1115                                                        Some(cvkg_core::KvasirId(node_id));
1116                                                    log::info!(
1117                                                        "[Native] Focus next: {:?}",
1118                                                        node_id
1119                                                    );
1120                                                }
1121                                            }
1122                                        }
1123                                        state.window.request_redraw();
1124                                    }
1125                                    _ => {}
1126                                }
1127                            }
1128                        }
1129                    }
1130
1131                    if let Some(vdom) = &state.vdom
1132                        && let Some(cvkg_event) = convert_keyboard_event(event, &self.modifiers)
1133                    {
1134                        vdom.dispatch_event(cvkg_event);
1135                        state.window.request_redraw();
1136                    }
1137                }
1138                WindowEvent::Ime(ime_event) => {
1139                    if let Some(vdom) = &state.vdom
1140                        && let Some(cvkg_event) = convert_ime_event(ime_event)
1141                    {
1142                        vdom.dispatch_event(cvkg_event);
1143                        state.window.request_redraw();
1144                    }
1145                }
1146                WindowEvent::ModifiersChanged(new_modifiers) => {
1147                    self.modifiers = new_modifiers.state();
1148                    let shift = self.modifiers.shift_key();
1149                    let ctrl = self.modifiers.control_key();
1150                    let alt = self.modifiers.alt_key();
1151                    let logo = self.modifiers.super_key();
1152                    update_system_state(|st| {
1153                        let mut new_st = st.clone();
1154                        new_st.modifiers_shift = shift;
1155                        new_st.modifiers_ctrl = ctrl;
1156                        new_st.modifiers_alt = alt;
1157                        new_st.modifiers_logo = logo;
1158                        new_st
1159                    });
1160                }
1161                WindowEvent::ScaleFactorChanged { .. } => {
1162                    if let Some(ctx) = self.window_manager.windows.get(&id) {
1163                        ctx.window.request_redraw();
1164                    }
1165                }
1166                _ => {}
1167            }
1168        }
1169
1170        if close_window {
1171            self.window_manager.close_window(id);
1172        }
1173        if quit_all {
1174            for wid in self.window_manager.window_order().to_vec() {
1175                self.window_manager.close_window(wid);
1176            }
1177        }
1178        if self.window_manager.windows.is_empty() {
1179            event_loop.exit();
1180        }
1181        if bring_to_front {
1182            self.window_manager.bring_to_front(id);
1183        }
1184        if create_new_window {
1185            self.window_manager.create_window(
1186                event_loop,
1187                &self.gpu,
1188                self.proxy.clone(),
1189                WindowConfig {
1190                    title: "New CVKG Window".to_string(),
1191                    size: (800.0, 600.0),
1192                    ..Default::default()
1193                },
1194                false,
1195                &self.view,
1196            );
1197        }
1198    }
1199
1200    fn user_event(&mut self, event_loop: &ActiveEventLoop, event: AppEvent) {
1201        match event {
1202            AppEvent::AccessibilityAction(request) => {
1203                let node_id = cvkg_core::KvasirId(request.target_node.0);
1204                let target_state = self.window_manager.windows.values_mut().find(|s| {
1205                    s.vdom
1206                        .as_ref()
1207                        .map_or(false, |v| v.nodes.contains_key(&node_id))
1208                });
1209
1210                if let Some(state) = target_state
1211                    && let Some(vdom) = &state.vdom
1212                    && let Some(node) = vdom.nodes.get(&node_id)
1213                    && request.action == accesskit::Action::Click
1214                {
1215                    let event = cvkg_core::Event::PointerClick {
1216                        x: node.layout.x + node.layout.width / 2.0,
1217                        y: node.layout.y + node.layout.height / 2.0,
1218                        button: 0,
1219                        tilt: None,
1220                        azimuth: None,
1221                        pressure: Some(1.0),
1222                        barrel_rotation: None,
1223                        pointer_precision: 0.0,
1224                    };
1225                    vdom.dispatch_event(event);
1226                }
1227            }
1228            AppEvent::AccessibilityInitialTreeRequested(winit_id) => {
1229                if let Some(state) = self.window_manager.windows.get_mut(&winit_id) {
1230                    if let Some(vdom) = &state.vdom {
1231                        let root_id = vdom.root.map(|id| id.0).unwrap_or(1);
1232                        let mut nodes = Vec::new();
1233                        for (id, node) in &vdom.nodes {
1234                            nodes.push((accesskit::NodeId(id.0), node.to_accesskit_node()));
1235                        }
1236                        let tree = accesskit::Tree::new(accesskit::NodeId(root_id));
1237                        if let Some(adapter) = &mut state.accesskit_adapter {
1238                            adapter.update_if_active(|| accesskit::TreeUpdate {
1239                                nodes,
1240                                tree: Some(tree),
1241                                focus: accesskit::NodeId(root_id),
1242                                tree_id: accesskit::TreeId::ROOT,
1243                            });
1244                        }
1245                    }
1246                }
1247            }
1248            AppEvent::CloseWindow(winit_id) => {
1249                self.window_manager.close_window(winit_id);
1250                if self.window_manager.windows.is_empty() {
1251                    event_loop.exit();
1252                }
1253            }
1254            AppEvent::SetTitle(winit_id, title) => {
1255                if let Some(data) = self.window_manager.windows.get(&winit_id) {
1256                    data.window.set_title(&title);
1257                }
1258            }
1259            AppEvent::SetSize(winit_id, width, height) => {
1260                if let Some(data) = self.window_manager.windows.get(&winit_id) {
1261                    let _ = data
1262                        .window
1263                        .request_inner_size(winit::dpi::LogicalSize::new(width, height));
1264                }
1265            }
1266            AppEvent::SetVisible(winit_id, visible) => {
1267                if let Some(data) = self.window_manager.windows.get(&winit_id) {
1268                    data.window.set_visible(visible);
1269                }
1270            }
1271            AppEvent::BringToFront(winit_id) => {
1272                self.window_manager.bring_to_front(winit_id);
1273            }
1274        }
1275    }
1276
1277    fn about_to_wait(&mut self, event_loop: &ActiveEventLoop) {
1278        self.rage = (self.rage - 0.02).max(0.0);
1279
1280        let now = std::time::Instant::now();
1281        let target_interval = std::time::Duration::from_micros(8_333);
1282
1283        if now.duration_since(self.last_frame_time) >= target_interval {
1284            self.last_frame_time = now;
1285            let needs_redraw = self.view.changed();
1286            if needs_redraw {
1287                for window_state in self.window_manager.windows.values() {
1288                    window_state.window.request_redraw();
1289                }
1290            }
1291            event_loop.set_control_flow(ControlFlow::WaitUntil(now + target_interval));
1292        } else {
1293            event_loop.set_control_flow(ControlFlow::WaitUntil(
1294                self.last_frame_time + target_interval,
1295            ));
1296        }
1297    }
1298}
1299
1300pub struct ShieldWall {
1301    pub(crate) proxy: EventLoopProxy<AppEvent>,
1302}
1303
1304impl accesskit::ActionHandler for ShieldWall {
1305    fn do_action(&mut self, request: accesskit::ActionRequest) {
1306        let _ = self
1307            .proxy
1308            .send_event(AppEvent::AccessibilityAction(request));
1309    }
1310}
1311
1312impl accesskit::ActivationHandler for ShieldWall {
1313    fn request_initial_tree(&mut self) -> Option<accesskit::TreeUpdate> {
1314        let mut root = accesskit::Node::new(accesskit::Role::Window);
1315        root.set_label("CVKG Application");
1316
1317        let root_id = accesskit::NodeId(1);
1318        Some(accesskit::TreeUpdate {
1319            nodes: vec![(root_id, root)],
1320            tree: Some(accesskit::Tree::new(root_id)),
1321            focus: root_id,
1322            tree_id: accesskit::TreeId::ROOT,
1323        })
1324    }
1325}
1326
1327impl accesskit::DeactivationHandler for ShieldWall {
1328    fn deactivate_accessibility(&mut self) {}
1329}