1use crate::core::{
3 AnimationError, AutoScroll, LayoutRect, MotionValue, ReorderAxis, Transition, move_item,
4};
5
6use crate::{Axis, MotionAnchor};
7
8use gpui_kit::{
9 AnyElement, App, Bounds, DispatchPhase, Div, Element, ElementId, EntityId, FocusHandle,
10 GlobalElementId, Hitbox, HitboxBehavior, InspectorElementId, IntoElement, KeyDownEvent,
11 LayoutId, MouseButton, MouseDownEvent, MouseMoveEvent, MouseUpEvent, Pixels, ScrollHandle,
12 Stateful, Window, point, prelude::*, px,
13};
14
15use std::{
16 cell::{Ref, RefCell},
17 collections::HashMap,
18 hash::Hash,
19 rc::Rc,
20 time::{Duration, Instant},
21};
22
23struct Metrics {
24 bounds: Bounds<Pixels>,
25 displayed: Bounds<Pixels>,
26 values: [MotionValue; 2],
27 focus: FocusHandle,
28 measured: bool,
29 slot: usize,
31}
32
33struct Grab<K> {
34 key: K,
35 offset: [f64; 2],
36 pointer: [f64; 2],
37 original: Vec<K>,
38 axis: ReorderAxis,
39}
40
41struct EdgeScroll {
42 handle: ScrollHandle,
43 viewport: MotionAnchor,
44 policy: AutoScroll,
45 last: Option<Instant>,
46}
47
48type Change<K> = Rc<dyn Fn(&[K], &mut Window, &mut App)>;
49
50struct Data<K> {
51 order: Vec<K>,
52 metrics: HashMap<K, Metrics>,
53 grab: Option<Grab<K>>,
54 mode: ReorderAxis,
55 epoch: Option<Instant>,
56 change: Option<Change<K>>,
57 rects: Vec<LayoutRect>,
58 indices: Vec<usize>,
59 scroll: Option<EdgeScroll>,
60 scroll_pending: bool,
61 owner: Option<EntityId>,
62 touch: bool,
63 layout_ready: bool,
64}
65
66fn rect(bounds: Bounds<Pixels>, offset: [f64; 2]) -> LayoutRect {
67 LayoutRect::new(
68 [
69 f64::from(bounds.origin.x) + offset[0],
70 f64::from(bounds.origin.y) + offset[1],
71 ],
72 [f64::from(bounds.size.width), f64::from(bounds.size.height)],
73 )
74 .expect("finite native bounds")
75}
76
77impl<K: Clone + Eq + Hash> Data<K> {
78 fn refresh(&mut self, offset: [f64; 2]) {
79 self.rects.clear();
80 self.indices.clear();
81
82 for key in &self.order {
83 if let Some(metric) = self.metrics.get(key).filter(|metric| metric.measured) {
84 self.rects.push(rect(metric.bounds, offset));
85 self.indices.push(metric.slot);
86 }
87 }
88 }
89
90 fn begin(&mut self, key: &K, pointer: [f64; 2], window: &mut Window, cx: &mut App) -> bool {
91 if self.grab.is_some() || !self.layout_ready || !window.is_window_active() {
92 return false;
93 }
94
95 let Some(metric) = self.metrics.get(key).filter(|metric| metric.measured) else {
96 return false;
97 };
98
99 let origin = rect(metric.displayed, [0.0; 2]).origin();
100 let focus = metric.focus.clone();
101
102 self.refresh([0.0; 2]);
103 self.grab = Some(Grab {
104 key: key.clone(),
105 pointer,
106 offset: std::array::from_fn(|i| pointer[i] - origin[i]),
107 original: self.order.clone(),
108 axis: self.mode.resolve(&self.rects),
109 });
110 self.touch = false;
111
112 if let Some(scroll) = &mut self.scroll {
113 scroll.last = Some(cx.background_executor().now());
114 }
115
116 focus.focus(window, cx);
117
118 true
119 }
120
121 fn cancel(&mut self) {
122 if let Some(grab) = self.grab.take() {
123 self.order = grab.original;
124 }
125
126 self.touch = false;
127
128 if let Some(scroll) = &mut self.scroll {
129 scroll.last = None;
130 }
131 }
132
133 fn reorder(&mut self, scroll_delta: [f64; 2]) -> bool {
134 let Some(grab) = &self.grab else {
135 return false;
136 };
137
138 let from = self
139 .order
140 .iter()
141 .position(|key| *key == grab.key)
142 .expect("retained key");
143
144 let axis = grab.axis;
145
146 let Some(metric) = self.metrics.get(&grab.key) else {
147 return false;
148 };
149
150 let size = rect(metric.bounds, [0.0; 2]).size();
151 let center = std::array::from_fn(|i| grab.pointer[i] - grab.offset[i] + size[i] * 0.5);
152
153 self.refresh(scroll_delta);
154
155 let Some(destination) = axis
156 .destination(center, &self.rects)
157 .map(|index| self.indices[index])
158 else {
159 return false;
160 };
161
162 if from == destination {
163 return false;
164 }
165
166 move_item(&mut self.order, from, destination).expect("measured indices");
167
168 true
169 }
170
171 fn keyboard(&mut self, key: &K, direction: &str) -> Option<(usize, usize)> {
172 if !self.layout_ready {
173 return None;
174 }
175
176 self.refresh([0.0; 2]);
177 let mode = self.mode.resolve(&self.rects);
178
179 let (axis, sign) = match direction {
180 "left" => (0, -1.0),
181 "right" => (0, 1.0),
182 "up" => (1, -1.0),
183 "down" => (1, 1.0),
184 _ => return None,
185 };
186
187 if (mode == ReorderAxis::X && axis == 1) || (mode == ReorderAxis::Y && axis == 0) {
188 return None;
189 }
190
191 let from = self.order.iter().position(|old| old == key)?;
192 let origin_slot = self.indices.iter().position(|slot| *slot == from)?;
193 let origin = self.rects[origin_slot].center();
194
195 let destination = self
196 .rects
197 .iter()
198 .enumerate()
199 .filter_map(|(i, rect)| {
200 let center = rect.center();
201 let forward = (center[axis] - origin[axis]) * sign;
202
203 (forward > 0.01).then_some((
204 self.indices[i],
205 forward + (center[1 - axis] - origin[1 - axis]).abs() * 2.0,
206 ))
207 })
208 .min_by(|a, b| a.1.total_cmp(&b.1))
209 .map(|value| value.0)?;
210
211 Some((from, destination))
212 }
213
214 fn scroll_velocity(&self) -> [f64; 2] {
215 let Some(grab) = &self.grab else {
216 return [0.0; 2];
217 };
218
219 let Some(scroll) = &self.scroll else {
220 return [0.0; 2];
221 };
222
223 let Some(viewport) = scroll.viewport.rect() else {
224 return [0.0; 2];
225 };
226
227 let offset = scroll.handle.offset();
228 let max = scroll.handle.max_offset();
229
230 std::array::from_fn(|i| {
231 if (grab.axis == ReorderAxis::X && i == 1) || (grab.axis == ReorderAxis::Y && i == 0) {
232 return 0.0;
233 }
234
235 let velocity =
236 scroll
237 .policy
238 .velocity(grab.pointer[i], viewport.origin()[i], viewport.size()[i]);
239
240 let current = if i == 0 {
241 -f64::from(offset.x)
242 } else {
243 -f64::from(offset.y)
244 };
245
246 let extent = if i == 0 {
247 f64::from(max.x).abs()
248 } else {
249 f64::from(max.y).abs()
250 };
251
252 if (velocity < 0.0 && current > 0.01) || (velocity > 0.0 && current < extent - 0.01) {
253 velocity
254 } else {
255 0.0
256 }
257 })
258 }
259}
260
261#[derive(Clone)]
267pub struct ReorderGroup<K>(Rc<RefCell<Data<K>>>);
268
269impl<K: Clone + Eq + Hash + 'static> ReorderGroup<K> {
270 pub fn new(order: Vec<K>, axis: Axis) -> Result<Self, AnimationError> {
272 Self::spatial(
273 order,
274 match axis {
275 Axis::X => ReorderAxis::X,
276 Axis::Y => ReorderAxis::Y,
277 },
278 )
279 }
280
281 pub fn spatial(order: Vec<K>, mode: ReorderAxis) -> Result<Self, AnimationError> {
283 if order
284 .iter()
285 .enumerate()
286 .any(|(i, key)| order[..i].contains(key))
287 {
288 return Err(AnimationError::Index);
289 }
290
291 let capacity = order.len();
292
293 Ok(Self(Rc::new(RefCell::new(Data {
294 order,
295 metrics: HashMap::new(),
296 grab: None,
297 mode,
298 epoch: None,
299 change: None,
300 rects: Vec::with_capacity(capacity),
301 indices: Vec::with_capacity(capacity),
302 scroll: None,
303 scroll_pending: false,
304 owner: None,
305 touch: false,
306 layout_ready: false,
307 }))))
308 }
309
310 pub fn autoscroll(
314 self,
315 handle: ScrollHandle,
316 viewport: MotionAnchor,
317 policy: AutoScroll,
318 ) -> Self {
319 self.0.borrow_mut().scroll = Some(EdgeScroll {
320 handle,
321 viewport,
322 policy,
323 last: None,
324 });
325
326 self
327 }
328
329 pub fn order(&self) -> Ref<'_, [K]> {
331 Ref::map(self.0.borrow(), |data| data.order.as_slice())
332 }
333
334 pub fn on_change(self, callback: impl Fn(&[K], &mut Window, &mut App) + 'static) -> Self {
337 self.0.borrow_mut().change = Some(Rc::new(callback));
338
339 self
340 }
341
342 pub fn set_order(&self, order: Vec<K>) -> Result<(), AnimationError> {
345 if order
346 .iter()
347 .enumerate()
348 .any(|(i, key)| order[..i].contains(key))
349 {
350 return Err(AnimationError::Index);
351 }
352
353 let mut data = self.0.borrow_mut();
354
355 if data.grab.is_none() && data.order == order {
356 return Ok(());
357 }
358
359 data.cancel();
360 data.layout_ready = false;
361 data.metrics.retain(|key, _| order.contains(key));
362 let extra_rects = order.len().saturating_sub(data.rects.len());
363 let extra_indices = order.len().saturating_sub(data.indices.len());
364
365 data.rects.reserve(extra_rects);
366 data.indices.reserve(extra_indices);
367 data.order = order;
368
369 Ok(())
370 }
371
372 pub fn start_drag(
374 &self,
375 key: &K,
376 pointer: [f64; 2],
377 window: &mut Window,
378 cx: &mut App,
379 ) -> bool {
380 assert!(
381 pointer.iter().all(|v| v.is_finite()),
382 "invalid reorder pointer"
383 );
384 let mut data = self.0.borrow_mut();
385 let started = data.begin(key, pointer, window, cx);
386
387 if started && let Some(owner) = data.owner {
388 cx.notify(owner);
389 }
390
391 started
392 }
393
394 pub fn cancel(&self, cx: &mut App) {
396 let mut data = self.0.borrow_mut();
397
398 data.cancel();
399
400 if let Some(owner) = data.owner {
401 cx.notify(owner);
402 }
403 }
404
405 pub fn is_dragging(&self) -> bool {
407 self.0.borrow().grab.is_some()
408 }
409
410 pub fn item<E: IntoElement + StatefulInteractiveElement + 'static>(
413 &self,
414 id: impl Into<ElementId>,
415 key: K,
416 child: E,
417 ) -> ReorderElement<K, E> {
418 let slot = self
419 .0
420 .borrow()
421 .order
422 .iter()
423 .position(|old| old == &key)
424 .expect("unknown reorder key");
425
426 ReorderElement {
427 group: self.clone(),
428 id: id.into(),
429 key,
430 slot,
431 child: Some(child),
432 transition: Transition::default(),
433 listener: true,
434 }
435 }
436
437 fn finish(&self, key: &K, window: &mut Window, cx: &mut App) {
438 let mut data = self.0.borrow_mut();
439
440 if !data.grab.as_ref().is_some_and(|grab| grab.key == *key) {
441 return;
442 }
443
444 let changed = data.grab.take().expect("owned grab").original != data.order;
445
446 data.touch = false;
447
448 if let Some(scroll) = &mut data.scroll {
449 scroll.last = None;
450 }
451
452 let callback = if changed {
453 data.change
454 .clone()
455 .map(|callback| (callback, data.order.clone()))
456 } else {
457 None
458 };
459
460 let owner = data.owner;
461
462 drop(data);
463
464 if let Some((callback, order)) = callback {
465 callback(&order, window, cx);
466 }
467
468 if let Some(owner) = owner {
469 cx.notify(owner);
470 }
471 }
472}
473
474pub struct ReorderElement<K, E = Stateful<Div>> {
476 group: ReorderGroup<K>,
477 id: ElementId,
478 key: K,
479 slot: usize,
480 child: Option<E>,
481 transition: Transition,
482 listener: bool,
483}
484
485impl<K, E> ReorderElement<K, E> {
486 pub fn transition(mut self, transition: Transition) -> Self {
488 self.transition = transition;
489
490 self
491 }
492
493 pub fn drag_listener(mut self, enabled: bool) -> Self {
495 self.listener = enabled;
496
497 self
498 }
499}
500
501impl<K: Clone + Eq + Hash + 'static, E: IntoElement + StatefulInteractiveElement + 'static>
502 IntoElement for ReorderElement<K, E>
503{
504 type Element = Self;
505
506 fn into_element(self) -> Self {
507 self
508 }
509}
510
511impl<K: Clone + Eq + Hash + 'static, E: IntoElement + StatefulInteractiveElement + 'static> Element
512 for ReorderElement<K, E>
513{
514 type RequestLayoutState = AnyElement;
515 type PrepaintState = Hitbox;
516
517 fn id(&self) -> Option<ElementId> {
518 Some(self.id.clone())
519 }
520
521 fn source_location(&self) -> Option<&'static std::panic::Location<'static>> {
522 None
523 }
524
525 fn request_layout(
526 &mut self,
527 _: Option<&GlobalElementId>,
528 _: Option<&InspectorElementId>,
529 window: &mut Window,
530 cx: &mut App,
531 ) -> (LayoutId, AnyElement) {
532 let mut data = self.group.0.borrow_mut();
533
534 data.owner = Some(window.current_view());
535 data.epoch
536 .get_or_insert_with(|| cx.background_executor().now());
537
538 if !window.is_window_active()
539 || data.grab.as_ref().is_some_and(|grab| {
540 data.metrics
541 .get(&grab.key)
542 .is_some_and(|metric| !metric.focus.is_focused(window))
543 })
544 {
545 let was_dragging = data.grab.is_some();
546
547 data.cancel();
548
549 if was_dragging {
550 window.request_animation_frame();
551 }
552 }
553
554 let metric = data
555 .metrics
556 .entry(self.key.clone())
557 .or_insert_with(|| Metrics {
558 bounds: Bounds::default(),
559 displayed: Bounds::default(),
560 values: [MotionValue::new(0.0), MotionValue::new(0.0)],
561 focus: cx.focus_handle().tab_stop(true),
562 measured: false,
563 slot: 0,
564 });
565
566 let focus = metric.focus.clone();
567
568 drop(data);
569 let mut child = self
570 .child
571 .take()
572 .expect("one layout")
573 .track_focus(&focus)
574 .tab_index(0)
575 .into_any_element();
576
577 let layout = child.request_layout(window, cx);
578
579 (layout, child)
580 }
581
582 fn prepaint(
583 &mut self,
584 _: Option<&GlobalElementId>,
585 _: Option<&InspectorElementId>,
586 bounds: Bounds<Pixels>,
587 child: &mut AnyElement,
588 window: &mut Window,
589 cx: &mut App,
590 ) -> Hitbox {
591 let mut data = self.group.0.borrow_mut();
592
593 data.layout_ready = true;
594
595 let now = cx
596 .background_executor()
597 .now()
598 .saturating_duration_since(data.epoch.expect("layout epoch"));
599
600 let raw = bounds.origin - window.pixel_snap_point(window.element_offset());
601 let target = [f64::from(raw.x), f64::from(raw.y)];
602
603 let drag = data
604 .grab
605 .as_ref()
606 .filter(|grab| grab.key == self.key)
607 .map(|grab| {
608 (
609 std::array::from_fn::<_, 2, _>(|i| grab.pointer[i] - grab.offset[i]),
610 grab.axis,
611 )
612 });
613
614 let metric = data.metrics.get_mut(&self.key).expect("measured key");
615 let first = !metric.measured;
616
617 metric.measured = true;
618 metric.slot = self.slot;
619 metric.bounds = bounds;
620 let mut presented = target;
621
622 for i in 0..2 {
623 if first {
624 metric.values[i].jump(target[i], now);
625 }
626
627 if let Some((position, axis)) = drag
628 && (axis == ReorderAxis::XY
629 || (axis == ReorderAxis::X && i == 0)
630 || (axis == ReorderAxis::Y && i == 1))
631 {
632 let offset = if i == 0 {
633 f64::from(window.element_offset().x)
634 } else {
635 f64::from(window.element_offset().y)
636 };
637
638 metric.values[i].set(position[i] - offset, now);
639 } else {
640 metric.values[i].animate(target[i], self.transition, now);
641 }
642
643 let frame = metric.values[i].sample(now, cx.reduce_motion());
644
645 presented[i] = frame.value();
646
647 if frame.is_animating() {
648 window.request_animation_frame();
649 }
650 }
651
652 let offset = point(
653 px((presented[0] - target[0]) as f32),
654 px((presented[1] - target[1]) as f32),
655 );
656
657 let displayed = Bounds {
658 origin: bounds.origin + offset,
659 size: bounds.size,
660 };
661
662 metric.displayed = displayed;
663 drop(data);
664 let hitbox = window.insert_hitbox(displayed, HitboxBehavior::Normal);
665
666 window.with_element_offset(offset, |window| {
667 child.prepaint(window, cx);
668 });
669 schedule_scroll(&self.group, &self.key, window, cx);
670
671 hitbox
672 }
673
674 fn paint(
675 &mut self,
676 _: Option<&GlobalElementId>,
677 _: Option<&InspectorElementId>,
678 _: Bounds<Pixels>,
679 child: &mut AnyElement,
680 hitbox: &mut Hitbox,
681 window: &mut Window,
682 cx: &mut App,
683 ) {
684 let group = self.group.clone();
685 let key = self.key.clone();
686 let region = hitbox.clone();
687 let listener = self.listener;
688
689 window.on_mouse_event(move |event: &MouseDownEvent, phase, window, cx| {
690 if listener
691 && phase == DispatchPhase::Bubble
692 && event.button == MouseButton::Left
693 && region.is_hovered(window)
694 && group.start_drag(
695 &key,
696 [f64::from(event.position.x), f64::from(event.position.y)],
697 window,
698 cx,
699 )
700 {
701 cx.stop_propagation();
702 }
703 });
704 let group = self.group.clone();
705 let key = self.key.clone();
706
707 window.on_mouse_event(move |event: &MouseMoveEvent, phase, _, cx| {
708 if phase != DispatchPhase::Capture {
709 return;
710 }
711
712 let mut data = group.0.borrow_mut();
713
714 if data.touch || !data.grab.as_ref().is_some_and(|grab| grab.key == key) {
715 return;
716 }
717
718 if event.pressed_button != Some(MouseButton::Left) {
719 data.cancel();
720 } else {
721 data.grab.as_mut().expect("owned grab").pointer =
722 [f64::from(event.position.x), f64::from(event.position.y)];
723 data.reorder([0.0; 2]);
724 }
725
726 if let Some(owner) = data.owner {
727 cx.notify(owner);
728 }
729 });
730 let group = self.group.clone();
731 let key = self.key.clone();
732
733 window.on_mouse_event(move |event: &MouseUpEvent, phase, window, cx| {
734 if phase == DispatchPhase::Capture
735 && event.button == MouseButton::Left
736 && !group.0.borrow().touch
737 {
738 group.finish(&key, window, cx);
739 }
740 });
741 let group = self.group.clone();
742 let key = self.key.clone();
743
744 window.on_key_event(move |event: &KeyDownEvent, phase, window, cx| {
745 if phase != DispatchPhase::Capture {
746 return;
747 }
748
749 let mut data = group.0.borrow_mut();
750
751 if !data
752 .metrics
753 .get(&key)
754 .is_some_and(|metric| metric.focus.is_focused(window))
755 {
756 return;
757 }
758
759 if event.keystroke.key == "escape" {
760 data.cancel();
761
762 if let Some(owner) = data.owner {
763 cx.notify(owner);
764 }
765
766 return;
767 }
768
769 if !event.keystroke.modifiers.alt {
770 return;
771 }
772
773 let Some((from, to)) = data.keyboard(&key, event.keystroke.key.as_str()) else {
774 return;
775 };
776
777 if from == to {
778 return;
779 }
780
781 move_item(&mut data.order, from, to).expect("measured order");
782 let callback = data
783 .change
784 .clone()
785 .map(|callback| (callback, data.order.clone()));
786
787 let owner = data.owner;
788
789 drop(data);
790
791 if let Some((callback, order)) = callback {
792 callback(&order, window, cx);
793 }
794
795 if let Some(owner) = owner {
796 cx.notify(owner);
797 }
798
799 cx.stop_propagation();
800 });
801 let group = self.group.clone();
802 let key = self.key.clone();
803 let region = hitbox.clone();
804 let listener = self.listener;
805
806 window.on_mouse_event(move |event: &gpui_kit::TouchDragEvent, phase, window, cx| {
807 if phase != DispatchPhase::Bubble {
808 return;
809 }
810
811 if event.phase != gpui_kit::TouchPhase::Started {
812 let data = group.0.borrow();
813
814 if !data.touch || !data.grab.as_ref().is_some_and(|grab| grab.key == key) {
815 return;
816 }
817 }
818
819 match event.phase {
820 gpui_kit::TouchPhase::Started => {
821 if !listener || !region.is_hovered(window) {
822 return;
823 }
824
825 let mut data = group.0.borrow_mut();
826
827 if data.begin(
828 &key,
829 [f64::from(event.position.x), f64::from(event.position.y)],
830 window,
831 cx,
832 ) {
833 data.touch = true;
834 window.prevent_default();
835
836 if let Some(owner) = data.owner {
837 cx.notify(owner);
838 }
839 }
840 }
841 gpui_kit::TouchPhase::Moved => {
842 let mut data = group.0.borrow_mut();
843
844 if !data.touch || !data.grab.as_ref().is_some_and(|grab| grab.key == key) {
845 return;
846 }
847
848 data.grab.as_mut().expect("owned touch").pointer =
849 [f64::from(event.position.x), f64::from(event.position.y)];
850 data.reorder([0.0; 2]);
851
852 if let Some(owner) = data.owner {
853 cx.notify(owner);
854 }
855 }
856 gpui_kit::TouchPhase::Ended => {
857 if group.0.borrow().touch {
858 group.finish(&key, window, cx);
859 }
860 }
861 gpui_kit::TouchPhase::Cancelled => {
862 if group.0.borrow().touch {
863 group.cancel(cx);
864 }
865 }
866 }
867
868 cx.stop_propagation();
869 });
870 child.paint(window, cx);
872 }
873}
874
875fn schedule_scroll<K: Clone + Eq + Hash + 'static>(
876 group: &ReorderGroup<K>,
877 key: &K,
878 window: &mut Window,
879 cx: &App,
880) {
881 let mut data = group.0.borrow_mut();
882
883 if data.scroll_pending
884 || !data.grab.as_ref().is_some_and(|grab| grab.key == *key)
885 || data.scroll_velocity() == [0.0; 2]
886 {
887 return;
888 }
889
890 data.scroll_pending = true;
891
892 if let Some(scroll) = &mut data.scroll {
893 scroll
894 .last
895 .get_or_insert_with(|| cx.background_executor().now());
896 }
897
898 drop(data);
899 let weak = Rc::downgrade(&group.0);
900
901 window.on_next_frame(move |window, cx| {
902 let Some(group) = weak.upgrade() else {
903 return;
904 };
905
906 let mut data = group.borrow_mut();
907
908 data.scroll_pending = false;
909
910 if !window.is_window_active()
911 || data.grab.as_ref().is_some_and(|grab| {
912 data.metrics
913 .get(&grab.key)
914 .is_some_and(|metric| !metric.focus.is_focused(window))
915 })
916 {
917 data.cancel();
918
919 return;
920 }
921
922 let velocity = data.scroll_velocity();
923
924 if velocity == [0.0; 2] {
925 return;
926 }
927
928 let scroll = data.scroll.as_mut().expect("scroll policy");
929 let now = cx.background_executor().now();
930
931 let dt = now
932 .saturating_duration_since(scroll.last.replace(now).unwrap_or(now))
933 .min(Duration::from_millis(50))
934 .as_secs_f64();
935
936 let old = scroll.handle.offset();
937 let max = scroll.handle.max_offset();
938
939 let next = point(
940 px((f64::from(old.x) - velocity[0] * dt).clamp(-f64::from(max.x).abs(), 0.0) as f32),
941 px((f64::from(old.y) - velocity[1] * dt).clamp(-f64::from(max.y).abs(), 0.0) as f32),
942 );
943
944 scroll.handle.set_offset(next);
945 data.reorder([f64::from(next.x - old.x), f64::from(next.y - old.y)]);
946
947 if let Some(owner) = data.owner {
948 cx.notify(owner);
949 }
950 });
951}