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, SyntheticEvent, WindowEventData,
12
    },
13
    geom::{LogicalPosition, LogicalRect},
14
    id::NodeId,
15
    styled_dom::NodeHierarchyItemId,
16
    task::{Instant, SystemTick},
17
    window::{CursorPosition, WindowPosition},
18
};
19

            
20
use std::collections::BTreeSet;
21

            
22
use crate::window_state::FullWindowState;
23

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

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

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

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

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

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

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

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

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

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

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

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

            
203
    events
204
}
205

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

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

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

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

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

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

            
287
    // Helper: get deepest hovered node as the event target for mouse events
288
11
    let mouse_target = hover_manager
289
11
        .current_hover_node()
290
11
        .map(|node_id| DomNodeId {
291
            dom: DomId { inner: 0 },
292
            node: NodeHierarchyItemId::from_crate_internal(Some(node_id)),
293
        })
294
11
        .unwrap_or(root_node.clone());
295

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

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

            
310
11
    let current_mouse_down = current_state.mouse_state.mouse_down();
311
11
    let previous_mouse_down = previous_state.mouse_state.mouse_down();
312

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

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

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

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

            
395
    // ========================================================================
396
    // Mouse Movement Events
397
    // ========================================================================
398

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

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

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

            
424
    // ========================================================================
425
    // Per-Node MouseEnter/MouseLeave (W3C compliant)
426
    // ========================================================================
427
    // Compare FULL hover chains between current and previous frames.
428
    // Nodes that gained hover get MouseEnter, nodes that lost hover get MouseLeave.
429
    {
430
11
        let dom_id = DomId { inner: 0 };
431

            
432
11
        let current_hovered = get_all_hovered_nodes(hover_manager, 0);
433
11
        let previous_hovered = get_all_hovered_nodes(hover_manager, 1);
434

            
435
        // Nodes that lost hover -> MouseLeave
436
11
        for node_id in previous_hovered.difference(&current_hovered) {
437
            events.push(SyntheticEvent::new(
438
                EventType::MouseLeave,
439
                EventSource::User,
440
                DomNodeId {
441
                    dom: dom_id,
442
                    node: NodeHierarchyItemId::from_crate_internal(Some(*node_id)),
443
                },
444
                timestamp.clone(),
445
                EventData::None,
446
            ));
447
        }
448

            
449
        // Nodes that gained hover -> MouseEnter
450
11
        for node_id in current_hovered.difference(&previous_hovered) {
451
            events.push(SyntheticEvent::new(
452
                EventType::MouseEnter,
453
                EventSource::User,
454
                DomNodeId {
455
                    dom: dom_id,
456
                    node: NodeHierarchyItemId::from_crate_internal(Some(*node_id)),
457
                },
458
                timestamp.clone(),
459
                EventData::None,
460
            ));
461
        }
462
    }
463

            
464
    // Window-level mouse enter/leave (cursor enters/exits OS window)
465
11
    if current_in_window && !previous_in_window {
466
        events.push(SyntheticEvent::new(
467
            EventType::MouseEnter,
468
            EventSource::User,
469
            root_node.clone(),
470
            timestamp.clone(),
471
            EventData::None,
472
        ));
473
11
    }
474
11
    if !current_in_window && previous_in_window {
475
        events.push(SyntheticEvent::new(
476
            EventType::MouseLeave,
477
            EventSource::User,
478
            root_node.clone(),
479
            timestamp.clone(),
480
            EventData::None,
481
        ));
482
11
    }
483

            
484
    // Keyboard Events
485
    // W3C: Keyboard events target the focused element, falling back to root
486

            
487
11
    let focus_target = focus_manager
488
11
        .get_focused_node()
489
11
        .cloned()
490
11
        .unwrap_or(root_node.clone());
491

            
492
11
    let current_key = current_state
493
11
        .keyboard_state
494
11
        .current_virtual_keycode
495
11
        .into_option();
496
11
    let previous_key = previous_state
497
11
        .keyboard_state
498
11
        .current_virtual_keycode
499
11
        .into_option();
500

            
501
    // KeyDown: Fires when a new key is pressed
502
    // Case 1: New key pressed (current != previous)
503
    // Case 2: Same key pressed again after release (current.is_some() && previous.is_none())
504
11
    let keyboard_data = EventData::Keyboard(KeyboardEventData {
505
11
        key_code: current_key.map(|k| k as u32).unwrap_or(0),
506
11
        char_code: None, // Character is available from the keyboard state
507
11
        modifiers,
508
        repeat: false,
509
    });
510

            
511
11
    if current_key.is_some() && (current_key != previous_key || previous_key.is_none()) {
512
4
        events.push(SyntheticEvent::new(
513
4
            EventType::KeyDown,
514
4
            EventSource::User,
515
4
            focus_target.clone(),
516
4
            timestamp.clone(),
517
4
            keyboard_data.clone(),
518
4
        ));
519
7
    }
520
11
    let key_up_data = EventData::Keyboard(KeyboardEventData {
521
11
        key_code: previous_key.map(|k| k as u32).unwrap_or(0),
522
11
        char_code: None,
523
11
        modifiers,
524
        repeat: false,
525
    });
526

            
527
11
    if previous_key.is_some() && current_key.is_none() {
528
1
        events.push(SyntheticEvent::new(
529
1
            EventType::KeyUp,
530
1
            EventSource::User,
531
1
            focus_target.clone(),
532
1
            timestamp.clone(),
533
1
            key_up_data,
534
1
        ));
535
10
    }
536

            
537
    // Window State Events
538

            
539
    // Window resize
540
11
    if current_state.size.dimensions != previous_state.size.dimensions {
541
        events.push(SyntheticEvent::new(
542
            EventType::WindowResize,
543
            EventSource::User,
544
            root_node.clone(),
545
            timestamp.clone(),
546
            EventData::Window(WindowEventData {
547
                size: Some(LogicalRect {
548
                    origin: LogicalPosition { x: 0.0, y: 0.0 },
549
                    size: current_state.size.dimensions.clone(),
550
                }),
551
                position: None,
552
            }),
553
        ));
554
11
    }
555

            
556
    // Window moved
557
11
    if current_state.position != previous_state.position {
558
        if let WindowPosition::Initialized(phys_pos) = current_state.position {
559
            events.push(SyntheticEvent::new(
560
                EventType::WindowMove,
561
                EventSource::User,
562
                root_node.clone(),
563
                timestamp.clone(),
564
                EventData::Window(WindowEventData {
565
                    size: None,
566
                    position: Some(LogicalPosition {
567
                        x: phys_pos.x as f32,
568
                        y: phys_pos.y as f32,
569
                    }),
570
                }),
571
            ));
572
        }
573
11
    }
574

            
575
    // Window close requested
576
11
    if current_state.flags.close_requested && !previous_state.flags.close_requested {
577
        events.push(SyntheticEvent::new(
578
            EventType::WindowClose,
579
            EventSource::User,
580
            root_node.clone(),
581
            timestamp.clone(),
582
            EventData::None,
583
        ));
584
11
    }
585

            
586
    // Window focus changed
587
11
    if current_state.window_focused && !previous_state.window_focused {
588
        events.push(SyntheticEvent::new(
589
            EventType::WindowFocusIn,
590
            EventSource::User,
591
            root_node.clone(),
592
            timestamp.clone(),
593
            EventData::None,
594
        ));
595
11
    }
596
11
    if !current_state.window_focused && previous_state.window_focused {
597
        events.push(SyntheticEvent::new(
598
            EventType::WindowFocusOut,
599
            EventSource::User,
600
            root_node.clone(),
601
            timestamp.clone(),
602
            EventData::None,
603
        ));
604
11
    }
605

            
606
    // Theme changed
607
11
    if current_state.theme != previous_state.theme {
608
        events.push(SyntheticEvent::new(
609
            EventType::ThemeChange,
610
            EventSource::User,
611
            root_node.clone(),
612
            timestamp.clone(),
613
            EventData::None,
614
        ));
615
11
    }
616

            
617
    // DPI changed (moved to a different-DPI monitor, or system DPI setting changed)
618
11
    if current_state.size.dpi != previous_state.size.dpi {
619
        events.push(SyntheticEvent::new(
620
            EventType::WindowDpiChanged,
621
            EventSource::User,
622
            root_node.clone(),
623
            timestamp.clone(),
624
            EventData::None,
625
        ));
626
11
    }
627

            
628
    // Monitor changed (window moved to a different monitor)
629
11
    if current_state.monitor_id != previous_state.monitor_id
630
        && current_state.monitor_id != azul_css::corety::OptionU32::None
631
        && previous_state.monitor_id != azul_css::corety::OptionU32::None
632
    {
633
        events.push(SyntheticEvent::new(
634
            EventType::WindowMonitorChanged,
635
            EventSource::User,
636
            root_node.clone(),
637
            timestamp.clone(),
638
            EventData::None,
639
        ));
640
11
    }
641

            
642
    // File Drop Events
643

            
644
11
    if file_drop_manager.get_hovered_file().is_some() {
645
        events.push(SyntheticEvent::new(
646
            EventType::FileHover,
647
            EventSource::User,
648
            root_node.clone(),
649
            timestamp.clone(),
650
            EventData::None,
651
        ));
652
11
    }
653

            
654
11
    if file_drop_manager.dropped_file.is_some() {
655
        events.push(SyntheticEvent::new(
656
            EventType::FileDrop,
657
            EventSource::User,
658
            root_node.clone(),
659
            timestamp.clone(),
660
            EventData::None,
661
        ));
662
11
    }
663

            
664
    // Pen / Stylus Events (W3C PointerEvent, pointerType "pen")
665
    // Diff the gesture manager's pen state; `pen_event_pending` gates one diff per
666
    // update (the event loop clears it after this pass, like the sensor manager).
667
    // Targets the hovered node (pen-as-pointer); full pen data via CallbackInfo.
668
11
    if let Some(manager) = gesture_manager {
669
        if manager.pen_event_pending {
670
            let pen_data = make_mouse_data(MouseButton::Left);
671
            match (manager.get_previous_pen_state(), manager.get_pen_state()) {
672
                (None, Some(_)) => events.push(SyntheticEvent::new(
673
                    EventType::PenEnter,
674
                    EventSource::User,
675
                    mouse_target.clone(),
676
                    timestamp.clone(),
677
                    pen_data.clone(),
678
                )),
679
                (Some(_), None) => events.push(SyntheticEvent::new(
680
                    EventType::PenLeave,
681
                    EventSource::User,
682
                    mouse_target.clone(),
683
                    timestamp.clone(),
684
                    pen_data.clone(),
685
                )),
686
                (Some(p), Some(c)) => {
687
                    if !p.in_contact && c.in_contact {
688
                        events.push(SyntheticEvent::new(
689
                            EventType::PenDown,
690
                            EventSource::User,
691
                            mouse_target.clone(),
692
                            timestamp.clone(),
693
                            pen_data.clone(),
694
                        ));
695
                    } else if p.in_contact && !c.in_contact {
696
                        events.push(SyntheticEvent::new(
697
                            EventType::PenUp,
698
                            EventSource::User,
699
                            mouse_target.clone(),
700
                            timestamp.clone(),
701
                            pen_data.clone(),
702
                        ));
703
                    }
704
                    if p.position != c.position {
705
                        events.push(SyntheticEvent::new(
706
                            EventType::PenMove,
707
                            EventSource::User,
708
                            mouse_target.clone(),
709
                            timestamp.clone(),
710
                            pen_data.clone(),
711
                        ));
712
                    }
713
                }
714
                (None, None) => {}
715
            }
716
        }
717
11
    }
718

            
719
    // Gesture Events
720

            
721
11
    if let Some(manager) = gesture_manager {
722
        let event_was_mouse_release = !current_mouse_down && previous_mouse_down;
723

            
724
        // Detect DragStart (targeted at hovered node)
725
        if manager.detect_drag().is_some() {
726
            if !manager.is_dragging() {
727
                events.push(SyntheticEvent::new(
728
                    EventType::DragStart,
729
                    EventSource::User,
730
                    mouse_target.clone(),
731
                    timestamp.clone(),
732
                    make_mouse_data(MouseButton::Left),
733
                ));
734
            }
735
        }
736

            
737
        // Detect Drag (continuous movement, targeted at hovered node)
738
        if manager.is_dragging() && current_mouse_down {
739
            let current_pos = current_state.mouse_state.cursor_position.get_position();
740
            let previous_pos = previous_state.mouse_state.cursor_position.get_position();
741

            
742
            if current_pos != previous_pos {
743
                events.push(SyntheticEvent::new(
744
                    EventType::Drag,
745
                    EventSource::User,
746
                    mouse_target.clone(),
747
                    timestamp.clone(),
748
                    make_mouse_data(MouseButton::Left),
749
                ));
750
            }
751
        }
752

            
753
        // Detect DragEnd (targeted at hovered node)
754
        if manager.is_dragging() && event_was_mouse_release {
755
            events.push(SyntheticEvent::new(
756
                EventType::DragEnd,
757
                EventSource::User,
758
                mouse_target.clone(),
759
                timestamp.clone(),
760
                make_mouse_data(MouseButton::Left),
761
            ));
762

            
763
            // When mouse is released during a node drag, generate a Drop event
764
            // on the current drop target (the node under the cursor)
765
            if manager.is_node_drag_active() {
766
                events.push(SyntheticEvent::new(
767
                    EventType::Drop,
768
                    EventSource::User,
769
                    mouse_target.clone(), // W3C: Drop targets the node under cursor
770
                    timestamp.clone(),
771
                    make_mouse_data(MouseButton::Left),
772
                ));
773
            }
774
        }
775

            
776
        // Detect DragEnter/DragOver/DragLeave events on drop targets
777
        // W3C: These fire ON the drop target node (the node UNDER the cursor)
778
        if manager.is_node_drag_active() && current_mouse_down {
779
            let dom_id = DomId { inner: 0 };
780
            let current_hover = hover_manager.current_hover_node();
781
            let previous_hover = hover_manager.previous_hover_node();
782

            
783
            // If the hover node changed, generate DragLeave on old + DragEnter on new
784
            if current_hover != previous_hover {
785
                if let Some(prev_node) = previous_hover {
786
                    events.push(SyntheticEvent::new(
787
                        EventType::DragLeave,
788
                        EventSource::User,
789
                        DomNodeId {
790
                            dom: dom_id,
791
                            node: NodeHierarchyItemId::from_crate_internal(Some(prev_node)),
792
                        },
793
                        timestamp.clone(),
794
                        EventData::None,
795
                    ));
796
                }
797
                if let Some(curr_node) = current_hover {
798
                    events.push(SyntheticEvent::new(
799
                        EventType::DragEnter,
800
                        EventSource::User,
801
                        DomNodeId {
802
                            dom: dom_id,
803
                            node: NodeHierarchyItemId::from_crate_internal(Some(curr_node)),
804
                        },
805
                        timestamp.clone(),
806
                        EventData::None,
807
                    ));
808
                }
809
            }
810

            
811
            // DragOver fires continuously while hovering a drop target
812
            if let Some(curr_node) = current_hover {
813
                events.push(SyntheticEvent::new(
814
                    EventType::DragOver,
815
                    EventSource::User,
816
                    DomNodeId {
817
                        dom: dom_id,
818
                        node: NodeHierarchyItemId::from_crate_internal(Some(curr_node)),
819
                    },
820
                    timestamp.clone(),
821
                    EventData::None,
822
                ));
823
            }
824
        }
825

            
826
        // Detect DoubleClick (targeted at hovered node)
827
        if manager.detect_double_click() {
828
            events.push(SyntheticEvent::new(
829
                EventType::DoubleClick,
830
                EventSource::User,
831
                mouse_target.clone(),
832
                timestamp.clone(),
833
                make_mouse_data(MouseButton::Left),
834
            ));
835
        }
836

            
837
        // Detect LongPress (targeted at hovered node)
838
        if let Some(long_press) = manager.detect_long_press() {
839
            if !long_press.callback_invoked {
840
                events.push(SyntheticEvent::new(
841
                    EventType::LongPress,
842
                    EventSource::User,
843
                    mouse_target.clone(),
844
                    timestamp.clone(),
845
                    make_mouse_data(MouseButton::Left),
846
                ));
847
            }
848
        }
849

            
850
        // Detect Swipe gestures (targeted at hovered node)
851
        if let Some(direction) = manager.detect_swipe_direction() {
852
            use crate::managers::gesture::GestureDirection;
853
            let event_type = match direction {
854
                GestureDirection::Left => EventType::SwipeLeft,
855
                GestureDirection::Right => EventType::SwipeRight,
856
                GestureDirection::Up => EventType::SwipeUp,
857
                GestureDirection::Down => EventType::SwipeDown,
858
            };
859
            events.push(SyntheticEvent::new(
860
                event_type,
861
                EventSource::User,
862
                mouse_target.clone(),
863
                timestamp.clone(),
864
                EventData::None,
865
            ));
866
        }
867

            
868
        // Detect Pinch gestures (targeted at hovered node)
869
        if let Some(pinch) = manager.detect_pinch() {
870
            let event_type = if pinch.scale < 1.0 {
871
                EventType::PinchIn
872
            } else {
873
                EventType::PinchOut
874
            };
875
            events.push(SyntheticEvent::new(
876
                event_type,
877
                EventSource::User,
878
                mouse_target.clone(),
879
                timestamp.clone(),
880
                EventData::None,
881
            ));
882
        }
883

            
884
        // Detect Rotation gestures (targeted at hovered node)
885
        if let Some(rotation) = manager.detect_rotation() {
886
            let event_type = if rotation.angle_radians > 0.0 {
887
                EventType::RotateClockwise
888
            } else {
889
                EventType::RotateCounterClockwise
890
            };
891
            events.push(SyntheticEvent::new(
892
                event_type,
893
                EventSource::User,
894
                mouse_target.clone(),
895
                timestamp.clone(),
896
                EventData::None,
897
            ));
898
        }
899

            
900
        // Detect Pen events (targeted at hovered node)
901
        if let Some(pen_state) = manager.get_pen_state() {
902
            if pen_state.in_contact {
903
                let current_pos = current_state.mouse_state.cursor_position.get_position();
904
                let previous_pos = previous_state.mouse_state.cursor_position.get_position();
905

            
906
                if current_pos != previous_pos {
907
                    events.push(SyntheticEvent::new(
908
                        EventType::TouchMove, // Use TouchMove as fallback
909
                        EventSource::User,
910
                        mouse_target.clone(),
911
                        timestamp.clone(),
912
                        EventData::None,
913
                    ));
914
                }
915
            }
916
        }
917
11
    }
918

            
919
    // Manager Events (Scroll, Text Input, etc.)
920

            
921
11
    for manager in managers {
922
        events.extend(manager.get_pending_events(timestamp.clone()));
923
    }
924

            
925
    // Deduplication
926

            
927
11
    deduplicate_synthetic_events(events)
928
11
}
929

            
930
#[cfg(test)]
931
mod tests {
932
    use super::*;
933
    use azul_core::window::{
934
        VirtualKeyCode, VirtualKeyCodeVec,
935
        OptionVirtualKeyCode,
936
    };
937

            
938
11
    fn ts() -> Instant { Instant::Tick(SystemTick::new(0)) }
939

            
940
20
    fn default_state() -> crate::window_state::FullWindowState {
941
20
        crate::window_state::FullWindowState::default()
942
20
    }
943

            
944
10
    fn state_with_key(vk: VirtualKeyCode) -> crate::window_state::FullWindowState {
945
10
        let mut s = default_state();
946
10
        s.keyboard_state.current_virtual_keycode = OptionVirtualKeyCode::Some(vk);
947
10
        s.keyboard_state.pressed_virtual_keycodes = VirtualKeyCodeVec::from_vec(vec![vk]);
948
10
        s
949
10
    }
950

            
951
2
    fn state_with_left_down(x: f32, y: f32) -> crate::window_state::FullWindowState {
952
2
        let mut s = default_state();
953
2
        s.mouse_state.cursor_position = CursorPosition::InWindow(LogicalPosition::new(x, y));
954
2
        s.mouse_state.left_down = true;
955
2
        s
956
2
    }
957

            
958
4
    fn state_with_cursor(x: f32, y: f32) -> crate::window_state::FullWindowState {
959
4
        let mut s = default_state();
960
4
        s.mouse_state.cursor_position = CursorPosition::InWindow(LogicalPosition::new(x, y));
961
4
        s
962
4
    }
963

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

            
966
11
    fn run_determine(
967
11
        current: &crate::window_state::FullWindowState,
968
11
        previous: &crate::window_state::FullWindowState,
969
11
    ) -> Vec<SyntheticEvent> {
970
11
        let focus = crate::managers::focus_cursor::FocusManager::new();
971
11
        let hover = crate::managers::hover::HoverManager::new();
972
11
        let filedrop = crate::managers::file_drop::FileDropManager::new();
973
11
        let providers = empty_providers();
974
11
        determine_all_events(current, previous, &hover, &focus, &filedrop, None, &providers, ts())
975
11
    }
976

            
977
    // === Keyboard tests ===
978

            
979
    #[test]
980
1
    fn keydown_fires_when_key_newly_pressed() {
981
1
        let events = run_determine(&state_with_key(VirtualKeyCode::A), &default_state());
982
1
        let kd: Vec<_> = events.iter().filter(|e| e.event_type == EventType::KeyDown).collect();
983
1
        assert_eq!(kd.len(), 1);
984
1
        assert!(matches!(kd[0].data, EventData::Keyboard(_)));
985
1
    }
986

            
987
    #[test]
988
1
    fn keydown_skipped_when_same_key_held() {
989
1
        let s = state_with_key(VirtualKeyCode::A);
990
1
        let events = run_determine(&s, &s);
991
1
        let kd: Vec<_> = events.iter().filter(|e| e.event_type == EventType::KeyDown).collect();
992
1
        assert_eq!(kd.len(), 0, "KeyDown should not fire when same key held");
993
1
    }
994

            
995
    #[test]
996
1
    fn keydown_fires_for_different_key() {
997
1
        let events = run_determine(
998
1
            &state_with_key(VirtualKeyCode::B),
999
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
    }
}