1#[cfg(not(feature = "std"))]
4use alloc::string::{String, ToString};
5use alloc::{
6 boxed::Box,
7 collections::{btree_map::BTreeMap, btree_set::BTreeSet},
8 vec::Vec,
9};
10
11use azul_css::AzString;
12
13use crate::{
14 callbacks::Update,
15 dom::{DomId, DomNodeId, On},
16 geom::{LogicalPosition, LogicalRect},
17 hit_test::{FullHitTest, HitTestItem},
18 id::NodeId,
19 styled_dom::{ChangedCssProperty, NodeHierarchyItemId},
20 task::Instant,
21 OrderedMap,
22};
23
24#[derive(Debug, Clone, Copy, PartialEq, Eq)]
26pub enum EasingFunction {
27 Linear,
28 EaseInOut,
29 EaseOut,
30}
31
32pub type RestyleNodes = BTreeMap<NodeId, Vec<ChangedCssProperty>>;
33pub type RelayoutNodes = BTreeMap<NodeId, Vec<ChangedCssProperty>>;
34pub type RelayoutWords = BTreeMap<NodeId, AzString>;
35
36#[derive(Debug, Clone, Copy, PartialEq, Eq)]
37pub struct FocusChange {
38 pub old: Option<DomNodeId>,
39 pub new: Option<DomNodeId>,
40}
41
42#[derive(Debug, Clone, Copy, PartialEq, Eq)]
43pub struct CallbackToCall {
44 pub node_id: NodeId,
45 pub hit_test_item: Option<HitTestItem>,
46 pub event_filter: EventFilter,
47}
48
49impl CallbackToCall {
50 #[must_use] pub const fn new(
51 node_id: NodeId,
52 hit_test_item: Option<HitTestItem>,
53 event_filter: EventFilter,
54 ) -> Self {
55 Self { node_id, hit_test_item, event_filter }
56 }
57
58 #[must_use] pub fn from_hit_test(
62 hit_test: &FullHitTest,
63 dom_id: DomId,
64 event_filter: EventFilter,
65 ) -> Vec<Self> {
66 let Some(hit) = hit_test.hovered_nodes.get(&dom_id) else {
67 return Vec::new();
68 };
69 hit.regular_hit_test_nodes
70 .iter()
71 .map(|(node_id, item)| Self {
72 node_id: *node_id,
73 hit_test_item: Some(*item),
74 event_filter,
75 })
76 .collect()
77 }
78}
79
80#[derive(Debug, Copy, Clone, PartialEq, Eq)]
81#[must_use = "ProcessEventResult must be used to determine if relayout/repaint is needed"]
82pub enum ProcessEventResult {
83 DoNothing = 0,
84 ShouldReRenderCurrentWindow = 1,
85 ShouldUpdateDisplayListCurrentWindow = 2,
86 UpdateHitTesterAndProcessAgain = 3,
89 ShouldIncrementalRelayout = 4,
92 ShouldRegenerateDomCurrentWindow = 5,
94 ShouldRegenerateDomAllWindows = 6,
95}
96
97impl ProcessEventResult {
98 #[must_use] pub const fn order(&self) -> usize {
99 use self::ProcessEventResult::{DoNothing, ShouldReRenderCurrentWindow, ShouldUpdateDisplayListCurrentWindow, UpdateHitTesterAndProcessAgain, ShouldIncrementalRelayout, ShouldRegenerateDomCurrentWindow, ShouldRegenerateDomAllWindows};
100 match self {
101 DoNothing => 0,
102 ShouldReRenderCurrentWindow => 1,
103 ShouldUpdateDisplayListCurrentWindow => 2,
104 UpdateHitTesterAndProcessAgain => 3,
105 ShouldIncrementalRelayout => 4,
106 ShouldRegenerateDomCurrentWindow => 5,
107 ShouldRegenerateDomAllWindows => 6,
108 }
109 }
110}
111
112impl PartialOrd for ProcessEventResult {
113 fn partial_cmp(&self, other: &Self) -> Option<core::cmp::Ordering> {
114 self.order().partial_cmp(&other.order())
115 }
116}
117
118impl Ord for ProcessEventResult {
119 fn cmp(&self, other: &Self) -> core::cmp::Ordering {
120 self.order().cmp(&other.order())
121 }
122}
123
124impl ProcessEventResult {
125 pub fn max_self(self, other: Self) -> Self {
126 self.max(other)
127 }
128}
129
130#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
135#[repr(C)]
136pub enum EventSource {
137 User,
139 Programmatic,
141 Synthetic,
143 Lifecycle,
145}
146
147#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
154#[repr(C)]
155#[derive(Default)]
156pub enum EventPhase {
157 Capture,
159 Target,
161 #[default]
163 Bubble,
164}
165
166
167#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
169#[repr(C)]
170pub enum MouseButton {
171 Left,
172 Middle,
173 Right,
174 Other(u8),
175}
176
177#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
179#[repr(C)]
180pub enum ScrollDeltaMode {
181 Pixel,
183 Line,
185 Page,
187}
188
189#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
191#[repr(C)]
192pub enum ScrollDirection {
193 Up,
194 Down,
195 Left,
196 Right,
197}
198
199#[repr(C)]
208#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
209pub struct ScrollIntoViewOptions {
210 pub block: ScrollLogicalPosition,
212 pub inline_axis: ScrollLogicalPosition,
215 pub behavior: ScrollIntoViewBehavior,
217}
218
219impl ScrollIntoViewOptions {
220 #[must_use] pub const fn nearest() -> Self {
222 Self {
223 block: ScrollLogicalPosition::Nearest,
224 inline_axis: ScrollLogicalPosition::Nearest,
225 behavior: ScrollIntoViewBehavior::Auto,
226 }
227 }
228
229 #[must_use] pub const fn center() -> Self {
231 Self {
232 block: ScrollLogicalPosition::Center,
233 inline_axis: ScrollLogicalPosition::Center,
234 behavior: ScrollIntoViewBehavior::Auto,
235 }
236 }
237
238 #[must_use] pub const fn start() -> Self {
240 Self {
241 block: ScrollLogicalPosition::Start,
242 inline_axis: ScrollLogicalPosition::Start,
243 behavior: ScrollIntoViewBehavior::Auto,
244 }
245 }
246
247 #[must_use] pub const fn end() -> Self {
249 Self {
250 block: ScrollLogicalPosition::End,
251 inline_axis: ScrollLogicalPosition::End,
252 behavior: ScrollIntoViewBehavior::Auto,
253 }
254 }
255
256 #[must_use] pub const fn with_instant(mut self) -> Self {
258 self.behavior = ScrollIntoViewBehavior::Instant;
259 self
260 }
261
262 #[must_use] pub const fn with_smooth(mut self) -> Self {
264 self.behavior = ScrollIntoViewBehavior::Smooth;
265 self
266 }
267}
268
269#[repr(C)]
274#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord, Default)]
275pub enum ScrollLogicalPosition {
276 Start,
278 Center,
280 End,
282 #[default]
284 Nearest,
285}
286
287#[repr(C)]
292#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord, Default)]
293pub enum ScrollIntoViewBehavior {
294 #[default]
296 Auto,
297 Instant,
299 Smooth,
301}
302
303#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
305#[repr(C)]
306pub enum LifecycleReason {
307 InitialMount,
309 Remount,
311 Resize,
313 Update,
315 Unmount,
317}
318
319#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, Default)]
321#[repr(C)]
322pub struct KeyModifiers {
323 pub shift: bool,
324 pub ctrl: bool,
325 pub alt: bool,
326 pub meta: bool,
327}
328
329impl KeyModifiers {
330 #[must_use] pub fn new() -> Self {
331 Self::default()
332 }
333
334 #[must_use] pub const fn with_shift(mut self) -> Self {
335 self.shift = true;
336 self
337 }
338
339 #[must_use] pub const fn with_ctrl(mut self) -> Self {
340 self.ctrl = true;
341 self
342 }
343
344 #[must_use] pub const fn with_alt(mut self) -> Self {
345 self.alt = true;
346 self
347 }
348
349 #[must_use] pub const fn with_meta(mut self) -> Self {
350 self.meta = true;
351 self
352 }
353
354 #[must_use] pub const fn is_empty(&self) -> bool {
355 !self.shift && !self.ctrl && !self.alt && !self.meta
356 }
357}
358
359#[derive(Debug, Clone, Copy, PartialEq, Eq)]
361pub struct MouseEventData {
362 pub position: LogicalPosition,
364 pub button: MouseButton,
366 pub buttons: u8,
368 pub modifiers: KeyModifiers,
370}
371
372#[derive(Debug, Clone, Copy, PartialEq, Eq)]
374pub struct KeyboardEventData {
375 pub key_code: u32,
377 pub char_code: Option<char>,
379 pub modifiers: KeyModifiers,
381 pub repeat: bool,
383}
384
385#[derive(Debug, Clone, Copy, PartialEq, Eq)]
387pub struct ScrollEventData {
388 pub delta: LogicalPosition,
390 pub delta_mode: ScrollDeltaMode,
392}
393
394#[derive(Debug, Clone, Copy, PartialEq)]
396pub struct TouchEventData {
397 pub id: u64,
399 pub position: LogicalPosition,
401 pub force: f32,
403}
404
405#[derive(Debug, Clone, PartialEq, Eq)]
407pub struct ClipboardEventData {
408 pub content: Option<String>,
410}
411
412#[derive(Debug, Clone, Copy, PartialEq, Eq)]
414pub struct LifecycleEventData {
415 pub reason: LifecycleReason,
417 pub previous_bounds: Option<LogicalRect>,
419 pub current_bounds: LogicalRect,
421}
422
423#[derive(Debug, Clone, Copy, PartialEq, Eq)]
425pub struct WindowEventData {
426 pub size: Option<LogicalRect>,
428 pub position: Option<LogicalPosition>,
430}
431
432#[derive(Debug, Clone, PartialEq, Eq)]
440pub struct TextInputEventData {
441 pub inserted_text: String,
443 pub old_text: String,
445}
446
447#[derive(Debug, Clone, PartialEq)]
449pub enum EventData {
450 Mouse(MouseEventData),
452 Keyboard(KeyboardEventData),
454 Scroll(ScrollEventData),
456 Touch(TouchEventData),
458 Clipboard(ClipboardEventData),
460 TextInput(TextInputEventData),
462 Lifecycle(LifecycleEventData),
464 Window(WindowEventData),
466 None,
468}
469
470#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
476#[repr(C)]
477pub enum EventType {
478 MouseOver,
481 MouseEnter,
483 MouseLeave,
485 MouseOut,
487 MouseDown,
489 MouseUp,
491 Click,
493 DoubleClick,
495 ContextMenu,
497
498 KeyDown,
501 KeyUp,
503 KeyPress,
505
506 CompositionStart,
509 CompositionUpdate,
511 CompositionEnd,
513
514 Focus,
517 Blur,
519 FocusIn,
521 FocusOut,
523
524 Input,
527 Change,
529 Submit,
531 Reset,
533 Invalid,
535
536 Scroll,
539 ScrollStart,
541 ScrollEnd,
543
544 DragStart,
547 Drag,
549 DragEnd,
551 DragEnter,
553 DragOver,
555 DragLeave,
557 Drop,
559
560 TouchStart,
563 TouchMove,
565 TouchEnd,
567 TouchCancel,
569
570 PenDown,
573 PenMove,
575 PenUp,
577 PenEnter,
579 PenLeave,
581
582 LongPress,
585 SwipeLeft,
587 SwipeRight,
589 SwipeUp,
591 SwipeDown,
593 PinchIn,
595 PinchOut,
597 RotateClockwise,
599 RotateCounterClockwise,
601
602 Copy,
605 Cut,
607 Paste,
609
610 Play,
613 Pause,
615 Ended,
617 TimeUpdate,
619 VolumeChange,
621 MediaError,
623
624 Mount,
627 Unmount,
629 Update,
631 Resize,
633
634 WindowResize,
637 WindowMove,
639 WindowClose,
641 WindowFocusIn,
643 WindowFocusOut,
645 ThemeChange,
647 WindowDpiChanged,
649 WindowMonitorChanged,
651
652 MonitorConnected,
655 MonitorDisconnected,
657
658 FileHover,
661 FileDrop,
663 FileHoverCancel,
665
666 SensorChanged,
670 GamepadInput,
673
674 GeolocationFix,
680 GeolocationError,
683
684 PermissionChanged,
691 BiometricResult,
694 KeyringResult,
697}
698
699#[derive(Debug, Clone, PartialEq)]
704pub struct SyntheticEvent {
705 pub event_type: EventType,
707
708 pub source: EventSource,
710
711 pub phase: EventPhase,
713
714 pub target: DomNodeId,
716
717 pub current_target: DomNodeId,
719
720 pub timestamp: Instant,
722
723 pub data: EventData,
725
726 pub stopped: bool,
728
729 pub stopped_immediate: bool,
731
732 pub prevented_default: bool,
734}
735
736impl SyntheticEvent {
737 #[must_use] pub const fn new(
742 event_type: EventType,
743 source: EventSource,
744 target: DomNodeId,
745 timestamp: Instant,
746 data: EventData,
747 ) -> Self {
748 Self {
749 event_type,
750 source,
751 phase: EventPhase::Target,
752 target,
753 current_target: target,
754 timestamp,
755 data,
756 stopped: false,
757 stopped_immediate: false,
758 prevented_default: false,
759 }
760 }
761
762 pub const fn stop_propagation(&mut self) {
767 self.stopped = true;
768 }
769
770 pub const fn stop_immediate_propagation(&mut self) {
775 self.stopped_immediate = true;
776 self.stopped = true;
777 }
778
779 pub const fn prevent_default(&mut self) {
784 self.prevented_default = true;
785 }
786
787 #[must_use] pub const fn is_propagation_stopped(&self) -> bool {
789 self.stopped
790 }
791
792 #[must_use] pub const fn is_immediate_propagation_stopped(&self) -> bool {
794 self.stopped_immediate
795 }
796
797 #[must_use] pub const fn is_default_prevented(&self) -> bool {
799 self.prevented_default
800 }
801}
802
803#[derive(Debug, Clone)]
805#[derive(Default)]
806pub struct PropagationResult {
807 pub callbacks_to_invoke: Vec<(NodeId, EventFilter)>,
809 pub default_prevented: bool,
811}
812
813#[must_use] pub fn get_dom_path(
820 node_hierarchy: &crate::id::NodeHierarchy,
821 target_node: NodeHierarchyItemId,
822) -> Vec<NodeId> {
823 let mut path = Vec::new();
824 let Some(target_node_id) = target_node.into_crate_internal() else {
825 return path;
826 };
827
828 let hier_ref = node_hierarchy.as_ref();
829
830 let node_count = hier_ref.len();
835 let mut visited: BTreeSet<NodeId> = BTreeSet::new();
836 let mut current = Some(target_node_id);
837 while let Some(node_id) = current {
838 if path.len() > node_count || !visited.insert(node_id) {
839 break;
841 }
842 path.push(node_id);
843 current = hier_ref.get(node_id).and_then(|node| node.parent);
844 }
845
846 path.reverse();
848 path
849}
850
851pub fn propagate_event(
865 event: &mut SyntheticEvent,
866 node_hierarchy: &crate::id::NodeHierarchy,
867 callbacks: &BTreeMap<NodeId, Vec<EventFilter>>,
868) -> PropagationResult {
869 let path = get_dom_path(node_hierarchy, event.target.node);
870 if path.is_empty() {
871 return PropagationResult::default();
872 }
873
874 let ancestors = &path[..path.len().saturating_sub(1)];
875 let target_node_id = *path.last().unwrap();
876
877 let mut result = PropagationResult::default();
878
879 propagate_phase(
881 event,
882 ancestors.iter().copied(),
883 EventPhase::Capture,
884 callbacks,
885 &mut result,
886 );
887
888 if !event.stopped {
890 propagate_target_phase(event, target_node_id, callbacks, &mut result);
891 }
892
893 if !event.stopped {
895 propagate_phase(
896 event,
897 ancestors.iter().rev().copied(),
898 EventPhase::Bubble,
899 callbacks,
900 &mut result,
901 );
902 }
903
904 result.default_prevented = event.prevented_default;
905 result
906}
907
908fn propagate_phase(
910 event: &mut SyntheticEvent,
911 nodes: impl Iterator<Item = NodeId>,
912 phase: EventPhase,
913 callbacks: &BTreeMap<NodeId, Vec<EventFilter>>,
914 result: &mut PropagationResult,
915) {
916 event.phase = phase;
917
918 for node_id in nodes {
919 if event.stopped_immediate || event.stopped {
920 return;
921 }
922
923 event.current_target = DomNodeId {
924 dom: event.target.dom,
925 node: NodeHierarchyItemId::from_crate_internal(Some(node_id)),
926 };
927
928 collect_matching_callbacks(event, node_id, phase, callbacks, result);
929 }
930}
931
932fn propagate_target_phase(
934 event: &mut SyntheticEvent,
935 target_node_id: NodeId,
936 callbacks: &BTreeMap<NodeId, Vec<EventFilter>>,
937 result: &mut PropagationResult,
938) {
939 event.phase = EventPhase::Target;
940 event.current_target = event.target;
941
942 collect_matching_callbacks(event, target_node_id, EventPhase::Target, callbacks, result);
943}
944
945fn collect_matching_callbacks(
947 event: &SyntheticEvent,
948 node_id: NodeId,
949 phase: EventPhase,
950 callbacks: &BTreeMap<NodeId, Vec<EventFilter>>,
951 result: &mut PropagationResult,
952) {
953 let Some(node_callbacks) = callbacks.get(&node_id) else {
954 return;
955 };
956
957 let matching = node_callbacks
958 .iter()
959 .take_while(|_| !event.stopped_immediate)
960 .filter(|filter| matches_filter_phase(**filter, event, phase))
961 .map(|filter| (node_id, *filter));
962
963 result.callbacks_to_invoke.extend(matching);
964}
965
966
967#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
986#[repr(C, u8)]
987pub enum DefaultAction {
988 FocusNext,
990 FocusPrevious,
992 FocusFirst,
994 FocusLast,
996 ClearFocus,
998 ActivateFocusedElement {
1001 target: DomNodeId,
1002 },
1003 SubmitForm {
1005 form_node: DomNodeId,
1006 },
1007 CloseModal {
1009 modal_node: DomNodeId,
1010 },
1011 ScrollFocusedContainer {
1013 direction: ScrollDirection,
1014 amount: ScrollAmount,
1015 },
1016 SelectAllText,
1018 None,
1020}
1021
1022#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
1024#[repr(C)]
1025pub enum ScrollAmount {
1026 Line,
1028 Page,
1030 Document,
1032}
1033
1034#[derive(Debug, Clone, Copy)]
1041#[repr(C)]
1042pub struct DefaultActionResult {
1043 pub action: DefaultAction,
1045 pub prevented: bool,
1047}
1048
1049impl Default for DefaultActionResult {
1050 fn default() -> Self {
1051 Self {
1052 action: DefaultAction::None,
1053 prevented: false,
1054 }
1055 }
1056}
1057
1058impl DefaultActionResult {
1059 #[must_use] pub const fn new(action: DefaultAction) -> Self {
1061 Self {
1062 action,
1063 prevented: false,
1064 }
1065 }
1066
1067 #[must_use] pub const fn prevented() -> Self {
1069 Self {
1070 action: DefaultAction::None,
1071 prevented: true,
1072 }
1073 }
1074
1075 #[must_use] pub const fn has_action(&self) -> bool {
1077 !self.prevented && !matches!(self.action, DefaultAction::None)
1078 }
1079}
1080
1081pub trait ActivationBehavior {
1093 fn has_activation_behavior(&self) -> bool;
1095
1096 fn is_activatable(&self) -> bool;
1099}
1100
1101pub trait Focusable {
1103 fn get_tabindex(&self) -> Option<i32>;
1105
1106 fn is_focusable(&self) -> bool;
1108
1109 fn is_in_tab_order(&self) -> bool {
1111 self.get_tabindex().map_or_else(|| self.is_naturally_focusable(), |i| i >= 0)
1112 }
1113
1114 fn is_naturally_focusable(&self) -> bool;
1117}
1118
1119fn matches_filter_phase(
1124 filter: EventFilter,
1125 event: &SyntheticEvent,
1126 current_phase: EventPhase,
1127) -> bool {
1128 if matches!(current_phase, EventPhase::Capture) {
1136 return false;
1137 }
1138
1139 match filter {
1140 EventFilter::Hover(hover_filter) => {
1141 matches_hover_filter(hover_filter, event, current_phase)
1142 }
1143 EventFilter::Focus(focus_filter) => {
1144 matches_focus_filter(focus_filter, event, current_phase)
1145 }
1146 EventFilter::Window(window_filter) => {
1147 matches_window_filter(window_filter, event, current_phase)
1148 }
1149 EventFilter::Component(component_filter) => {
1150 matches_component_filter(component_filter, event, current_phase)
1151 }
1152 EventFilter::Application(_) => {
1153 false
1155 }
1156 }
1157}
1158
1159const fn matches_component_filter(
1167 filter: ComponentEventFilter,
1168 event: &SyntheticEvent,
1169 _phase: EventPhase,
1170) -> bool {
1171 matches!(
1172 (filter, &event.event_type),
1173 (ComponentEventFilter::AfterMount, EventType::Mount)
1174 | (ComponentEventFilter::BeforeUnmount, EventType::Unmount)
1175 | (ComponentEventFilter::Updated, EventType::Update)
1176 | (ComponentEventFilter::NodeResized, EventType::Resize)
1177 )
1178}
1179
1180fn check_mouse_button(data: &EventData, expected: MouseButton) -> bool {
1182 if let EventData::Mouse(mouse_data) = data {
1183 mouse_data.button == expected
1184 } else {
1185 false
1186 }
1187}
1188
1189#[allow(clippy::match_same_arms)]
1194fn matches_hover_filter(
1195 filter: HoverEventFilter,
1196 event: &SyntheticEvent,
1197 _phase: EventPhase,
1198) -> bool {
1199 use HoverEventFilter::{MouseOver, MouseDown, LeftMouseDown, RightMouseDown, MiddleMouseDown, MouseUp, LeftMouseUp, RightMouseUp, MiddleMouseUp, MouseEnter, MouseLeave, Scroll, ScrollStart, ScrollEnd, TextInput, VirtualKeyDown, VirtualKeyUp, HoveredFile, DroppedFile, HoveredFileCancelled, TouchStart, TouchMove, TouchEnd, TouchCancel, PenDown, PenMove, PenUp, PenEnter, PenLeave, DragStart, Drag, DragEnd, DragEnter, DragOver, DragLeave, Drop, DoubleClick, SensorChanged, GamepadInput, GeolocationFix, GeolocationError, PermissionChanged, BiometricResult, KeyringResult};
1200
1201 match (filter, &event.event_type) {
1202 (MouseOver, EventType::MouseOver) => true,
1203 (MouseDown, EventType::MouseDown) => true,
1204 (LeftMouseDown, EventType::MouseDown) => check_mouse_button(&event.data, MouseButton::Left),
1205 (RightMouseDown, EventType::MouseDown) => {
1206 check_mouse_button(&event.data, MouseButton::Right)
1207 }
1208 (MiddleMouseDown, EventType::MouseDown) => {
1209 check_mouse_button(&event.data, MouseButton::Middle)
1210 }
1211 (MouseUp, EventType::MouseUp) => true,
1212 (LeftMouseUp, EventType::MouseUp) => check_mouse_button(&event.data, MouseButton::Left),
1213 (RightMouseUp, EventType::MouseUp) => check_mouse_button(&event.data, MouseButton::Right),
1214 (MiddleMouseUp, EventType::MouseUp) => check_mouse_button(&event.data, MouseButton::Middle),
1215 (MouseEnter, EventType::MouseEnter) => true,
1216 (MouseLeave, EventType::MouseLeave) => true,
1217 (Scroll, EventType::Scroll) => true,
1218 (ScrollStart, EventType::ScrollStart) => true,
1219 (ScrollEnd, EventType::ScrollEnd) => true,
1220 (TextInput, EventType::Input) => true,
1221 (VirtualKeyDown, EventType::KeyDown) => true,
1222 (VirtualKeyUp, EventType::KeyUp) => true,
1223 (HoveredFile, EventType::FileHover) => true,
1224 (DroppedFile, EventType::FileDrop) => true,
1225 (HoveredFileCancelled, EventType::FileHoverCancel) => true,
1226 (TouchStart, EventType::TouchStart) => true,
1227 (TouchMove, EventType::TouchMove) => true,
1228 (TouchEnd, EventType::TouchEnd) => true,
1229 (TouchCancel, EventType::TouchCancel) => true,
1230 (PenDown, EventType::PenDown) => true,
1231 (PenMove, EventType::PenMove) => true,
1232 (PenUp, EventType::PenUp) => true,
1233 (PenEnter, EventType::PenEnter) => true,
1234 (PenLeave, EventType::PenLeave) => true,
1235 (DragStart, EventType::DragStart) => true,
1236 (Drag, EventType::Drag) => true,
1237 (DragEnd, EventType::DragEnd) => true,
1238 (DragEnter, EventType::DragEnter) => true,
1239 (DragOver, EventType::DragOver) => true,
1240 (DragLeave, EventType::DragLeave) => true,
1241 (Drop, EventType::Drop) => true,
1242 (DoubleClick, EventType::DoubleClick) => true,
1243 (SensorChanged, EventType::SensorChanged) => true,
1244 (GamepadInput, EventType::GamepadInput) => true,
1245 (GeolocationFix, EventType::GeolocationFix) => true,
1246 (GeolocationError, EventType::GeolocationError) => true,
1247 (PermissionChanged, EventType::PermissionChanged) => true,
1248 (BiometricResult, EventType::BiometricResult) => true,
1249 (KeyringResult, EventType::KeyringResult) => true,
1250 _ => false,
1251 }
1252}
1253
1254#[allow(clippy::match_same_arms)]
1257fn matches_focus_filter(
1258 filter: FocusEventFilter,
1259 event: &SyntheticEvent,
1260 _phase: EventPhase,
1261) -> bool {
1262 use FocusEventFilter::{MouseOver, MouseDown, LeftMouseDown, RightMouseDown, MiddleMouseDown, MouseUp, LeftMouseUp, RightMouseUp, MiddleMouseUp, MouseEnter, MouseLeave, Scroll, ScrollStart, ScrollEnd, TextInput, VirtualKeyDown, VirtualKeyUp, FocusReceived, FocusLost, DragStart, Drag, DragEnd, DragEnter, DragOver, DragLeave, Drop};
1263
1264 match (filter, &event.event_type) {
1265 (MouseOver, EventType::MouseOver) => true,
1266 (MouseDown, EventType::MouseDown) => true,
1267 (LeftMouseDown, EventType::MouseDown) => check_mouse_button(&event.data, MouseButton::Left),
1268 (RightMouseDown, EventType::MouseDown) => {
1269 check_mouse_button(&event.data, MouseButton::Right)
1270 }
1271 (MiddleMouseDown, EventType::MouseDown) => {
1272 check_mouse_button(&event.data, MouseButton::Middle)
1273 }
1274 (MouseUp, EventType::MouseUp) => true,
1275 (LeftMouseUp, EventType::MouseUp) => check_mouse_button(&event.data, MouseButton::Left),
1276 (RightMouseUp, EventType::MouseUp) => check_mouse_button(&event.data, MouseButton::Right),
1277 (MiddleMouseUp, EventType::MouseUp) => check_mouse_button(&event.data, MouseButton::Middle),
1278 (MouseEnter, EventType::MouseEnter) => true,
1279 (MouseLeave, EventType::MouseLeave) => true,
1280 (Scroll, EventType::Scroll) => true,
1281 (ScrollStart, EventType::ScrollStart) => true,
1282 (ScrollEnd, EventType::ScrollEnd) => true,
1283 (TextInput, EventType::Input) => true,
1284 (VirtualKeyDown, EventType::KeyDown) => true,
1285 (VirtualKeyUp, EventType::KeyUp) => true,
1286 (FocusReceived, EventType::Focus) => true,
1287 (FocusLost, EventType::Blur) => true,
1288 (DragStart, EventType::DragStart) => true,
1289 (Drag, EventType::Drag) => true,
1290 (DragEnd, EventType::DragEnd) => true,
1291 (DragEnter, EventType::DragEnter) => true,
1292 (DragOver, EventType::DragOver) => true,
1293 (DragLeave, EventType::DragLeave) => true,
1294 (Drop, EventType::Drop) => true,
1295 (FocusEventFilter::Copy, EventType::Copy) => true,
1299 (FocusEventFilter::Cut, EventType::Cut) => true,
1300 (FocusEventFilter::Paste, EventType::Paste) => true,
1301 _ => false,
1302 }
1303}
1304
1305#[allow(clippy::match_same_arms)]
1308fn matches_window_filter(
1309 filter: WindowEventFilter,
1310 event: &SyntheticEvent,
1311 _phase: EventPhase,
1312) -> bool {
1313 use WindowEventFilter::{MouseOver, MouseDown, LeftMouseDown, RightMouseDown, MiddleMouseDown, MouseUp, LeftMouseUp, RightMouseUp, MiddleMouseUp, MouseEnter, MouseLeave, Scroll, ScrollStart, ScrollEnd, TextInput, VirtualKeyDown, VirtualKeyUp, HoveredFile, DroppedFile, HoveredFileCancelled, Resized, Moved, TouchStart, TouchMove, TouchEnd, TouchCancel, PenDown, PenMove, PenUp, PenEnter, PenLeave, FocusReceived, FocusLost, CloseRequested, ThemeChanged, WindowFocusReceived, WindowFocusLost, SensorChanged, GamepadInput, GeolocationFix, GeolocationError, PermissionChanged, BiometricResult, KeyringResult, DragStart, Drag, DragEnd, DragEnter, DragOver, DragLeave, Drop};
1314
1315 match (filter, &event.event_type) {
1316 (MouseOver, EventType::MouseOver) => true,
1317 (MouseDown, EventType::MouseDown) => true,
1318 (LeftMouseDown, EventType::MouseDown) => check_mouse_button(&event.data, MouseButton::Left),
1319 (RightMouseDown, EventType::MouseDown) => {
1320 check_mouse_button(&event.data, MouseButton::Right)
1321 }
1322 (MiddleMouseDown, EventType::MouseDown) => {
1323 check_mouse_button(&event.data, MouseButton::Middle)
1324 }
1325 (MouseUp, EventType::MouseUp) => true,
1326 (LeftMouseUp, EventType::MouseUp) => check_mouse_button(&event.data, MouseButton::Left),
1327 (RightMouseUp, EventType::MouseUp) => check_mouse_button(&event.data, MouseButton::Right),
1328 (MiddleMouseUp, EventType::MouseUp) => check_mouse_button(&event.data, MouseButton::Middle),
1329 (MouseEnter, EventType::MouseEnter) => true,
1330 (MouseLeave, EventType::MouseLeave) => true,
1331 (Scroll, EventType::Scroll) => true,
1332 (ScrollStart, EventType::ScrollStart) => true,
1333 (ScrollEnd, EventType::ScrollEnd) => true,
1334 (TextInput, EventType::Input) => true,
1335 (VirtualKeyDown, EventType::KeyDown) => true,
1336 (VirtualKeyUp, EventType::KeyUp) => true,
1337 (HoveredFile, EventType::FileHover) => true,
1338 (DroppedFile, EventType::FileDrop) => true,
1339 (HoveredFileCancelled, EventType::FileHoverCancel) => true,
1340 (Resized, EventType::WindowResize) => true,
1341 (Moved, EventType::WindowMove) => true,
1342 (TouchStart, EventType::TouchStart) => true,
1343 (TouchMove, EventType::TouchMove) => true,
1344 (TouchEnd, EventType::TouchEnd) => true,
1345 (TouchCancel, EventType::TouchCancel) => true,
1346 (PenDown, EventType::PenDown) => true,
1347 (PenMove, EventType::PenMove) => true,
1348 (PenUp, EventType::PenUp) => true,
1349 (PenEnter, EventType::PenEnter) => true,
1350 (PenLeave, EventType::PenLeave) => true,
1351 (FocusReceived, EventType::Focus) => true,
1352 (FocusLost, EventType::Blur) => true,
1353 (CloseRequested, EventType::WindowClose) => true,
1354 (ThemeChanged, EventType::ThemeChange) => true,
1355 (WindowFocusReceived, EventType::WindowFocusIn) => true,
1356 (WindowFocusLost, EventType::WindowFocusOut) => true,
1357 (SensorChanged, EventType::SensorChanged) => true,
1358 (GamepadInput, EventType::GamepadInput) => true,
1359 (GeolocationFix, EventType::GeolocationFix) => true,
1360 (GeolocationError, EventType::GeolocationError) => true,
1361 (PermissionChanged, EventType::PermissionChanged) => true,
1362 (BiometricResult, EventType::BiometricResult) => true,
1363 (KeyringResult, EventType::KeyringResult) => true,
1364 (DragStart, EventType::DragStart) => true,
1365 (Drag, EventType::Drag) => true,
1366 (DragEnd, EventType::DragEnd) => true,
1367 (DragEnter, EventType::DragEnter) => true,
1368 (DragOver, EventType::DragOver) => true,
1369 (DragLeave, EventType::DragLeave) => true,
1370 (Drop, EventType::Drop) => true,
1371 _ => false,
1372 }
1373}
1374
1375#[allow(clippy::needless_pass_by_value)] #[must_use] pub fn detect_lifecycle_events(
1384 old_dom_id: DomId,
1385 new_dom_id: DomId,
1386 old_hierarchy: Option<&crate::id::NodeHierarchy>,
1387 new_hierarchy: Option<&crate::id::NodeHierarchy>,
1388 old_layout: Option<&BTreeMap<NodeId, LogicalRect>>,
1389 new_layout: Option<&BTreeMap<NodeId, LogicalRect>>,
1390 timestamp: Instant,
1391) -> Vec<SyntheticEvent> {
1392 let old_nodes = collect_node_ids(old_hierarchy);
1393 let new_nodes = collect_node_ids(new_hierarchy);
1394
1395 let mut events = Vec::new();
1396
1397 if let Some(layout) = new_layout {
1399 for &node_id in new_nodes.difference(&old_nodes) {
1400 events.push(create_mount_event(node_id, new_dom_id, layout, ×tamp));
1401 }
1402 }
1403
1404 if let Some(layout) = old_layout {
1406 for &node_id in old_nodes.difference(&new_nodes) {
1407 events.push(create_unmount_event(
1408 node_id, old_dom_id, layout, ×tamp,
1409 ));
1410 }
1411 }
1412
1413 if let (Some(old_l), Some(new_l)) = (old_layout, new_layout) {
1415 for &node_id in old_nodes.intersection(&new_nodes) {
1416 if let Some(ev) = create_resize_event(node_id, new_dom_id, old_l, new_l, ×tamp) {
1417 events.push(ev);
1418 }
1419 }
1420 }
1421
1422 events
1423}
1424
1425fn collect_node_ids(hierarchy: Option<&crate::id::NodeHierarchy>) -> BTreeSet<NodeId> {
1426 hierarchy
1427 .map(|h| h.as_ref().linear_iter().collect())
1428 .unwrap_or_default()
1429}
1430
1431fn create_lifecycle_event(
1432 event_type: EventType,
1433 node_id: NodeId,
1434 dom_id: DomId,
1435 timestamp: &Instant,
1436 data: LifecycleEventData,
1437) -> SyntheticEvent {
1438 let dom_node_id = DomNodeId {
1439 dom: dom_id,
1440 node: NodeHierarchyItemId::from_crate_internal(Some(node_id)),
1441 };
1442 SyntheticEvent {
1443 event_type,
1444 source: EventSource::Lifecycle,
1445 phase: EventPhase::Target,
1446 target: dom_node_id,
1447 current_target: dom_node_id,
1448 timestamp: timestamp.clone(),
1449 data: EventData::Lifecycle(data),
1450 stopped: false,
1451 stopped_immediate: false,
1452 prevented_default: false,
1453 }
1454}
1455
1456fn create_mount_event(
1457 node_id: NodeId,
1458 dom_id: DomId,
1459 layout: &BTreeMap<NodeId, LogicalRect>,
1460 timestamp: &Instant,
1461) -> SyntheticEvent {
1462 let current_bounds = layout.get(&node_id).copied().unwrap_or(LogicalRect::zero());
1463 create_lifecycle_event(
1464 EventType::Mount,
1465 node_id,
1466 dom_id,
1467 timestamp,
1468 LifecycleEventData {
1469 reason: LifecycleReason::InitialMount,
1470 previous_bounds: None,
1471 current_bounds,
1472 },
1473 )
1474}
1475
1476fn create_unmount_event(
1477 node_id: NodeId,
1478 dom_id: DomId,
1479 layout: &BTreeMap<NodeId, LogicalRect>,
1480 timestamp: &Instant,
1481) -> SyntheticEvent {
1482 let previous_bounds = layout.get(&node_id).copied().unwrap_or(LogicalRect::zero());
1483 create_lifecycle_event(
1484 EventType::Unmount,
1485 node_id,
1486 dom_id,
1487 timestamp,
1488 LifecycleEventData {
1489 reason: LifecycleReason::Unmount,
1490 previous_bounds: Some(previous_bounds),
1491 current_bounds: LogicalRect::zero(),
1492 },
1493 )
1494}
1495
1496fn size_changed(old: crate::geom::LogicalSize, new: crate::geom::LogicalSize) -> bool {
1500 fn dim_changed(a: f32, b: f32) -> bool {
1501 if a.is_nan() && b.is_nan() {
1502 return false;
1503 }
1504 #[allow(clippy::cast_possible_truncation)] let q = |v: f32| -> i64 {
1509 if v.is_nan() {
1510 i64::MIN
1511 } else {
1512 (v * 1000.0) as i64
1513 }
1514 };
1515 q(a) != q(b)
1516 }
1517 dim_changed(old.width, new.width) || dim_changed(old.height, new.height)
1518}
1519
1520fn create_resize_event(
1521 node_id: NodeId,
1522 dom_id: DomId,
1523 old_layout: &BTreeMap<NodeId, LogicalRect>,
1524 new_layout: &BTreeMap<NodeId, LogicalRect>,
1525 timestamp: &Instant,
1526) -> Option<SyntheticEvent> {
1527 let old_bounds = *old_layout.get(&node_id)?;
1528 let new_bounds = *new_layout.get(&node_id)?;
1529
1530 if !size_changed(old_bounds.size, new_bounds.size) {
1536 return None;
1537 }
1538
1539 Some(create_lifecycle_event(
1540 EventType::Resize,
1541 node_id,
1542 dom_id,
1543 timestamp,
1544 LifecycleEventData {
1545 reason: LifecycleReason::Resize,
1546 previous_bounds: Some(old_bounds),
1547 current_bounds: new_bounds,
1548 },
1549 ))
1550}
1551
1552#[derive(Debug, Clone, Default)]
1557pub struct LifecycleEventResult {
1558 pub events: Vec<SyntheticEvent>,
1560 pub node_id_mapping: OrderedMap<NodeId, NodeId>,
1563}
1564
1565#[must_use] pub fn detect_lifecycle_events_with_reconciliation(
1619 dom_id: DomId,
1620 old_node_data: &[crate::dom::NodeData],
1621 new_node_data: &[crate::dom::NodeData],
1622 old_hierarchy: &[crate::styled_dom::NodeHierarchyItem],
1623 new_hierarchy: &[crate::styled_dom::NodeHierarchyItem],
1624 old_layout: &OrderedMap<NodeId, LogicalRect>,
1625 new_layout: &OrderedMap<NodeId, LogicalRect>,
1626 timestamp: Instant,
1627) -> LifecycleEventResult {
1628 let diff_result = crate::diff::reconcile_dom(
1629 old_node_data,
1630 new_node_data,
1631 old_hierarchy,
1632 new_hierarchy,
1633 old_layout,
1634 new_layout,
1635 dom_id,
1636 timestamp,
1637 );
1638
1639 LifecycleEventResult {
1640 events: diff_result.events,
1641 node_id_mapping: crate::diff::create_migration_map(&diff_result.node_moves),
1642 }
1643}
1644
1645#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
1647#[repr(C)]
1648pub enum HoverEventFilter {
1649 MouseOver,
1651 MouseDown,
1653 LeftMouseDown,
1655 RightMouseDown,
1657 MiddleMouseDown,
1659 MouseUp,
1661 LeftMouseUp,
1663 RightMouseUp,
1665 MiddleMouseUp,
1667 MouseEnter,
1669 MouseLeave,
1671 Scroll,
1673 ScrollStart,
1675 ScrollEnd,
1677 TextInput,
1679 VirtualKeyDown,
1681 VirtualKeyUp,
1683 HoveredFile,
1685 DroppedFile,
1687 HoveredFileCancelled,
1689 TouchStart,
1691 TouchMove,
1693 TouchEnd,
1695 TouchCancel,
1697 PenDown,
1699 PenMove,
1701 PenUp,
1703 PenEnter,
1705 PenLeave,
1707 PenSqueeze,
1712 PenDoubleTap,
1715 PenHover,
1721 GeolocationFix,
1725 GeolocationError,
1728 SensorChanged,
1731 GamepadInput,
1734 DragStart,
1736 Drag,
1738 DragEnd,
1740 DragEnter,
1742 DragOver,
1744 DragLeave,
1746 Drop,
1748 DoubleClick,
1750 LongPress,
1752 SwipeLeft,
1754 SwipeRight,
1756 SwipeUp,
1758 SwipeDown,
1760 PinchIn,
1762 PinchOut,
1764 RotateClockwise,
1766 RotateCounterClockwise,
1768
1769 MouseOut,
1772
1773 FocusIn,
1776 FocusOut,
1778
1779 CompositionStart,
1782 CompositionUpdate,
1784 CompositionEnd,
1786
1787 #[doc(hidden)]
1789 SystemTextSingleClick,
1791 #[doc(hidden)]
1792 SystemTextDoubleClick,
1794 #[doc(hidden)]
1795 SystemTextTripleClick,
1797
1798 PermissionChanged,
1802 BiometricResult,
1804 KeyringResult,
1806}
1807
1808impl HoverEventFilter {
1809 #[must_use] pub const fn is_system_internal(&self) -> bool {
1811 matches!(
1812 self,
1813 Self::SystemTextSingleClick
1814 | Self::SystemTextDoubleClick
1815 | Self::SystemTextTripleClick
1816 )
1817 }
1818
1819 #[allow(clippy::match_same_arms)]
1822 #[must_use] pub const fn to_focus_event_filter(&self) -> Option<FocusEventFilter> {
1823 match self {
1824 Self::MouseOver => Some(FocusEventFilter::MouseOver),
1825 Self::MouseDown => Some(FocusEventFilter::MouseDown),
1826 Self::LeftMouseDown => Some(FocusEventFilter::LeftMouseDown),
1827 Self::RightMouseDown => Some(FocusEventFilter::RightMouseDown),
1828 Self::MiddleMouseDown => Some(FocusEventFilter::MiddleMouseDown),
1829 Self::MouseUp => Some(FocusEventFilter::MouseUp),
1830 Self::LeftMouseUp => Some(FocusEventFilter::LeftMouseUp),
1831 Self::RightMouseUp => Some(FocusEventFilter::RightMouseUp),
1832 Self::MiddleMouseUp => Some(FocusEventFilter::MiddleMouseUp),
1833 Self::MouseEnter => Some(FocusEventFilter::MouseEnter),
1834 Self::MouseLeave => Some(FocusEventFilter::MouseLeave),
1835 Self::Scroll => Some(FocusEventFilter::Scroll),
1836 Self::ScrollStart => Some(FocusEventFilter::ScrollStart),
1837 Self::ScrollEnd => Some(FocusEventFilter::ScrollEnd),
1838 Self::TextInput => Some(FocusEventFilter::TextInput),
1839 Self::VirtualKeyDown => Some(FocusEventFilter::VirtualKeyDown),
1840 Self::VirtualKeyUp => Some(FocusEventFilter::VirtualKeyUp),
1841 Self::HoveredFile => None,
1842 Self::DroppedFile => None,
1843 Self::HoveredFileCancelled => None,
1844 Self::TouchStart => None,
1845 Self::TouchMove => None,
1846 Self::TouchEnd => None,
1847 Self::TouchCancel => None,
1848 Self::PenDown => Some(FocusEventFilter::PenDown),
1849 Self::PenMove => Some(FocusEventFilter::PenMove),
1850 Self::PenUp => Some(FocusEventFilter::PenUp),
1851 Self::PenEnter => None,
1852 Self::PenLeave => None,
1853 Self::PenSqueeze => None,
1854 Self::PenDoubleTap => None,
1855 Self::PenHover => None,
1856 Self::GeolocationFix => None,
1857 Self::GeolocationError => None,
1858 Self::SensorChanged => None,
1859 Self::GamepadInput => None,
1860 Self::DragStart => Some(FocusEventFilter::DragStart),
1861 Self::Drag => Some(FocusEventFilter::Drag),
1862 Self::DragEnd => Some(FocusEventFilter::DragEnd),
1863 Self::DragEnter => Some(FocusEventFilter::DragEnter),
1864 Self::DragOver => Some(FocusEventFilter::DragOver),
1865 Self::DragLeave => Some(FocusEventFilter::DragLeave),
1866 Self::Drop => Some(FocusEventFilter::Drop),
1867 Self::DoubleClick => Some(FocusEventFilter::DoubleClick),
1868 Self::LongPress => Some(FocusEventFilter::LongPress),
1869 Self::SwipeLeft => Some(FocusEventFilter::SwipeLeft),
1870 Self::SwipeRight => Some(FocusEventFilter::SwipeRight),
1871 Self::SwipeUp => Some(FocusEventFilter::SwipeUp),
1872 Self::SwipeDown => Some(FocusEventFilter::SwipeDown),
1873 Self::PinchIn => Some(FocusEventFilter::PinchIn),
1874 Self::PinchOut => Some(FocusEventFilter::PinchOut),
1875 Self::RotateClockwise => Some(FocusEventFilter::RotateClockwise),
1876 Self::RotateCounterClockwise => {
1877 Some(FocusEventFilter::RotateCounterClockwise)
1878 }
1879 Self::MouseOut => Some(FocusEventFilter::MouseLeave), Self::FocusIn => Some(FocusEventFilter::FocusIn),
1881 Self::FocusOut => Some(FocusEventFilter::FocusOut),
1882 Self::CompositionStart => Some(FocusEventFilter::CompositionStart),
1883 Self::CompositionUpdate => Some(FocusEventFilter::CompositionUpdate),
1884 Self::CompositionEnd => Some(FocusEventFilter::CompositionEnd),
1885 Self::SystemTextSingleClick => None,
1887 Self::SystemTextDoubleClick => None,
1888 Self::SystemTextTripleClick => None,
1889 Self::PermissionChanged => None,
1891 Self::BiometricResult => None,
1892 Self::KeyringResult => None,
1893 }
1894 }
1895}
1896
1897#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
1902#[repr(C)]
1903pub enum FocusEventFilter {
1904 MouseOver,
1906 MouseDown,
1908 LeftMouseDown,
1910 RightMouseDown,
1912 MiddleMouseDown,
1914 MouseUp,
1916 LeftMouseUp,
1918 RightMouseUp,
1920 MiddleMouseUp,
1922 MouseEnter,
1924 MouseLeave,
1926 Scroll,
1928 ScrollStart,
1930 ScrollEnd,
1932 TextInput,
1934 VirtualKeyDown,
1936 VirtualKeyUp,
1938 FocusReceived,
1940 FocusLost,
1942 PenDown,
1944 PenMove,
1946 PenUp,
1948 DragStart,
1950 Drag,
1952 DragEnd,
1954 DragEnter,
1956 DragOver,
1958 DragLeave,
1960 Drop,
1962 DoubleClick,
1964 LongPress,
1966 SwipeLeft,
1968 SwipeRight,
1970 SwipeUp,
1972 SwipeDown,
1974 PinchIn,
1976 PinchOut,
1978 RotateClockwise,
1980 RotateCounterClockwise,
1982
1983 FocusIn,
1986 FocusOut,
1988
1989 CompositionStart,
1992 CompositionUpdate,
1994 CompositionEnd,
1996
1997 Copy,
2003 Cut,
2005 Paste,
2007}
2008
2009#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
2012#[repr(C)]
2013pub enum WindowEventFilter {
2014 MouseOver,
2016 MouseDown,
2018 LeftMouseDown,
2020 RightMouseDown,
2022 MiddleMouseDown,
2024 MouseUp,
2026 LeftMouseUp,
2028 RightMouseUp,
2030 MiddleMouseUp,
2032 MouseEnter,
2034 MouseLeave,
2036 Scroll,
2038 ScrollStart,
2040 ScrollEnd,
2042 TextInput,
2044 VirtualKeyDown,
2046 VirtualKeyUp,
2048 HoveredFile,
2050 DroppedFile,
2052 HoveredFileCancelled,
2054 Resized,
2056 Moved,
2058 TouchStart,
2060 TouchMove,
2062 TouchEnd,
2064 TouchCancel,
2066 FocusReceived,
2068 FocusLost,
2070 CloseRequested,
2072 ThemeChanged,
2074 WindowFocusReceived,
2076 WindowFocusLost,
2078 PenDown,
2080 PenMove,
2082 PenUp,
2084 PenEnter,
2086 PenLeave,
2088 PenSqueeze,
2091 PenDoubleTap,
2094 PenHover,
2097 GeolocationFix,
2105 GeolocationError,
2108 SensorChanged,
2111 GamepadInput,
2114 DragStart,
2116 Drag,
2118 DragEnd,
2120 DragEnter,
2122 DragOver,
2124 DragLeave,
2126 Drop,
2128 DoubleClick,
2130 LongPress,
2132 SwipeLeft,
2134 SwipeRight,
2136 SwipeUp,
2138 SwipeDown,
2140 PinchIn,
2142 PinchOut,
2144 RotateClockwise,
2146 RotateCounterClockwise,
2148 DpiChanged,
2151 MonitorChanged,
2154
2155 PermissionChanged,
2159 BiometricResult,
2161 KeyringResult,
2163}
2164
2165impl WindowEventFilter {
2166 #[allow(clippy::match_same_arms)]
2168 #[must_use] pub const fn to_hover_event_filter(&self) -> Option<HoverEventFilter> {
2169 match self {
2170 Self::MouseOver => Some(HoverEventFilter::MouseOver),
2171 Self::MouseDown => Some(HoverEventFilter::MouseDown),
2172 Self::LeftMouseDown => Some(HoverEventFilter::LeftMouseDown),
2173 Self::RightMouseDown => Some(HoverEventFilter::RightMouseDown),
2174 Self::MiddleMouseDown => Some(HoverEventFilter::MiddleMouseDown),
2175 Self::MouseUp => Some(HoverEventFilter::MouseUp),
2176 Self::LeftMouseUp => Some(HoverEventFilter::LeftMouseUp),
2177 Self::RightMouseUp => Some(HoverEventFilter::RightMouseUp),
2178 Self::MiddleMouseUp => Some(HoverEventFilter::MiddleMouseUp),
2179 Self::Scroll => Some(HoverEventFilter::Scroll),
2180 Self::ScrollStart => Some(HoverEventFilter::ScrollStart),
2181 Self::ScrollEnd => Some(HoverEventFilter::ScrollEnd),
2182 Self::TextInput => Some(HoverEventFilter::TextInput),
2183 Self::VirtualKeyDown => Some(HoverEventFilter::VirtualKeyDown),
2184 Self::VirtualKeyUp => Some(HoverEventFilter::VirtualKeyUp),
2185 Self::HoveredFile => Some(HoverEventFilter::HoveredFile),
2186 Self::DroppedFile => Some(HoverEventFilter::DroppedFile),
2187 Self::HoveredFileCancelled => Some(HoverEventFilter::HoveredFileCancelled),
2188 Self::MouseEnter => None,
2191 Self::MouseLeave => None,
2192 Self::Resized => None,
2193 Self::Moved => None,
2194 Self::TouchStart => Some(HoverEventFilter::TouchStart),
2195 Self::TouchMove => Some(HoverEventFilter::TouchMove),
2196 Self::TouchEnd => Some(HoverEventFilter::TouchEnd),
2197 Self::TouchCancel => Some(HoverEventFilter::TouchCancel),
2198 Self::FocusReceived => None,
2199 Self::FocusLost => None,
2200 Self::CloseRequested => None,
2201 Self::ThemeChanged => None,
2202 Self::WindowFocusReceived => None, Self::WindowFocusLost => None, Self::PenDown => Some(HoverEventFilter::PenDown),
2205 Self::PenMove => Some(HoverEventFilter::PenMove),
2206 Self::PenUp => Some(HoverEventFilter::PenUp),
2207 Self::PenEnter => Some(HoverEventFilter::PenEnter),
2208 Self::PenLeave => Some(HoverEventFilter::PenLeave),
2209 Self::PenSqueeze => Some(HoverEventFilter::PenSqueeze),
2210 Self::PenDoubleTap => Some(HoverEventFilter::PenDoubleTap),
2211 Self::PenHover => Some(HoverEventFilter::PenHover),
2212 Self::GeolocationFix => Some(HoverEventFilter::GeolocationFix),
2213 Self::GeolocationError => Some(HoverEventFilter::GeolocationError),
2214 Self::SensorChanged => Some(HoverEventFilter::SensorChanged),
2215 Self::GamepadInput => Some(HoverEventFilter::GamepadInput),
2216 Self::DragStart => Some(HoverEventFilter::DragStart),
2217 Self::Drag => Some(HoverEventFilter::Drag),
2218 Self::DragEnd => Some(HoverEventFilter::DragEnd),
2219 Self::DragEnter => Some(HoverEventFilter::DragEnter),
2220 Self::DragOver => Some(HoverEventFilter::DragOver),
2221 Self::DragLeave => Some(HoverEventFilter::DragLeave),
2222 Self::Drop => Some(HoverEventFilter::Drop),
2223 Self::DoubleClick => Some(HoverEventFilter::DoubleClick),
2224 Self::LongPress => Some(HoverEventFilter::LongPress),
2225 Self::SwipeLeft => Some(HoverEventFilter::SwipeLeft),
2226 Self::SwipeRight => Some(HoverEventFilter::SwipeRight),
2227 Self::SwipeUp => Some(HoverEventFilter::SwipeUp),
2228 Self::SwipeDown => Some(HoverEventFilter::SwipeDown),
2229 Self::PinchIn => Some(HoverEventFilter::PinchIn),
2230 Self::PinchOut => Some(HoverEventFilter::PinchOut),
2231 Self::RotateClockwise => Some(HoverEventFilter::RotateClockwise),
2232 Self::RotateCounterClockwise => {
2233 Some(HoverEventFilter::RotateCounterClockwise)
2234 }
2235 Self::DpiChanged => None,
2237 Self::MonitorChanged => None,
2238 Self::PermissionChanged => Some(HoverEventFilter::PermissionChanged),
2240 Self::BiometricResult => Some(HoverEventFilter::BiometricResult),
2241 Self::KeyringResult => Some(HoverEventFilter::KeyringResult),
2242 }
2243 }
2244}
2245
2246#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
2248#[repr(C)]
2249pub enum ComponentEventFilter {
2250 AfterMount,
2252 BeforeUnmount,
2254 NodeResized,
2256 DefaultAction,
2258 Selected,
2260 Updated,
2262}
2263
2264#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
2266#[repr(C)]
2267pub enum ApplicationEventFilter {
2268 DeviceConnected,
2270 DeviceDisconnected,
2272 MonitorConnected,
2275 MonitorDisconnected,
2277}
2278
2279#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
2284#[repr(C, u8)]
2285pub enum EventFilter {
2286 Hover(HoverEventFilter),
2289 Focus(FocusEventFilter),
2291 Window(WindowEventFilter),
2301 Component(ComponentEventFilter),
2303 Application(ApplicationEventFilter),
2305}
2306
2307impl EventFilter {
2308 #[must_use] pub const fn is_focus_callback(&self) -> bool {
2309 matches!(self, Self::Focus(_))
2310 }
2311 #[must_use] pub const fn is_window_callback(&self) -> bool {
2312 matches!(self, Self::Window(_))
2313 }
2314}
2315
2316macro_rules! get_single_enum_type {
2319 ($fn_name:ident, $enum_name:ident:: $variant:ident($return_type:ty)) => {
2320 #[must_use] pub const fn $fn_name(&self) -> Option<$return_type> {
2321 use self::$enum_name::*;
2322 match self {
2323 $variant(e) => Some(*e),
2324 _ => None,
2325 }
2326 }
2327 };
2328}
2329
2330impl EventFilter {
2331 get_single_enum_type!(as_hover_event_filter, EventFilter::Hover(HoverEventFilter));
2332 get_single_enum_type!(as_focus_event_filter, EventFilter::Focus(FocusEventFilter));
2333 get_single_enum_type!(
2334 as_window_event_filter,
2335 EventFilter::Window(WindowEventFilter)
2336 );
2337}
2338
2339impl From<On> for EventFilter {
2345 #[allow(clippy::match_same_arms)]
2349 fn from(input: On) -> Self {
2350 use crate::dom::On::{MouseOver, MouseDown, LeftMouseDown, MiddleMouseDown, RightMouseDown, MouseUp, LeftMouseUp, MiddleMouseUp, RightMouseUp, MouseEnter, MouseLeave, Scroll, TextInput, VirtualKeyDown, VirtualKeyUp, HoveredFile, DroppedFile, HoveredFileCancelled, FocusReceived, FocusLost, Default, Collapse, Expand, Increment, Decrement};
2351 match input {
2352 MouseOver => Self::Hover(HoverEventFilter::MouseOver),
2353 MouseDown => Self::Hover(HoverEventFilter::MouseDown),
2354 LeftMouseDown => Self::Hover(HoverEventFilter::LeftMouseDown),
2355 MiddleMouseDown => Self::Hover(HoverEventFilter::MiddleMouseDown),
2356 RightMouseDown => Self::Hover(HoverEventFilter::RightMouseDown),
2357 MouseUp => Self::Hover(HoverEventFilter::MouseUp),
2358 LeftMouseUp => Self::Hover(HoverEventFilter::LeftMouseUp),
2359 MiddleMouseUp => Self::Hover(HoverEventFilter::MiddleMouseUp),
2360 RightMouseUp => Self::Hover(HoverEventFilter::RightMouseUp),
2361
2362 MouseEnter => Self::Hover(HoverEventFilter::MouseEnter),
2363 MouseLeave => Self::Hover(HoverEventFilter::MouseLeave),
2364 Scroll => Self::Hover(HoverEventFilter::Scroll),
2365 TextInput => Self::Focus(FocusEventFilter::TextInput), VirtualKeyDown => Self::Window(WindowEventFilter::VirtualKeyDown), VirtualKeyUp => Self::Window(WindowEventFilter::VirtualKeyUp), HoveredFile => Self::Hover(HoverEventFilter::HoveredFile),
2369 DroppedFile => Self::Hover(HoverEventFilter::DroppedFile),
2370 HoveredFileCancelled => Self::Hover(HoverEventFilter::HoveredFileCancelled),
2371 FocusReceived => Self::Focus(FocusEventFilter::FocusReceived), FocusLost => Self::Focus(FocusEventFilter::FocusLost), Default => Self::Hover(HoverEventFilter::MouseUp), Collapse => Self::Hover(HoverEventFilter::MouseUp), Expand => Self::Hover(HoverEventFilter::MouseUp), Increment => Self::Hover(HoverEventFilter::MouseUp), Decrement => Self::Hover(HoverEventFilter::MouseUp), }
2381 }
2382}
2383
2384pub trait EventProvider {
2395 fn get_pending_events(&self, timestamp: Instant) -> Vec<SyntheticEvent>;
2407}
2408
2409#[must_use] pub fn deduplicate_synthetic_events(mut events: Vec<SyntheticEvent>) -> Vec<SyntheticEvent> {
2413 if events.len() <= 1 {
2414 return events;
2415 }
2416
2417 events.sort_by_key(|e| (e.target.dom, e.target.node, e.event_type));
2418
2419 let mut result = Vec::with_capacity(events.len());
2421 let mut iter = events.into_iter();
2422
2423 if let Some(mut prev) = iter.next() {
2424 for curr in iter {
2425 if prev.target == curr.target && prev.event_type == curr.event_type {
2426 prev = if curr.timestamp > prev.timestamp {
2428 curr
2429 } else {
2430 prev
2431 };
2432 } else {
2433 result.push(prev);
2434 prev = curr;
2435 }
2436 }
2437 result.push(prev);
2438 }
2439
2440 result
2441}
2442
2443
2444
2445#[allow(clippy::match_same_arms)]
2452#[must_use] pub fn event_type_to_filters(event_type: EventType, event_data: &EventData) -> Vec<EventFilter> {
2453 use EventFilter as EF;
2454 use EventType as E;
2455 use FocusEventFilter as F;
2456 use HoverEventFilter as H;
2457 use WindowEventFilter as W;
2458
2459 let button_specific_down = || -> Option<EventFilter> {
2461 match event_data {
2462 EventData::Mouse(m) => match m.button {
2463 MouseButton::Left => Some(EF::Hover(H::LeftMouseDown)),
2464 MouseButton::Right => Some(EF::Hover(H::RightMouseDown)),
2465 MouseButton::Middle => Some(EF::Hover(H::MiddleMouseDown)),
2466 MouseButton::Other(_) => None, },
2468 _ => Some(EF::Hover(H::LeftMouseDown)), }
2470 };
2471
2472 let button_specific_up = || -> Option<EventFilter> {
2473 match event_data {
2474 EventData::Mouse(m) => match m.button {
2475 MouseButton::Left => Some(EF::Hover(H::LeftMouseUp)),
2476 MouseButton::Right => Some(EF::Hover(H::RightMouseUp)),
2477 MouseButton::Middle => Some(EF::Hover(H::MiddleMouseUp)),
2478 MouseButton::Other(_) => None, },
2480 _ => Some(EF::Hover(H::LeftMouseUp)), }
2482 };
2483
2484 match event_type {
2485 E::MouseDown => {
2487 let mut v = vec![EF::Hover(H::MouseDown)];
2488 if let Some(f) = button_specific_down() { v.push(f); }
2489 v
2490 }
2491 E::MouseUp => {
2492 let mut v = vec![EF::Hover(H::MouseUp)];
2493 if let Some(f) = button_specific_up() { v.push(f); }
2494 v
2495 }
2496
2497 E::Click => vec![EF::Hover(H::LeftMouseUp)],
2502
2503 E::MouseOver => vec![EF::Hover(H::MouseOver)],
2505 E::MouseEnter => vec![EF::Hover(H::MouseEnter)],
2506 E::MouseLeave => vec![EF::Hover(H::MouseLeave)],
2507 E::MouseOut => vec![EF::Hover(H::MouseOut)],
2508
2509 E::DoubleClick => vec![EF::Hover(H::DoubleClick), EF::Window(W::DoubleClick)],
2510 E::ContextMenu => vec![EF::Hover(H::RightMouseDown)],
2511
2512 E::KeyDown => vec![EF::Focus(F::VirtualKeyDown)],
2514 E::KeyUp => vec![EF::Focus(F::VirtualKeyUp)],
2515 E::KeyPress => vec![EF::Focus(F::TextInput)],
2516
2517 E::CompositionStart => vec![EF::Hover(H::CompositionStart), EF::Focus(F::CompositionStart)],
2519 E::CompositionUpdate => vec![EF::Hover(H::CompositionUpdate), EF::Focus(F::CompositionUpdate)],
2520 E::CompositionEnd => vec![EF::Hover(H::CompositionEnd), EF::Focus(F::CompositionEnd)],
2521
2522 E::Focus => vec![EF::Focus(F::FocusReceived)],
2524 E::Blur => vec![EF::Focus(F::FocusLost)],
2525 E::FocusIn => vec![EF::Hover(H::FocusIn), EF::Focus(F::FocusIn)],
2526 E::FocusOut => vec![EF::Hover(H::FocusOut), EF::Focus(F::FocusOut)],
2527
2528 E::Input | E::Change => vec![EF::Focus(F::TextInput)],
2530
2531 E::Scroll | E::ScrollStart | E::ScrollEnd => vec![EF::Hover(H::Scroll)],
2533
2534 E::DragStart => vec![EF::Hover(H::DragStart), EF::Window(W::DragStart)],
2536 E::Drag => vec![EF::Hover(H::Drag), EF::Window(W::Drag)],
2537 E::DragEnd => vec![EF::Hover(H::DragEnd), EF::Window(W::DragEnd)],
2538 E::DragEnter => vec![EF::Hover(H::DragEnter), EF::Window(W::DragEnter)],
2539 E::DragOver => vec![EF::Hover(H::DragOver), EF::Window(W::DragOver)],
2540 E::DragLeave => vec![EF::Hover(H::DragLeave), EF::Window(W::DragLeave)],
2541 E::Drop => vec![EF::Hover(H::Drop), EF::Window(W::Drop)],
2542
2543 E::TouchStart => vec![EF::Hover(H::TouchStart)],
2545 E::TouchMove => vec![EF::Hover(H::TouchMove)],
2546 E::TouchEnd => vec![EF::Hover(H::TouchEnd)],
2547 E::TouchCancel => vec![EF::Hover(H::TouchCancel)],
2548
2549 E::WindowResize => vec![EF::Window(W::Resized)],
2551 E::WindowMove => vec![EF::Window(W::Moved)],
2552 E::WindowClose => vec![EF::Window(W::CloseRequested)],
2553 E::WindowFocusIn => vec![EF::Window(W::WindowFocusReceived)],
2554 E::WindowFocusOut => vec![EF::Window(W::WindowFocusLost)],
2555 E::ThemeChange => vec![EF::Window(W::ThemeChanged)],
2556 E::WindowDpiChanged => vec![EF::Window(W::DpiChanged)],
2557 E::WindowMonitorChanged => vec![EF::Window(W::MonitorChanged)],
2558
2559 E::MonitorConnected => vec![EF::Application(ApplicationEventFilter::MonitorConnected)],
2561 E::MonitorDisconnected => vec![EF::Application(ApplicationEventFilter::MonitorDisconnected)],
2562
2563 E::FileHover => vec![EF::Hover(H::HoveredFile), EF::Window(W::HoveredFile)],
2569 E::FileDrop => vec![EF::Hover(H::DroppedFile), EF::Window(W::DroppedFile)],
2570 E::FileHoverCancel => vec![
2571 EF::Hover(H::HoveredFileCancelled),
2572 EF::Window(W::HoveredFileCancelled),
2573 ],
2574
2575 E::Mount => vec![EF::Component(ComponentEventFilter::AfterMount)],
2580 E::Unmount => vec![EF::Component(ComponentEventFilter::BeforeUnmount)],
2581 E::Update => vec![EF::Component(ComponentEventFilter::Updated)],
2582 E::Resize => vec![EF::Component(ComponentEventFilter::NodeResized)],
2583
2584 E::SensorChanged => vec![EF::Hover(H::SensorChanged), EF::Window(W::SensorChanged)],
2587 E::GamepadInput => vec![EF::Hover(H::GamepadInput), EF::Window(W::GamepadInput)],
2588
2589 E::GeolocationFix => vec![EF::Hover(H::GeolocationFix), EF::Window(W::GeolocationFix)],
2593 E::GeolocationError => vec![EF::Hover(H::GeolocationError), EF::Window(W::GeolocationError)],
2594
2595 E::PermissionChanged => vec![EF::Hover(H::PermissionChanged), EF::Window(W::PermissionChanged)],
2599 E::BiometricResult => vec![EF::Hover(H::BiometricResult), EF::Window(W::BiometricResult)],
2600 E::KeyringResult => vec![EF::Hover(H::KeyringResult), EF::Window(W::KeyringResult)],
2601
2602 E::Copy => vec![EF::Focus(F::Copy)],
2606 E::Cut => vec![EF::Focus(F::Cut)],
2607 E::Paste => vec![EF::Focus(F::Paste)],
2608
2609 _ => vec![],
2611 }
2612}
2613
2614
2615
2616#[derive(Debug, Clone, PartialEq, Eq)]
2628#[must_use = "SystemChange must be processed through apply_system_change()"]
2629pub enum SystemChange {
2630 TextSelectionClick {
2634 position: LogicalPosition,
2635 timestamp: Instant,
2636 },
2637 TextSelectionDrag {
2639 start_position: LogicalPosition,
2640 current_position: LogicalPosition,
2641 },
2642 ApplySelectionOp {
2647 target: DomNodeId,
2648 op: SelectionOp,
2649 },
2650
2651 CopyToClipboard,
2655 CutToClipboard { target: DomNodeId },
2657 PasteFromClipboard,
2659 SelectAllText,
2661 UndoTextEdit { target: DomNodeId },
2663 RedoTextEdit { target: DomNodeId },
2665
2666 AddCursorAtClick {
2671 position: LogicalPosition,
2672 },
2673 SelectNextOccurrence {
2676 target: DomNodeId,
2677 },
2678
2679 ApplyPendingTextInput,
2683 ApplyTextChangeset,
2685
2686 ActivateNodeDrag {
2690 dom_id: DomId,
2691 node_id: NodeId,
2692 },
2693 ActivateWindowDrag,
2695 InitDragVisualState,
2697 SetDragOverState { target: DomNodeId, active: bool },
2699 UpdateDropTarget { target: DomNodeId },
2701 UpdateDragGpuTransform,
2703 DeactivateDrag,
2705
2706 SetFocus {
2712 new_focus: Option<DomNodeId>,
2713 old_focus: Option<DomNodeId>,
2714 },
2715 ClearAllSelections,
2717 FinalizePendingFocusChanges,
2719
2720 ScrollSelectionIntoView,
2724 ScrollNodeIntoView { target: DomNodeId },
2726 ScrollCursorIntoViewAfterTextInput,
2728
2729 StartAutoScrollTimer,
2733 StopAutoScrollTimer,
2735}
2736
2737impl_option!(
2738 SystemChange,
2739 OptionSystemChange,
2740 copy = false,
2741 clone = false,
2742 [Debug, Clone, PartialEq, Eq]
2743);
2744
2745impl_vec!(SystemChange, SystemChangeVec, SystemChangeVecDestructor, SystemChangeVecDestructorType, SystemChangeVecSlice, OptionSystemChange);
2746impl_vec_debug!(SystemChange, SystemChangeVec);
2747impl_vec_clone!(SystemChange, SystemChangeVec, SystemChangeVecDestructor);
2748impl_vec_partialeq!(SystemChange, SystemChangeVec);
2749
2750#[derive(Debug, Clone, PartialEq)]
2752pub struct PreCallbackFilterResult {
2753 pub system_changes: Vec<SystemChange>,
2755 pub user_events: Vec<SyntheticEvent>,
2757}
2758
2759#[derive(Debug, Clone, Copy)]
2761pub struct InputInterpreterState {
2762 pub focused_node: Option<DomNodeId>,
2763 pub click_count: u8,
2764 pub drag_start_position: Option<LogicalPosition>,
2765 pub has_selection: bool,
2766}
2767
2768#[derive(Debug)]
2773pub struct InputInterpreterInfo<'a> {
2774 pub events: &'a [SyntheticEvent],
2775 pub hit_test: Option<&'a FullHitTest>,
2776 pub keyboard_state: &'a crate::window::KeyboardState,
2777 pub mouse_state: &'a crate::window::MouseState,
2778 pub state: InputInterpreterState,
2779}
2780
2781pub type InputInterpreterCallbackType = extern "C" fn(
2791 crate::refany::RefAny,
2792 *const InputInterpreterInfo<'static>, ) -> PreCallbackFilterResult;
2794
2795#[repr(C)]
2805pub struct InputInterpreterCallback {
2806 pub cb: InputInterpreterCallbackType,
2807 pub ctx: crate::refany::OptionRefAny,
2808}
2809
2810impl_callback!(InputInterpreterCallback, InputInterpreterCallbackType);
2811
2812impl Default for InputInterpreterCallback {
2813 fn default() -> Self {
2814 Self {
2815 cb: default_input_interpreter_extern,
2816 ctx: crate::refany::OptionRefAny::None,
2817 }
2818 }
2819}
2820
2821pub type PostFilterCallbackType = extern "C" fn(
2823 crate::refany::RefAny,
2824 bool, SystemChangeVecSlice, DomNodeId, DomNodeId, ) -> SystemChangeVec;
2829
2830#[repr(C)]
2832pub struct PostFilterCallback {
2833 pub cb: PostFilterCallbackType,
2834 pub ctx: crate::refany::OptionRefAny,
2835}
2836
2837impl_callback!(PostFilterCallback, PostFilterCallbackType);
2838
2839impl Default for PostFilterCallback {
2840 fn default() -> Self {
2841 Self {
2842 cb: default_post_filter_extern,
2843 ctx: crate::refany::OptionRefAny::None,
2844 }
2845 }
2846}
2847
2848pub type InputInterpreterFn = fn(
2850 info: &InputInterpreterInfo<'_>,
2851) -> PreCallbackFilterResult;
2852
2853pub type PostFilterFn = fn(
2854 prevent_default: bool,
2855 pre_changes: &[SystemChange],
2856 old_focus: Option<DomNodeId>,
2857 new_focus: Option<DomNodeId>,
2858) -> Vec<SystemChange>;
2859
2860#[derive(Debug, Clone, Copy, PartialEq, Eq)]
2862pub struct MouseButtonState {
2863 pub left_down: bool,
2864 pub right_down: bool,
2865 pub middle_down: bool,
2866}
2867
2868#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
2870pub enum ArrowDirection {
2871 Left,
2872 Right,
2873 Up,
2874 Down,
2875 LineStart,
2877 LineEnd,
2879 DocumentStart,
2881 DocumentEnd,
2883}
2884
2885impl ArrowDirection {
2886 #[must_use] pub const fn from_key(vk: crate::window::VirtualKeyCode, ctrl: bool) -> Option<Self> {
2889 use crate::window::VirtualKeyCode::{Left, Right, Up, Down, Home, End};
2890 Some(match vk {
2891 Left => Self::Left,
2892 Right => Self::Right,
2893 Up => Self::Up,
2894 Down => Self::Down,
2895 Home if ctrl => Self::DocumentStart,
2896 Home => Self::LineStart,
2897 End if ctrl => Self::DocumentEnd,
2898 End => Self::LineEnd,
2899 _ => return None,
2900 })
2901 }
2902
2903 #[must_use] pub const fn to_selection(self, ctrl: bool) -> (SelectionDirection, SelectionStep) {
2906 match self {
2907 Self::Left if ctrl => (SelectionDirection::Backward, SelectionStep::Word),
2908 Self::Right if ctrl => (SelectionDirection::Forward, SelectionStep::Word),
2909 Self::Left => (SelectionDirection::Backward, SelectionStep::Character),
2910 Self::Right => (SelectionDirection::Forward, SelectionStep::Character),
2911 Self::Up => (SelectionDirection::Backward, SelectionStep::VisualLine),
2912 Self::Down => (SelectionDirection::Forward, SelectionStep::VisualLine),
2913 Self::LineStart => (SelectionDirection::Backward, SelectionStep::Line),
2914 Self::LineEnd => (SelectionDirection::Forward, SelectionStep::Line),
2915 Self::DocumentStart => (SelectionDirection::Backward, SelectionStep::Document),
2916 Self::DocumentEnd => (SelectionDirection::Forward, SelectionStep::Document),
2917 }
2918 }
2919}
2920
2921#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
2923#[repr(C)]
2924pub enum SelectionDirection {
2925 Forward,
2926 Backward,
2927}
2928
2929#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
2935#[repr(C)]
2936pub enum SelectionStep {
2937 Character,
2939 Word,
2941 Line,
2943 VisualLine,
2945 Document,
2947}
2948
2949#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
2951#[repr(C)]
2952pub enum SelectionMode {
2953 Move,
2955 Extend,
2957 Delete,
2960}
2961
2962#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
2974#[repr(C)]
2975pub struct SelectionOp {
2976 pub direction: SelectionDirection,
2977 pub step: SelectionStep,
2978 pub mode: SelectionMode,
2979 pub repeat: usize,
2980}
2981
2982impl SelectionOp {
2983 #[must_use] pub const fn new(direction: SelectionDirection, step: SelectionStep, mode: SelectionMode) -> Self {
2984 Self { direction, step, mode, repeat: 1 }
2985 }
2986}
2987
2988#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
2990pub enum KeyboardShortcut {
2991 Copy, Cut, Paste, SelectAll, Undo, Redo, }
2998
2999impl KeyboardShortcut {
3000 #[must_use] pub const fn from_key(vk: crate::window::VirtualKeyCode, primary: bool, shift: bool) -> Option<Self> {
3007 use crate::window::VirtualKeyCode::{C, X, V, A, Z, Y};
3008 if !primary {
3009 return None;
3010 }
3011 Some(match vk {
3012 C => Self::Copy,
3013 X => Self::Cut,
3014 V => Self::Paste,
3015 A => Self::SelectAll,
3016 Z if shift => Self::Redo,
3017 Z => Self::Undo,
3018 Y => Self::Redo,
3019 _ => return None,
3020 })
3021 }
3022}
3023
3024#[allow(clippy::not_unsafe_ptr_arg_deref)] #[must_use] pub extern "C" fn default_input_interpreter_extern(
3032 _user_data: crate::refany::RefAny,
3033 info_ptr: *const InputInterpreterInfo<'static>,
3034) -> PreCallbackFilterResult {
3035 if info_ptr.is_null() {
3036 return PreCallbackFilterResult {
3037 system_changes: Vec::new(),
3038 user_events: Vec::new(),
3039 };
3040 }
3041 let info = unsafe { &*info_ptr };
3042 default_input_interpreter(info)
3043}
3044
3045#[must_use] pub extern "C" fn default_post_filter_extern(
3047 _user_data: crate::refany::RefAny,
3048 prevent_default: bool,
3049 pre_changes: SystemChangeVecSlice,
3050 old_focus: DomNodeId,
3051 new_focus: DomNodeId,
3052) -> SystemChangeVec {
3053 let pre_changes_slice = pre_changes.as_slice();
3054 let old = old_focus.node.into_crate_internal().map(|_| old_focus);
3055 let new = new_focus.node.into_crate_internal().map(|_| new_focus);
3056 default_post_filter(prevent_default, pre_changes_slice, old, new).into()
3057}
3058
3059#[must_use] pub fn default_input_interpreter(
3060 info: &InputInterpreterInfo<'_>,
3061) -> PreCallbackFilterResult {
3062 let ctx = FilterContext {
3063 hit_test: info.hit_test,
3064 keyboard_state: info.keyboard_state,
3065 mouse_state: info.mouse_state,
3066 click_count: info.state.click_count,
3067 focused_node: info.state.focused_node,
3068 drag_start_position: info.state.drag_start_position,
3069 };
3070
3071 let (system_changes, user_events) = info.events.iter().fold(
3072 (Vec::new(), Vec::new()),
3073 |(mut internal, mut user), event| {
3074 match process_event_for_internal(&ctx, event) {
3075 Some(InternalEventAction::AddAndSkip(evt)) => {
3076 internal.push(evt);
3077 }
3078 Some(InternalEventAction::AddAndPass(evt)) => {
3079 internal.push(evt);
3080 user.push(event.clone());
3081 }
3082 None => {
3083 user.push(event.clone());
3084 }
3085 }
3086 (internal, user)
3087 },
3088 );
3089
3090 PreCallbackFilterResult {
3091 system_changes,
3092 user_events,
3093 }
3094}
3095
3096pub fn pre_callback_filter_internal_events<SM, FM>(
3098 events: &[SyntheticEvent],
3099 hit_test: Option<&FullHitTest>,
3100 keyboard_state: &crate::window::KeyboardState,
3101 mouse_state: &crate::window::MouseState,
3102 selection_manager: &SM,
3103 focus_manager: &FM,
3104) -> PreCallbackFilterResult
3105where
3106 SM: SelectionManagerQuery,
3107 FM: FocusManagerQuery,
3108{
3109 let info = InputInterpreterInfo {
3110 events,
3111 hit_test,
3112 keyboard_state,
3113 mouse_state,
3114 state: InputInterpreterState {
3115 focused_node: focus_manager.get_focused_node_id(),
3116 click_count: selection_manager.get_click_count(),
3117 drag_start_position: selection_manager.get_drag_start_position(),
3118 has_selection: selection_manager.has_selection(),
3119 },
3120 };
3121 default_input_interpreter(&info)
3122}
3123
3124struct FilterContext<'a> {
3126 hit_test: Option<&'a FullHitTest>,
3127 keyboard_state: &'a crate::window::KeyboardState,
3128 mouse_state: &'a crate::window::MouseState,
3129 click_count: u8,
3130 focused_node: Option<DomNodeId>,
3131 drag_start_position: Option<LogicalPosition>,
3132}
3133
3134fn process_event_for_internal(
3136 ctx: &FilterContext<'_>,
3137 event: &SyntheticEvent,
3138) -> Option<InternalEventAction> {
3139 match event.event_type {
3140 EventType::MouseDown => handle_mouse_down(event, ctx.hit_test, ctx.click_count, ctx.mouse_state, ctx.keyboard_state),
3141 EventType::MouseOver => handle_mouse_over(
3142 event,
3143 ctx.hit_test,
3144 ctx.mouse_state,
3145 ctx.drag_start_position,
3146 ),
3147 EventType::KeyDown => handle_key_down(
3148 event,
3149 ctx.keyboard_state,
3150 ctx.focused_node,
3151 ),
3152 _ => None,
3153 }
3154}
3155
3156enum InternalEventAction {
3158 AddAndSkip(SystemChange),
3160 AddAndPass(SystemChange),
3162}
3163
3164fn get_first_hovered_node(hit_test: Option<&FullHitTest>) -> Option<DomNodeId> {
3172 let ht = hit_test?;
3173 let mut best: Option<(DomId, NodeId, u32)> = None;
3174 for (dom_id, hit_data) in &ht.hovered_nodes {
3175 for (node_id, item) in &hit_data.regular_hit_test_nodes {
3176 let is_better = match best {
3177 None => true,
3178 Some((_, _, best_depth)) => item.hit_depth < best_depth,
3179 };
3180 if is_better {
3181 best = Some((*dom_id, *node_id, item.hit_depth));
3182 }
3183 }
3184 }
3185 let (dom_id, node_id, _) = best?;
3186 Some(DomNodeId {
3187 dom: dom_id,
3188 node: NodeHierarchyItemId::from_crate_internal(Some(node_id)),
3189 })
3190}
3191
3192fn get_mouse_position_with_fallback(
3194 event: &SyntheticEvent,
3195 mouse_state: &crate::window::MouseState,
3196) -> LogicalPosition {
3197 match &event.data {
3198 EventData::Mouse(mouse_data) => mouse_data.position,
3199 _ => {
3200 mouse_state.cursor_position.get_position().unwrap_or(LogicalPosition::zero())
3204 }
3205 }
3206}
3207
3208fn handle_mouse_down(
3210 event: &SyntheticEvent,
3211 hit_test: Option<&FullHitTest>,
3212 click_count: u8,
3213 mouse_state: &crate::window::MouseState,
3214 keyboard_state: &crate::window::KeyboardState,
3215) -> Option<InternalEventAction> {
3216 let effective_click_count = if click_count == 0 { 1 } else { click_count };
3217
3218 if effective_click_count > 3 {
3219 return None;
3220 }
3221
3222 let _target = get_first_hovered_node(hit_test)?;
3223 let position = get_mouse_position_with_fallback(event, mouse_state);
3224
3225 if keyboard_state.primary_down() && effective_click_count == 1 {
3230 return Some(InternalEventAction::AddAndPass(
3231 SystemChange::AddCursorAtClick { position },
3232 ));
3233 }
3234
3235 Some(InternalEventAction::AddAndPass(
3236 SystemChange::TextSelectionClick {
3237 position,
3238 timestamp: event.timestamp.clone(),
3239 },
3240 ))
3241}
3242
3243fn handle_mouse_over(
3245 event: &SyntheticEvent,
3246 hit_test: Option<&FullHitTest>,
3247 mouse_state: &crate::window::MouseState,
3248 drag_start_position: Option<LogicalPosition>,
3249) -> Option<InternalEventAction> {
3250 if !mouse_state.left_down {
3251 return None;
3252 }
3253
3254 let start_position = drag_start_position?;
3255
3256 let _target = get_first_hovered_node(hit_test)?;
3257 let current_position = get_mouse_position_with_fallback(event, mouse_state);
3258
3259 Some(InternalEventAction::AddAndPass(
3260 SystemChange::TextSelectionDrag {
3261 start_position,
3262 current_position,
3263 },
3264 ))
3265}
3266
3267fn handle_key_down(
3269 event: &SyntheticEvent,
3270 keyboard_state: &crate::window::KeyboardState,
3271 focused_node: Option<DomNodeId>,
3272) -> Option<InternalEventAction> {
3273 use crate::window::VirtualKeyCode;
3274
3275 let target = focused_node?;
3276 let EventData::Keyboard(kbd) = &event.data else {
3277 return None;
3278 };
3279
3280 let _ = keyboard_state;
3286
3287 let primary = if cfg!(target_os = "macos") {
3291 kbd.modifiers.meta
3292 } else {
3293 kbd.modifiers.ctrl
3294 };
3295 let word_mod = if cfg!(target_os = "macos") {
3296 kbd.modifiers.alt
3297 } else {
3298 kbd.modifiers.ctrl
3299 };
3300 let shift = kbd.modifiers.shift;
3301 let vk_owned = VirtualKeyCode::from_u32(kbd.key_code)?;
3302 let vk = &vk_owned;
3303
3304 if primary {
3309 if let Some(shortcut) = KeyboardShortcut::from_key(*vk, primary, shift) {
3310 let change = match shortcut {
3311 KeyboardShortcut::Copy => SystemChange::CopyToClipboard,
3312 KeyboardShortcut::Cut => SystemChange::CutToClipboard { target },
3313 KeyboardShortcut::Paste => SystemChange::PasteFromClipboard,
3314 KeyboardShortcut::SelectAll => SystemChange::SelectAllText,
3315 KeyboardShortcut::Undo => SystemChange::UndoTextEdit { target },
3316 KeyboardShortcut::Redo => SystemChange::RedoTextEdit { target },
3317 };
3318 return Some(InternalEventAction::AddAndSkip(change));
3319 }
3320 if matches!(vk, VirtualKeyCode::D) {
3321 return Some(InternalEventAction::AddAndSkip(
3322 SystemChange::SelectNextOccurrence { target },
3323 ));
3324 }
3325 }
3326
3327 let mode_for_shift = if shift { SelectionMode::Extend } else { SelectionMode::Move };
3329 let selection_op = if let Some(arrow) = ArrowDirection::from_key(*vk, word_mod) {
3330 let (direction, step) = arrow.to_selection(word_mod);
3331 SelectionOp::new(direction, step, mode_for_shift)
3332 } else {
3333 match vk {
3334 VirtualKeyCode::Back => SelectionOp::new(
3337 SelectionDirection::Backward,
3338 if word_mod { SelectionStep::Word } else { SelectionStep::Character },
3339 SelectionMode::Delete,
3340 ),
3341 VirtualKeyCode::Delete => SelectionOp::new(
3342 SelectionDirection::Forward,
3343 if word_mod { SelectionStep::Word } else { SelectionStep::Character },
3344 SelectionMode::Delete,
3345 ),
3346 _ => return None,
3347 }
3348 };
3349
3350 Some(InternalEventAction::AddAndSkip(
3351 SystemChange::ApplySelectionOp { target, op: selection_op },
3352 ))
3353}
3354
3355pub trait SelectionManagerQuery {
3360 fn get_click_count(&self) -> u8;
3362
3363 fn get_drag_start_position(&self) -> Option<LogicalPosition>;
3365
3366 fn has_selection(&self) -> bool;
3368}
3369
3370pub trait FocusManagerQuery {
3375 fn get_focused_node_id(&self) -> Option<DomNodeId>;
3377}
3378
3379#[must_use] pub fn default_post_filter(
3385 prevent_default: bool,
3386 pre_changes: &[SystemChange],
3387 old_focus: Option<DomNodeId>,
3388 new_focus: Option<DomNodeId>,
3389) -> Vec<SystemChange> {
3390 post_callback_filter_system_changes(prevent_default, pre_changes, old_focus, new_focus)
3391}
3392
3393#[allow(clippy::match_same_arms)]
3396#[must_use] pub fn post_callback_filter_system_changes(
3397 prevent_default: bool,
3398 pre_changes: &[SystemChange],
3399 old_focus: Option<DomNodeId>,
3400 new_focus: Option<DomNodeId>,
3401) -> Vec<SystemChange> {
3402 let mut changes = Vec::new();
3403
3404 if prevent_default {
3405 if old_focus != new_focus {
3407 changes.push(SystemChange::SetFocus { new_focus, old_focus });
3408 }
3409 return changes;
3410 }
3411
3412 changes.push(SystemChange::ApplyPendingTextInput);
3414
3415 for change in pre_changes {
3417 match change {
3418 SystemChange::TextSelectionClick { .. }
3419 | SystemChange::ApplySelectionOp { .. }
3420 | SystemChange::AddCursorAtClick { .. }
3421 | SystemChange::SelectNextOccurrence { .. } => {
3422 changes.push(SystemChange::ScrollSelectionIntoView);
3423 }
3424 SystemChange::TextSelectionDrag { .. } => {
3425 changes.push(SystemChange::StartAutoScrollTimer);
3426 }
3427 SystemChange::CutToClipboard { .. }
3428 | SystemChange::PasteFromClipboard
3429 | SystemChange::UndoTextEdit { .. }
3430 | SystemChange::RedoTextEdit { .. }
3431 | SystemChange::SelectAllText => {
3432 changes.push(SystemChange::ScrollSelectionIntoView);
3433 }
3434 _ => {}
3436 }
3437 }
3438
3439 if old_focus != new_focus {
3441 changes.push(SystemChange::SetFocus { new_focus, old_focus });
3442 }
3443
3444 changes
3445}
3446
3447
3448#[cfg(test)]
3449mod tests {
3450 use super::*;
3451 use azul_css::AzString;
3452 use crate::dom::{DomId, DomNodeId};
3453 use crate::styled_dom::NodeHierarchyItemId;
3454 use crate::id::NodeId;
3455 use crate::window::{KeyboardState, MouseState, VirtualKeyCode, VirtualKeyCodeVec, OptionVirtualKeyCode};
3456 use crate::geom::LogicalPosition;
3457 use crate::task::{Instant, SystemTick};
3458
3459 struct MockSelectionManager {
3460 click_count: u8,
3461 has_sel: bool,
3462 }
3463 impl SelectionManagerQuery for MockSelectionManager {
3464 fn get_click_count(&self) -> u8 { self.click_count }
3465 fn get_drag_start_position(&self) -> Option<LogicalPosition> { None }
3466 fn has_selection(&self) -> bool { self.has_sel }
3467 }
3468
3469 struct MockFocusManager(Option<DomNodeId>);
3470 impl FocusManagerQuery for MockFocusManager {
3471 fn get_focused_node_id(&self) -> Option<DomNodeId> { self.0 }
3472 }
3473
3474 fn focused_node(node_idx: usize) -> DomNodeId {
3475 DomNodeId {
3476 dom: DomId { inner: 0 },
3477 node: NodeHierarchyItemId::from_crate_internal(Some(NodeId::new(node_idx))),
3478 }
3479 }
3480
3481 fn make_keyboard_state(vk: VirtualKeyCode) -> KeyboardState {
3482 KeyboardState {
3483 current_virtual_keycode: OptionVirtualKeyCode::Some(vk),
3484 pressed_virtual_keycodes: VirtualKeyCodeVec::from_vec(vec![vk]),
3485 ..KeyboardState::default()
3486 }
3487 }
3488
3489 fn make_keydown_event(target: DomNodeId) -> SyntheticEvent {
3490 SyntheticEvent::new(
3491 EventType::KeyDown,
3492 EventSource::User,
3493 target,
3494 Instant::Tick(SystemTick::new(0)),
3495 EventData::Keyboard(KeyboardEventData {
3496 key_code: VirtualKeyCode::Back as u32,
3497 char_code: None,
3498 modifiers: KeyModifiers::default(),
3499 repeat: false,
3500 }),
3501 )
3502 }
3503
3504 #[test]
3505 fn backspace_generates_delete_text_selection() {
3506 let target = focused_node(2);
3507 let events = vec![make_keydown_event(target)];
3508 let kb = make_keyboard_state(VirtualKeyCode::Back);
3509 let mouse = MouseState::default();
3510 let sel = MockSelectionManager { click_count: 0, has_sel: false };
3511 let focus = MockFocusManager(Some(target));
3512
3513 let result = pre_callback_filter_internal_events(
3514 &events, None, &kb, &mouse, &sel, &focus,
3515 );
3516
3517 let ops: Vec<_> = result.system_changes.iter()
3518 .filter(|c| matches!(c, SystemChange::ApplySelectionOp { .. }))
3519 .collect();
3520 assert_eq!(ops.len(), 1, "Backspace should generate ApplySelectionOp");
3521 match &ops[0] {
3522 SystemChange::ApplySelectionOp { op, .. } => {
3523 assert_eq!(op.direction, SelectionDirection::Backward);
3524 assert_eq!(op.step, SelectionStep::Character);
3525 assert_eq!(op.mode, SelectionMode::Delete);
3526 }
3527 _ => unreachable!(),
3528 }
3529 }
3530
3531 #[test]
3532 fn delete_key_generates_forward_deletion() {
3533 let target = focused_node(2);
3534 let event = SyntheticEvent::new(
3535 EventType::KeyDown, EventSource::User, target,
3536 Instant::Tick(SystemTick::new(0)),
3537 EventData::Keyboard(KeyboardEventData {
3538 key_code: VirtualKeyCode::Delete as u32,
3539 char_code: None, modifiers: KeyModifiers::default(), repeat: false,
3540 }),
3541 );
3542 let kb = make_keyboard_state(VirtualKeyCode::Delete);
3543 let mouse = MouseState::default();
3544 let sel = MockSelectionManager { click_count: 0, has_sel: false };
3545 let focus = MockFocusManager(Some(target));
3546 let result = pre_callback_filter_internal_events(&[event], None, &kb, &mouse, &sel, &focus);
3547 let ops: Vec<_> = result.system_changes.iter()
3548 .filter(|c| matches!(c, SystemChange::ApplySelectionOp { .. }))
3549 .collect();
3550 assert_eq!(ops.len(), 1);
3551 match &ops[0] {
3552 SystemChange::ApplySelectionOp { op, .. } => {
3553 assert_eq!(op.direction, SelectionDirection::Forward);
3554 assert_eq!(op.step, SelectionStep::Character);
3555 assert_eq!(op.mode, SelectionMode::Delete);
3556 }
3557 _ => unreachable!(),
3558 }
3559 }
3560
3561 #[test]
3562 fn arrow_left_generates_navigation() {
3563 let target = focused_node(2);
3564 let event = SyntheticEvent::new(
3565 EventType::KeyDown, EventSource::User, target,
3566 Instant::Tick(SystemTick::new(0)),
3567 EventData::Keyboard(KeyboardEventData {
3568 key_code: VirtualKeyCode::Left as u32,
3569 char_code: None, modifiers: KeyModifiers::default(), repeat: false,
3570 }),
3571 );
3572 let kb = make_keyboard_state(VirtualKeyCode::Left);
3573 let mouse = MouseState::default();
3574 let sel = MockSelectionManager { click_count: 0, has_sel: false };
3575 let focus = MockFocusManager(Some(target));
3576 let result = pre_callback_filter_internal_events(&[event], None, &kb, &mouse, &sel, &focus);
3577 let ops: Vec<_> = result.system_changes.iter()
3578 .filter(|c| matches!(c, SystemChange::ApplySelectionOp { .. }))
3579 .collect();
3580 assert_eq!(ops.len(), 1, "Left arrow should generate ApplySelectionOp");
3581 match &ops[0] {
3582 SystemChange::ApplySelectionOp { op, .. } => {
3583 assert_eq!(op.direction, SelectionDirection::Backward);
3584 assert_eq!(op.step, SelectionStep::Character);
3585 assert_eq!(op.mode, SelectionMode::Move);
3586 }
3587 _ => unreachable!(),
3588 }
3589 }
3590
3591 #[test]
3592 fn no_focused_node_means_no_keyboard_system_changes() {
3593 let target = focused_node(2);
3594 let event = make_keydown_event(target);
3595 let kb = make_keyboard_state(VirtualKeyCode::Back);
3596 let mouse = MouseState::default();
3597 let sel = MockSelectionManager { click_count: 0, has_sel: false };
3598 let focus = MockFocusManager(None); let result = pre_callback_filter_internal_events(
3601 &[event], None, &kb, &mouse, &sel, &focus,
3602 );
3603
3604 assert!(result.system_changes.is_empty(),
3605 "No system changes should be generated without focused node");
3606 }
3607
3608 #[test]
3609 fn keydown_without_keyboard_data_generates_no_system_change() {
3610 let target = focused_node(2);
3611 let event = SyntheticEvent::new(
3612 EventType::KeyDown,
3613 EventSource::User,
3614 target,
3615 Instant::Tick(SystemTick::new(0)),
3616 EventData::None, );
3618 let kb = make_keyboard_state(VirtualKeyCode::Back);
3619 let mouse = MouseState::default();
3620 let sel = MockSelectionManager { click_count: 0, has_sel: false };
3621 let focus = MockFocusManager(Some(target));
3622
3623 let result = pre_callback_filter_internal_events(
3624 &[event], None, &kb, &mouse, &sel, &focus,
3625 );
3626
3627 assert!(result.system_changes.is_empty(),
3630 "EventData::None should not generate system changes (documents the old bug)");
3631 }
3632
3633 #[test]
3634 fn ctrl_c_generates_copy() {
3635 let primary_key = if cfg!(target_os = "macos") {
3641 VirtualKeyCode::LWin
3642 } else {
3643 VirtualKeyCode::LControl
3644 };
3645 let target = focused_node(2);
3646 let event = SyntheticEvent::new(
3647 EventType::KeyDown,
3648 EventSource::User,
3649 target,
3650 Instant::Tick(SystemTick::new(0)),
3651 EventData::Keyboard(KeyboardEventData {
3652 key_code: VirtualKeyCode::C as u32,
3653 char_code: Some('c'),
3654 modifiers: KeyModifiers {
3655 ctrl: !cfg!(target_os = "macos"),
3656 shift: false,
3657 alt: false,
3658 meta: cfg!(target_os = "macos"),
3659 },
3660 repeat: false,
3661 }),
3662 );
3663 let mut kb = make_keyboard_state(VirtualKeyCode::C);
3664 kb.pressed_virtual_keycodes = VirtualKeyCodeVec::from_vec(
3665 vec![VirtualKeyCode::C, primary_key]
3666 );
3667 let mouse = MouseState::default();
3668 let sel = MockSelectionManager { click_count: 0, has_sel: false };
3669 let focus = MockFocusManager(Some(target));
3670
3671 let result = pre_callback_filter_internal_events(
3672 &[event], None, &kb, &mouse, &sel, &focus,
3673 );
3674
3675 let copy_changes = result.system_changes.iter()
3676 .filter(|c| matches!(c, SystemChange::CopyToClipboard))
3677 .count();
3678
3679 assert_eq!(copy_changes, 1, "primary+C should generate CopyToClipboard");
3680 }
3681
3682 fn make_hit_test_with_node(node_idx: usize) -> FullHitTest {
3683 use crate::hit_test::{FullHitTest, HitTest, HitTestItem};
3684 use crate::dom::OptionDomNodeId;
3685 use std::collections::BTreeMap;
3686
3687 let node_id = NodeId::new(node_idx);
3688 let dom_id = DomId { inner: 0 };
3689
3690 let mut regular = BTreeMap::new();
3691 regular.insert(node_id, HitTestItem {
3692 point_in_viewport: LogicalPosition::new(100.0, 200.0),
3693 point_relative_to_item: LogicalPosition::new(50.0, 30.0),
3694 is_focusable: true,
3695 is_virtual_view_hit: None,
3696 hit_depth: 0,
3697 });
3698
3699 let mut hovered = BTreeMap::new();
3700 hovered.insert(dom_id, HitTest {
3701 regular_hit_test_nodes: regular,
3702 scroll_hit_test_nodes: BTreeMap::new(),
3703 scrollbar_hit_test_nodes: BTreeMap::new(),
3704 cursor_hit_test_nodes: BTreeMap::new(),
3705 });
3706
3707 FullHitTest {
3708 hovered_nodes: hovered,
3709 focused_node: OptionDomNodeId::None,
3710 }
3711 }
3712
3713 #[test]
3714 fn mousedown_generates_text_selection_click() {
3715 let target = focused_node(2);
3716 let event = SyntheticEvent::new(
3717 EventType::MouseDown,
3718 EventSource::User,
3719 target,
3720 Instant::Tick(SystemTick::new(0)),
3721 EventData::Mouse(MouseEventData {
3722 position: LogicalPosition::new(100.0, 200.0),
3723 button: MouseButton::Left,
3724 buttons: 1,
3725 modifiers: KeyModifiers::default(),
3726 }),
3727 );
3728 let hit_test = make_hit_test_with_node(2);
3729 let kb = KeyboardState::default();
3730 let mouse = MouseState::default();
3731 let sel = MockSelectionManager { click_count: 1, has_sel: false };
3732 let focus = MockFocusManager(Some(target));
3733
3734 let result = pre_callback_filter_internal_events(
3735 &[event], Some(&hit_test), &kb, &mouse, &sel, &focus,
3736 );
3737
3738 let click_changes = result.system_changes.iter()
3739 .filter(|c| matches!(c, SystemChange::TextSelectionClick { .. }))
3740 .count();
3741
3742 assert_eq!(click_changes, 1, "MouseDown with hit_test should generate TextSelectionClick");
3743 }
3744
3745 #[test]
3746 fn process_event_result_max_self_picks_higher_variant() {
3747 let lo = ProcessEventResult::ShouldReRenderCurrentWindow;
3748 let hi = ProcessEventResult::ShouldRegenerateDomCurrentWindow;
3749 assert_eq!(lo.max_self(hi), hi);
3750 assert_eq!(hi.max_self(lo), hi);
3751 assert_eq!(lo.max_self(lo), lo);
3752 }
3753
3754 #[test]
3755 fn keyboard_shortcut_keys_off_primary_modifier() {
3756 use crate::window::VirtualKeyCode::{A, C, V, X, Z};
3757 assert_eq!(KeyboardShortcut::from_key(C, false, false), None);
3759 assert_eq!(KeyboardShortcut::from_key(Z, false, true), None);
3760 assert_eq!(KeyboardShortcut::from_key(C, true, false), Some(KeyboardShortcut::Copy));
3762 assert_eq!(KeyboardShortcut::from_key(X, true, false), Some(KeyboardShortcut::Cut));
3763 assert_eq!(KeyboardShortcut::from_key(V, true, false), Some(KeyboardShortcut::Paste));
3764 assert_eq!(KeyboardShortcut::from_key(A, true, false), Some(KeyboardShortcut::SelectAll));
3765 assert_eq!(KeyboardShortcut::from_key(Z, true, false), Some(KeyboardShortcut::Undo));
3766 assert_eq!(KeyboardShortcut::from_key(Z, true, true), Some(KeyboardShortcut::Redo));
3767 }
3768
3769 #[test]
3770 fn primary_modifier_is_platform_correct() {
3771 use crate::window::{KeyboardState, VirtualKeyCode};
3772 let cmd_held = KeyboardState {
3773 pressed_virtual_keycodes: vec![VirtualKeyCode::LWin].into(),
3774 ..Default::default()
3775 };
3776 assert_eq!(cmd_held.primary_down(), cfg!(target_os = "macos"));
3778
3779 let ctrl_held = KeyboardState {
3780 pressed_virtual_keycodes: vec![VirtualKeyCode::LControl].into(),
3781 ..Default::default()
3782 };
3783 assert_eq!(ctrl_held.primary_down(), !cfg!(target_os = "macos"));
3785 }
3786
3787 #[test]
3788 fn arrow_direction_from_key_maps_arrows_and_home_end() {
3789 use crate::window::VirtualKeyCode::*;
3790 assert_eq!(ArrowDirection::from_key(Left, false), Some(ArrowDirection::Left));
3791 assert_eq!(ArrowDirection::from_key(Right, false), Some(ArrowDirection::Right));
3792 assert_eq!(ArrowDirection::from_key(Up, false), Some(ArrowDirection::Up));
3793 assert_eq!(ArrowDirection::from_key(Down, false), Some(ArrowDirection::Down));
3794 assert_eq!(ArrowDirection::from_key(Home, false), Some(ArrowDirection::LineStart));
3795 assert_eq!(ArrowDirection::from_key(End, false), Some(ArrowDirection::LineEnd));
3796 assert_eq!(ArrowDirection::from_key(Home, true), Some(ArrowDirection::DocumentStart));
3797 assert_eq!(ArrowDirection::from_key(End, true), Some(ArrowDirection::DocumentEnd));
3798 assert_eq!(ArrowDirection::from_key(C, false), None);
3799 }
3800
3801 #[test]
3802 fn arrow_direction_to_selection_respects_ctrl() {
3803 let (d, s) = ArrowDirection::Left.to_selection(false);
3804 assert_eq!((d, s), (SelectionDirection::Backward, SelectionStep::Character));
3805 let (d, s) = ArrowDirection::Left.to_selection(true);
3806 assert_eq!((d, s), (SelectionDirection::Backward, SelectionStep::Word));
3807 let (d, s) = ArrowDirection::Up.to_selection(false);
3808 assert_eq!((d, s), (SelectionDirection::Backward, SelectionStep::VisualLine));
3809 let (d, s) = ArrowDirection::DocumentEnd.to_selection(false);
3810 assert_eq!((d, s), (SelectionDirection::Forward, SelectionStep::Document));
3811 }
3812
3813 #[test]
3814 fn keyboard_shortcut_from_key_recognizes_editing_combos() {
3815 use crate::window::VirtualKeyCode::*;
3816 assert_eq!(KeyboardShortcut::from_key(C, true, false), Some(KeyboardShortcut::Copy));
3817 assert_eq!(KeyboardShortcut::from_key(X, true, false), Some(KeyboardShortcut::Cut));
3818 assert_eq!(KeyboardShortcut::from_key(V, true, false), Some(KeyboardShortcut::Paste));
3819 assert_eq!(KeyboardShortcut::from_key(A, true, false), Some(KeyboardShortcut::SelectAll));
3820 assert_eq!(KeyboardShortcut::from_key(Z, true, false), Some(KeyboardShortcut::Undo));
3821 assert_eq!(KeyboardShortcut::from_key(Z, true, true), Some(KeyboardShortcut::Redo));
3822 assert_eq!(KeyboardShortcut::from_key(Y, true, false), Some(KeyboardShortcut::Redo));
3823 assert_eq!(KeyboardShortcut::from_key(C, false, false), None);
3825 assert_eq!(KeyboardShortcut::from_key(D, true, false), None);
3827 }
3828
3829 #[test]
3830 fn mouse_button_state_round_trips_from_mouse_state() {
3831 let ms = MouseState {
3832 left_down: true,
3833 middle_down: true,
3834 ..MouseState::default()
3835 };
3836 let bs: MouseButtonState = (&ms).into();
3837 assert!(bs.left_down);
3838 assert!(!bs.right_down);
3839 assert!(bs.middle_down);
3840 assert!(bs.any_down());
3841
3842 let none = MouseButtonState { left_down: false, right_down: false, middle_down: false };
3843 assert!(!none.any_down());
3844 }
3845
3846 #[test]
3847 fn callback_to_call_collects_hits_for_dom() {
3848 let dom_id = DomId { inner: 0 };
3849 let hit_test = make_hit_test_with_node(2);
3850 let filter = EventFilter::Hover(HoverEventFilter::MouseDown);
3851 let calls = CallbackToCall::from_hit_test(&hit_test, dom_id, filter);
3852 assert_eq!(calls.len(), 1);
3853 assert_eq!(calls[0].node_id, NodeId::new(2));
3854 assert_eq!(calls[0].event_filter, filter);
3855 assert!(calls[0].hit_test_item.is_some());
3856
3857 let other = CallbackToCall::from_hit_test(
3859 &hit_test,
3860 DomId { inner: 999 },
3861 EventFilter::Hover(HoverEventFilter::MouseUp),
3862 );
3863 assert!(other.is_empty());
3864
3865 let direct = CallbackToCall::new(
3867 NodeId::new(7),
3868 None,
3869 EventFilter::Focus(FocusEventFilter::FocusReceived),
3870 );
3871 assert_eq!(direct.node_id, NodeId::new(7));
3872 assert!(direct.hit_test_item.is_none());
3873 }
3874
3875 #[test]
3876 fn restyle_relayout_aliases_are_btreemap_compatible() {
3877 let restyle: RestyleNodes = BTreeMap::new();
3880 let relayout: RelayoutNodes = BTreeMap::new();
3881 assert!(restyle.is_empty());
3882 assert!(relayout.is_empty());
3883
3884 let mut words: RelayoutWords = BTreeMap::new();
3886 words.insert(NodeId::new(1), AzString::from_const_str("hello"));
3887 assert_eq!(words.get(&NodeId::new(1)).map(azul_css::AzString::as_str), Some("hello"));
3888 }
3889
3890 #[test]
3891 fn detect_lifecycle_events_with_reconciliation_is_callable() {
3892 let dom_id = DomId { inner: 0 };
3896 let old_data: Vec<crate::dom::NodeData> = Vec::new();
3897 let new_data: Vec<crate::dom::NodeData> = Vec::new();
3898 let old_hier: Vec<crate::styled_dom::NodeHierarchyItem> = Vec::new();
3899 let new_hier: Vec<crate::styled_dom::NodeHierarchyItem> = Vec::new();
3900 let old_layout = OrderedMap::default();
3901 let new_layout = OrderedMap::default();
3902 let result: LifecycleEventResult = detect_lifecycle_events_with_reconciliation(
3903 dom_id,
3904 &old_data,
3905 &new_data,
3906 &old_hier,
3907 &new_hier,
3908 &old_layout,
3909 &new_layout,
3910 Instant::Tick(SystemTick::new(0)),
3911 );
3912 assert!(result.events.is_empty());
3913 assert!(result.node_id_mapping.is_empty());
3914 }
3915
3916 #[test]
3917 fn nodedata_focusable_and_activation_traits_are_wired() {
3918 use crate::dom::{NodeData, NodeType};
3919 use crate::events::{ActivationBehavior as _, Focusable as _};
3920
3921 let btn = NodeData::create_node(NodeType::Button);
3923 assert!(<NodeData as Focusable>::is_naturally_focusable(&btn));
3924 assert!(<NodeData as Focusable>::is_focusable(&btn));
3925 assert!(<NodeData as ActivationBehavior>::has_activation_behavior(&btn));
3926 assert!(<NodeData as ActivationBehavior>::is_activatable(&btn));
3927
3928 let div = NodeData::create_node(NodeType::Div);
3930 assert!(!<NodeData as Focusable>::is_naturally_focusable(&div));
3931 assert!(!<NodeData as ActivationBehavior>::has_activation_behavior(&div));
3932
3933 let input = NodeData::create_node(NodeType::Input);
3935 assert!(<NodeData as Focusable>::is_naturally_focusable(&input));
3936 }
3937
3938 #[test]
3939 fn first_hovered_node_picks_frontmost_by_depth() {
3940 use crate::hit_test::{FullHitTest, HitTest, HitTestItem};
3941 use crate::dom::OptionDomNodeId;
3942 use std::collections::BTreeMap;
3943
3944 let item = |depth: u32| HitTestItem {
3945 point_in_viewport: LogicalPosition::zero(),
3946 point_relative_to_item: LogicalPosition::zero(),
3947 is_focusable: true,
3948 is_virtual_view_hit: None,
3949 hit_depth: depth,
3950 };
3951
3952 let mut regular = BTreeMap::new();
3956 regular.insert(NodeId::new(2), item(5));
3957 regular.insert(NodeId::new(5), item(0));
3958
3959 let mut hovered = BTreeMap::new();
3960 hovered.insert(DomId { inner: 0 }, HitTest {
3961 regular_hit_test_nodes: regular,
3962 scroll_hit_test_nodes: BTreeMap::new(),
3963 scrollbar_hit_test_nodes: BTreeMap::new(),
3964 cursor_hit_test_nodes: BTreeMap::new(),
3965 });
3966 let ht = FullHitTest { hovered_nodes: hovered, focused_node: OptionDomNodeId::None };
3967
3968 let got = get_first_hovered_node(Some(&ht)).unwrap();
3969 assert_eq!(got.node.into_crate_internal(), Some(NodeId::new(5)));
3970 }
3971
3972 #[test]
3973 fn size_changed_nan_guard_stops_resize_loop() {
3974 use crate::geom::LogicalSize;
3975 let a = LogicalSize::new(f32::NAN, 100.0);
3978 let b = LogicalSize::new(f32::NAN, 100.0);
3979 assert!(!size_changed(a, b));
3980 assert!(size_changed(LogicalSize::new(100.0, 100.0), LogicalSize::new(100.0, 120.0)));
3982 assert!(!size_changed(LogicalSize::new(100.0, 100.0), LogicalSize::new(100.00005, 100.0)));
3984 }
3985
3986 #[test]
3987 fn dom_path_terminates_on_parent_cycle() {
3988 use crate::id::{Node, NodeHierarchy};
3989 let nodes = vec![
3991 Node { parent: Some(NodeId::new(1)), ..Node::ROOT },
3992 Node { parent: Some(NodeId::new(0)), ..Node::ROOT },
3993 ];
3994 let hier = NodeHierarchy::new(nodes);
3995 let target = NodeHierarchyItemId::from_crate_internal(Some(NodeId::new(0)));
3996 let path = get_dom_path(&hier, target);
3998 assert!(path.len() <= 2);
3999 }
4000
4001 #[test]
4002 fn click_event_maps_to_left_mouse_up() {
4003 let filters = event_type_to_filters(EventType::Click, &EventData::None);
4004 assert!(filters.contains(&EventFilter::Hover(HoverEventFilter::LeftMouseUp)));
4005 assert!(!filters.contains(&EventFilter::Hover(HoverEventFilter::LeftMouseDown)));
4006 }
4007}
4008
4009#[cfg(test)]
4010#[allow(clippy::float_cmp, clippy::too_many_lines)]
4011mod autotest_generated {
4012 use super::*;
4013 use crate::{
4014 dom::{DomId, DomNodeId, OptionDomNodeId},
4015 geom::{LogicalPosition, LogicalRect, LogicalSize},
4016 hit_test::{FullHitTest, HitTest, HitTestItem},
4017 id::{Node, NodeHierarchy, NodeId},
4018 styled_dom::NodeHierarchyItemId,
4019 task::{Instant, SystemTick},
4020 window::{CursorPosition, KeyboardState, MouseState, VirtualKeyCode, VirtualKeyCodeVec},
4021 };
4022
4023 fn tick(n: u64) -> Instant {
4026 Instant::Tick(SystemTick::new(n))
4027 }
4028
4029 fn dnid(dom: usize, node: usize) -> DomNodeId {
4030 DomNodeId {
4031 dom: DomId { inner: dom },
4032 node: NodeHierarchyItemId::from_crate_internal(Some(NodeId::new(node))),
4033 }
4034 }
4035
4036 fn dnid_none(dom: usize) -> DomNodeId {
4038 DomNodeId {
4039 dom: DomId { inner: dom },
4040 node: NodeHierarchyItemId::NONE,
4041 }
4042 }
4043
4044 fn hit_item(depth: u32) -> HitTestItem {
4045 HitTestItem {
4046 point_in_viewport: LogicalPosition::new(1.0, 2.0),
4047 point_relative_to_item: LogicalPosition::new(3.0, 4.0),
4048 is_focusable: true,
4049 is_virtual_view_hit: None,
4050 hit_depth: depth,
4051 }
4052 }
4053
4054 fn hit_test_with(dom: usize, nodes: &[(usize, u32)]) -> FullHitTest {
4056 let mut regular = BTreeMap::new();
4057 for (idx, depth) in nodes {
4058 regular.insert(NodeId::new(*idx), hit_item(*depth));
4059 }
4060 let mut hovered = BTreeMap::new();
4061 hovered.insert(
4062 DomId { inner: dom },
4063 HitTest {
4064 regular_hit_test_nodes: regular,
4065 scroll_hit_test_nodes: BTreeMap::new(),
4066 scrollbar_hit_test_nodes: BTreeMap::new(),
4067 cursor_hit_test_nodes: BTreeMap::new(),
4068 },
4069 );
4070 FullHitTest {
4071 hovered_nodes: hovered,
4072 focused_node: OptionDomNodeId::None,
4073 }
4074 }
4075
4076 fn empty_hit_test() -> FullHitTest {
4077 FullHitTest {
4078 hovered_nodes: BTreeMap::new(),
4079 focused_node: OptionDomNodeId::None,
4080 }
4081 }
4082
4083 fn mouse_event(ty: EventType, button: MouseButton, pos: LogicalPosition) -> SyntheticEvent {
4084 SyntheticEvent::new(
4085 ty,
4086 EventSource::User,
4087 dnid(0, 0),
4088 tick(0),
4089 EventData::Mouse(MouseEventData {
4090 position: pos,
4091 button,
4092 buttons: 1,
4093 modifiers: KeyModifiers::default(),
4094 }),
4095 )
4096 }
4097
4098 fn key_event(key_code: u32, modifiers: KeyModifiers) -> SyntheticEvent {
4099 SyntheticEvent::new(
4100 EventType::KeyDown,
4101 EventSource::User,
4102 dnid(0, 0),
4103 tick(0),
4104 EventData::Keyboard(KeyboardEventData {
4105 key_code,
4106 char_code: None,
4107 modifiers,
4108 repeat: false,
4109 }),
4110 )
4111 }
4112
4113 fn hierarchy_chain(len: usize) -> NodeHierarchy {
4115 let nodes = (0..len)
4116 .map(|i| Node {
4117 parent: if i == 0 { None } else { Some(NodeId::new(i - 1)) },
4118 ..Node::ROOT
4119 })
4120 .collect::<Vec<_>>();
4121 NodeHierarchy::new(nodes)
4122 }
4123
4124 fn primary_modifiers() -> KeyModifiers {
4126 if cfg!(target_os = "macos") {
4127 KeyModifiers::new().with_meta()
4128 } else {
4129 KeyModifiers::new().with_ctrl()
4130 }
4131 }
4132
4133 fn keyboard_with_primary_held() -> KeyboardState {
4134 let key = if cfg!(target_os = "macos") {
4135 VirtualKeyCode::LWin
4136 } else {
4137 VirtualKeyCode::LControl
4138 };
4139 KeyboardState {
4140 pressed_virtual_keycodes: VirtualKeyCodeVec::from_vec(vec![key]),
4141 ..KeyboardState::default()
4142 }
4143 }
4144
4145 #[test]
4149 fn size_changed_zero_and_identity() {
4150 assert!(!size_changed(LogicalSize::zero(), LogicalSize::zero()));
4151 assert!(!size_changed(
4152 LogicalSize::new(0.0, 0.0),
4153 LogicalSize::new(-0.0, -0.0)
4154 ));
4155 assert!(size_changed(LogicalSize::zero(), LogicalSize::new(0.0, 1.0)));
4157 assert!(size_changed(LogicalSize::zero(), LogicalSize::new(1.0, 0.0)));
4158 }
4159
4160 #[test]
4161 fn size_changed_single_sided_nan_is_a_change() {
4162 assert!(size_changed(
4165 LogicalSize::new(f32::NAN, 10.0),
4166 LogicalSize::new(10.0, 10.0)
4167 ));
4168 assert!(size_changed(
4169 LogicalSize::new(10.0, 10.0),
4170 LogicalSize::new(10.0, f32::NAN)
4171 ));
4172 assert!(!size_changed(
4175 LogicalSize::new(f32::NAN, f32::NAN),
4176 LogicalSize::new(f32::NAN, f32::NAN)
4177 ));
4178 }
4179
4180 #[test]
4181 fn size_changed_negative_and_infinite_do_not_panic() {
4182 assert!(size_changed(
4184 LogicalSize::new(-100.0, 0.0),
4185 LogicalSize::new(100.0, 0.0)
4186 ));
4187 assert!(!size_changed(
4188 LogicalSize::new(-100.0, -50.0),
4189 LogicalSize::new(-100.0, -50.0)
4190 ));
4191 assert!(!size_changed(
4194 LogicalSize::new(f32::INFINITY, f32::INFINITY),
4195 LogicalSize::new(f32::INFINITY, f32::INFINITY)
4196 ));
4197 assert!(size_changed(
4198 LogicalSize::new(f32::INFINITY, 0.0),
4199 LogicalSize::new(f32::NEG_INFINITY, 0.0)
4200 ));
4201 assert!(!size_changed(
4205 LogicalSize::new(f32::MAX, 0.0),
4206 LogicalSize::new(f32::INFINITY, 0.0)
4207 ));
4208 }
4209
4210 #[test]
4211 fn size_changed_ignores_sub_quantum_jitter_but_sees_one_quantum() {
4212 assert!(!size_changed(
4214 LogicalSize::new(50.0, 50.0),
4215 LogicalSize::new(50.0004, 50.0)
4216 ));
4217 assert!(size_changed(
4219 LogicalSize::new(50.0, 50.0),
4220 LogicalSize::new(50.002, 50.0)
4221 ));
4222 }
4223
4224 #[test]
4227 fn create_mount_event_without_layout_entry_falls_back_to_zero_rect() {
4228 let layout: BTreeMap<NodeId, LogicalRect> = BTreeMap::new();
4229 let ev = create_mount_event(NodeId::new(3), DomId { inner: 0 }, &layout, &tick(7));
4230 assert_eq!(ev.event_type, EventType::Mount);
4231 assert_eq!(ev.source, EventSource::Lifecycle);
4232 assert_eq!(ev.phase, EventPhase::Target);
4233 assert_eq!(ev.target, ev.current_target);
4234 assert_eq!(ev.target.node.into_crate_internal(), Some(NodeId::new(3)));
4235 match ev.data {
4236 EventData::Lifecycle(d) => {
4237 assert_eq!(d.reason, LifecycleReason::InitialMount);
4238 assert!(d.previous_bounds.is_none());
4239 assert_eq!(d.current_bounds, LogicalRect::zero());
4240 }
4241 _ => panic!("mount event must carry lifecycle data"),
4242 }
4243 }
4244
4245 #[test]
4246 fn create_unmount_event_reports_previous_bounds_and_zero_current() {
4247 let mut layout = BTreeMap::new();
4248 let rect = LogicalRect::new(LogicalPosition::new(1.0, 2.0), LogicalSize::new(3.0, 4.0));
4249 layout.insert(NodeId::new(1), rect);
4250 let ev = create_unmount_event(NodeId::new(1), DomId { inner: 2 }, &layout, &tick(9));
4251 assert_eq!(ev.event_type, EventType::Unmount);
4252 match ev.data {
4253 EventData::Lifecycle(d) => {
4254 assert_eq!(d.reason, LifecycleReason::Unmount);
4255 assert_eq!(d.previous_bounds, Some(rect));
4256 assert_eq!(d.current_bounds, LogicalRect::zero());
4257 }
4258 _ => panic!("unmount event must carry lifecycle data"),
4259 }
4260 }
4261
4262 #[test]
4263 fn create_lifecycle_event_survives_extreme_node_ids() {
4264 let huge = NodeId::new(usize::MAX - 1);
4267 let layout: BTreeMap<NodeId, LogicalRect> = BTreeMap::new();
4268 let ev = create_mount_event(huge, DomId { inner: usize::MAX }, &layout, &tick(0));
4269 assert_eq!(ev.target.node.into_crate_internal(), Some(huge));
4270 assert_eq!(ev.target.dom, DomId { inner: usize::MAX });
4271
4272 let root = create_mount_event(NodeId::ZERO, DomId { inner: 0 }, &layout, &tick(0));
4275 assert_eq!(
4276 root.target.node.into_crate_internal(),
4277 Some(NodeId::ZERO),
4278 "NodeId 0 must not decode as `None`"
4279 );
4280 }
4281
4282 #[test]
4283 fn create_resize_event_returns_none_for_missing_or_unchanged_layout() {
4284 let dom = DomId { inner: 0 };
4285 let node = NodeId::new(1);
4286 let rect = LogicalRect::new(LogicalPosition::zero(), LogicalSize::new(10.0, 10.0));
4287
4288 let empty: BTreeMap<NodeId, LogicalRect> = BTreeMap::new();
4289 let mut one = BTreeMap::new();
4290 one.insert(node, rect);
4291
4292 assert!(create_resize_event(node, dom, &empty, &one, &tick(0)).is_none());
4294 assert!(create_resize_event(node, dom, &one, &empty, &tick(0)).is_none());
4295 assert!(create_resize_event(node, dom, &empty, &empty, &tick(0)).is_none());
4296 assert!(create_resize_event(node, dom, &one, &one, &tick(0)).is_none());
4298 }
4299
4300 #[test]
4301 fn create_resize_event_ignores_pure_origin_moves() {
4302 let dom = DomId { inner: 0 };
4305 let node = NodeId::new(0);
4306 let size = LogicalSize::new(10.0, 10.0);
4307 let mut old = BTreeMap::new();
4308 old.insert(node, LogicalRect::new(LogicalPosition::new(0.0, 0.0), size));
4309 let mut new = BTreeMap::new();
4310 new.insert(
4311 node,
4312 LogicalRect::new(LogicalPosition::new(500.0, 500.0), size),
4313 );
4314 assert!(create_resize_event(node, dom, &old, &new, &tick(0)).is_none());
4315 }
4316
4317 #[test]
4318 fn create_resize_event_nan_size_does_not_loop_forever() {
4319 let dom = DomId { inner: 0 };
4322 let node = NodeId::new(0);
4323 let nan_rect = LogicalRect::new(
4324 LogicalPosition::zero(),
4325 LogicalSize::new(f32::NAN, 100.0),
4326 );
4327 let mut old = BTreeMap::new();
4328 old.insert(node, nan_rect);
4329 let mut new = BTreeMap::new();
4330 new.insert(node, nan_rect);
4331 assert!(create_resize_event(node, dom, &old, &new, &tick(0)).is_none());
4332 }
4333
4334 #[test]
4335 fn create_resize_event_reports_both_bounds_on_real_change() {
4336 let dom = DomId { inner: 0 };
4337 let node = NodeId::new(0);
4338 let old_rect =
4339 LogicalRect::new(LogicalPosition::zero(), LogicalSize::new(10.0, 10.0));
4340 let new_rect =
4341 LogicalRect::new(LogicalPosition::zero(), LogicalSize::new(10.0, 20.0));
4342 let mut old = BTreeMap::new();
4343 old.insert(node, old_rect);
4344 let mut new = BTreeMap::new();
4345 new.insert(node, new_rect);
4346
4347 let ev = create_resize_event(node, dom, &old, &new, &tick(3))
4348 .expect("a real size change must emit a Resize");
4349 assert_eq!(ev.event_type, EventType::Resize);
4350 match ev.data {
4351 EventData::Lifecycle(d) => {
4352 assert_eq!(d.reason, LifecycleReason::Resize);
4353 assert_eq!(d.previous_bounds, Some(old_rect));
4354 assert_eq!(d.current_bounds, new_rect);
4355 }
4356 _ => panic!("resize event must carry lifecycle data"),
4357 }
4358 }
4359
4360 #[test]
4363 fn detect_lifecycle_events_all_none_is_empty() {
4364 let events = detect_lifecycle_events(
4365 DomId { inner: 0 },
4366 DomId { inner: 0 },
4367 None,
4368 None,
4369 None,
4370 None,
4371 tick(0),
4372 );
4373 assert!(events.is_empty());
4374 }
4375
4376 #[test]
4377 fn detect_lifecycle_events_without_layout_emits_nothing() {
4378 let old = hierarchy_chain(1);
4380 let new = hierarchy_chain(4);
4381 let events = detect_lifecycle_events(
4382 DomId { inner: 0 },
4383 DomId { inner: 0 },
4384 Some(&old),
4385 Some(&new),
4386 None,
4387 None,
4388 tick(0),
4389 );
4390 assert!(events.is_empty());
4391 }
4392
4393 #[test]
4394 fn detect_lifecycle_events_emits_mounts_unmounts_and_resizes() {
4395 let dom = DomId { inner: 0 };
4396 let old_hier = hierarchy_chain(2); let new_hier = hierarchy_chain(3); let r = |h: f32| LogicalRect::new(LogicalPosition::zero(), LogicalSize::new(10.0, h));
4400 let mut old_layout = BTreeMap::new();
4401 old_layout.insert(NodeId::new(0), r(10.0));
4402 old_layout.insert(NodeId::new(1), r(10.0));
4403 let mut new_layout = BTreeMap::new();
4404 new_layout.insert(NodeId::new(0), r(10.0)); new_layout.insert(NodeId::new(1), r(99.0)); new_layout.insert(NodeId::new(2), r(10.0)); let events = detect_lifecycle_events(
4409 dom,
4410 dom,
4411 Some(&old_hier),
4412 Some(&new_hier),
4413 Some(&old_layout),
4414 Some(&new_layout),
4415 tick(5),
4416 );
4417
4418 let mounts: Vec<_> = events
4419 .iter()
4420 .filter(|e| e.event_type == EventType::Mount)
4421 .collect();
4422 let resizes: Vec<_> = events
4423 .iter()
4424 .filter(|e| e.event_type == EventType::Resize)
4425 .collect();
4426 assert_eq!(mounts.len(), 1, "only node 2 is new");
4427 assert_eq!(
4428 mounts[0].target.node.into_crate_internal(),
4429 Some(NodeId::new(2))
4430 );
4431 assert_eq!(resizes.len(), 1, "only node 1 changed size");
4432 assert_eq!(
4433 resizes[0].target.node.into_crate_internal(),
4434 Some(NodeId::new(1))
4435 );
4436 assert!(
4437 !events.iter().any(|e| e.event_type == EventType::Unmount),
4438 "nothing was removed"
4439 );
4440 assert!(events.iter().all(|e| e.source == EventSource::Lifecycle));
4441
4442 let events = detect_lifecycle_events(
4444 dom,
4445 dom,
4446 Some(&new_hier),
4447 Some(&old_hier),
4448 Some(&new_layout),
4449 Some(&old_layout),
4450 tick(6),
4451 );
4452 let unmounts: Vec<_> = events
4453 .iter()
4454 .filter(|e| e.event_type == EventType::Unmount)
4455 .collect();
4456 assert_eq!(unmounts.len(), 1);
4457 assert_eq!(
4458 unmounts[0].target.node.into_crate_internal(),
4459 Some(NodeId::new(2))
4460 );
4461 }
4462
4463 #[test]
4464 fn detect_lifecycle_events_mount_of_node_missing_from_layout_uses_zero_rect() {
4465 let dom = DomId { inner: 0 };
4466 let new_hier = hierarchy_chain(2);
4467 let new_layout: BTreeMap<NodeId, LogicalRect> = BTreeMap::new(); let events = detect_lifecycle_events(
4469 dom,
4470 dom,
4471 None,
4472 Some(&new_hier),
4473 None,
4474 Some(&new_layout),
4475 tick(0),
4476 );
4477 assert_eq!(events.len(), 2);
4478 for ev in &events {
4479 match ev.data {
4480 EventData::Lifecycle(d) => assert_eq!(d.current_bounds, LogicalRect::zero()),
4481 _ => panic!("expected lifecycle data"),
4482 }
4483 }
4484 }
4485
4486 #[test]
4487 fn collect_node_ids_handles_none_and_empty_hierarchies() {
4488 assert!(collect_node_ids(None).is_empty());
4489 let empty = NodeHierarchy::new(Vec::new());
4490 assert!(collect_node_ids(Some(&empty)).is_empty());
4491 let three = hierarchy_chain(3);
4492 let ids = collect_node_ids(Some(&three));
4493 assert_eq!(ids.len(), 3);
4494 assert!(ids.contains(&NodeId::ZERO));
4495 assert!(ids.contains(&NodeId::new(2)));
4496 }
4497
4498 #[test]
4501 fn process_event_result_order_is_dense_and_monotonic() {
4502 let all = [
4503 ProcessEventResult::DoNothing,
4504 ProcessEventResult::ShouldReRenderCurrentWindow,
4505 ProcessEventResult::ShouldUpdateDisplayListCurrentWindow,
4506 ProcessEventResult::UpdateHitTesterAndProcessAgain,
4507 ProcessEventResult::ShouldIncrementalRelayout,
4508 ProcessEventResult::ShouldRegenerateDomCurrentWindow,
4509 ProcessEventResult::ShouldRegenerateDomAllWindows,
4510 ];
4511 for (i, r) in all.iter().enumerate() {
4512 assert_eq!(r.order(), i, "order() must match declaration index");
4513 }
4514 for a in all {
4516 for b in all {
4517 assert_eq!(a < b, a.order() < b.order());
4518 let joined = a.max_self(b);
4519 assert_eq!(joined.order(), a.order().max(b.order()));
4520 assert_eq!(joined, b.max_self(a), "max_self must be commutative");
4521 assert_eq!(a.max_self(a), a, "max_self must be idempotent");
4522 }
4523 }
4524 }
4525
4526 #[test]
4527 fn key_modifiers_builders_are_orthogonal_and_is_empty_tracks_them() {
4528 let empty = KeyModifiers::new();
4529 assert!(empty.is_empty());
4530 assert_eq!(empty, KeyModifiers::default());
4531
4532 assert_eq!(
4534 KeyModifiers::new().with_shift(),
4535 KeyModifiers { shift: true, ctrl: false, alt: false, meta: false }
4536 );
4537 assert_eq!(
4538 KeyModifiers::new().with_ctrl(),
4539 KeyModifiers { shift: false, ctrl: true, alt: false, meta: false }
4540 );
4541 assert_eq!(
4542 KeyModifiers::new().with_alt(),
4543 KeyModifiers { shift: false, ctrl: false, alt: true, meta: false }
4544 );
4545 assert_eq!(
4546 KeyModifiers::new().with_meta(),
4547 KeyModifiers { shift: false, ctrl: false, alt: false, meta: true }
4548 );
4549
4550 assert!(!KeyModifiers::new().with_shift().is_empty());
4552 assert!(!KeyModifiers::new().with_ctrl().is_empty());
4553 assert!(!KeyModifiers::new().with_alt().is_empty());
4554 assert!(!KeyModifiers::new().with_meta().is_empty());
4555 assert_eq!(
4556 KeyModifiers::new().with_ctrl().with_ctrl(),
4557 KeyModifiers::new().with_ctrl()
4558 );
4559 let all = KeyModifiers::new().with_shift().with_ctrl().with_alt().with_meta();
4560 assert!(!all.is_empty());
4561 assert!(all.shift && all.ctrl && all.alt && all.meta);
4562 }
4563
4564 #[test]
4565 fn scroll_into_view_options_presets_and_behavior_setters() {
4566 assert_eq!(
4567 ScrollIntoViewOptions::default(),
4568 ScrollIntoViewOptions::nearest(),
4569 "Default must be the `nearest`/`auto` preset"
4570 );
4571 for (opts, expected) in [
4572 (ScrollIntoViewOptions::nearest(), ScrollLogicalPosition::Nearest),
4573 (ScrollIntoViewOptions::center(), ScrollLogicalPosition::Center),
4574 (ScrollIntoViewOptions::start(), ScrollLogicalPosition::Start),
4575 (ScrollIntoViewOptions::end(), ScrollLogicalPosition::End),
4576 ] {
4577 assert_eq!(opts.block, expected);
4578 assert_eq!(opts.inline_axis, expected, "both axes must be aligned alike");
4579 assert_eq!(opts.behavior, ScrollIntoViewBehavior::Auto);
4580
4581 let instant = opts.with_instant();
4583 assert_eq!(instant.behavior, ScrollIntoViewBehavior::Instant);
4584 assert_eq!(instant.block, opts.block);
4585 assert_eq!(instant.inline_axis, opts.inline_axis);
4586
4587 let smooth = opts.with_smooth();
4588 assert_eq!(smooth.behavior, ScrollIntoViewBehavior::Smooth);
4589 assert_eq!(
4590 opts.with_instant().with_smooth().behavior,
4591 ScrollIntoViewBehavior::Smooth
4592 );
4593 assert_eq!(
4594 opts.with_smooth().with_instant().behavior,
4595 ScrollIntoViewBehavior::Instant
4596 );
4597 }
4598 }
4599
4600 #[test]
4601 fn default_action_result_has_action_predicate() {
4602 assert!(!DefaultActionResult::default().has_action());
4603 assert!(!DefaultActionResult::prevented().has_action());
4604 assert!(DefaultActionResult::prevented().prevented);
4605 assert_eq!(DefaultActionResult::prevented().action, DefaultAction::None);
4606
4607 let none = DefaultActionResult::new(DefaultAction::None);
4609 assert!(!none.prevented);
4610 assert!(!none.has_action());
4611
4612 for action in [
4614 DefaultAction::FocusNext,
4615 DefaultAction::FocusPrevious,
4616 DefaultAction::FocusFirst,
4617 DefaultAction::FocusLast,
4618 DefaultAction::ClearFocus,
4619 DefaultAction::SelectAllText,
4620 DefaultAction::ActivateFocusedElement { target: dnid(0, 1) },
4621 DefaultAction::SubmitForm { form_node: dnid(0, 1) },
4622 DefaultAction::CloseModal { modal_node: dnid(0, 1) },
4623 DefaultAction::ScrollFocusedContainer {
4624 direction: ScrollDirection::Down,
4625 amount: ScrollAmount::Page,
4626 },
4627 ] {
4628 let r = DefaultActionResult::new(action);
4629 assert_eq!(r.action, action);
4630 assert!(!r.prevented);
4631 assert!(r.has_action(), "{action:?} must be reported as actionable");
4632 }
4633 }
4634
4635 #[test]
4636 fn synthetic_event_constructor_invariants_and_flag_transitions() {
4637 let target = dnid(3, 7);
4638 let mut ev = SyntheticEvent::new(
4639 EventType::Click,
4640 EventSource::Programmatic,
4641 target,
4642 tick(42),
4643 EventData::None,
4644 );
4645 assert_eq!(ev.event_type, EventType::Click);
4647 assert_eq!(ev.source, EventSource::Programmatic);
4648 assert_eq!(ev.phase, EventPhase::Target);
4649 assert_eq!(ev.target, target);
4650 assert_eq!(ev.current_target, target);
4651 assert_eq!(ev.timestamp, tick(42));
4652 assert!(!ev.is_propagation_stopped());
4653 assert!(!ev.is_immediate_propagation_stopped());
4654 assert!(!ev.is_default_prevented());
4655
4656 ev.stop_propagation();
4658 assert!(ev.is_propagation_stopped());
4659 assert!(!ev.is_immediate_propagation_stopped());
4660
4661 let mut ev2 = SyntheticEvent::new(
4664 EventType::Click,
4665 EventSource::User,
4666 target,
4667 tick(0),
4668 EventData::None,
4669 );
4670 ev2.stop_immediate_propagation();
4671 assert!(ev2.is_immediate_propagation_stopped());
4672 assert!(
4673 ev2.is_propagation_stopped(),
4674 "immediate stop must also stop normal propagation"
4675 );
4676
4677 let mut ev3 = ev2.clone();
4679 ev3.stop_immediate_propagation();
4680 ev3.prevent_default();
4681 ev3.prevent_default();
4682 assert!(ev3.is_default_prevented());
4683 assert!(!ev.is_default_prevented(), "flags must not leak across events");
4684 }
4685
4686 #[test]
4687 fn hover_filter_is_system_internal_only_for_system_text_clicks() {
4688 for f in [
4689 HoverEventFilter::SystemTextSingleClick,
4690 HoverEventFilter::SystemTextDoubleClick,
4691 HoverEventFilter::SystemTextTripleClick,
4692 ] {
4693 assert!(f.is_system_internal(), "{f:?} is internal");
4694 assert!(
4695 f.to_focus_event_filter().is_none(),
4696 "internal filters must never be exposed as focus callbacks"
4697 );
4698 }
4699 for f in [
4700 HoverEventFilter::MouseOver,
4701 HoverEventFilter::MouseDown,
4702 HoverEventFilter::Drop,
4703 HoverEventFilter::KeyringResult,
4704 HoverEventFilter::MouseOut,
4705 ] {
4706 assert!(!f.is_system_internal(), "{f:?} is a user-visible filter");
4707 }
4708 }
4709
4710 #[test]
4711 fn event_filter_kind_predicates_are_mutually_exclusive() {
4712 let hover = EventFilter::Hover(HoverEventFilter::MouseDown);
4713 let focus = EventFilter::Focus(FocusEventFilter::FocusReceived);
4714 let window = EventFilter::Window(WindowEventFilter::Resized);
4715 let component = EventFilter::Component(ComponentEventFilter::AfterMount);
4716 let app = EventFilter::Application(ApplicationEventFilter::DeviceConnected);
4717
4718 assert!(focus.is_focus_callback());
4719 assert!(window.is_window_callback());
4720 for f in [hover, window, component, app] {
4721 assert!(!f.is_focus_callback(), "{f:?} is not a focus callback");
4722 }
4723 for f in [hover, focus, component, app] {
4724 assert!(!f.is_window_callback(), "{f:?} is not a window callback");
4725 }
4726 assert_eq!(hover.as_hover_event_filter(), Some(HoverEventFilter::MouseDown));
4728 assert_eq!(hover.as_focus_event_filter(), None);
4729 assert_eq!(hover.as_window_event_filter(), None);
4730 assert_eq!(focus.as_focus_event_filter(), Some(FocusEventFilter::FocusReceived));
4731 assert_eq!(window.as_window_event_filter(), Some(WindowEventFilter::Resized));
4732 assert_eq!(component.as_hover_event_filter(), None);
4733 }
4734
4735 #[test]
4738 fn window_to_hover_filter_mapping_never_yields_an_internal_filter() {
4739 for w in [
4742 WindowEventFilter::MouseOver,
4743 WindowEventFilter::MouseDown,
4744 WindowEventFilter::LeftMouseDown,
4745 WindowEventFilter::RightMouseDown,
4746 WindowEventFilter::MiddleMouseDown,
4747 WindowEventFilter::MouseUp,
4748 WindowEventFilter::LeftMouseUp,
4749 WindowEventFilter::RightMouseUp,
4750 WindowEventFilter::MiddleMouseUp,
4751 WindowEventFilter::Scroll,
4752 WindowEventFilter::TextInput,
4753 WindowEventFilter::VirtualKeyDown,
4754 WindowEventFilter::VirtualKeyUp,
4755 WindowEventFilter::HoveredFile,
4756 WindowEventFilter::DroppedFile,
4757 WindowEventFilter::HoveredFileCancelled,
4758 WindowEventFilter::TouchStart,
4759 WindowEventFilter::TouchEnd,
4760 WindowEventFilter::PenDown,
4761 WindowEventFilter::DragStart,
4762 WindowEventFilter::Drop,
4763 WindowEventFilter::DoubleClick,
4764 WindowEventFilter::PermissionChanged,
4765 WindowEventFilter::BiometricResult,
4766 WindowEventFilter::KeyringResult,
4767 ] {
4768 let hover = w
4769 .to_hover_event_filter()
4770 .unwrap_or_else(|| panic!("{w:?} should have a hover twin"));
4771 assert!(
4772 !hover.is_system_internal(),
4773 "{w:?} must not map onto an internal filter"
4774 );
4775 }
4776
4777 for w in [
4779 WindowEventFilter::MouseEnter,
4780 WindowEventFilter::MouseLeave,
4781 WindowEventFilter::Resized,
4782 WindowEventFilter::Moved,
4783 WindowEventFilter::FocusReceived,
4784 WindowEventFilter::FocusLost,
4785 WindowEventFilter::CloseRequested,
4786 WindowEventFilter::ThemeChanged,
4787 WindowEventFilter::WindowFocusReceived,
4788 WindowEventFilter::WindowFocusLost,
4789 WindowEventFilter::DpiChanged,
4790 WindowEventFilter::MonitorChanged,
4791 ] {
4792 assert_eq!(
4793 w.to_hover_event_filter(),
4794 None,
4795 "{w:?} is window-specific and must not map to a hover filter"
4796 );
4797 }
4798 }
4799
4800 #[test]
4801 fn window_hover_focus_filter_names_round_trip() {
4802 let pairs = [
4806 (
4807 WindowEventFilter::MouseOver,
4808 HoverEventFilter::MouseOver,
4809 Some(FocusEventFilter::MouseOver),
4810 ),
4811 (
4812 WindowEventFilter::LeftMouseDown,
4813 HoverEventFilter::LeftMouseDown,
4814 Some(FocusEventFilter::LeftMouseDown),
4815 ),
4816 (
4817 WindowEventFilter::RightMouseUp,
4818 HoverEventFilter::RightMouseUp,
4819 Some(FocusEventFilter::RightMouseUp),
4820 ),
4821 (
4822 WindowEventFilter::TextInput,
4823 HoverEventFilter::TextInput,
4824 Some(FocusEventFilter::TextInput),
4825 ),
4826 (
4827 WindowEventFilter::VirtualKeyDown,
4828 HoverEventFilter::VirtualKeyDown,
4829 Some(FocusEventFilter::VirtualKeyDown),
4830 ),
4831 (
4832 WindowEventFilter::DragStart,
4833 HoverEventFilter::DragStart,
4834 Some(FocusEventFilter::DragStart),
4835 ),
4836 (
4837 WindowEventFilter::Drop,
4838 HoverEventFilter::Drop,
4839 Some(FocusEventFilter::Drop),
4840 ),
4841 (
4843 WindowEventFilter::DroppedFile,
4844 HoverEventFilter::DroppedFile,
4845 None,
4846 ),
4847 (
4848 WindowEventFilter::TouchStart,
4849 HoverEventFilter::TouchStart,
4850 None,
4851 ),
4852 ];
4853 for (w, h, f) in pairs {
4854 assert_eq!(w.to_hover_event_filter(), Some(h), "window->hover for {w:?}");
4855 assert_eq!(h.to_focus_event_filter(), f, "hover->focus for {h:?}");
4856 }
4857 }
4858
4859 #[test]
4860 fn on_to_event_filter_conversion_is_stable() {
4861 use crate::dom::On;
4862 assert_eq!(
4865 EventFilter::from(On::TextInput),
4866 EventFilter::Focus(FocusEventFilter::TextInput)
4867 );
4868 assert_eq!(
4869 EventFilter::from(On::VirtualKeyDown),
4870 EventFilter::Window(WindowEventFilter::VirtualKeyDown)
4871 );
4872 assert_eq!(
4873 EventFilter::from(On::MouseOver),
4874 EventFilter::Hover(HoverEventFilter::MouseOver)
4875 );
4876 for on in [On::Default, On::Collapse, On::Expand, On::Increment, On::Decrement] {
4878 assert_eq!(
4879 EventFilter::from(on),
4880 EventFilter::Hover(HoverEventFilter::MouseUp),
4881 "{on:?} must map to the click filter"
4882 );
4883 }
4884 assert!(EventFilter::from(On::TextInput).is_focus_callback());
4885 assert!(EventFilter::from(On::VirtualKeyUp).is_window_callback());
4886 }
4887
4888 #[test]
4889 fn virtual_keycode_round_trips_for_every_key_events_rs_interprets() {
4890 for vk in [
4894 VirtualKeyCode::Left,
4895 VirtualKeyCode::Right,
4896 VirtualKeyCode::Up,
4897 VirtualKeyCode::Down,
4898 VirtualKeyCode::Home,
4899 VirtualKeyCode::End,
4900 VirtualKeyCode::Back,
4901 VirtualKeyCode::Delete,
4902 VirtualKeyCode::A,
4903 VirtualKeyCode::C,
4904 VirtualKeyCode::D,
4905 VirtualKeyCode::V,
4906 VirtualKeyCode::X,
4907 VirtualKeyCode::Y,
4908 VirtualKeyCode::Z,
4909 ] {
4910 assert_eq!(
4911 VirtualKeyCode::from_u32(vk as u32),
4912 Some(vk),
4913 "{vk:?} must survive the as-u32 / from_u32 round trip"
4914 );
4915 }
4916 assert_eq!(VirtualKeyCode::from_u32(u32::MAX), None);
4918 assert_eq!(VirtualKeyCode::from_u32(100_000), None);
4919 }
4920
4921 #[test]
4924 fn arrow_direction_from_key_is_total_over_every_decodable_key() {
4925 let nav = [
4928 VirtualKeyCode::Left,
4929 VirtualKeyCode::Right,
4930 VirtualKeyCode::Up,
4931 VirtualKeyCode::Down,
4932 VirtualKeyCode::Home,
4933 VirtualKeyCode::End,
4934 ];
4935 for raw in 0u32..1024 {
4936 let Some(vk) = VirtualKeyCode::from_u32(raw) else {
4937 continue;
4938 };
4939 for ctrl in [false, true] {
4940 let got = ArrowDirection::from_key(vk, ctrl);
4941 assert_eq!(
4942 got.is_some(),
4943 nav.contains(&vk),
4944 "{vk:?} (ctrl={ctrl}) must map to an ArrowDirection iff it is a nav key"
4945 );
4946 if let Some(dir) = got {
4947 let (_d, _s) = dir.to_selection(ctrl);
4949 }
4950 }
4951 }
4952 }
4953
4954 #[test]
4955 fn arrow_direction_ctrl_only_upgrades_horizontal_arrows_to_words() {
4956 for (dir, expect_no_ctrl, expect_ctrl) in [
4959 (
4960 ArrowDirection::Left,
4961 (SelectionDirection::Backward, SelectionStep::Character),
4962 (SelectionDirection::Backward, SelectionStep::Word),
4963 ),
4964 (
4965 ArrowDirection::Right,
4966 (SelectionDirection::Forward, SelectionStep::Character),
4967 (SelectionDirection::Forward, SelectionStep::Word),
4968 ),
4969 (
4970 ArrowDirection::Up,
4971 (SelectionDirection::Backward, SelectionStep::VisualLine),
4972 (SelectionDirection::Backward, SelectionStep::VisualLine),
4973 ),
4974 (
4975 ArrowDirection::Down,
4976 (SelectionDirection::Forward, SelectionStep::VisualLine),
4977 (SelectionDirection::Forward, SelectionStep::VisualLine),
4978 ),
4979 (
4980 ArrowDirection::LineStart,
4981 (SelectionDirection::Backward, SelectionStep::Line),
4982 (SelectionDirection::Backward, SelectionStep::Line),
4983 ),
4984 (
4985 ArrowDirection::DocumentEnd,
4986 (SelectionDirection::Forward, SelectionStep::Document),
4987 (SelectionDirection::Forward, SelectionStep::Document),
4988 ),
4989 ] {
4990 assert_eq!(dir.to_selection(false), expect_no_ctrl, "{dir:?} plain");
4991 assert_eq!(dir.to_selection(true), expect_ctrl, "{dir:?} + ctrl");
4992 }
4993 assert_eq!(
4995 ArrowDirection::from_key(VirtualKeyCode::Home, true),
4996 Some(ArrowDirection::DocumentStart)
4997 );
4998 assert_eq!(
4999 ArrowDirection::from_key(VirtualKeyCode::End, true),
5000 Some(ArrowDirection::DocumentEnd)
5001 );
5002 }
5003
5004 #[test]
5005 fn keyboard_shortcut_from_key_requires_primary_for_every_key() {
5006 for raw in 0u32..1024 {
5009 let Some(vk) = VirtualKeyCode::from_u32(raw) else {
5010 continue;
5011 };
5012 for shift in [false, true] {
5013 assert_eq!(
5014 KeyboardShortcut::from_key(vk, false, shift),
5015 None,
5016 "{vk:?} (shift={shift}) must need the primary modifier"
5017 );
5018 }
5019 }
5020 assert_eq!(
5022 KeyboardShortcut::from_key(VirtualKeyCode::Z, true, true),
5023 Some(KeyboardShortcut::Redo),
5024 "primary+shift+Z is Redo, not Undo"
5025 );
5026 assert_eq!(
5027 KeyboardShortcut::from_key(VirtualKeyCode::Y, true, true),
5028 Some(KeyboardShortcut::Redo),
5029 "shift must not disturb primary+Y"
5030 );
5031 assert_eq!(
5032 KeyboardShortcut::from_key(VirtualKeyCode::C, true, true),
5033 Some(KeyboardShortcut::Copy),
5034 "shift must not disturb primary+C"
5035 );
5036 assert_eq!(KeyboardShortcut::from_key(VirtualKeyCode::D, true, false), None);
5038 }
5039
5040 #[test]
5041 fn selection_op_new_defaults_to_a_single_repeat() {
5042 let op = SelectionOp::new(
5043 SelectionDirection::Forward,
5044 SelectionStep::Word,
5045 SelectionMode::Delete,
5046 );
5047 assert_eq!(op.direction, SelectionDirection::Forward);
5048 assert_eq!(op.step, SelectionStep::Word);
5049 assert_eq!(op.mode, SelectionMode::Delete);
5050 assert_eq!(op.repeat, 1, "a fresh op must apply exactly once");
5051 }
5052
5053 #[test]
5056 fn capture_phase_never_matches_any_filter() {
5057 let ev = mouse_event(EventType::MouseDown, MouseButton::Left, LogicalPosition::zero());
5060 for filter in [
5061 EventFilter::Hover(HoverEventFilter::MouseDown),
5062 EventFilter::Hover(HoverEventFilter::LeftMouseDown),
5063 EventFilter::Focus(FocusEventFilter::MouseDown),
5064 EventFilter::Window(WindowEventFilter::MouseDown),
5065 EventFilter::Component(ComponentEventFilter::AfterMount),
5066 EventFilter::Application(ApplicationEventFilter::DeviceConnected),
5067 ] {
5068 assert!(
5069 !matches_filter_phase(filter, &ev, EventPhase::Capture),
5070 "{filter:?} must not match in the capture phase"
5071 );
5072 }
5073 for phase in [EventPhase::Target, EventPhase::Bubble] {
5075 assert!(matches_filter_phase(
5076 EventFilter::Hover(HoverEventFilter::MouseDown),
5077 &ev,
5078 phase
5079 ));
5080 }
5081 }
5082
5083 #[test]
5084 fn application_filters_never_match_yet() {
5085 let ev = mouse_event(EventType::MouseDown, MouseButton::Left, LogicalPosition::zero());
5087 for phase in [EventPhase::Capture, EventPhase::Target, EventPhase::Bubble] {
5088 assert!(!matches_filter_phase(
5089 EventFilter::Application(ApplicationEventFilter::MonitorConnected),
5090 &ev,
5091 phase
5092 ));
5093 }
5094 }
5095
5096 #[test]
5097 fn check_mouse_button_is_false_for_every_non_mouse_payload() {
5098 for data in [
5099 EventData::None,
5100 EventData::Keyboard(KeyboardEventData {
5101 key_code: 0,
5102 char_code: None,
5103 modifiers: KeyModifiers::default(),
5104 repeat: false,
5105 }),
5106 EventData::Touch(TouchEventData {
5107 id: u64::MAX,
5108 position: LogicalPosition::zero(),
5109 force: f32::NAN,
5110 }),
5111 EventData::Clipboard(ClipboardEventData { content: None }),
5112 ] {
5113 for button in [MouseButton::Left, MouseButton::Right, MouseButton::Middle] {
5114 assert!(
5115 !check_mouse_button(&data, button),
5116 "non-mouse payload must never claim a button"
5117 );
5118 }
5119 }
5120 let other_max = EventData::Mouse(MouseEventData {
5122 position: LogicalPosition::zero(),
5123 button: MouseButton::Other(u8::MAX),
5124 buttons: u8::MAX,
5125 modifiers: KeyModifiers::default(),
5126 });
5127 assert!(check_mouse_button(&other_max, MouseButton::Other(u8::MAX)));
5128 assert!(!check_mouse_button(&other_max, MouseButton::Other(0)));
5129 assert!(!check_mouse_button(&other_max, MouseButton::Left));
5130 }
5131
5132 #[test]
5133 fn button_specific_filters_require_the_matching_button() {
5134 let left = mouse_event(EventType::MouseDown, MouseButton::Left, LogicalPosition::zero());
5135 let right = mouse_event(EventType::MouseDown, MouseButton::Right, LogicalPosition::zero());
5136 let middle = mouse_event(EventType::MouseDown, MouseButton::Middle, LogicalPosition::zero());
5137
5138 for ev in [&left, &right, &middle] {
5140 assert!(matches_hover_filter(
5141 HoverEventFilter::MouseDown,
5142 ev,
5143 EventPhase::Target
5144 ));
5145 }
5146 assert!(matches_hover_filter(HoverEventFilter::LeftMouseDown, &left, EventPhase::Target));
5148 assert!(!matches_hover_filter(HoverEventFilter::LeftMouseDown, &right, EventPhase::Target));
5149 assert!(matches_hover_filter(HoverEventFilter::RightMouseDown, &right, EventPhase::Target));
5150 assert!(!matches_hover_filter(HoverEventFilter::MiddleMouseDown, &right, EventPhase::Target));
5151 assert!(matches_hover_filter(HoverEventFilter::MiddleMouseDown, &middle, EventPhase::Target));
5152
5153 let up = mouse_event(EventType::MouseUp, MouseButton::Left, LogicalPosition::zero());
5155 assert!(!matches_hover_filter(HoverEventFilter::MouseDown, &up, EventPhase::Target));
5156 assert!(!matches_hover_filter(HoverEventFilter::MouseUp, &left, EventPhase::Target));
5157 assert!(matches_focus_filter(FocusEventFilter::LeftMouseUp, &up, EventPhase::Target));
5158 assert!(matches_window_filter(WindowEventFilter::LeftMouseUp, &up, EventPhase::Target));
5159
5160 let payloadless = SyntheticEvent::new(
5163 EventType::MouseDown,
5164 EventSource::Synthetic,
5165 dnid(0, 0),
5166 tick(0),
5167 EventData::None,
5168 );
5169 assert!(matches_hover_filter(HoverEventFilter::MouseDown, &payloadless, EventPhase::Target));
5170 assert!(!matches_hover_filter(
5171 HoverEventFilter::LeftMouseDown,
5172 &payloadless,
5173 EventPhase::Target
5174 ));
5175 }
5176
5177 #[test]
5178 fn component_filter_matches_only_its_own_lifecycle_event() {
5179 let lifecycle = |ty: EventType| {
5180 SyntheticEvent::new(ty, EventSource::Lifecycle, dnid(0, 0), tick(0), EventData::None)
5181 };
5182 let pairs = [
5183 (ComponentEventFilter::AfterMount, EventType::Mount),
5184 (ComponentEventFilter::BeforeUnmount, EventType::Unmount),
5185 (ComponentEventFilter::Updated, EventType::Update),
5186 (ComponentEventFilter::NodeResized, EventType::Resize),
5187 ];
5188 for (filter, ty) in pairs {
5189 let ev = lifecycle(ty);
5190 assert!(
5191 matches_component_filter(filter, &ev, EventPhase::Target),
5192 "{filter:?} must match {ty:?}"
5193 );
5194 for (_, other_ty) in pairs.iter().filter(|(_, t)| *t != ty) {
5196 assert!(
5197 !matches_component_filter(filter, &lifecycle(*other_ty), EventPhase::Target),
5198 "{filter:?} must not match {other_ty:?}"
5199 );
5200 }
5201 }
5202 for filter in [ComponentEventFilter::DefaultAction, ComponentEventFilter::Selected] {
5205 for (_, ty) in pairs {
5206 assert!(!matches_component_filter(filter, &lifecycle(ty), EventPhase::Target));
5207 }
5208 }
5209 }
5210
5211 #[test]
5212 fn event_type_to_filters_never_panics_and_stays_synced_with_the_hover_matcher() {
5213 const KNOWN_DESYNC: &[EventType] = &[
5222 EventType::Click, EventType::ContextMenu, EventType::MouseOut, EventType::ScrollStart, EventType::ScrollEnd, EventType::FocusIn, EventType::FocusOut, EventType::CompositionStart, EventType::CompositionUpdate, EventType::CompositionEnd, ];
5233
5234 let mouse_data = EventData::Mouse(MouseEventData {
5235 position: LogicalPosition::new(1.0, 1.0),
5236 button: MouseButton::Left,
5237 buttons: 1,
5238 modifiers: KeyModifiers::default(),
5239 });
5240
5241 let cases: Vec<(EventType, EventData)> = vec![
5242 (EventType::MouseOver, EventData::None),
5243 (EventType::MouseEnter, EventData::None),
5244 (EventType::MouseLeave, EventData::None),
5245 (EventType::MouseOut, EventData::None),
5246 (EventType::MouseDown, mouse_data.clone()),
5247 (EventType::MouseUp, mouse_data.clone()),
5248 (EventType::Click, mouse_data.clone()),
5249 (EventType::DoubleClick, mouse_data.clone()),
5250 (EventType::ContextMenu, mouse_data.clone()),
5251 (EventType::KeyDown, EventData::None),
5252 (EventType::KeyUp, EventData::None),
5253 (EventType::KeyPress, EventData::None),
5254 (EventType::CompositionStart, EventData::None),
5255 (EventType::CompositionUpdate, EventData::None),
5256 (EventType::CompositionEnd, EventData::None),
5257 (EventType::Focus, EventData::None),
5258 (EventType::Blur, EventData::None),
5259 (EventType::FocusIn, EventData::None),
5260 (EventType::FocusOut, EventData::None),
5261 (EventType::Input, EventData::None),
5262 (EventType::Change, EventData::None),
5263 (EventType::Scroll, EventData::None),
5264 (EventType::ScrollStart, EventData::None),
5265 (EventType::ScrollEnd, EventData::None),
5266 (EventType::DragStart, EventData::None),
5267 (EventType::Drag, EventData::None),
5268 (EventType::DragEnd, EventData::None),
5269 (EventType::DragEnter, EventData::None),
5270 (EventType::DragOver, EventData::None),
5271 (EventType::DragLeave, EventData::None),
5272 (EventType::Drop, EventData::None),
5273 (EventType::TouchStart, EventData::None),
5274 (EventType::TouchMove, EventData::None),
5275 (EventType::TouchEnd, EventData::None),
5276 (EventType::TouchCancel, EventData::None),
5277 (EventType::Mount, EventData::None),
5278 (EventType::Unmount, EventData::None),
5279 (EventType::Update, EventData::None),
5280 (EventType::Resize, EventData::None),
5281 (EventType::WindowResize, EventData::None),
5282 (EventType::WindowMove, EventData::None),
5283 (EventType::WindowClose, EventData::None),
5284 (EventType::ThemeChange, EventData::None),
5285 (EventType::FileHover, EventData::None),
5286 (EventType::FileDrop, EventData::None),
5287 (EventType::FileHoverCancel, EventData::None),
5288 (EventType::Copy, EventData::None),
5289 (EventType::Cut, EventData::None),
5290 (EventType::Paste, EventData::None),
5291 (EventType::SensorChanged, EventData::None),
5292 (EventType::GamepadInput, EventData::None),
5293 (EventType::GeolocationFix, EventData::None),
5294 (EventType::GeolocationError, EventData::None),
5295 (EventType::PermissionChanged, EventData::None),
5296 (EventType::BiometricResult, EventData::None),
5297 (EventType::KeyringResult, EventData::None),
5298 (EventType::LongPress, EventData::None),
5299 (EventType::Play, EventData::None),
5300 ];
5301
5302 for (ty, data) in cases {
5303 let filters = event_type_to_filters(ty, &data);
5304 let ev = SyntheticEvent::new(ty, EventSource::User, dnid(0, 0), tick(0), data);
5305
5306 let mut seen = BTreeSet::new();
5308 for f in &filters {
5309 assert!(seen.insert(*f), "{ty:?} emitted {f:?} twice");
5310 }
5311
5312 for f in &filters {
5313 if !matches!(f, EventFilter::Hover(_)) {
5314 continue; }
5316 if matches_filter_phase(*f, &ev, EventPhase::Target) {
5317 continue;
5318 }
5319 assert!(
5320 KNOWN_DESYNC.contains(&ty),
5321 "NEW DESYNC: event_type_to_filters({ty:?}) emits {f:?}, but \
5322 matches_filter_phase rejects it at the Target phase, so the \
5323 callback would be collected and then silently dropped"
5324 );
5325 }
5326 }
5327 }
5328
5329 #[test]
5330 fn event_type_to_filters_omits_button_specific_filter_for_exotic_buttons() {
5331 let data = EventData::Mouse(MouseEventData {
5333 position: LogicalPosition::zero(),
5334 button: MouseButton::Other(u8::MAX),
5335 buttons: 0,
5336 modifiers: KeyModifiers::default(),
5337 });
5338 let down = event_type_to_filters(EventType::MouseDown, &data);
5339 assert_eq!(down, vec![EventFilter::Hover(HoverEventFilter::MouseDown)]);
5340 let up = event_type_to_filters(EventType::MouseUp, &data);
5341 assert_eq!(up, vec![EventFilter::Hover(HoverEventFilter::MouseUp)]);
5342
5343 for ty in [
5345 EventType::Submit,
5346 EventType::Reset,
5347 EventType::Invalid,
5348 EventType::Play,
5349 EventType::Pause,
5350 EventType::Ended,
5351 EventType::TimeUpdate,
5352 EventType::VolumeChange,
5353 EventType::MediaError,
5354 EventType::PinchIn,
5355 EventType::RotateClockwise,
5356 EventType::SwipeLeft,
5357 ] {
5358 assert!(
5359 event_type_to_filters(ty, &EventData::None).is_empty(),
5360 "{ty:?} is unmapped and must yield no filters"
5361 );
5362 }
5363 }
5364
5365 #[test]
5368 fn get_dom_path_none_target_yields_empty_path() {
5369 let hier = hierarchy_chain(3);
5370 assert!(get_dom_path(&hier, NodeHierarchyItemId::NONE).is_empty());
5371 let empty = NodeHierarchy::new(Vec::new());
5373 let path = get_dom_path(&empty, NodeHierarchyItemId::from_crate_internal(Some(NodeId::ZERO)));
5374 assert_eq!(path, vec![NodeId::ZERO], "unknown nodes still path to themselves");
5375 }
5376
5377 #[test]
5378 fn get_dom_path_out_of_range_target_does_not_panic() {
5379 let hier = hierarchy_chain(3);
5380 let huge = NodeHierarchyItemId::from_crate_internal(Some(NodeId::new(usize::MAX - 1)));
5381 let path = get_dom_path(&hier, huge);
5382 assert_eq!(path, vec![NodeId::new(usize::MAX - 1)]);
5383 }
5384
5385 #[test]
5386 fn get_dom_path_returns_root_to_target_order() {
5387 let hier = hierarchy_chain(4); let path = get_dom_path(&hier, NodeHierarchyItemId::from_crate_internal(Some(NodeId::new(3))));
5389 assert_eq!(
5390 path,
5391 vec![NodeId::new(0), NodeId::new(1), NodeId::new(2), NodeId::new(3)],
5392 "path must run root -> target"
5393 );
5394 let root_path = get_dom_path(&hier, NodeHierarchyItemId::from_crate_internal(Some(NodeId::ZERO)));
5396 assert_eq!(root_path, vec![NodeId::ZERO]);
5397 }
5398
5399 #[test]
5400 fn get_dom_path_terminates_on_a_self_parent_cycle() {
5401 let hier = NodeHierarchy::new(vec![Node {
5403 parent: Some(NodeId::ZERO),
5404 ..Node::ROOT
5405 }]);
5406 let path = get_dom_path(&hier, NodeHierarchyItemId::from_crate_internal(Some(NodeId::ZERO)));
5407 assert_eq!(path, vec![NodeId::ZERO]);
5408 }
5409
5410 #[test]
5411 fn get_dom_path_handles_a_deep_chain_without_recursing() {
5412 let hier = hierarchy_chain(5000);
5415 let path = get_dom_path(
5416 &hier,
5417 NodeHierarchyItemId::from_crate_internal(Some(NodeId::new(4999))),
5418 );
5419 assert_eq!(path.len(), 5000);
5420 assert_eq!(path[0], NodeId::ZERO);
5421 assert_eq!(path[4999], NodeId::new(4999));
5422 }
5423
5424 #[test]
5425 fn propagate_event_visits_each_node_exactly_once() {
5426 let hier = hierarchy_chain(3); let mut callbacks: BTreeMap<NodeId, Vec<EventFilter>> = BTreeMap::new();
5430 for i in 0..3 {
5431 callbacks.insert(
5432 NodeId::new(i),
5433 vec![EventFilter::Hover(HoverEventFilter::MouseDown)],
5434 );
5435 }
5436 let mut ev = SyntheticEvent::new(
5437 EventType::MouseDown,
5438 EventSource::User,
5439 dnid(0, 2),
5440 tick(0),
5441 EventData::Mouse(MouseEventData {
5442 position: LogicalPosition::zero(),
5443 button: MouseButton::Left,
5444 buttons: 1,
5445 modifiers: KeyModifiers::default(),
5446 }),
5447 );
5448
5449 let result = propagate_event(&mut ev, &hier, &callbacks);
5450 let nodes: Vec<NodeId> = result.callbacks_to_invoke.iter().map(|(n, _)| *n).collect();
5451 assert_eq!(
5452 nodes,
5453 vec![NodeId::new(2), NodeId::new(1), NodeId::new(0)],
5454 "target first, then bubbling up to the root — each node once"
5455 );
5456 assert!(!result.default_prevented);
5457 }
5458
5459 #[test]
5460 fn propagate_event_on_a_dangling_target_is_a_no_op() {
5461 let hier = hierarchy_chain(2);
5462 let callbacks: BTreeMap<NodeId, Vec<EventFilter>> = BTreeMap::new();
5463
5464 let mut ev = SyntheticEvent::new(
5467 EventType::MouseDown,
5468 EventSource::User,
5469 dnid_none(0),
5470 tick(0),
5471 EventData::None,
5472 );
5473 let result = propagate_event(&mut ev, &hier, &callbacks);
5474 assert!(result.callbacks_to_invoke.is_empty());
5475 assert!(!result.default_prevented);
5476
5477 let mut ev = SyntheticEvent::new(
5479 EventType::MouseDown,
5480 EventSource::User,
5481 dnid(0, 10_000),
5482 tick(0),
5483 EventData::None,
5484 );
5485 let result = propagate_event(&mut ev, &hier, &callbacks);
5486 assert!(result.callbacks_to_invoke.is_empty());
5487 }
5488
5489 #[test]
5490 fn propagate_event_respects_a_pre_stopped_event() {
5491 let hier = hierarchy_chain(3);
5492 let mut callbacks: BTreeMap<NodeId, Vec<EventFilter>> = BTreeMap::new();
5493 for i in 0..3 {
5494 callbacks.insert(
5495 NodeId::new(i),
5496 vec![EventFilter::Hover(HoverEventFilter::MouseOver)],
5497 );
5498 }
5499 let base = SyntheticEvent::new(
5500 EventType::MouseOver,
5501 EventSource::User,
5502 dnid(0, 2),
5503 tick(0),
5504 EventData::None,
5505 );
5506
5507 let mut stopped = base.clone();
5509 stopped.stop_propagation();
5510 let r = propagate_event(&mut stopped, &hier, &callbacks);
5511 assert!(r.callbacks_to_invoke.is_empty(), "a stopped event collects nothing");
5512
5513 let mut immediate = base.clone();
5515 immediate.stop_immediate_propagation();
5516 let r = propagate_event(&mut immediate, &hier, &callbacks);
5517 assert!(r.callbacks_to_invoke.is_empty());
5518
5519 let mut prevented = base;
5521 prevented.prevent_default();
5522 let r = propagate_event(&mut prevented, &hier, &callbacks);
5523 assert!(r.default_prevented);
5524 assert_eq!(r.callbacks_to_invoke.len(), 3, "preventDefault must not stop dispatch");
5525 }
5526
5527 #[test]
5528 fn propagate_event_ignores_filters_that_do_not_match_the_event() {
5529 let hier = hierarchy_chain(2);
5530 let mut callbacks: BTreeMap<NodeId, Vec<EventFilter>> = BTreeMap::new();
5531 callbacks.insert(
5532 NodeId::new(1),
5533 vec![
5534 EventFilter::Hover(HoverEventFilter::MouseUp), EventFilter::Hover(HoverEventFilter::RightMouseDown), EventFilter::Hover(HoverEventFilter::LeftMouseDown), ],
5538 );
5539 let mut ev = mouse_event(EventType::MouseDown, MouseButton::Left, LogicalPosition::zero());
5540 ev.target = dnid(0, 1);
5541 ev.current_target = ev.target;
5542
5543 let r = propagate_event(&mut ev, &hier, &callbacks);
5544 assert_eq!(
5545 r.callbacks_to_invoke,
5546 vec![(
5547 NodeId::new(1),
5548 EventFilter::Hover(HoverEventFilter::LeftMouseDown)
5549 )]
5550 );
5551 assert_eq!(ev.phase, EventPhase::Bubble);
5556 assert_eq!(ev.current_target, dnid(0, 0));
5557 assert_eq!(ev.target, dnid(0, 1), "the target itself must never be rewritten");
5558 }
5559
5560 #[test]
5561 fn collect_matching_callbacks_collects_nothing_once_immediate_stop_is_set() {
5562 let mut result = PropagationResult::default();
5563 let mut callbacks: BTreeMap<NodeId, Vec<EventFilter>> = BTreeMap::new();
5564 callbacks.insert(
5565 NodeId::ZERO,
5566 vec![EventFilter::Hover(HoverEventFilter::MouseOver)],
5567 );
5568 let mut ev = SyntheticEvent::new(
5569 EventType::MouseOver,
5570 EventSource::User,
5571 dnid(0, 0),
5572 tick(0),
5573 EventData::None,
5574 );
5575 ev.stop_immediate_propagation();
5576 collect_matching_callbacks(&ev, NodeId::ZERO, EventPhase::Target, &callbacks, &mut result);
5577 assert!(result.callbacks_to_invoke.is_empty());
5578
5579 let mut fresh = PropagationResult::default();
5581 let clean = SyntheticEvent::new(
5582 EventType::MouseOver,
5583 EventSource::User,
5584 dnid(0, 0),
5585 tick(0),
5586 EventData::None,
5587 );
5588 collect_matching_callbacks(&clean, NodeId::new(9), EventPhase::Target, &callbacks, &mut fresh);
5589 assert!(fresh.callbacks_to_invoke.is_empty());
5590 }
5591
5592 #[test]
5593 fn propagate_phase_over_an_empty_iterator_only_sets_the_phase() {
5594 let mut result = PropagationResult::default();
5595 let callbacks: BTreeMap<NodeId, Vec<EventFilter>> = BTreeMap::new();
5596 let mut ev = SyntheticEvent::new(
5597 EventType::MouseOver,
5598 EventSource::User,
5599 dnid(0, 0),
5600 tick(0),
5601 EventData::None,
5602 );
5603 propagate_phase(
5604 &mut ev,
5605 core::iter::empty(),
5606 EventPhase::Bubble,
5607 &callbacks,
5608 &mut result,
5609 );
5610 assert_eq!(ev.phase, EventPhase::Bubble);
5611 assert!(result.callbacks_to_invoke.is_empty());
5612
5613 propagate_target_phase(&mut ev, NodeId::ZERO, &callbacks, &mut result);
5615 assert_eq!(ev.phase, EventPhase::Target);
5616 assert_eq!(ev.current_target, ev.target);
5617 }
5618
5619 #[test]
5622 fn deduplicate_synthetic_events_handles_empty_and_single() {
5623 assert!(deduplicate_synthetic_events(Vec::new()).is_empty());
5624 let one = vec![SyntheticEvent::new(
5625 EventType::Scroll,
5626 EventSource::User,
5627 dnid(0, 0),
5628 tick(1),
5629 EventData::None,
5630 )];
5631 assert_eq!(deduplicate_synthetic_events(one).len(), 1);
5632 }
5633
5634 #[test]
5635 fn deduplicate_synthetic_events_keeps_the_latest_timestamp_per_target_and_type() {
5636 let mk = |node: usize, ty: EventType, t: u64| {
5637 SyntheticEvent::new(ty, EventSource::User, dnid(0, node), tick(t), EventData::None)
5638 };
5639 let events = vec![
5641 mk(1, EventType::Scroll, 5),
5642 mk(1, EventType::Scroll, 99),
5643 mk(1, EventType::Scroll, 1),
5644 ];
5645 let out = deduplicate_synthetic_events(events);
5646 assert_eq!(out.len(), 1);
5647 assert_eq!(out[0].timestamp, tick(99), "the newest event must survive");
5648
5649 let events = vec![
5651 mk(1, EventType::Scroll, 1),
5652 mk(2, EventType::Scroll, 1),
5653 mk(1, EventType::MouseOver, 1),
5654 ];
5655 assert_eq!(deduplicate_synthetic_events(events).len(), 3);
5656
5657 let a = SyntheticEvent::new(EventType::Scroll, EventSource::User, dnid(0, 1), tick(0), EventData::None);
5659 let b = SyntheticEvent::new(EventType::Scroll, EventSource::User, dnid(1, 1), tick(0), EventData::None);
5660 assert_eq!(deduplicate_synthetic_events(vec![a, b]).len(), 2);
5661 }
5662
5663 #[test]
5664 fn deduplicate_synthetic_events_collapses_a_large_duplicate_burst() {
5665 let events: Vec<SyntheticEvent> = (0..10_000u64)
5668 .map(|t| {
5669 SyntheticEvent::new(
5670 EventType::Scroll,
5671 EventSource::User,
5672 dnid(0, 0),
5673 tick(t),
5674 EventData::None,
5675 )
5676 })
5677 .collect();
5678 let out = deduplicate_synthetic_events(events);
5679 assert_eq!(out.len(), 1);
5680 assert_eq!(out[0].timestamp, tick(9_999));
5681 }
5682
5683 #[test]
5684 fn deduplicate_synthetic_events_preserves_unicode_payloads() {
5685 let text = "🦀 グラフ é\u{0301} مرحبا \u{1F1E6}\u{1F1F9}".repeat(200);
5688 let ev = SyntheticEvent::new(
5689 EventType::Input,
5690 EventSource::User,
5691 dnid(0, 0),
5692 tick(1),
5693 EventData::TextInput(TextInputEventData {
5694 inserted_text: text.clone(),
5695 old_text: String::new(),
5696 }),
5697 );
5698 let newer = SyntheticEvent::new(
5699 EventType::Input,
5700 EventSource::User,
5701 dnid(0, 0),
5702 tick(2),
5703 EventData::TextInput(TextInputEventData {
5704 inserted_text: text.clone(),
5705 old_text: text.clone(),
5706 }),
5707 );
5708 let out = deduplicate_synthetic_events(vec![ev, newer]);
5709 assert_eq!(out.len(), 1);
5710 match &out[0].data {
5711 EventData::TextInput(d) => {
5712 assert_eq!(d.inserted_text, text);
5713 assert_eq!(d.old_text, text, "the newer event won");
5714 }
5715 _ => panic!("payload must be preserved"),
5716 }
5717 }
5718
5719 #[test]
5722 fn get_first_hovered_node_on_empty_input() {
5723 assert!(get_first_hovered_node(None).is_none());
5724 assert!(
5725 get_first_hovered_node(Some(&empty_hit_test())).is_none(),
5726 "a hit test with no hovered DOMs has no front-most node"
5727 );
5728 let ht = hit_test_with(0, &[]);
5730 assert!(get_first_hovered_node(Some(&ht)).is_none());
5731 }
5732
5733 #[test]
5734 fn get_first_hovered_node_picks_minimum_depth_and_breaks_ties_deterministically() {
5735 let ht = hit_test_with(0, &[(2, 5), (5, 0), (9, 3)]);
5738 let got = get_first_hovered_node(Some(&ht)).unwrap();
5739 assert_eq!(got.node.into_crate_internal(), Some(NodeId::new(5)));
5740
5741 let ht = hit_test_with(0, &[(7, 2), (3, 2), (11, 2)]);
5744 let a = get_first_hovered_node(Some(&ht)).unwrap();
5745 let b = get_first_hovered_node(Some(&ht)).unwrap();
5746 assert_eq!(a, b, "tie-breaking must be deterministic");
5747 assert_eq!(a.node.into_crate_internal(), Some(NodeId::new(3)));
5748
5749 let ht = hit_test_with(0, &[(1, u32::MAX)]);
5751 let got = get_first_hovered_node(Some(&ht)).unwrap();
5752 assert_eq!(got.node.into_crate_internal(), Some(NodeId::new(1)));
5753 assert_eq!(got.dom, DomId { inner: 0 });
5754 }
5755
5756 #[test]
5757 fn get_mouse_position_with_fallback_prefers_the_event_payload() {
5758 let mouse = MouseState {
5759 cursor_position: CursorPosition::InWindow(LogicalPosition::new(9.0, 9.0)),
5760 ..MouseState::default()
5761 };
5762 let ev = mouse_event(
5763 EventType::MouseDown,
5764 MouseButton::Left,
5765 LogicalPosition::new(1.0, 2.0),
5766 );
5767 assert_eq!(
5768 get_mouse_position_with_fallback(&ev, &mouse),
5769 LogicalPosition::new(1.0, 2.0),
5770 "the event's own payload wins over the live cursor"
5771 );
5772
5773 let keyless = SyntheticEvent::new(
5775 EventType::MouseDown,
5776 EventSource::Synthetic,
5777 dnid(0, 0),
5778 tick(0),
5779 EventData::None,
5780 );
5781 assert_eq!(
5782 get_mouse_position_with_fallback(&keyless, &mouse),
5783 LogicalPosition::new(9.0, 9.0)
5784 );
5785
5786 for cursor in [
5789 CursorPosition::Uninitialized,
5790 CursorPosition::OutOfWindow(LogicalPosition::new(-5.0, -5.0)),
5791 ] {
5792 let ms = MouseState { cursor_position: cursor, ..MouseState::default() };
5793 assert_eq!(
5794 get_mouse_position_with_fallback(&keyless, &ms),
5795 LogicalPosition::zero()
5796 );
5797 }
5798 }
5799
5800 #[test]
5801 fn get_mouse_position_with_fallback_passes_through_extreme_coordinates() {
5802 let mouse = MouseState::default();
5803 for pos in [
5804 LogicalPosition::new(f32::NAN, f32::NAN),
5805 LogicalPosition::new(f32::INFINITY, f32::NEG_INFINITY),
5806 LogicalPosition::new(f32::MAX, f32::MIN),
5807 LogicalPosition::new(-0.0, 0.0),
5808 ] {
5809 let ev = mouse_event(EventType::MouseDown, MouseButton::Left, pos);
5810 let got = get_mouse_position_with_fallback(&ev, &mouse);
5811 assert_eq!(got.x.to_bits(), pos.x.to_bits());
5814 assert_eq!(got.y.to_bits(), pos.y.to_bits());
5815 }
5816 }
5817
5818 #[test]
5821 fn handle_mouse_down_treats_zero_click_count_as_one() {
5822 let ht = hit_test_with(0, &[(0, 0)]);
5823 let mouse = MouseState::default();
5824 let kb = KeyboardState::default();
5825 let ev = mouse_event(
5826 EventType::MouseDown,
5827 MouseButton::Left,
5828 LogicalPosition::new(4.0, 5.0),
5829 );
5830
5831 let action = handle_mouse_down(&ev, Some(&ht), 0, &mouse, &kb)
5833 .expect("click_count 0 must be treated as a single click");
5834 match action {
5835 InternalEventAction::AddAndPass(SystemChange::TextSelectionClick { position, .. }) => {
5836 assert_eq!(position, LogicalPosition::new(4.0, 5.0));
5837 }
5838 _ => panic!("expected a passed-through TextSelectionClick"),
5839 }
5840 }
5841
5842 #[test]
5843 fn handle_mouse_down_saturates_above_a_triple_click() {
5844 let ht = hit_test_with(0, &[(0, 0)]);
5845 let mouse = MouseState::default();
5846 let kb = KeyboardState::default();
5847 let ev = mouse_event(EventType::MouseDown, MouseButton::Left, LogicalPosition::zero());
5848
5849 for count in 1u8..=3 {
5851 assert!(
5852 handle_mouse_down(&ev, Some(&ht), count, &mouse, &kb).is_some(),
5853 "click_count {count} must produce a selection click"
5854 );
5855 }
5856 for count in [4u8, 5, 100, u8::MAX] {
5858 assert!(
5859 handle_mouse_down(&ev, Some(&ht), count, &mouse, &kb).is_none(),
5860 "click_count {count} must be ignored"
5861 );
5862 }
5863 }
5864
5865 #[test]
5866 fn handle_mouse_down_without_a_hit_test_is_a_no_op() {
5867 let mouse = MouseState::default();
5868 let kb = KeyboardState::default();
5869 let ev = mouse_event(EventType::MouseDown, MouseButton::Left, LogicalPosition::zero());
5870 assert!(handle_mouse_down(&ev, None, 1, &mouse, &kb).is_none());
5871 assert!(handle_mouse_down(&ev, Some(&empty_hit_test()), 1, &mouse, &kb).is_none());
5872 }
5873
5874 #[test]
5875 fn handle_mouse_down_with_primary_held_adds_a_cursor_only_on_a_single_click() {
5876 let ht = hit_test_with(0, &[(0, 0)]);
5877 let mouse = MouseState::default();
5878 let kb = keyboard_with_primary_held();
5879 let ev = mouse_event(
5880 EventType::MouseDown,
5881 MouseButton::Left,
5882 LogicalPosition::new(7.0, 8.0),
5883 );
5884
5885 match handle_mouse_down(&ev, Some(&ht), 1, &mouse, &kb) {
5887 Some(InternalEventAction::AddAndPass(SystemChange::AddCursorAtClick { position })) => {
5888 assert_eq!(position, LogicalPosition::new(7.0, 8.0));
5889 }
5890 _ => panic!("primary+click must add a cursor at the click position"),
5891 }
5892 match handle_mouse_down(&ev, Some(&ht), 2, &mouse, &kb) {
5894 Some(InternalEventAction::AddAndPass(SystemChange::TextSelectionClick { .. })) => {}
5895 _ => panic!("primary+double-click must not add a cursor"),
5896 }
5897 }
5898
5899 #[test]
5900 fn handle_mouse_over_requires_a_held_button_and_a_drag_origin() {
5901 let ht = hit_test_with(0, &[(0, 0)]);
5902 let start = LogicalPosition::new(1.0, 1.0);
5903 let ev = mouse_event(
5904 EventType::MouseOver,
5905 MouseButton::Left,
5906 LogicalPosition::new(50.0, 60.0),
5907 );
5908
5909 let up = MouseState::default();
5911 assert!(handle_mouse_over(&ev, Some(&ht), &up, Some(start)).is_none());
5912
5913 let down = MouseState { left_down: true, ..MouseState::default() };
5915 assert!(handle_mouse_over(&ev, Some(&ht), &down, None).is_none());
5916
5917 assert!(handle_mouse_over(&ev, None, &down, Some(start)).is_none());
5919 assert!(handle_mouse_over(&ev, Some(&empty_hit_test()), &down, Some(start)).is_none());
5920
5921 match handle_mouse_over(&ev, Some(&ht), &down, Some(start)) {
5923 Some(InternalEventAction::AddAndPass(SystemChange::TextSelectionDrag {
5924 start_position,
5925 current_position,
5926 })) => {
5927 assert_eq!(start_position, start);
5928 assert_eq!(current_position, LogicalPosition::new(50.0, 60.0));
5929 }
5930 _ => panic!("expected a TextSelectionDrag"),
5931 }
5932 }
5933
5934 #[test]
5935 fn handle_key_down_needs_a_focused_node_and_a_keyboard_payload() {
5936 let kb = KeyboardState::default();
5937 let ev = key_event(VirtualKeyCode::Back as u32, KeyModifiers::default());
5938 assert!(
5939 handle_key_down(&ev, &kb, None).is_none(),
5940 "no focus => no keyboard system change"
5941 );
5942
5943 let payloadless = SyntheticEvent::new(
5945 EventType::KeyDown,
5946 EventSource::User,
5947 dnid(0, 1),
5948 tick(0),
5949 EventData::None,
5950 );
5951 assert!(handle_key_down(&payloadless, &kb, Some(dnid(0, 1))).is_none());
5952 }
5953
5954 #[test]
5955 fn handle_key_down_rejects_undecodable_key_codes() {
5956 let kb = KeyboardState::default();
5957 let target = Some(dnid(0, 1));
5958 for code in [u32::MAX, u32::MAX - 1, 100_000, 9_999] {
5961 let ev = key_event(code, KeyModifiers::default());
5962 assert!(
5963 handle_key_down(&ev, &kb, target).is_none(),
5964 "key_code {code} must decode to None"
5965 );
5966 }
5967 }
5968
5969 #[test]
5970 fn handle_key_down_reads_modifiers_from_the_event_not_the_live_keyboard() {
5971 let kb = KeyboardState::default();
5975 let target = dnid(0, 1);
5976 let ev = key_event(VirtualKeyCode::C as u32, primary_modifiers());
5977 match handle_key_down(&ev, &kb, Some(target)) {
5978 Some(InternalEventAction::AddAndSkip(SystemChange::CopyToClipboard)) => {}
5979 _ => panic!("primary+C in the payload must copy, regardless of the live state"),
5980 }
5981
5982 let live = keyboard_with_primary_held();
5984 let plain = key_event(VirtualKeyCode::C as u32, KeyModifiers::default());
5985 assert!(
5986 handle_key_down(&plain, &live, Some(target)).is_none(),
5987 "an unmodified C is plain text input, not a copy"
5988 );
5989 }
5990
5991 #[test]
5992 fn handle_key_down_maps_backspace_and_delete_to_selection_ops() {
5993 let kb = KeyboardState::default();
5994 let target = dnid(0, 1);
5995
5996 let expect_op = |ev: &SyntheticEvent| -> SelectionOp {
5997 match handle_key_down(ev, &kb, Some(target)) {
5998 Some(InternalEventAction::AddAndSkip(SystemChange::ApplySelectionOp {
5999 target: t,
6000 op,
6001 })) => {
6002 assert_eq!(t, target);
6003 op
6004 }
6005 _ => panic!("expected an ApplySelectionOp"),
6006 }
6007 };
6008
6009 let back = expect_op(&key_event(VirtualKeyCode::Back as u32, KeyModifiers::default()));
6010 assert_eq!(back.direction, SelectionDirection::Backward);
6011 assert_eq!(back.step, SelectionStep::Character);
6012 assert_eq!(back.mode, SelectionMode::Delete);
6013
6014 let del = expect_op(&key_event(VirtualKeyCode::Delete as u32, KeyModifiers::default()));
6015 assert_eq!(del.direction, SelectionDirection::Forward);
6016 assert_eq!(del.step, SelectionStep::Character);
6017 assert_eq!(del.mode, SelectionMode::Delete);
6018
6019 let shift_right = expect_op(&key_event(
6021 VirtualKeyCode::Right as u32,
6022 KeyModifiers::new().with_shift(),
6023 ));
6024 assert_eq!(shift_right.mode, SelectionMode::Extend);
6025 assert_eq!(shift_right.step, SelectionStep::Character);
6026
6027 let word_mod = if cfg!(target_os = "macos") {
6029 KeyModifiers::new().with_alt()
6030 } else {
6031 KeyModifiers::new().with_ctrl()
6032 };
6033 let word_back = expect_op(&key_event(VirtualKeyCode::Back as u32, word_mod));
6034 assert_eq!(word_back.step, SelectionStep::Word);
6035 assert_eq!(word_back.mode, SelectionMode::Delete);
6036 }
6037
6038 #[test]
6039 fn handle_key_down_ignores_keys_it_does_not_interpret() {
6040 let kb = KeyboardState::default();
6041 let target = Some(dnid(0, 1));
6042 for vk in [VirtualKeyCode::B, VirtualKeyCode::Q, VirtualKeyCode::Space, VirtualKeyCode::F5] {
6044 let ev = key_event(vk as u32, KeyModifiers::default());
6045 assert!(
6046 handle_key_down(&ev, &kb, target).is_none(),
6047 "{vk:?} must not generate a system change"
6048 );
6049 }
6050 }
6051
6052 #[test]
6055 fn default_input_interpreter_with_no_events_produces_nothing() {
6056 let kb = KeyboardState::default();
6057 let mouse = MouseState::default();
6058 let info = InputInterpreterInfo {
6059 events: &[],
6060 hit_test: None,
6061 keyboard_state: &kb,
6062 mouse_state: &mouse,
6063 state: InputInterpreterState {
6064 focused_node: None,
6065 click_count: 0,
6066 drag_start_position: None,
6067 has_selection: false,
6068 },
6069 };
6070 let r = default_input_interpreter(&info);
6071 assert!(r.system_changes.is_empty());
6072 assert!(r.user_events.is_empty());
6073 }
6074
6075 #[test]
6076 fn default_input_interpreter_skips_shortcut_events_but_passes_clicks_through() {
6077 let kb = KeyboardState::default();
6078 let mouse = MouseState::default();
6079 let ht = hit_test_with(0, &[(0, 0)]);
6080 let target = dnid(0, 1);
6081
6082 let copy = key_event(VirtualKeyCode::C as u32, primary_modifiers());
6085 let click = mouse_event(EventType::MouseDown, MouseButton::Left, LogicalPosition::zero());
6087 let scroll = SyntheticEvent::new(
6089 EventType::Scroll,
6090 EventSource::User,
6091 target,
6092 tick(0),
6093 EventData::None,
6094 );
6095
6096 let events = vec![copy, click, scroll];
6097 let info = InputInterpreterInfo {
6098 events: &events,
6099 hit_test: Some(&ht),
6100 keyboard_state: &kb,
6101 mouse_state: &mouse,
6102 state: InputInterpreterState {
6103 focused_node: Some(target),
6104 click_count: 1,
6105 drag_start_position: None,
6106 has_selection: false,
6107 },
6108 };
6109 let r = default_input_interpreter(&info);
6110
6111 assert_eq!(r.system_changes.len(), 2, "copy + selection click");
6112 assert!(r.system_changes.contains(&SystemChange::CopyToClipboard));
6113 assert!(r
6114 .system_changes
6115 .iter()
6116 .any(|c| matches!(c, SystemChange::TextSelectionClick { .. })));
6117
6118 assert_eq!(r.user_events.len(), 2, "the consumed KeyDown must not be forwarded");
6119 assert!(!r.user_events.iter().any(|e| e.event_type == EventType::KeyDown));
6120 assert!(r.user_events.iter().any(|e| e.event_type == EventType::MouseDown));
6121 assert!(r.user_events.iter().any(|e| e.event_type == EventType::Scroll));
6122 }
6123
6124 #[test]
6125 fn default_input_interpreter_extern_survives_a_null_info_pointer() {
6126 let user_data = crate::refany::RefAny::new(0u8);
6128 let r = default_input_interpreter_extern(user_data, core::ptr::null());
6129 assert!(r.system_changes.is_empty());
6130 assert!(r.user_events.is_empty());
6131 }
6132
6133 #[test]
6136 fn post_filter_with_prevent_default_only_lets_focus_changes_through() {
6137 let old = Some(dnid(0, 1));
6138 let new = Some(dnid(0, 2));
6139 let pre = vec![
6140 SystemChange::TextSelectionClick {
6141 position: LogicalPosition::zero(),
6142 timestamp: tick(0),
6143 },
6144 SystemChange::PasteFromClipboard,
6145 ];
6146
6147 let out = default_post_filter(true, &pre, old, old);
6150 assert!(out.is_empty(), "preventDefault must suppress every side effect");
6151
6152 let out = default_post_filter(true, &pre, old, new);
6154 assert_eq!(out, vec![SystemChange::SetFocus { new_focus: new, old_focus: old }]);
6155 }
6156
6157 #[test]
6158 fn post_filter_maps_pre_changes_to_their_follow_ups() {
6159 let out = default_post_filter(false, &[], None, None);
6161 assert_eq!(out, vec![SystemChange::ApplyPendingTextInput]);
6162
6163 for change in [
6165 SystemChange::TextSelectionClick {
6166 position: LogicalPosition::zero(),
6167 timestamp: tick(0),
6168 },
6169 SystemChange::ApplySelectionOp {
6170 target: dnid(0, 1),
6171 op: SelectionOp::new(
6172 SelectionDirection::Forward,
6173 SelectionStep::Character,
6174 SelectionMode::Move,
6175 ),
6176 },
6177 SystemChange::AddCursorAtClick { position: LogicalPosition::zero() },
6178 SystemChange::SelectNextOccurrence { target: dnid(0, 1) },
6179 SystemChange::CutToClipboard { target: dnid(0, 1) },
6180 SystemChange::PasteFromClipboard,
6181 SystemChange::UndoTextEdit { target: dnid(0, 1) },
6182 SystemChange::RedoTextEdit { target: dnid(0, 1) },
6183 SystemChange::SelectAllText,
6184 ] {
6185 let out = default_post_filter(false, core::slice::from_ref(&change), None, None);
6186 assert!(
6187 out.contains(&SystemChange::ScrollSelectionIntoView),
6188 "{change:?} must schedule a scroll-into-view"
6189 );
6190 assert_eq!(out[0], SystemChange::ApplyPendingTextInput);
6191 }
6192
6193 let drag = SystemChange::TextSelectionDrag {
6195 start_position: LogicalPosition::zero(),
6196 current_position: LogicalPosition::new(1.0, 1.0),
6197 };
6198 let out = default_post_filter(false, core::slice::from_ref(&drag), None, None);
6199 assert!(out.contains(&SystemChange::StartAutoScrollTimer));
6200 assert!(!out.contains(&SystemChange::ScrollSelectionIntoView));
6201
6202 let out = default_post_filter(false, &[SystemChange::CopyToClipboard], None, None);
6204 assert_eq!(out, vec![SystemChange::ApplyPendingTextInput]);
6205 }
6206
6207 #[test]
6208 fn post_filter_emits_set_focus_only_when_focus_actually_moved() {
6209 let a = Some(dnid(0, 1));
6210 let b = Some(dnid(0, 2));
6211 for (old, new) in [(a, a), (None, None)] {
6213 let out = default_post_filter(false, &[], old, new);
6214 assert!(!out.iter().any(|c| matches!(c, SystemChange::SetFocus { .. })));
6215 }
6216 for (old, new) in [(a, b), (None, a), (a, None)] {
6218 let out = default_post_filter(false, &[], old, new);
6219 assert_eq!(
6220 out.last(),
6221 Some(&SystemChange::SetFocus { new_focus: new, old_focus: old })
6222 );
6223 assert_eq!(
6224 out.iter()
6225 .filter(|c| matches!(c, SystemChange::SetFocus { .. }))
6226 .count(),
6227 1
6228 );
6229 }
6230 }
6231
6232 #[test]
6233 fn post_filter_handles_a_large_pre_change_list_without_blowing_up() {
6234 let pre: Vec<SystemChange> = (0..5000)
6236 .map(|_| SystemChange::AddCursorAtClick { position: LogicalPosition::zero() })
6237 .collect();
6238 let out = default_post_filter(false, &pre, None, None);
6239 assert_eq!(out.len(), 5001);
6240 assert_eq!(out[0], SystemChange::ApplyPendingTextInput);
6241 assert!(out[1..]
6242 .iter()
6243 .all(|c| *c == SystemChange::ScrollSelectionIntoView));
6244 }
6245
6246 #[test]
6247 fn default_post_filter_delegates_to_post_callback_filter_system_changes() {
6248 let pre = vec![
6249 SystemChange::TextSelectionDrag {
6250 start_position: LogicalPosition::zero(),
6251 current_position: LogicalPosition::new(2.0, 2.0),
6252 },
6253 SystemChange::SelectAllText,
6254 ];
6255 for prevent in [false, true] {
6256 for (old, new) in [(None, None), (Some(dnid(0, 1)), Some(dnid(0, 2)))] {
6257 assert_eq!(
6258 default_post_filter(prevent, &pre, old, new),
6259 post_callback_filter_system_changes(prevent, &pre, old, new),
6260 "the two entry points must stay in lock-step"
6261 );
6262 }
6263 }
6264 }
6265
6266 #[test]
6267 fn default_post_filter_extern_decodes_the_none_focus_sentinel() {
6268 let pre: Vec<SystemChange> = Vec::new();
6270 let slice = SystemChangeVecSlice {
6271 ptr: pre.as_ptr(),
6272 len: pre.len(),
6273 };
6274 let out = default_post_filter_extern(
6275 crate::refany::RefAny::new(0u8),
6276 false,
6277 slice,
6278 dnid_none(0),
6279 dnid(0, 4),
6280 );
6281 let changes = out.as_slice();
6282 assert_eq!(changes.first(), Some(&SystemChange::ApplyPendingTextInput));
6283 assert_eq!(
6284 changes.last(),
6285 Some(&SystemChange::SetFocus {
6286 new_focus: Some(dnid(0, 4)),
6287 old_focus: None,
6288 }),
6289 "a `NONE` node id must decode to `None`, not to node 0"
6290 );
6291
6292 let out = default_post_filter_extern(
6294 crate::refany::RefAny::new(0u8),
6295 true,
6296 SystemChangeVecSlice::empty(),
6297 dnid_none(0),
6298 dnid_none(0),
6299 );
6300 assert!(out.as_slice().is_empty());
6301 }
6302}