1
//! Unified Event Determination
2
//!
3
//! This module provides the single source of truth for what
4
//! events occurred in a frame. It combines window state changes
5
//! with events from all managers (scroll, text input, etc.).
6

            
7
use azul_core::{
8
    dom::{DomId, DomNodeId},
9
    events::{
10
        deduplicate_synthetic_events, EventData, EventProvider, EventSource, EventType,
11
        KeyModifiers, KeyboardEventData, MouseButton, MouseEventData, ScrollDeltaMode,
12
        ScrollEventData, SyntheticEvent, WindowEventData,
13
    },
14
    geom::{LogicalPosition, LogicalRect},
15
    id::NodeId,
16
    styled_dom::NodeHierarchyItemId,
17
    task::{Instant, SystemTick},
18
    window::{CursorPosition, WindowPosition},
19
};
20

            
21
use std::collections::BTreeSet;
22

            
23
use crate::window_state::FullWindowState;
24

            
25
/// Unified event determination from all sources.
26
///
27
/// This is the **single source of truth** for what events occurred in a frame.
28
///
29
/// It combines:
30
///
31
/// 1. Window state changes (resize, move, theme, etc.)
32
/// 2. Manager-reported events (scroll, text input, focus, hover)
33
/// 3. Deduplicates by node + event type
34
///
35
/// ## Architecture
36
///
37
/// ```text
38
/// Platform Input
39
///     ↓
40
/// Update Window State + Managers (record_sample, record_input, etc.)
41
///     ↓
42
/// determine_events_from_managers() ← Query all managers (immutable)
43
///     ↓
44
/// Vec<SyntheticEvent> (deduplicated)
45
///     ↓
46
/// dispatch_events() ← Route to callbacks
47
///     ↓
48
/// Invoke callbacks
49
/// ```
50
///
51
/// ## Arguments
52
///
53
/// All arguments are **immutable** references - no state is modified here.
54
/// State changes happen earlier via `record_sample()`, `record_input()`, etc.
55
///
56
/// - `current_state` - Current window state (after platform updates)
57
/// - `previous_state` - Window state from previous frame
58
/// - `managers` - All managers that can provide events
59
/// - `timestamp` - Current time for event timestamps
60
///
61
/// ## Returns
62
///
63
/// - Vector of SyntheticEvents, deduplicated and ready for dispatch.
64
pub fn determine_events_from_managers<'a>(
65
    current_state: &FullWindowState,
66
    previous_state: &FullWindowState,
67
    managers: &[&'a dyn EventProvider],
68
    timestamp: Instant,
69
) -> Vec<SyntheticEvent> {
70
    let mut events = Vec::new();
71

            
72
    // 1. Detect window state changes (simple diffing)
73
    events.extend(detect_window_state_events(
74
        current_state,
75
        previous_state,
76
        timestamp.clone(),
77
    ));
78

            
79
    // 2. Query all managers for their pending events
80
    for manager in managers {
81
        events.extend(manager.get_pending_events(timestamp.clone()));
82
    }
83

            
84
    // 3. Deduplicate by (node, event_type)
85
    deduplicate_synthetic_events(events)
86
}
87

            
88
/// Detect window-level events by comparing states.
89
///
90
/// This is a simple sub-function that only handles window-level changes:
91
///
92
/// - Window resized
93
/// - Window moved
94
/// - Theme changed
95
/// - Mouse entered/left window
96
/// - Window focus changed
97
///
98
/// Node-level events (hover, focus, scroll, text) come from managers.
99
fn detect_window_state_events(
100
    current: &FullWindowState,
101
    previous: &FullWindowState,
102
    timestamp: Instant,
103
) -> Vec<SyntheticEvent> {
104
    let mut events = Vec::new();
105

            
106
    // Window resized
107
    if current.size != previous.size {
108
        events.push(SyntheticEvent::new(
109
            EventType::WindowResize,
110
            EventSource::User,
111
            DomNodeId {
112
                dom: DomId { inner: 0 },
113
                node: NodeHierarchyItemId::from_crate_internal(Some(NodeId::ZERO)),
114
            },
115
            timestamp.clone(),
116
            EventData::Window(WindowEventData {
117
                size: Some(LogicalRect {
118
                    origin: LogicalPosition { x: 0.0, y: 0.0 },
119
                    size: current.size.dimensions.clone(),
120
                }),
121
                position: None,
122
            }),
123
        ));
124
    }
125

            
126
    // Window moved
127
    if current.position != previous.position {
128
        let position = match current.position {
129
            WindowPosition::Initialized(phys_pos)
130
            | WindowPosition::RelativeToParentWindow(phys_pos) => Some(LogicalPosition {
131
                x: phys_pos.x as f32,
132
                y: phys_pos.y as f32,
133
            }),
134
            WindowPosition::Uninitialized => None,
135
        };
136

            
137
        if let Some(pos) = position {
138
            events.push(SyntheticEvent::new(
139
                EventType::WindowMove,
140
                EventSource::User,
141
                DomNodeId {
142
                    dom: DomId { inner: 0 },
143
                    node: NodeHierarchyItemId::from_crate_internal(Some(NodeId::ZERO)),
144
                },
145
                timestamp.clone(),
146
                EventData::Window(WindowEventData {
147
                    size: None,
148
                    position: Some(pos),
149
                }),
150
            ));
151
        }
152
    }
153

            
154
    // Theme changed
155
    if current.theme != previous.theme {
156
        events.push(SyntheticEvent::new(
157
            EventType::ThemeChange,
158
            EventSource::User,
159
            DomNodeId {
160
                dom: DomId { inner: 0 },
161
                node: NodeHierarchyItemId::from_crate_internal(Some(NodeId::ZERO)),
162
            },
163
            timestamp.clone(),
164
            EventData::None,
165
        ));
166
    }
167

            
168
    // Mouse entered window
169
    let prev_mouse_in_window = matches!(
170
        previous.mouse_state.cursor_position,
171
        CursorPosition::InWindow(_)
172
    );
173
    let curr_mouse_in_window = matches!(
174
        current.mouse_state.cursor_position,
175
        CursorPosition::InWindow(_)
176
    );
177

            
178
    if curr_mouse_in_window && !prev_mouse_in_window {
179
        events.push(SyntheticEvent::new(
180
            EventType::MouseEnter,
181
            EventSource::User,
182
            DomNodeId {
183
                dom: DomId { inner: 0 },
184
                node: NodeHierarchyItemId::from_crate_internal(Some(NodeId::ZERO)),
185
            },
186
            timestamp.clone(),
187
            EventData::None,
188
        ));
189
    }
190

            
191
    // Mouse left window
192
    if !curr_mouse_in_window && prev_mouse_in_window {
193
        events.push(SyntheticEvent::new(
194
            EventType::MouseLeave,
195
            EventSource::User,
196
            DomNodeId {
197
                dom: DomId { inner: 0 },
198
                node: NodeHierarchyItemId::from_crate_internal(Some(NodeId::ZERO)),
199
            },
200
            timestamp.clone(),
201
            EventData::None,
202
        ));
203
    }
204

            
205
    events
206
}
207

            
208
/// Get all hovered node IDs from the hover manager for a given frame.
209
///
210
/// frame_index 0 = current frame, 1 = previous frame, etc.
211
/// Returns a BTreeSet of all NodeIds that are hovered (the full hover chain).
212
22
fn get_all_hovered_nodes(
213
22
    hover_manager: &crate::managers::hover::HoverManager,
214
22
    frame_index: usize,
215
22
) -> BTreeSet<NodeId> {
216
    use crate::managers::hover::InputPointId;
217
22
    let dom_id = DomId { inner: 0 };
218
22
    hover_manager
219
22
        .get_frame(&InputPointId::Mouse, frame_index)
220
22
        .and_then(|ht| ht.hovered_nodes.get(&dom_id))
221
22
        .map(|ht| ht.regular_hit_test_nodes.keys().copied().collect())
222
22
        .unwrap_or_default()
223
22
}
224

            
225
/// Comprehensive event determination including mouse, keyboard, and gesture events.
226
///
227
/// This is the primary event determination function.
228
/// It generates SyntheticEvents for all event types including:
229
///
230
/// - Mouse button events (down/up for left/right/middle)
231
/// - Mouse movement (MouseOver)
232
/// - Keyboard events (VirtualKeyDown/Up)
233
/// - Window state changes (resize, move, theme, focus)
234
/// - Gesture events (DragStart, Drag, DragEnd, DoubleClick, LongPress, Swipe, Pinch, Rotate, Pen)
235
/// - File drop events (HoveredFile, DroppedFile)
236
///
237
/// ## Arguments
238
///
239
/// * `current_state` - Current window state
240
/// * `previous_state` - Previous window state
241
/// * `hover_manager` - For hover/mouse enter/leave detection
242
/// * `focus_manager` - For focus event detection
243
/// * `file_drop_manager` - For file drop detection
244
/// * `gesture_manager` - Optional gesture detection
245
/// * `managers` - Additional managers (scroll, text, etc.) that implement EventProvider
246
/// * `timestamp` - Current time
247
///
248
/// ## Returns
249
///
250
/// - Deduplicated vector of SyntheticEvents ready for dispatch
251
11
pub fn determine_all_events(
252
11
    current_state: &FullWindowState,
253
11
    previous_state: &FullWindowState,
254
11
    hover_manager: &crate::managers::hover::HoverManager,
255
11
    focus_manager: &crate::managers::focus_cursor::FocusManager,
256
11
    file_drop_manager: &crate::managers::file_drop::FileDropManager,
257
11
    gesture_manager: Option<&crate::managers::gesture::GestureAndDragManager>,
258
11
    managers: &[&dyn EventProvider],
259
11
    wheel_delta: Option<LogicalPosition>,
260
11
    timestamp: Instant,
261
11
) -> Vec<SyntheticEvent> {
262
11
    let mut events = Vec::new();
263

            
264
    // Get root node for window-level events
265
11
    let root_node = DomNodeId {
266
11
        dom: DomId { inner: 0 },
267
11
        node: NodeHierarchyItemId::from_crate_internal(Some(NodeId::ZERO)),
268
11
    };
269

            
270
    // Helper: get cursor position
271
11
    let cursor_pos = current_state
272
11
        .mouse_state
273
11
        .cursor_position
274
11
        .get_position()
275
11
        .unwrap_or(LogicalPosition { x: 0.0, y: 0.0 });
276

            
277
    // Helper: build key modifiers from keyboard state
278
11
    let modifiers = KeyModifiers {
279
11
        shift: current_state.keyboard_state.shift_down(),
280
11
        ctrl: current_state.keyboard_state.ctrl_down(),
281
11
        alt: current_state.keyboard_state.alt_down(),
282
11
        meta: current_state.keyboard_state.super_down(),
283
11
    };
284

            
285
    // Helper: compute mouse buttons bitmask
286
11
    let buttons: u8 = (if current_state.mouse_state.left_down { 1 } else { 0 })
287
11
        | (if current_state.mouse_state.right_down { 2 } else { 0 })
288
11
        | (if current_state.mouse_state.middle_down { 4 } else { 0 });
289

            
290
    // Helper: get deepest hovered node as the event target for mouse events.
291
    // Multi-DOM aware: targets VirtualView / iframe child DOMs (higher DomId,
292
    // composited on top) when the cursor is over them, not just the root DOM.
293
    // For single-DOM apps only DomId 0 is ever hit, so this is unchanged.
294
11
    let mouse_target = hover_manager
295
11
        .current_hover_node_full()
296
11
        .unwrap_or(root_node.clone());
297

            
298
    // Helper: build MouseEventData for a specific button
299
11
    let make_mouse_data = |button: MouseButton| -> EventData {
300
3
        EventData::Mouse(MouseEventData {
301
3
            position: cursor_pos,
302
3
            button,
303
3
            buttons,
304
3
            modifiers,
305
3
        })
306
3
    };
307

            
308
    // ========================================================================
309
    // Mouse Button Events (with proper EventData::Mouse and per-button types)
310
    // ========================================================================
311

            
312
11
    let current_mouse_down = current_state.mouse_state.mouse_down();
313
11
    let previous_mouse_down = previous_state.mouse_state.mouse_down();
314

            
315
    // Left mouse button down
316
11
    if current_state.mouse_state.left_down && !previous_state.mouse_state.left_down {
317
1
        events.push(SyntheticEvent::new(
318
1
            EventType::MouseDown,
319
1
            EventSource::User,
320
1
            mouse_target.clone(),
321
1
            timestamp.clone(),
322
1
            make_mouse_data(MouseButton::Left),
323
1
        ));
324
10
    }
325
    // Left mouse button up
326
11
    if !current_state.mouse_state.left_down && previous_state.mouse_state.left_down {
327
1
        events.push(SyntheticEvent::new(
328
1
            EventType::MouseUp,
329
1
            EventSource::User,
330
1
            mouse_target.clone(),
331
1
            timestamp.clone(),
332
1
            make_mouse_data(MouseButton::Left),
333
1
        ));
334
10
    }
335

            
336
    // Right mouse button down
337
11
    if current_state.mouse_state.right_down && !previous_state.mouse_state.right_down {
338
        events.push(SyntheticEvent::new(
339
            EventType::MouseDown,
340
            EventSource::User,
341
            mouse_target.clone(),
342
            timestamp.clone(),
343
            make_mouse_data(MouseButton::Right),
344
        ));
345
11
    }
346
    // Right mouse button up
347
11
    if !current_state.mouse_state.right_down && previous_state.mouse_state.right_down {
348
        events.push(SyntheticEvent::new(
349
            EventType::MouseUp,
350
            EventSource::User,
351
            mouse_target.clone(),
352
            timestamp.clone(),
353
            make_mouse_data(MouseButton::Right),
354
        ));
355
11
    }
356

            
357
    // Middle mouse button down
358
11
    if current_state.mouse_state.middle_down && !previous_state.mouse_state.middle_down {
359
        events.push(SyntheticEvent::new(
360
            EventType::MouseDown,
361
            EventSource::User,
362
            mouse_target.clone(),
363
            timestamp.clone(),
364
            make_mouse_data(MouseButton::Middle),
365
        ));
366
11
    }
367
    // Middle mouse button up
368
11
    if !current_state.mouse_state.middle_down && previous_state.mouse_state.middle_down {
369
        events.push(SyntheticEvent::new(
370
            EventType::MouseUp,
371
            EventSource::User,
372
            mouse_target.clone(),
373
            timestamp.clone(),
374
            make_mouse_data(MouseButton::Middle),
375
        ));
376
11
    }
377

            
378
    // ========================================================================
379
    // Click synthesis: if left mouse released on the same node as down
380
    // ========================================================================
381
    // Note: proper click synthesis requires tracking mousedown target across frames.
382
    // For now, if left mouse was released and the hover node hasn't changed, emit Click.
383
11
    if !current_state.mouse_state.left_down && previous_state.mouse_state.left_down {
384
1
        let prev_hover = hover_manager.previous_hover_node_full();
385
1
        let curr_hover = hover_manager.current_hover_node_full();
386
1
        if prev_hover == curr_hover && curr_hover.is_some() {
387
            events.push(SyntheticEvent::new(
388
                EventType::Click,
389
                EventSource::User,
390
                mouse_target.clone(),
391
                timestamp.clone(),
392
                make_mouse_data(MouseButton::Left),
393
            ));
394
1
        }
395
10
    }
396

            
397
    // ========================================================================
398
    // Mouse Movement Events
399
    // ========================================================================
400

            
401
11
    let current_in_window = matches!(
402
11
        current_state.mouse_state.cursor_position,
403
        CursorPosition::InWindow(_)
404
    );
405
11
    let previous_in_window = matches!(
406
11
        previous_state.mouse_state.cursor_position,
407
        CursorPosition::InWindow(_)
408
    );
409

            
410
11
    if current_in_window {
411
3
        let current_pos = current_state.mouse_state.cursor_position.get_position();
412
3
        let previous_pos = previous_state.mouse_state.cursor_position.get_position();
413

            
414
        // MouseOver fires on ANY mouse movement (targeted at hovered node)
415
3
        if current_pos != previous_pos {
416
1
            events.push(SyntheticEvent::new(
417
1
                EventType::MouseOver,
418
1
                EventSource::User,
419
1
                mouse_target.clone(),
420
1
                timestamp.clone(),
421
1
                make_mouse_data(MouseButton::Left), // MouseOver doesn't care about button
422
1
            ));
423
2
        }
424
8
    }
425

            
426
    // ========================================================================
427
    // Wheel / trackpad scroll  →  Scroll event on the hovered node
428
    // ========================================================================
429
    // The platform recorded a wheel delta this pass. The scroll-physics path
430
    // handles scroll *containers*; here we additionally fire a W3C-style Scroll
431
    // event at the hovered node so widgets that treat the wheel specially (the
432
    // map zooms) get a `HoverEventFilter::Scroll` callback. The delta itself is
433
    // read back by the callback via `CallbackInfo::get_scroll_delta`.
434
11
    if let Some(delta) = wheel_delta {
435
        if delta.x != 0.0 || delta.y != 0.0 {
436
            events.push(SyntheticEvent::new(
437
                EventType::Scroll,
438
                EventSource::User,
439
                mouse_target.clone(),
440
                timestamp.clone(),
441
                EventData::Scroll(ScrollEventData {
442
                    delta,
443
                    delta_mode: ScrollDeltaMode::Pixel,
444
                }),
445
            ));
446
        }
447
11
    }
448

            
449
    // ========================================================================
450
    // Per-Node MouseEnter/MouseLeave (W3C compliant)
451
    // ========================================================================
452
    // Compare FULL hover chains between current and previous frames.
453
    // Nodes that gained hover get MouseEnter, nodes that lost hover get MouseLeave.
454
    {
455
11
        let dom_id = DomId { inner: 0 };
456

            
457
11
        let current_hovered = get_all_hovered_nodes(hover_manager, 0);
458
11
        let previous_hovered = get_all_hovered_nodes(hover_manager, 1);
459

            
460
        // Nodes that lost hover -> MouseLeave
461
11
        for node_id in previous_hovered.difference(&current_hovered) {
462
            events.push(SyntheticEvent::new(
463
                EventType::MouseLeave,
464
                EventSource::User,
465
                DomNodeId {
466
                    dom: dom_id,
467
                    node: NodeHierarchyItemId::from_crate_internal(Some(*node_id)),
468
                },
469
                timestamp.clone(),
470
                EventData::None,
471
            ));
472
        }
473

            
474
        // Nodes that gained hover -> MouseEnter
475
11
        for node_id in current_hovered.difference(&previous_hovered) {
476
            events.push(SyntheticEvent::new(
477
                EventType::MouseEnter,
478
                EventSource::User,
479
                DomNodeId {
480
                    dom: dom_id,
481
                    node: NodeHierarchyItemId::from_crate_internal(Some(*node_id)),
482
                },
483
                timestamp.clone(),
484
                EventData::None,
485
            ));
486
        }
487
    }
488

            
489
    // Window-level mouse enter/leave (cursor enters/exits OS window)
490
11
    if current_in_window && !previous_in_window {
491
        events.push(SyntheticEvent::new(
492
            EventType::MouseEnter,
493
            EventSource::User,
494
            root_node.clone(),
495
            timestamp.clone(),
496
            EventData::None,
497
        ));
498
11
    }
499
11
    if !current_in_window && previous_in_window {
500
        events.push(SyntheticEvent::new(
501
            EventType::MouseLeave,
502
            EventSource::User,
503
            root_node.clone(),
504
            timestamp.clone(),
505
            EventData::None,
506
        ));
507
11
    }
508

            
509
    // Keyboard Events
510
    // W3C: Keyboard events target the focused element, falling back to root
511

            
512
11
    let focus_target = focus_manager
513
11
        .get_focused_node()
514
11
        .cloned()
515
11
        .unwrap_or(root_node.clone());
516

            
517
11
    let current_key = current_state
518
11
        .keyboard_state
519
11
        .current_virtual_keycode
520
11
        .into_option();
521
11
    let previous_key = previous_state
522
11
        .keyboard_state
523
11
        .current_virtual_keycode
524
11
        .into_option();
525

            
526
    // KeyDown: Fires when a new key is pressed
527
    // Case 1: New key pressed (current != previous)
528
    // Case 2: Same key pressed again after release (current.is_some() && previous.is_none())
529
11
    let keyboard_data = EventData::Keyboard(KeyboardEventData {
530
11
        key_code: current_key.map(|k| k as u32).unwrap_or(0),
531
11
        char_code: None, // Character is available from the keyboard state
532
11
        modifiers,
533
        repeat: false,
534
    });
535

            
536
11
    if current_key.is_some() && (current_key != previous_key || previous_key.is_none()) {
537
4
        events.push(SyntheticEvent::new(
538
4
            EventType::KeyDown,
539
4
            EventSource::User,
540
4
            focus_target.clone(),
541
4
            timestamp.clone(),
542
4
            keyboard_data.clone(),
543
4
        ));
544
7
    }
545
11
    let key_up_data = EventData::Keyboard(KeyboardEventData {
546
11
        key_code: previous_key.map(|k| k as u32).unwrap_or(0),
547
11
        char_code: None,
548
11
        modifiers,
549
        repeat: false,
550
    });
551

            
552
11
    if previous_key.is_some() && current_key.is_none() {
553
1
        events.push(SyntheticEvent::new(
554
1
            EventType::KeyUp,
555
1
            EventSource::User,
556
1
            focus_target.clone(),
557
1
            timestamp.clone(),
558
1
            key_up_data,
559
1
        ));
560
10
    }
561

            
562
    // Window State Events
563

            
564
    // Window resize
565
11
    if current_state.size.dimensions != previous_state.size.dimensions {
566
        events.push(SyntheticEvent::new(
567
            EventType::WindowResize,
568
            EventSource::User,
569
            root_node.clone(),
570
            timestamp.clone(),
571
            EventData::Window(WindowEventData {
572
                size: Some(LogicalRect {
573
                    origin: LogicalPosition { x: 0.0, y: 0.0 },
574
                    size: current_state.size.dimensions.clone(),
575
                }),
576
                position: None,
577
            }),
578
        ));
579
11
    }
580

            
581
    // Window moved
582
11
    if current_state.position != previous_state.position {
583
        if let WindowPosition::Initialized(phys_pos) = current_state.position {
584
            events.push(SyntheticEvent::new(
585
                EventType::WindowMove,
586
                EventSource::User,
587
                root_node.clone(),
588
                timestamp.clone(),
589
                EventData::Window(WindowEventData {
590
                    size: None,
591
                    position: Some(LogicalPosition {
592
                        x: phys_pos.x as f32,
593
                        y: phys_pos.y as f32,
594
                    }),
595
                }),
596
            ));
597
        }
598
11
    }
599

            
600
    // Window close requested
601
11
    if current_state.flags.close_requested && !previous_state.flags.close_requested {
602
        events.push(SyntheticEvent::new(
603
            EventType::WindowClose,
604
            EventSource::User,
605
            root_node.clone(),
606
            timestamp.clone(),
607
            EventData::None,
608
        ));
609
11
    }
610

            
611
    // Window focus changed
612
11
    if current_state.window_focused && !previous_state.window_focused {
613
        events.push(SyntheticEvent::new(
614
            EventType::WindowFocusIn,
615
            EventSource::User,
616
            root_node.clone(),
617
            timestamp.clone(),
618
            EventData::None,
619
        ));
620
11
    }
621
11
    if !current_state.window_focused && previous_state.window_focused {
622
        events.push(SyntheticEvent::new(
623
            EventType::WindowFocusOut,
624
            EventSource::User,
625
            root_node.clone(),
626
            timestamp.clone(),
627
            EventData::None,
628
        ));
629
11
    }
630

            
631
    // Theme changed
632
11
    if current_state.theme != previous_state.theme {
633
        events.push(SyntheticEvent::new(
634
            EventType::ThemeChange,
635
            EventSource::User,
636
            root_node.clone(),
637
            timestamp.clone(),
638
            EventData::None,
639
        ));
640
11
    }
641

            
642
    // DPI changed (moved to a different-DPI monitor, or system DPI setting changed)
643
11
    if current_state.size.dpi != previous_state.size.dpi {
644
        events.push(SyntheticEvent::new(
645
            EventType::WindowDpiChanged,
646
            EventSource::User,
647
            root_node.clone(),
648
            timestamp.clone(),
649
            EventData::None,
650
        ));
651
11
    }
652

            
653
    // Monitor changed (window moved to a different monitor)
654
11
    if current_state.monitor_id != previous_state.monitor_id
655
        && current_state.monitor_id != azul_css::corety::OptionU32::None
656
        && previous_state.monitor_id != azul_css::corety::OptionU32::None
657
    {
658
        events.push(SyntheticEvent::new(
659
            EventType::WindowMonitorChanged,
660
            EventSource::User,
661
            root_node.clone(),
662
            timestamp.clone(),
663
            EventData::None,
664
        ));
665
11
    }
666

            
667
    // File Drop Events
668

            
669
11
    if file_drop_manager.get_hovered_file().is_some() {
670
        events.push(SyntheticEvent::new(
671
            EventType::FileHover,
672
            EventSource::User,
673
            root_node.clone(),
674
            timestamp.clone(),
675
            EventData::None,
676
        ));
677
11
    }
678

            
679
11
    if file_drop_manager.dropped_file.is_some() {
680
        events.push(SyntheticEvent::new(
681
            EventType::FileDrop,
682
            EventSource::User,
683
            root_node.clone(),
684
            timestamp.clone(),
685
            EventData::None,
686
        ));
687
11
    }
688

            
689
    // Pen / Stylus Events (W3C PointerEvent, pointerType "pen")
690
    // Diff the gesture manager's pen state; `pen_event_pending` gates one diff per
691
    // update (the event loop clears it after this pass, like the sensor manager).
692
    // Targets the hovered node (pen-as-pointer); full pen data via CallbackInfo.
693
11
    if let Some(manager) = gesture_manager {
694
        if manager.pen_event_pending {
695
            let pen_data = make_mouse_data(MouseButton::Left);
696
            match (manager.get_previous_pen_state(), manager.get_pen_state()) {
697
                (None, Some(_)) => events.push(SyntheticEvent::new(
698
                    EventType::PenEnter,
699
                    EventSource::User,
700
                    mouse_target.clone(),
701
                    timestamp.clone(),
702
                    pen_data.clone(),
703
                )),
704
                (Some(_), None) => events.push(SyntheticEvent::new(
705
                    EventType::PenLeave,
706
                    EventSource::User,
707
                    mouse_target.clone(),
708
                    timestamp.clone(),
709
                    pen_data.clone(),
710
                )),
711
                (Some(p), Some(c)) => {
712
                    if !p.in_contact && c.in_contact {
713
                        events.push(SyntheticEvent::new(
714
                            EventType::PenDown,
715
                            EventSource::User,
716
                            mouse_target.clone(),
717
                            timestamp.clone(),
718
                            pen_data.clone(),
719
                        ));
720
                    } else if p.in_contact && !c.in_contact {
721
                        events.push(SyntheticEvent::new(
722
                            EventType::PenUp,
723
                            EventSource::User,
724
                            mouse_target.clone(),
725
                            timestamp.clone(),
726
                            pen_data.clone(),
727
                        ));
728
                    }
729
                    if p.position != c.position {
730
                        events.push(SyntheticEvent::new(
731
                            EventType::PenMove,
732
                            EventSource::User,
733
                            mouse_target.clone(),
734
                            timestamp.clone(),
735
                            pen_data.clone(),
736
                        ));
737
                    }
738
                }
739
                (None, None) => {}
740
            }
741
        }
742
11
    }
743

            
744
    // Gesture Events
745

            
746
11
    if let Some(manager) = gesture_manager {
747
        let event_was_mouse_release = !current_mouse_down && previous_mouse_down;
748

            
749
        // Detect DragStart (targeted at hovered node)
750
        if manager.detect_drag().is_some() {
751
            if !manager.is_dragging() {
752
                events.push(SyntheticEvent::new(
753
                    EventType::DragStart,
754
                    EventSource::User,
755
                    mouse_target.clone(),
756
                    timestamp.clone(),
757
                    make_mouse_data(MouseButton::Left),
758
                ));
759
            }
760
        }
761

            
762
        // Detect Drag (continuous movement, targeted at hovered node)
763
        if manager.is_dragging() && current_mouse_down {
764
            let current_pos = current_state.mouse_state.cursor_position.get_position();
765
            let previous_pos = previous_state.mouse_state.cursor_position.get_position();
766

            
767
            if current_pos != previous_pos {
768
                events.push(SyntheticEvent::new(
769
                    EventType::Drag,
770
                    EventSource::User,
771
                    mouse_target.clone(),
772
                    timestamp.clone(),
773
                    make_mouse_data(MouseButton::Left),
774
                ));
775
            }
776
        }
777

            
778
        // Detect DragEnd (targeted at hovered node)
779
        if manager.is_dragging() && event_was_mouse_release {
780
            events.push(SyntheticEvent::new(
781
                EventType::DragEnd,
782
                EventSource::User,
783
                mouse_target.clone(),
784
                timestamp.clone(),
785
                make_mouse_data(MouseButton::Left),
786
            ));
787

            
788
            // When mouse is released during a node drag, generate a Drop event
789
            // on the current drop target (the node under the cursor)
790
            if manager.is_node_drag_active() {
791
                events.push(SyntheticEvent::new(
792
                    EventType::Drop,
793
                    EventSource::User,
794
                    mouse_target.clone(), // W3C: Drop targets the node under cursor
795
                    timestamp.clone(),
796
                    make_mouse_data(MouseButton::Left),
797
                ));
798
            }
799
        }
800

            
801
        // Detect DragEnter/DragOver/DragLeave events on drop targets
802
        // W3C: These fire ON the drop target node (the node UNDER the cursor)
803
        if manager.is_node_drag_active() && current_mouse_down {
804
            let dom_id = DomId { inner: 0 };
805
            let current_hover = hover_manager.current_hover_node();
806
            let previous_hover = hover_manager.previous_hover_node();
807

            
808
            // If the hover node changed, generate DragLeave on old + DragEnter on new
809
            if current_hover != previous_hover {
810
                if let Some(prev_node) = previous_hover {
811
                    events.push(SyntheticEvent::new(
812
                        EventType::DragLeave,
813
                        EventSource::User,
814
                        DomNodeId {
815
                            dom: dom_id,
816
                            node: NodeHierarchyItemId::from_crate_internal(Some(prev_node)),
817
                        },
818
                        timestamp.clone(),
819
                        EventData::None,
820
                    ));
821
                }
822
                if let Some(curr_node) = current_hover {
823
                    events.push(SyntheticEvent::new(
824
                        EventType::DragEnter,
825
                        EventSource::User,
826
                        DomNodeId {
827
                            dom: dom_id,
828
                            node: NodeHierarchyItemId::from_crate_internal(Some(curr_node)),
829
                        },
830
                        timestamp.clone(),
831
                        EventData::None,
832
                    ));
833
                }
834
            }
835

            
836
            // DragOver fires continuously while hovering a drop target
837
            if let Some(curr_node) = current_hover {
838
                events.push(SyntheticEvent::new(
839
                    EventType::DragOver,
840
                    EventSource::User,
841
                    DomNodeId {
842
                        dom: dom_id,
843
                        node: NodeHierarchyItemId::from_crate_internal(Some(curr_node)),
844
                    },
845
                    timestamp.clone(),
846
                    EventData::None,
847
                ));
848
            }
849
        }
850

            
851
        // Detect DoubleClick (targeted at hovered node)
852
        if manager.detect_double_click() {
853
            events.push(SyntheticEvent::new(
854
                EventType::DoubleClick,
855
                EventSource::User,
856
                mouse_target.clone(),
857
                timestamp.clone(),
858
                make_mouse_data(MouseButton::Left),
859
            ));
860
        }
861

            
862
        // Detect LongPress (targeted at hovered node)
863
        if let Some(long_press) = manager.detect_long_press() {
864
            if !long_press.callback_invoked {
865
                events.push(SyntheticEvent::new(
866
                    EventType::LongPress,
867
                    EventSource::User,
868
                    mouse_target.clone(),
869
                    timestamp.clone(),
870
                    make_mouse_data(MouseButton::Left),
871
                ));
872
            }
873
        }
874

            
875
        // Detect Swipe gestures (targeted at hovered node)
876
        if let Some(direction) = manager.detect_swipe_direction() {
877
            use crate::managers::gesture::GestureDirection;
878
            let event_type = match direction {
879
                GestureDirection::Left => EventType::SwipeLeft,
880
                GestureDirection::Right => EventType::SwipeRight,
881
                GestureDirection::Up => EventType::SwipeUp,
882
                GestureDirection::Down => EventType::SwipeDown,
883
            };
884
            events.push(SyntheticEvent::new(
885
                event_type,
886
                EventSource::User,
887
                mouse_target.clone(),
888
                timestamp.clone(),
889
                EventData::None,
890
            ));
891
        }
892

            
893
        // Detect Pinch gestures (targeted at hovered node)
894
        if let Some(pinch) = manager.detect_pinch() {
895
            let event_type = if pinch.scale < 1.0 {
896
                EventType::PinchIn
897
            } else {
898
                EventType::PinchOut
899
            };
900
            events.push(SyntheticEvent::new(
901
                event_type,
902
                EventSource::User,
903
                mouse_target.clone(),
904
                timestamp.clone(),
905
                EventData::None,
906
            ));
907
        }
908

            
909
        // Detect Rotation gestures (targeted at hovered node)
910
        if let Some(rotation) = manager.detect_rotation() {
911
            let event_type = if rotation.angle_radians > 0.0 {
912
                EventType::RotateClockwise
913
            } else {
914
                EventType::RotateCounterClockwise
915
            };
916
            events.push(SyntheticEvent::new(
917
                event_type,
918
                EventSource::User,
919
                mouse_target.clone(),
920
                timestamp.clone(),
921
                EventData::None,
922
            ));
923
        }
924

            
925
        // Detect Pen events (targeted at hovered node)
926
        if let Some(pen_state) = manager.get_pen_state() {
927
            if pen_state.in_contact {
928
                let current_pos = current_state.mouse_state.cursor_position.get_position();
929
                let previous_pos = previous_state.mouse_state.cursor_position.get_position();
930

            
931
                if current_pos != previous_pos {
932
                    events.push(SyntheticEvent::new(
933
                        EventType::TouchMove, // Use TouchMove as fallback
934
                        EventSource::User,
935
                        mouse_target.clone(),
936
                        timestamp.clone(),
937
                        EventData::None,
938
                    ));
939
                }
940
            }
941
        }
942
11
    }
943

            
944
    // Manager Events (Scroll, Text Input, etc.)
945

            
946
11
    for manager in managers {
947
        events.extend(manager.get_pending_events(timestamp.clone()));
948
    }
949

            
950
    // Deduplication
951

            
952
11
    deduplicate_synthetic_events(events)
953
11
}
954

            
955
#[cfg(test)]
956
mod tests {
957
    use super::*;
958
    use azul_core::window::{
959
        VirtualKeyCode, VirtualKeyCodeVec,
960
        OptionVirtualKeyCode,
961
    };
962

            
963
11
    fn ts() -> Instant { Instant::Tick(SystemTick::new(0)) }
964

            
965
20
    fn default_state() -> crate::window_state::FullWindowState {
966
20
        crate::window_state::FullWindowState::default()
967
20
    }
968

            
969
10
    fn state_with_key(vk: VirtualKeyCode) -> crate::window_state::FullWindowState {
970
10
        let mut s = default_state();
971
10
        s.keyboard_state.current_virtual_keycode = OptionVirtualKeyCode::Some(vk);
972
10
        s.keyboard_state.pressed_virtual_keycodes = VirtualKeyCodeVec::from_vec(vec![vk]);
973
10
        s
974
10
    }
975

            
976
2
    fn state_with_left_down(x: f32, y: f32) -> crate::window_state::FullWindowState {
977
2
        let mut s = default_state();
978
2
        s.mouse_state.cursor_position = CursorPosition::InWindow(LogicalPosition::new(x, y));
979
2
        s.mouse_state.left_down = true;
980
2
        s
981
2
    }
982

            
983
4
    fn state_with_cursor(x: f32, y: f32) -> crate::window_state::FullWindowState {
984
4
        let mut s = default_state();
985
4
        s.mouse_state.cursor_position = CursorPosition::InWindow(LogicalPosition::new(x, y));
986
4
        s
987
4
    }
988

            
989
11
    fn empty_providers() -> Vec<&'static dyn EventProvider> { vec![] }
990

            
991
11
    fn run_determine(
992
11
        current: &crate::window_state::FullWindowState,
993
11
        previous: &crate::window_state::FullWindowState,
994
11
    ) -> Vec<SyntheticEvent> {
995
11
        let focus = crate::managers::focus_cursor::FocusManager::new();
996
11
        let hover = crate::managers::hover::HoverManager::new();
997
11
        let filedrop = crate::managers::file_drop::FileDropManager::new();
998
11
        let providers = empty_providers();
999
11
        determine_all_events(current, previous, &hover, &focus, &filedrop, None, &providers, None, ts())
11
    }
    // === Keyboard tests ===
    #[test]
1
    fn keydown_fires_when_key_newly_pressed() {
1
        let events = run_determine(&state_with_key(VirtualKeyCode::A), &default_state());
1
        let kd: Vec<_> = events.iter().filter(|e| e.event_type == EventType::KeyDown).collect();
1
        assert_eq!(kd.len(), 1);
1
        assert!(matches!(kd[0].data, EventData::Keyboard(_)));
1
    }
    #[test]
1
    fn keydown_skipped_when_same_key_held() {
1
        let s = state_with_key(VirtualKeyCode::A);
1
        let events = run_determine(&s, &s);
1
        let kd: Vec<_> = events.iter().filter(|e| e.event_type == EventType::KeyDown).collect();
1
        assert_eq!(kd.len(), 0, "KeyDown should not fire when same key held");
1
    }
    #[test]
1
    fn keydown_fires_for_different_key() {
1
        let events = run_determine(
1
            &state_with_key(VirtualKeyCode::B),
1
            &state_with_key(VirtualKeyCode::A),
        );
1
        let kd: Vec<_> = events.iter().filter(|e| e.event_type == EventType::KeyDown).collect();
1
        assert_eq!(kd.len(), 1);
1
    }
    #[test]
1
    fn keyup_fires_when_key_released() {
1
        let events = run_determine(&default_state(), &state_with_key(VirtualKeyCode::A));
1
        let ku: Vec<_> = events.iter().filter(|e| e.event_type == EventType::KeyUp).collect();
1
        assert_eq!(ku.len(), 1);
1
        assert!(matches!(ku[0].data, EventData::Keyboard(_)));
1
    }
    #[test]
1
    fn backspace_keydown_has_keyboard_data() {
1
        let events = run_determine(&state_with_key(VirtualKeyCode::Back), &default_state());
1
        let kd: Vec<_> = events.iter().filter(|e| e.event_type == EventType::KeyDown).collect();
1
        assert_eq!(kd.len(), 1);
1
        match &kd[0].data {
1
            EventData::Keyboard(kb) => assert_eq!(kb.key_code, VirtualKeyCode::Back as u32),
            other => panic!("Expected Keyboard data, got {:?}", other),
        }
1
    }
    // === Mouse tests ===
    #[test]
1
    fn mousedown_fires_on_left_press() {
1
        let events = run_determine(
1
            &state_with_left_down(100.0, 200.0),
1
            &state_with_cursor(100.0, 200.0),
        );
1
        let md: Vec<_> = events.iter().filter(|e| e.event_type == EventType::MouseDown).collect();
1
        assert_eq!(md.len(), 1);
1
    }
    #[test]
1
    fn mouseup_fires_on_left_release() {
1
        let events = run_determine(
1
            &state_with_cursor(100.0, 200.0),
1
            &state_with_left_down(100.0, 200.0),
        );
1
        let mu: Vec<_> = events.iter().filter(|e| e.event_type == EventType::MouseUp).collect();
1
        assert_eq!(mu.len(), 1);
1
    }
    #[test]
1
    fn no_events_when_state_unchanged() {
1
        let s = default_state();
1
        let events = run_determine(&s, &s);
1
        assert!(events.is_empty(), "Got {} events when state unchanged", events.len());
1
    }
    #[test]
1
    fn mouseover_fires_on_cursor_move() {
1
        let events = run_determine(
1
            &state_with_cursor(150.0, 250.0),
1
            &state_with_cursor(100.0, 200.0),
        );
1
        let mo: Vec<_> = events.iter().filter(|e| e.event_type == EventType::MouseOver).collect();
1
        assert_eq!(mo.len(), 1);
1
    }
    #[test]
1
    fn key_repeat_fires_keydown_when_previous_cleared() {
        // Simulates what the platform layer does for key repeat:
        // previous has current_virtual_keycode=None (cleared by platform),
        // current has Some(Left). This should fire KeyDown even though
        // the key was already pressed in the previous frame.
1
        let mut previous = state_with_key(VirtualKeyCode::Left);
        // Platform clears this for repeat detection:
1
        previous.keyboard_state.current_virtual_keycode = OptionVirtualKeyCode::None;
1
        let current = state_with_key(VirtualKeyCode::Left);
1
        let events = run_determine(&current, &previous);
1
        let kd: Vec<_> = events.iter().filter(|e| e.event_type == EventType::KeyDown).collect();
1
        assert_eq!(kd.len(), 1, "Key repeat should fire KeyDown when previous is cleared");
1
    }
    #[test]
1
    fn key_repeat_skipped_when_previous_not_cleared() {
        // Without the platform fix: both previous and current have Same(Left).
        // KeyDown should NOT fire (this documents the limitation that the
        // platform layer MUST clear previous for repeats).
1
        let previous = state_with_key(VirtualKeyCode::Left);
1
        let current = state_with_key(VirtualKeyCode::Left);
1
        let events = run_determine(&current, &previous);
1
        let kd: Vec<_> = events.iter().filter(|e| e.event_type == EventType::KeyDown).collect();
1
        assert_eq!(kd.len(), 0, "Without platform clearing, repeat is not detected");
1
    }
}