1use crate::{
2 Axis, Bounds, Capslock, Context, Empty, IntoElement, IsZero, Keystroke, Modifiers, Pixels,
3 Point, Render, Window, point, px, seal::Sealed,
4};
5use smallvec::SmallVec;
6use std::{
7 any::Any,
8 fmt::Debug,
9 ops::Deref,
10 path::PathBuf,
11 time::{Duration, Instant},
12};
13
14pub trait InputEvent: Sealed + 'static {
16 fn to_platform_input(self) -> PlatformInput;
18}
19
20pub trait KeyEvent: InputEvent {}
22
23pub trait MouseEvent: InputEvent {}
25
26pub trait GestureEvent: InputEvent {}
28
29#[derive(Clone, Debug, Eq, PartialEq)]
31pub struct KeyDownEvent {
32 pub keystroke: Keystroke,
34
35 pub is_held: bool,
37}
38
39impl Sealed for KeyDownEvent {}
40impl InputEvent for KeyDownEvent {
41 fn to_platform_input(self) -> PlatformInput {
42 PlatformInput::KeyDown(self)
43 }
44}
45impl KeyEvent for KeyDownEvent {}
46
47#[derive(Clone, Debug)]
49pub struct KeyUpEvent {
50 pub keystroke: Keystroke,
52}
53
54impl Sealed for KeyUpEvent {}
55impl InputEvent for KeyUpEvent {
56 fn to_platform_input(self) -> PlatformInput {
57 PlatformInput::KeyUp(self)
58 }
59}
60impl KeyEvent for KeyUpEvent {}
61
62#[derive(Clone, Debug, Default)]
64pub struct ModifiersChangedEvent {
65 pub modifiers: Modifiers,
67 pub capslock: Capslock,
69}
70
71impl Sealed for ModifiersChangedEvent {}
72impl InputEvent for ModifiersChangedEvent {
73 fn to_platform_input(self) -> PlatformInput {
74 PlatformInput::ModifiersChanged(self)
75 }
76}
77impl KeyEvent for ModifiersChangedEvent {}
78
79impl Deref for ModifiersChangedEvent {
80 type Target = Modifiers;
81
82 fn deref(&self) -> &Self::Target {
83 &self.modifiers
84 }
85}
86
87#[derive(Clone, Copy, Debug, Default)]
90pub enum TouchPhase {
91 Started,
93 #[default]
95 Moved,
96 Ended,
98}
99
100#[derive(Clone, Debug, Default)]
102pub struct MouseDownEvent {
103 pub button: MouseButton,
105
106 pub position: Point<Pixels>,
108
109 pub modifiers: Modifiers,
111
112 pub click_count: usize,
114
115 pub first_mouse: bool,
117}
118
119impl Sealed for MouseDownEvent {}
120impl InputEvent for MouseDownEvent {
121 fn to_platform_input(self) -> PlatformInput {
122 PlatformInput::MouseDown(self)
123 }
124}
125impl MouseEvent for MouseDownEvent {}
126
127#[derive(Clone, Debug, Default)]
129pub struct MouseUpEvent {
130 pub button: MouseButton,
132
133 pub position: Point<Pixels>,
135
136 pub modifiers: Modifiers,
138
139 pub click_count: usize,
141}
142
143impl Sealed for MouseUpEvent {}
144impl InputEvent for MouseUpEvent {
145 fn to_platform_input(self) -> PlatformInput {
146 PlatformInput::MouseUp(self)
147 }
148}
149impl MouseEvent for MouseUpEvent {}
150
151#[derive(Clone, Debug, Default)]
153pub struct MouseClickEvent {
154 pub down: MouseDownEvent,
156
157 pub up: MouseUpEvent,
159}
160
161#[derive(Clone, Debug, Default)]
163pub struct KeyboardClickEvent {
164 pub button: KeyboardButton,
166
167 pub bounds: Bounds<Pixels>,
169}
170
171#[derive(Clone, Debug)]
173pub enum ClickEvent {
174 Mouse(MouseClickEvent),
176 Keyboard(KeyboardClickEvent),
178}
179
180impl Default for ClickEvent {
181 fn default() -> Self {
182 ClickEvent::Keyboard(KeyboardClickEvent::default())
183 }
184}
185
186impl ClickEvent {
187 pub fn modifiers(&self) -> Modifiers {
192 match self {
193 ClickEvent::Keyboard(_) => Modifiers::default(),
195 ClickEvent::Mouse(event) => event.up.modifiers,
199 }
200 }
201
202 pub fn position(&self) -> Point<Pixels> {
207 match self {
208 ClickEvent::Keyboard(event) => event.bounds.bottom_left(),
209 ClickEvent::Mouse(event) => event.up.position,
210 }
211 }
212
213 pub fn mouse_position(&self) -> Option<Point<Pixels>> {
218 match self {
219 ClickEvent::Keyboard(_) => None,
220 ClickEvent::Mouse(event) => Some(event.up.position),
221 }
222 }
223
224 pub fn is_right_click(&self) -> bool {
229 match self {
230 ClickEvent::Keyboard(_) => false,
231 ClickEvent::Mouse(event) => {
232 event.down.button == MouseButton::Right && event.up.button == MouseButton::Right
233 }
234 }
235 }
236
237 pub fn standard_click(&self) -> bool {
242 match self {
243 ClickEvent::Keyboard(_) => true,
244 ClickEvent::Mouse(event) => {
245 event.down.button == MouseButton::Left && event.up.button == MouseButton::Left
246 }
247 }
248 }
249
250 pub fn first_focus(&self) -> bool {
255 match self {
256 ClickEvent::Keyboard(_) => false,
257 ClickEvent::Mouse(event) => event.down.first_mouse,
258 }
259 }
260
261 pub fn click_count(&self) -> usize {
266 match self {
267 ClickEvent::Keyboard(_) => 1,
268 ClickEvent::Mouse(event) => event.up.click_count,
269 }
270 }
271
272 pub fn is_keyboard(&self) -> bool {
274 match self {
275 ClickEvent::Mouse(_) => false,
276 ClickEvent::Keyboard(_) => true,
277 }
278 }
279}
280
281#[derive(Hash, PartialEq, Eq, Copy, Clone, Debug, Default)]
283pub enum KeyboardButton {
284 #[default]
286 Enter,
287 Space,
289}
290
291#[derive(Hash, PartialEq, Eq, Copy, Clone, Debug, Default)]
293pub enum MouseButton {
294 #[default]
296 Left,
297
298 Right,
300
301 Middle,
303
304 Navigate(NavigationDirection),
306}
307
308impl MouseButton {
309 pub fn all() -> Vec<Self> {
311 vec![
312 MouseButton::Left,
313 MouseButton::Right,
314 MouseButton::Middle,
315 MouseButton::Navigate(NavigationDirection::Back),
316 MouseButton::Navigate(NavigationDirection::Forward),
317 ]
318 }
319}
320
321#[derive(Hash, PartialEq, Eq, Copy, Clone, Debug, Default)]
323pub enum NavigationDirection {
324 #[default]
326 Back,
327
328 Forward,
330}
331
332#[derive(Clone, Debug, Default)]
334pub struct MouseMoveEvent {
335 pub position: Point<Pixels>,
337
338 pub pressed_button: Option<MouseButton>,
340
341 pub modifiers: Modifiers,
343}
344
345impl Sealed for MouseMoveEvent {}
346impl InputEvent for MouseMoveEvent {
347 fn to_platform_input(self) -> PlatformInput {
348 PlatformInput::MouseMove(self)
349 }
350}
351impl MouseEvent for MouseMoveEvent {}
352
353impl MouseMoveEvent {
354 pub fn dragging(&self) -> bool {
356 self.pressed_button == Some(MouseButton::Left)
357 }
358}
359
360#[derive(Clone, Debug, Default)]
362pub struct ScrollWheelEvent {
363 pub position: Point<Pixels>,
365
366 pub delta: ScrollDelta,
368
369 pub modifiers: Modifiers,
371
372 pub touch_phase: TouchPhase,
374}
375
376impl Sealed for ScrollWheelEvent {}
377impl InputEvent for ScrollWheelEvent {
378 fn to_platform_input(self) -> PlatformInput {
379 PlatformInput::ScrollWheel(self)
380 }
381}
382impl MouseEvent for ScrollWheelEvent {}
383
384impl Deref for ScrollWheelEvent {
385 type Target = Modifiers;
386
387 fn deref(&self) -> &Self::Target {
388 &self.modifiers
389 }
390}
391
392#[derive(Clone, Copy, Debug)]
394pub enum ScrollDelta {
395 Pixels(Point<Pixels>),
397 Lines(Point<f32>),
399}
400
401impl Default for ScrollDelta {
402 fn default() -> Self {
403 Self::Lines(Default::default())
404 }
405}
406
407#[derive(Clone, Debug, Default)]
410pub struct PinchEvent {
411 pub position: Point<Pixels>,
413
414 pub delta: f32,
419
420 pub modifiers: Modifiers,
422
423 pub phase: TouchPhase,
425}
426
427impl Sealed for PinchEvent {}
428impl InputEvent for PinchEvent {
429 fn to_platform_input(self) -> PlatformInput {
430 PlatformInput::Pinch(self)
431 }
432}
433impl GestureEvent for PinchEvent {}
434impl MouseEvent for PinchEvent {}
435
436impl Deref for PinchEvent {
437 type Target = Modifiers;
438
439 fn deref(&self) -> &Self::Target {
440 &self.modifiers
441 }
442}
443
444impl ScrollDelta {
445 pub fn precise(&self) -> bool {
447 match self {
448 ScrollDelta::Pixels(_) => true,
449 ScrollDelta::Lines(_) => false,
450 }
451 }
452
453 pub fn pixel_delta(&self, line_height: Pixels) -> Point<Pixels> {
455 match self {
456 ScrollDelta::Pixels(delta) => *delta,
457 ScrollDelta::Lines(delta) => point(line_height * delta.x, line_height * delta.y),
458 }
459 }
460
461 pub fn coalesce(self, other: ScrollDelta) -> ScrollDelta {
466 match (self, other) {
467 (ScrollDelta::Pixels(a), ScrollDelta::Pixels(b)) => {
468 let x = if a.x.signum() == b.x.signum() {
469 a.x + b.x
470 } else {
471 b.x
472 };
473
474 let y = if a.y.signum() == b.y.signum() {
475 a.y + b.y
476 } else {
477 b.y
478 };
479
480 ScrollDelta::Pixels(point(x, y))
481 }
482
483 (ScrollDelta::Lines(a), ScrollDelta::Lines(b)) => {
484 let x = if a.x.signum() == b.x.signum() {
485 a.x + b.x
486 } else {
487 b.x
488 };
489
490 let y = if a.y.signum() == b.y.signum() {
491 a.y + b.y
492 } else {
493 b.y
494 };
495
496 ScrollDelta::Lines(point(x, y))
497 }
498
499 _ => other,
500 }
501 }
502}
503
504const SCROLL_EVENT_SEPARATION: Duration = Duration::from_millis(28);
505
506#[derive(Clone, Copy, Debug, Default)]
508pub struct OngoingScroll {
509 last_event: Option<Instant>,
510 axis: Option<Axis>,
511}
512
513impl OngoingScroll {
514 pub fn filter(&mut self, delta: &mut Point<Pixels>, touch_phase: TouchPhase) {
519 self.filter_at(delta, touch_phase, Instant::now())
520 }
521
522 fn filter_at(&mut self, delta: &mut Point<Pixels>, touch_phase: TouchPhase, now: Instant) {
523 const UNLOCK_PERCENT: f32 = 1.9;
524 const UNLOCK_LOWER_BOUND: Pixels = px(6.);
525
526 if matches!(touch_phase, TouchPhase::Ended) {
527 self.last_event = None;
528 self.axis = None;
529 return;
530 }
531
532 let x = delta.x.abs();
533 let y = delta.y.abs();
534 if x.is_zero() && y.is_zero() {
535 if matches!(touch_phase, TouchPhase::Started) {
536 self.last_event = None;
537 self.axis = None;
538 }
539 return;
540 }
541
542 let starts_new_gesture = matches!(touch_phase, TouchPhase::Started)
543 || self
544 .last_event
545 .is_none_or(|last_event| now.duration_since(last_event) >= SCROLL_EVENT_SEPARATION);
546 let mut axis = self.axis;
547 if starts_new_gesture {
548 axis = if x <= y {
549 Some(Axis::Vertical)
550 } else {
551 Some(Axis::Horizontal)
552 };
553 } else if x.max(y) >= UNLOCK_LOWER_BOUND {
554 match axis {
555 Some(Axis::Vertical) if x > y && x >= y * UNLOCK_PERCENT => {
556 axis = None;
557 }
558 Some(Axis::Horizontal) if y > x && y >= x * UNLOCK_PERCENT => {
559 axis = None;
560 }
561 _ => {}
562 }
563 }
564
565 self.last_event = Some(now);
566 self.axis = axis;
567 match axis {
568 Some(Axis::Vertical) => delta.x = Pixels::ZERO,
569 Some(Axis::Horizontal) => delta.y = Pixels::ZERO,
570 None => {}
571 }
572 }
573}
574
575#[derive(Clone, Debug, Default)]
577pub struct MouseExitEvent {
578 pub position: Point<Pixels>,
580 pub pressed_button: Option<MouseButton>,
582 pub modifiers: Modifiers,
584}
585
586impl Sealed for MouseExitEvent {}
587impl InputEvent for MouseExitEvent {
588 fn to_platform_input(self) -> PlatformInput {
589 PlatformInput::MouseExited(self)
590 }
591}
592impl MouseEvent for MouseExitEvent {}
593
594impl Deref for MouseExitEvent {
595 type Target = Modifiers;
596
597 fn deref(&self) -> &Self::Target {
598 &self.modifiers
599 }
600}
601
602#[derive(Debug, Clone, Default)]
604#[allow(missing_docs)]
605pub struct ExternalPaths(pub SmallVec<[PathBuf; 2]>);
606
607impl ExternalPaths {
608 pub fn paths(&self) -> &[PathBuf] {
610 &self.0
611 }
612}
613
614impl Render for ExternalPaths {
615 fn render(&mut self, _: &mut Window, _: &mut Context<Self>) -> impl IntoElement {
616 Empty
618 }
619}
620
621#[derive(Debug, Clone)]
623pub enum FileDropEvent {
624 Entered {
626 position: Point<Pixels>,
628 paths: ExternalPaths,
630 },
631 Pending {
633 position: Point<Pixels>,
635 },
636 Submit {
638 position: Point<Pixels>,
640 },
641 Exited,
643}
644
645impl Sealed for FileDropEvent {}
646impl InputEvent for FileDropEvent {
647 fn to_platform_input(self) -> PlatformInput {
648 PlatformInput::FileDrop(self)
649 }
650}
651impl MouseEvent for FileDropEvent {}
652
653#[derive(Clone, Debug)]
655pub enum PlatformInput {
656 KeyDown(KeyDownEvent),
658 KeyUp(KeyUpEvent),
660 ModifiersChanged(ModifiersChangedEvent),
662 MouseDown(MouseDownEvent),
664 MouseUp(MouseUpEvent),
666 MouseMove(MouseMoveEvent),
668 MouseExited(MouseExitEvent),
670 ScrollWheel(ScrollWheelEvent),
672 Pinch(PinchEvent),
674 FileDrop(FileDropEvent),
676}
677
678impl PlatformInput {
679 pub(crate) fn mouse_event(&self) -> Option<&dyn Any> {
680 match self {
681 PlatformInput::KeyDown { .. } => None,
682 PlatformInput::KeyUp { .. } => None,
683 PlatformInput::ModifiersChanged { .. } => None,
684 PlatformInput::MouseDown(event) => Some(event),
685 PlatformInput::MouseUp(event) => Some(event),
686 PlatformInput::MouseMove(event) => Some(event),
687 PlatformInput::MouseExited(event) => Some(event),
688 PlatformInput::ScrollWheel(event) => Some(event),
689 PlatformInput::Pinch(event) => Some(event),
690 PlatformInput::FileDrop(event) => Some(event),
691 }
692 }
693
694 pub(crate) fn keyboard_event(&self) -> Option<&dyn Any> {
695 match self {
696 PlatformInput::KeyDown(event) => Some(event),
697 PlatformInput::KeyUp(event) => Some(event),
698 PlatformInput::ModifiersChanged(event) => Some(event),
699 PlatformInput::MouseDown(_) => None,
700 PlatformInput::MouseUp(_) => None,
701 PlatformInput::MouseMove(_) => None,
702 PlatformInput::MouseExited(_) => None,
703 PlatformInput::ScrollWheel(_) => None,
704 PlatformInput::Pinch(_) => None,
705 PlatformInput::FileDrop(_) => None,
706 }
707 }
708}
709
710#[cfg(test)]
711mod test {
712
713 use crate::{
714 self as gpui, AppContext as _, Context, FocusHandle, InteractiveElement, IntoElement,
715 KeyBinding, Keystroke, Modifiers, ParentElement, Render, TestAppContext, Window, div,
716 };
717
718 struct TestView {
719 saw_key_down: bool,
720 saw_action: bool,
721 focus_handle: FocusHandle,
722 }
723
724 actions!(test_only, [TestAction]);
725
726 impl Render for TestView {
727 fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
728 div().id("testview").child(
729 div()
730 .key_context("parent")
731 .on_key_down(cx.listener(|this, _, _, cx| {
732 cx.stop_propagation();
733 this.saw_key_down = true
734 }))
735 .on_action(cx.listener(|this: &mut TestView, _: &TestAction, _, _| {
736 this.saw_action = true
737 }))
738 .child(
739 div()
740 .key_context("nested")
741 .track_focus(&self.focus_handle)
742 .into_element(),
743 ),
744 )
745 }
746 }
747
748 #[gpui::test]
749 fn test_on_events(cx: &mut TestAppContext) {
750 let window = cx.update(|cx| {
751 cx.open_window(Default::default(), |_, cx| {
752 cx.new(|cx| TestView {
753 saw_key_down: false,
754 saw_action: false,
755 focus_handle: cx.focus_handle(),
756 })
757 })
758 .unwrap()
759 });
760
761 cx.update(|cx| {
762 cx.bind_keys(vec![KeyBinding::new("ctrl-g", TestAction, Some("parent"))]);
763 });
764
765 window
766 .update(cx, |test_view, window, _cx| {
767 window.focus(&test_view.focus_handle)
768 })
769 .unwrap();
770
771 cx.dispatch_keystroke(*window, Keystroke::parse("a").unwrap());
772 cx.dispatch_keystroke(*window, Keystroke::parse("ctrl-g").unwrap());
773
774 window
775 .update(cx, |test_view, _, _| {
776 assert!(test_view.saw_key_down || test_view.saw_action);
777 assert!(test_view.saw_key_down);
778 assert!(test_view.saw_action);
779 })
780 .unwrap();
781 }
782
783 #[gpui::test]
784 fn test_multi_modifier_gesture_does_not_dispatch_standalone_modifier_binding(
785 cx: &mut TestAppContext,
786 ) {
787 let (test_view, cx) = cx.add_window_view(|_, cx| TestView {
788 saw_key_down: false,
789 saw_action: false,
790 focus_handle: cx.focus_handle(),
791 });
792
793 cx.update(|_, cx| {
794 cx.bind_keys(vec![KeyBinding::new("shift", TestAction, None)]);
795 });
796 test_view.update_in(cx, |test_view, window, _cx| {
797 window.focus(&test_view.focus_handle);
798 });
799
800 cx.simulate_modifiers_change(Modifiers::alt());
801 cx.simulate_modifiers_change(Modifiers::alt() | Modifiers::shift());
802 cx.simulate_modifiers_change(Modifiers::shift());
803 cx.simulate_modifiers_change(Modifiers::none());
804 assert!(!test_view.read_with(cx, |test_view, _| test_view.saw_action));
805
806 cx.simulate_modifiers_change(Modifiers::shift());
807 cx.simulate_modifiers_change(Modifiers::none());
808 assert!(test_view.read_with(cx, |test_view, _| test_view.saw_action));
809 }
810}
811
812#[cfg(test)]
813mod ongoing_scroll_tests {
814 use super::*;
815
816 #[test]
817 fn ongoing_scroll_locks_to_dominant_axis() {
818 let now = Instant::now();
819 let mut ongoing_scroll = OngoingScroll::default();
820 let mut horizontal_delta = point(px(10.), px(2.));
821 ongoing_scroll.filter_at(&mut horizontal_delta, TouchPhase::Started, now);
822 assert_eq!(ongoing_scroll.axis, Some(Axis::Horizontal));
823 assert_eq!(horizontal_delta, point(px(10.), px(0.)));
824
825 let mut continued_delta = point(px(3.), px(2.));
826 ongoing_scroll.filter_at(
827 &mut continued_delta,
828 TouchPhase::Moved,
829 now + Duration::from_millis(1),
830 );
831 assert_eq!(ongoing_scroll.axis, Some(Axis::Horizontal));
832 assert_eq!(continued_delta, point(px(3.), px(0.)));
833 }
834
835 #[test]
836 fn ongoing_scroll_unlocks_when_direction_changes() {
837 let now = Instant::now();
838 let mut ongoing_scroll = OngoingScroll::default();
839 let mut horizontal_delta = point(px(10.), px(2.));
840 ongoing_scroll.filter_at(&mut horizontal_delta, TouchPhase::Started, now);
841
842 let mut vertical_delta = point(px(2.), px(10.));
843 ongoing_scroll.filter_at(
844 &mut vertical_delta,
845 TouchPhase::Moved,
846 now + Duration::from_millis(1),
847 );
848 assert_eq!(ongoing_scroll.axis, None);
849 assert_eq!(vertical_delta, point(px(2.), px(10.)));
850 }
851
852 #[test]
853 fn ongoing_scroll_starts_new_gesture_at_timeout_boundary() {
854 let now = Instant::now();
855 let mut ongoing_scroll = OngoingScroll::default();
856 let mut horizontal_delta = point(px(10.), px(2.));
857 ongoing_scroll.filter_at(&mut horizontal_delta, TouchPhase::Moved, now);
858
859 let mut vertical_delta = point(px(2.), px(10.));
860 ongoing_scroll.filter_at(
861 &mut vertical_delta,
862 TouchPhase::Moved,
863 now + SCROLL_EVENT_SEPARATION,
864 );
865 assert_eq!(ongoing_scroll.axis, Some(Axis::Vertical));
866 assert_eq!(vertical_delta, point(px(0.), px(10.)));
867 }
868
869 #[test]
870 fn ongoing_scroll_ignores_zero_delta_and_resets_when_ended() {
871 let now = Instant::now();
872 let mut ongoing_scroll = OngoingScroll::default();
873 let mut horizontal_delta = point(px(10.), px(2.));
874 ongoing_scroll.filter_at(&mut horizontal_delta, TouchPhase::Started, now);
875
876 let mut zero_delta = Point::default();
877 ongoing_scroll.filter_at(
878 &mut zero_delta,
879 TouchPhase::Ended,
880 now + Duration::from_millis(1),
881 );
882 assert_eq!(ongoing_scroll.axis, None);
883
884 let mut vertical_delta = point(px(2.), px(3.));
885 ongoing_scroll.filter_at(
886 &mut vertical_delta,
887 TouchPhase::Moved,
888 now + Duration::from_millis(2),
889 );
890 assert_eq!(ongoing_scroll.axis, Some(Axis::Vertical));
891 assert_eq!(vertical_delta, point(px(0.), px(3.)));
892 }
893
894 #[test]
895 fn ongoing_scroll_ignores_zero_delta_movement() {
896 let now = Instant::now();
897 let mut ongoing_scroll = OngoingScroll::default();
898 let mut horizontal_delta = point(px(10.), px(2.));
899 ongoing_scroll.filter_at(&mut horizontal_delta, TouchPhase::Started, now);
900
901 let mut zero_delta = Point::default();
902 ongoing_scroll.filter_at(
903 &mut zero_delta,
904 TouchPhase::Moved,
905 now + SCROLL_EVENT_SEPARATION,
906 );
907
908 let mut vertical_delta = point(px(2.), px(10.));
909 ongoing_scroll.filter_at(
910 &mut vertical_delta,
911 TouchPhase::Moved,
912 now + SCROLL_EVENT_SEPARATION,
913 );
914 assert_eq!(ongoing_scroll.axis, Some(Axis::Vertical));
915 assert_eq!(vertical_delta, point(px(0.), px(10.)));
916 }
917
918 #[test]
919 fn ongoing_scroll_supports_moved_only_platforms() {
920 let now = Instant::now();
921 let mut ongoing_scroll = OngoingScroll::default();
922 let mut horizontal_delta = point(px(10.), px(2.));
923 ongoing_scroll.filter_at(&mut horizontal_delta, TouchPhase::Moved, now);
924 assert_eq!(ongoing_scroll.axis, Some(Axis::Horizontal));
925 assert_eq!(horizontal_delta, point(px(10.), px(0.)));
926 }
927}