1#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
23pub enum ArkEventKind {
24 AccessibilityAction,
25 Click,
26 LongPress,
27 Change,
28 Submit,
29 Scroll,
30 ReachEnd,
31 SwiperChange,
32 Refresh,
33 AreaChange,
34 Focus,
35 Blur,
36 FocusIn,
37 FocusOut,
38 Hover,
39 HoverMove,
40 DragStart,
41 DragMove,
42 DragEnd,
43 DragLeave,
44 DragEnter,
45 Touch,
46 Key,
47 KeyPreIme,
48 Mouse,
49 Axis,
50}
51
52impl ArkEventKind {
53 pub const fn bubbles(self) -> bool {
55 matches!(
56 self,
57 Self::Click
58 | Self::LongPress
59 | Self::Touch
60 | Self::Key
61 | Self::KeyPreIme
62 | Self::Mouse
63 | Self::FocusIn
64 | Self::FocusOut
65 )
66 }
67}
68
69pub fn classify_event_name(name: &str) -> Option<ArkEventKind> {
81 crate::events::EVENT_KINDS
82 .iter()
83 .find_map(|(canonical, kind)| {
84 (*canonical == name || canonical.get("on".len()..) == Some(name)).then_some(*kind)
85 })
86}
87
88#[derive(Default, Clone, Debug)]
95pub enum ArkEventPayload {
96 #[default]
98 None,
99 Bool(bool),
101 Float(f32),
103 Int(i32),
105 AccessibilityAction(u32),
108 String(String),
110 ScrollIndex(ScrollIndexPayload),
112 ScrollOffset(ScrollOffsetPayload),
114 Layout(LayoutPayload),
116 Pointer(PointerPayload),
118 Key(KeyPayload),
120 Mouse(MousePayload),
122 Axis(AxisPayload),
123}
124
125#[derive(Default, Clone, Copy, Debug)]
127pub struct ScrollIndexPayload {
128 pub first: i32,
130 pub last: i32,
132 pub center: i32,
134}
135
136#[derive(Default, Clone, Copy, Debug, PartialEq)]
137pub struct ScrollOffsetPayload {
138 pub x: f32,
140 pub y: f32,
142}
143
144#[derive(Default, Clone, Copy, Debug, PartialEq)]
146pub struct LayoutPayload {
147 pub x: f32,
148 pub y: f32,
149 pub width: f32,
150 pub height: f32,
151}
152
153impl LayoutPayload {
154 pub fn is_measured(self) -> bool {
155 self.width > 0.0 && self.height > 0.0
156 }
157}
158
159#[derive(Default, Clone, Copy, Debug, PartialEq)]
161pub struct PointerPayload {
162 pub source: PointerSource,
163 pub action: PointerAction,
166 pub timestamp_nanos: u64,
169 pub pointer_id: i32,
171 pub buttons: u64,
173 pub pressure: f32,
175 pub x: f32,
177 pub y: f32,
179 pub window_x: f32,
181 pub window_y: f32,
183 pub target_x: f32,
185 pub target_y: f32,
187 pub target_width: f32,
189 pub target_height: f32,
191}
192
193#[derive(Default, Clone, Copy, Debug, PartialEq, Eq)]
194pub enum PointerSource {
195 #[default]
196 Unknown,
197 Touch,
198 Mouse,
199}
200
201#[derive(Default, Clone, Copy, Debug, PartialEq, Eq)]
203pub enum PointerAction {
204 #[default]
205 Unknown,
206 Cancel,
207 Down,
208 Move,
209 Up,
210}
211
212#[derive(Default, Clone, Copy, Debug, PartialEq, Eq)]
214pub enum MouseButton {
215 #[default]
216 None,
217 Primary,
218 Secondary,
219 Middle,
220 Back,
221 Forward,
222 Side,
223 Extra,
224 Task,
225 Other(i32),
226}
227
228#[derive(Default, Clone, Copy, Debug, PartialEq)]
230pub struct MousePayload {
231 pub button: MouseButton,
232 pub action: PointerAction,
233 pub x: f32,
234 pub y: f32,
235 pub window_x: f32,
236 pub window_y: f32,
237 pub target_x: f32,
238 pub target_y: f32,
239 pub target_width: f32,
240 pub target_height: f32,
241 pub modifiers: KeyModifiers,
242}
243
244impl From<MousePayload> for PointerPayload {
245 fn from(mouse: MousePayload) -> Self {
246 Self {
247 source: PointerSource::Mouse,
248 action: mouse.action,
249 x: mouse.x,
250 y: mouse.y,
251 window_x: mouse.window_x,
252 window_y: mouse.window_y,
253 target_x: mouse.target_x,
254 target_y: mouse.target_y,
255 target_width: mouse.target_width,
256 target_height: mouse.target_height,
257 ..Default::default()
258 }
259 }
260}
261
262#[derive(Default, Clone, Copy, Debug, PartialEq)]
263pub struct AxisPayload {
264 pub horizontal: f64,
265 pub vertical: f64,
266 pub x: f32,
267 pub y: f32,
268 pub modifiers: KeyModifiers,
269}
270
271#[derive(Default, Clone, Copy, Debug)]
272pub struct AxisData {
273 pub horizontal: f64,
274 pub vertical: f64,
275 pub x: f32,
276 pub y: f32,
277 pub modifiers: KeyModifiers,
278}
279
280impl From<ArkEventData> for AxisData {
281 fn from(data: ArkEventData) -> Self {
282 Self::from(&data)
283 }
284}
285impl From<&ArkEventData> for AxisData {
286 fn from(data: &ArkEventData) -> Self {
287 match data.payload {
288 ArkEventPayload::Axis(axis) => Self {
289 horizontal: axis.horizontal,
290 vertical: axis.vertical,
291 x: axis.x,
292 y: axis.y,
293 modifiers: axis.modifiers,
294 },
295 _ => Self::default(),
296 }
297 }
298}
299
300#[derive(Default, Clone, Copy, Debug, PartialEq, Eq)]
302pub enum KeyboardKey {
303 #[default]
304 Unknown,
305 Enter,
306 Space,
307 Tab,
308 Escape,
309 ArrowUp,
310 ArrowDown,
311 ArrowLeft,
312 ArrowRight,
313 Home,
314 End,
315 PageUp,
316 PageDown,
317 Delete,
318 Backspace,
319 Menu,
320 F10,
321 Function(u8),
322 Character(char),
323 Other(i32),
324}
325
326#[derive(Debug, Clone, Copy, PartialEq, Eq)]
328pub enum KeyboardNavigation {
329 Horizontal,
330 Vertical,
331 Grid(usize),
332}
333
334pub fn keyboard_navigation_target(
335 current: usize,
336 key: KeyboardKey,
337 navigation: KeyboardNavigation,
338 enabled: &[bool],
339) -> Option<usize> {
340 let count = enabled.len();
341 if count == 0 {
342 return None;
343 }
344 if key == KeyboardKey::Home {
345 return enabled.iter().position(|enabled| *enabled);
346 }
347 if key == KeyboardKey::End {
348 return enabled.iter().rposition(|enabled| *enabled);
349 }
350 let step = match (navigation, key) {
351 (KeyboardNavigation::Horizontal | KeyboardNavigation::Grid(_), KeyboardKey::ArrowLeft)
352 | (KeyboardNavigation::Vertical, KeyboardKey::ArrowUp) => -1,
353 (KeyboardNavigation::Horizontal | KeyboardNavigation::Grid(_), KeyboardKey::ArrowRight)
354 | (KeyboardNavigation::Vertical, KeyboardKey::ArrowDown) => 1,
355 (KeyboardNavigation::Grid(columns), KeyboardKey::ArrowUp) => -(columns.max(1) as isize),
356 (KeyboardNavigation::Grid(columns), KeyboardKey::ArrowDown) => columns.max(1) as isize,
357 _ => return None,
358 };
359 let mut next = current.min(count - 1) as isize;
360 for _ in 0..count {
361 next += step;
362 match navigation {
363 KeyboardNavigation::Grid(_) if next < 0 || next >= count as isize => return None,
364 KeyboardNavigation::Grid(_) => {}
365 _ => next = next.rem_euclid(count as isize),
366 }
367 if enabled[next as usize] {
368 return Some(next as usize);
369 }
370 }
371 None
372}
373
374#[derive(Default, Clone, Copy, Debug, PartialEq, Eq)]
376pub enum KeyAction {
377 #[default]
378 Unknown,
379 Down,
380 Up,
381 Repeat,
382 Click,
383}
384
385#[derive(Default, Clone, Copy, Debug, PartialEq, Eq)]
387pub struct KeyModifiers {
388 pub ctrl: bool,
389 pub shift: bool,
390 pub alt: bool,
391 pub function: bool,
392}
393
394#[derive(Default, Clone, Debug, PartialEq, Eq)]
396pub struct KeyPayload {
397 pub key: KeyboardKey,
398 pub action: KeyAction,
399 pub raw_code: i32,
400 pub text: String,
401 pub modifiers: KeyModifiers,
402}
403
404#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
406pub struct KeyboardCapture(u32);
407
408impl KeyboardCapture {
409 pub fn from_names(names: &str) -> Self {
410 let mut mask = 0;
411 for name in names.split([' ', ',', '|']).filter(|name| !name.is_empty()) {
412 mask |= match name {
413 "all" => u32::MAX,
414 "enter" => 1,
415 "space" => 2,
416 "tab" => 4,
417 "escape" => 8,
418 "up" => 16,
419 "down" => 32,
420 "left" => 64,
421 "right" => 128,
422 "home" => 256,
423 "end" => 512,
424 "page_up" => 1024,
425 "page_down" => 2048,
426 _ => 0,
427 };
428 }
429 Self(mask)
430 }
431
432 pub fn captures(self, key: &KeyPayload) -> bool {
433 if self.0 == u32::MAX {
434 return true;
435 }
436 if key.modifiers.ctrl || key.modifiers.alt {
437 return false;
438 }
439 let bit = match key.key {
440 KeyboardKey::Enter => 1,
441 KeyboardKey::Space => 2,
442 KeyboardKey::Tab => 4,
443 KeyboardKey::Escape => 8,
444 KeyboardKey::ArrowUp => 16,
445 KeyboardKey::ArrowDown => 32,
446 KeyboardKey::ArrowLeft => 64,
447 KeyboardKey::ArrowRight => 128,
448 KeyboardKey::Home => 256,
449 KeyboardKey::End => 512,
450 KeyboardKey::PageUp => 1024,
451 KeyboardKey::PageDown => 2048,
452 _ => 0,
453 };
454 self.0 & bit != 0
455 }
456}
457
458impl PointerPayload {
459 pub fn has_target_bounds(self) -> bool {
460 self.target_x.is_finite()
461 && self.target_y.is_finite()
462 && self.target_width.is_finite()
463 && self.target_height.is_finite()
464 && self.target_width > 0.0
465 && self.target_height > 0.0
466 }
467
468 pub fn has_window_position(self) -> bool {
469 self.window_x.is_finite()
470 && self.window_y.is_finite()
471 && (self.window_x != 0.0 || self.window_y != 0.0)
472 }
473}
474
475#[derive(Default)]
477pub struct ArkEventData {
478 pub payload: ArkEventPayload,
482}
483
484impl ArkEventData {
485 pub fn new() -> Self {
486 Self::default()
487 }
488
489 pub fn with_payload(payload: ArkEventPayload) -> Self {
490 Self { payload }
491 }
492}
493
494#[derive(Default, Clone, Copy, Debug)]
496pub struct ClickData {
497 pub pointer: Option<PointerPayload>,
498}
499
500#[derive(Default, Clone, Copy, Debug, PartialEq, Eq)]
502pub struct AccessibilityActionData {
503 pub action: u32,
505}
506
507impl From<ArkEventData> for AccessibilityActionData {
508 fn from(data: ArkEventData) -> Self {
509 Self::from(&data)
510 }
511}
512
513impl From<&ArkEventData> for AccessibilityActionData {
514 fn from(data: &ArkEventData) -> Self {
515 match &data.payload {
516 ArkEventPayload::AccessibilityAction(action) => Self { action: *action },
517 _ => Self::default(),
518 }
519 }
520}
521
522impl From<ArkEventData> for ClickData {
523 fn from(data: ArkEventData) -> Self {
524 Self::from(&data)
525 }
526}
527
528impl From<&ArkEventData> for ClickData {
529 fn from(data: &ArkEventData) -> Self {
530 let pointer = match &data.payload {
531 ArkEventPayload::Pointer(pointer) => Some(*pointer),
532 _ => None,
533 };
534 ClickData { pointer }
535 }
536}
537
538#[derive(Default, Clone, Copy, Debug)]
541pub struct HoverData {
542 pub is_hovering: bool,
543}
544
545#[derive(Default, Clone, Copy, Debug)]
547pub struct FocusData {
548 pub focused: bool,
549}
550
551impl From<ArkEventData> for FocusData {
552 fn from(data: ArkEventData) -> Self {
553 Self::from(&data)
554 }
555}
556
557impl From<&ArkEventData> for FocusData {
558 fn from(data: &ArkEventData) -> Self {
559 let focused = match &data.payload {
560 ArkEventPayload::Bool(focused) => *focused,
561 _ => false,
562 };
563 Self { focused }
564 }
565}
566
567impl From<ArkEventData> for HoverData {
568 fn from(data: ArkEventData) -> Self {
569 Self::from(&data)
570 }
571}
572
573impl From<&ArkEventData> for HoverData {
574 fn from(data: &ArkEventData) -> Self {
575 let is_hovering = match &data.payload {
576 ArkEventPayload::Bool(b) => *b,
577 _ => false,
578 };
579 HoverData { is_hovering }
580 }
581}
582
583#[derive(Default, Clone, Copy, Debug)]
588pub struct PointerData {
589 pub pointer: Option<PointerPayload>,
590}
591
592impl From<ArkEventData> for PointerData {
593 fn from(data: ArkEventData) -> Self {
594 Self::from(&data)
595 }
596}
597
598impl From<&ArkEventData> for PointerData {
599 fn from(data: &ArkEventData) -> Self {
600 let pointer = match &data.payload {
601 ArkEventPayload::Pointer(pointer) => Some(*pointer),
602 _ => None,
603 };
604 PointerData { pointer }
605 }
606}
607
608pub type MouseData = MousePayload;
610
611impl MouseData {
612 pub fn secondary_down(self) -> bool {
613 self.button == MouseButton::Secondary && self.action == PointerAction::Down
614 }
615}
616
617impl From<ArkEventData> for MouseData {
618 fn from(data: ArkEventData) -> Self {
619 Self::from(&data)
620 }
621}
622
623impl From<&ArkEventData> for MouseData {
624 fn from(data: &ArkEventData) -> Self {
625 match data.payload {
626 ArkEventPayload::Mouse(payload) => payload,
627 _ => Self::default(),
628 }
629 }
630}
631
632#[derive(Default, Clone, Debug, PartialEq, Eq)]
634pub struct KeyData {
635 pub key: KeyboardKey,
636 pub action: KeyAction,
637 pub raw_code: i32,
638 pub text: String,
639 pub modifiers: KeyModifiers,
640}
641
642impl KeyData {
643 pub fn is_down(&self) -> bool {
644 matches!(self.action, KeyAction::Down | KeyAction::Repeat)
645 }
646
647 pub fn activates(&self) -> bool {
648 self.action == KeyAction::Down
649 && !self.modifiers.ctrl
650 && !self.modifiers.alt
651 && matches!(self.key, KeyboardKey::Enter | KeyboardKey::Space)
652 }
653}
654
655impl From<ArkEventData> for KeyData {
656 fn from(data: ArkEventData) -> Self {
657 Self::from(&data)
658 }
659}
660
661impl From<&ArkEventData> for KeyData {
662 fn from(data: &ArkEventData) -> Self {
663 match &data.payload {
664 ArkEventPayload::Key(payload) => Self {
665 key: payload.key,
666 action: payload.action,
667 raw_code: payload.raw_code,
668 text: payload.text.clone(),
669 modifiers: payload.modifiers,
670 },
671 _ => Self::default(),
672 }
673 }
674}
675
676#[derive(Default, Clone, Debug)]
681pub struct ChangeData {
682 pub bool_value: bool,
684 pub float_value: f32,
686 pub string_value: String,
688}
689
690impl From<ArkEventData> for ChangeData {
691 fn from(data: ArkEventData) -> Self {
692 Self::from(&data)
693 }
694}
695
696impl From<&ArkEventData> for ChangeData {
697 fn from(data: &ArkEventData) -> Self {
698 let mut out = ChangeData::default();
699 match &data.payload {
700 ArkEventPayload::Bool(b) => out.bool_value = *b,
701 ArkEventPayload::Float(f) => out.float_value = *f,
702 ArkEventPayload::Int(i) => out.float_value = *i as f32,
703 ArkEventPayload::String(s) => out.string_value = s.clone(),
704 ArkEventPayload::ScrollIndex(_)
705 | ArkEventPayload::ScrollOffset(_)
706 | ArkEventPayload::Layout(_)
707 | ArkEventPayload::Pointer(_)
708 | ArkEventPayload::AccessibilityAction(_)
709 | ArkEventPayload::Key(_)
710 | ArkEventPayload::Mouse(_)
711 | ArkEventPayload::Axis(_)
712 | ArkEventPayload::None => {}
713 }
714 out
715 }
716}
717
718#[derive(Default, Clone, Copy, Debug)]
720pub struct AreaData {
721 pub frame: LayoutPayload,
722}
723
724impl From<ArkEventData> for AreaData {
725 fn from(data: ArkEventData) -> Self {
726 Self::from(&data)
727 }
728}
729
730impl From<&ArkEventData> for AreaData {
731 fn from(data: &ArkEventData) -> Self {
732 match &data.payload {
733 ArkEventPayload::Layout(frame) => AreaData { frame: *frame },
734 _ => AreaData::default(),
735 }
736 }
737}
738
739#[derive(Default, Clone, Copy, Debug)]
741pub struct ScrollData {
742 pub first_index: i32,
744 pub last_index: i32,
746 pub center_index: i32,
748 pub offset_x: f32,
750 pub offset_y: f32,
752 pub has_offset: bool,
754}
755
756impl From<ArkEventData> for ScrollData {
757 fn from(data: ArkEventData) -> Self {
758 Self::from(&data)
759 }
760}
761
762impl From<&ArkEventData> for ScrollData {
763 fn from(data: &ArkEventData) -> Self {
764 match &data.payload {
765 ArkEventPayload::ScrollIndex(s) => ScrollData {
766 first_index: s.first,
767 last_index: s.last,
768 center_index: s.center,
769 ..ScrollData::default()
770 },
771 ArkEventPayload::ScrollOffset(offset) => ScrollData {
772 offset_x: offset.x,
773 offset_y: offset.y,
774 has_offset: true,
775 ..ScrollData::default()
776 },
777 _ => ScrollData::default(),
778 }
779 }
780}
781
782#[derive(Default, Clone, Copy, Debug)]
785pub struct SubmitData {
786 pub return_code: i32,
787}
788
789impl From<ArkEventData> for SubmitData {
790 fn from(data: ArkEventData) -> Self {
791 Self::from(&data)
792 }
793}
794
795impl From<&ArkEventData> for SubmitData {
796 fn from(data: &ArkEventData) -> Self {
797 let return_code = match &data.payload {
798 ArkEventPayload::Int(i) => *i,
799 _ => 0,
800 };
801 SubmitData { return_code }
802 }
803}
804
805#[derive(Default, Clone, Copy, Debug)]
807pub struct SwiperChangeData {
808 pub index: i32,
809}
810
811impl From<ArkEventData> for SwiperChangeData {
812 fn from(data: ArkEventData) -> Self {
813 Self::from(&data)
814 }
815}
816
817impl From<&ArkEventData> for SwiperChangeData {
818 fn from(data: &ArkEventData) -> Self {
819 let index = match &data.payload {
820 ArkEventPayload::Int(i) => *i,
821 _ => 0,
822 };
823 SwiperChangeData { index }
824 }
825}
826
827#[derive(Default, Clone, Copy, Debug)]
829pub struct RefreshData;
830
831impl From<ArkEventData> for RefreshData {
832 fn from(_data: ArkEventData) -> Self {
833 RefreshData
834 }
835}
836
837impl From<&ArkEventData> for RefreshData {
838 fn from(_data: &ArkEventData) -> Self {
839 RefreshData
840 }
841}
842
843#[derive(Default, Clone, Copy, Debug)]
845pub struct ReachEndData;
846
847impl From<ArkEventData> for ReachEndData {
848 fn from(_data: ArkEventData) -> Self {
849 ReachEndData
850 }
851}
852
853impl From<&ArkEventData> for ReachEndData {
854 fn from(_data: &ArkEventData) -> Self {
855 ReachEndData
856 }
857}
858
859#[cfg(test)]
860mod tests {
861 use super::{
862 classify_event_name, ArkEventData, ArkEventKind, ArkEventPayload, KeyAction, KeyData,
863 KeyPayload, KeyboardKey, MouseButton, MouseData, MousePayload, PointerAction,
864 };
865
866 #[test]
867 fn canonical_event_names_share_one_semantic_identity() {
868 for name in ["onclick", "click"] {
869 assert_eq!(classify_event_name(name), Some(ArkEventKind::Click));
870 }
871 for name in ["onlongpress", "longpress"] {
872 assert_eq!(classify_event_name(name), Some(ArkEventKind::LongPress));
873 }
874 for name in ["onfocus", "focus"] {
875 assert_eq!(classify_event_name(name), Some(ArkEventKind::Focus));
876 }
877 for name in ["onblur", "blur"] {
878 assert_eq!(classify_event_name(name), Some(ArkEventKind::Blur));
879 }
880 for name in ["onreachend", "reachend"] {
881 assert_eq!(classify_event_name(name), Some(ArkEventKind::ReachEnd));
882 }
883 for name in ["onkey", "key"] {
884 assert_eq!(classify_event_name(name), Some(ArkEventKind::Key));
885 }
886 for name in ["onmouse", "mouse"] {
887 assert_eq!(classify_event_name(name), Some(ArkEventKind::Mouse));
888 }
889 assert!(ArkEventKind::Click.bubbles());
890 assert!(ArkEventKind::LongPress.bubbles());
891 assert!(!ArkEventKind::Change.bubbles());
892 }
893
894 #[test]
895 fn keyboard_activation_requires_a_press_of_enter_or_space() {
896 let enter = KeyData::from(ArkEventData::with_payload(ArkEventPayload::Key(
897 KeyPayload {
898 key: KeyboardKey::Enter,
899 action: KeyAction::Down,
900 ..Default::default()
901 },
902 )));
903 assert!(enter.activates());
904
905 let released_space = KeyData::from(ArkEventData::with_payload(ArkEventPayload::Key(
906 KeyPayload {
907 key: KeyboardKey::Space,
908 action: KeyAction::Up,
909 ..Default::default()
910 },
911 )));
912 assert!(!released_space.activates());
913 let repeated = KeyData {
914 key: KeyboardKey::Enter,
915 action: KeyAction::Repeat,
916 ..Default::default()
917 };
918 assert!(repeated.is_down());
919 assert!(!repeated.activates());
920 let shortcut = KeyData {
921 key: KeyboardKey::Enter,
922 action: KeyAction::Down,
923 modifiers: super::KeyModifiers {
924 ctrl: true,
925 ..Default::default()
926 },
927 ..Default::default()
928 };
929 assert!(!shortcut.activates());
930 }
931
932 #[test]
933 fn native_key_capture_preserves_unclaimed_editing_and_shortcuts() {
934 let capture = super::KeyboardCapture::from_names("enter space left right escape");
935 let mut key = KeyPayload {
936 key: KeyboardKey::ArrowRight,
937 action: KeyAction::Down,
938 ..Default::default()
939 };
940 assert!(capture.captures(&key));
941 key.modifiers.ctrl = true;
942 assert!(!capture.captures(&key));
943 key.key = KeyboardKey::Character('c');
944 assert!(!capture.captures(&key));
945 assert!(super::KeyboardCapture::from_names("all").captures(&key));
946 }
947
948 #[test]
949 fn roving_navigation_skips_disabled_items_and_keeps_calendar_columns() {
950 use super::{keyboard_navigation_target as next, KeyboardNavigation};
951 let enabled = [true, false, true, true];
952 assert_eq!(
953 next(
954 0,
955 KeyboardKey::ArrowRight,
956 KeyboardNavigation::Horizontal,
957 &enabled
958 ),
959 Some(2)
960 );
961 assert_eq!(
962 next(
963 0,
964 KeyboardKey::ArrowLeft,
965 KeyboardNavigation::Horizontal,
966 &enabled
967 ),
968 Some(3)
969 );
970 let grid = [true; 42];
971 assert_eq!(
972 next(
973 8,
974 KeyboardKey::ArrowDown,
975 KeyboardNavigation::Grid(7),
976 &grid
977 ),
978 Some(15)
979 );
980 assert_eq!(
981 next(8, KeyboardKey::ArrowUp, KeyboardNavigation::Grid(7), &grid),
982 Some(1)
983 );
984 assert_eq!(
985 next(1, KeyboardKey::ArrowUp, KeyboardNavigation::Grid(7), &grid),
986 None
987 );
988 assert_eq!(
989 next(0, KeyboardKey::End, KeyboardNavigation::Vertical, &enabled),
990 Some(3)
991 );
992 assert_eq!(
993 next(
994 0,
995 KeyboardKey::ArrowRight,
996 KeyboardNavigation::Horizontal,
997 &[false; 3]
998 ),
999 None
1000 );
1001 }
1002
1003 #[test]
1004 fn secondary_mouse_press_is_preserved() {
1005 let mouse = MouseData::from(ArkEventData::with_payload(ArkEventPayload::Mouse(
1006 MousePayload {
1007 button: MouseButton::Secondary,
1008 action: PointerAction::Down,
1009 window_x: 24.0,
1010 window_y: 48.0,
1011 ..Default::default()
1012 },
1013 )));
1014 assert!(mouse.secondary_down());
1015 assert_eq!((mouse.window_x, mouse.window_y), (24.0, 48.0));
1016 }
1017
1018 #[test]
1021 fn every_declared_listener_classifies_in_both_spellings() {
1022 let mut seen = std::collections::HashSet::new();
1023 assert!(!crate::events::EVENT_KINDS.is_empty());
1024 for (canonical, kind) in crate::events::EVENT_KINDS {
1025 assert!(canonical.starts_with("on"), "{canonical}");
1026 assert!(seen.insert(*canonical), "duplicate listener {canonical}");
1027 assert_eq!(classify_event_name(canonical), Some(*kind), "{canonical}");
1028 let compact = &canonical["on".len()..];
1029 assert_eq!(classify_event_name(compact), Some(*kind), "{compact}");
1030 }
1031 }
1032
1033 #[test]
1034 fn historical_snake_case_aliases_are_rejected() {
1035 for name in [
1036 "on_press",
1037 "_press",
1038 "on_long_press",
1039 "_long_press",
1040 "on_focus",
1041 "_focus",
1042 "on_blur",
1043 "_blur",
1044 "on_reach_end",
1045 "_reach_end",
1046 ] {
1047 assert_eq!(classify_event_name(name), None, "{name}");
1048 }
1049 }
1050}