Seregon/StratoSDK

StratoSDK is a framework with a declarative approach similar to Flutter/React, written and designed entirely for Rust.

Rust/27.3 KB/No license
crates/strato-ui-core/src/elements/drag/draggable.rs
StratoSDK / crates / strato-ui-core / src / elements / drag / draggable.rs
1use std::cmp::Ordering;
2use std::sync::atomic::{AtomicBool, Ordering as AtomicOrdering};
3use std::sync::Arc;
4 
5use crate::elements::DropTargetData;
6use crate::platform::Cursor;
7use crate::{
8 elements::Point, AfterLayoutContext, AppContext, Element, EventContext, LayoutContext,
9 PaintContext, SizeConstraint,
10};
11 
12use crate::{
13 event::{DispatchedEvent, Event},
14 presenter::PositionCache,
15 scene::{ClipBounds, ZIndex},
16};
17use itertools::Itertools;
18use parking_lot::Mutex;
19use pathfinder_geometry::rect::RectF;
20use pathfinder_geometry::vector::{vec2f, Vector2F};
21 
22/// The default drag threshold used when no value is explicitly set by the creator
23const DEFAULT_DRAG_THRESHOLD: f32 = 5.;
24 
25/// Opaque state container for maintaining drag across re-renders
26///
27/// Cheap to clone so that the owning View can easily create new Elements with the state
28#[derive(Clone, Default)]
29pub struct DraggableState {
30 inner: Arc<Mutex<DragState>>,
31 suppress_overlay_paint: Arc<AtomicBool>,
32}
33 
34impl DraggableState {
35 /// Determine if the current state represents an actual drag or not
36 pub fn is_dragging(&self) -> bool {
37 matches!(*self.inner.lock(), DragState::Dragging { .. })
38 }
39 
40 /// When true, the drag overlay visual will not be painted.
41 /// Used during preview capture to exclude the drag ghost from the captured frame.
42 pub fn suppress_overlay_paint(&self) -> bool {
43 self.suppress_overlay_paint.load(AtomicOrdering::Relaxed)
44 }
45 
46 /// Set whether to suppress painting the drag overlay visual.
47 pub fn set_suppress_overlay_paint(&self, suppress: bool) {
48 self.suppress_overlay_paint
49 .store(suppress, AtomicOrdering::Relaxed);
50 }
51 
52 /// Copy the actual drag state value out of the container
53 fn read(&self) -> DragState {
54 *self.inner.lock()
55 }
56 
57 /// Returns the cursor offset within the draggable element, if drag state is available.
58 pub fn cursor_offset_within_element(&self) -> Option<Vector2F> {
59 match self.read() {
60 DragState::None => None,
61 DragState::WaitingToDrag {
62 mouse_down_offset, ..
63 } => Some(-mouse_down_offset),
64 DragState::Dragging { mouse_offset, .. } => Some(-mouse_offset),
65 }
66 }
67 
68 pub fn adjust_mouse_position(&self, delta: Vector2F) {
69 let mut guard = self.inner.lock();
70 if let DragState::Dragging { mouse_position, .. } = &mut *guard {
71 *mouse_position += delta;
72 }
73 }
74 
75 pub fn set_dragging(&self, new_mouse_position: Vector2F, new_mouse_offset: Vector2F) {
76 self.store(DragState::Dragging {
77 mouse_position: new_mouse_position,
78 mouse_offset: new_mouse_offset,
79 is_on_accepted_drop_target: false,
80 });
81 }
82 
83 pub fn cancel_drag(&self) {
84 self.store(DragState::None);
85 }
86 
87 /// Update the drag state with a new value
88 fn store(&self, new_state: DragState) {
89 *self.inner.lock() = new_state;
90 }
91}
92 
93/// Internal state tracking whether or not we are dragging the element and the parameters of the
94/// drag
95#[derive(Clone, Copy, Default)]
96enum DragState {
97 /// No dragging is happening
98 #[default]
99 None,
100 /// The mouse is held down, but has not yet moved beyond the drag threshold
101 WaitingToDrag {
102 /// The position where the mouse down occurred, used to check against the threshold
103 mouse_down_position: Vector2F,
104 /// The offset from the mouse down position to the natural origin of the element
105 mouse_down_offset: Vector2F,
106 },
107 Dragging {
108 /// The most recently reported position of the mouse
109 mouse_position: Vector2F,
110 /// The offset from the mouse to the origin of the Element
111 ///
112 /// This is determined by the mouse position when dragging starts and is used during
113 /// dragging to calculate the dragged origin via `origin = mouse_position + mouse_offset`
114 mouse_offset: Vector2F,
115 /// Whether the dragged element is currently on an accepted drop target.
116 is_on_accepted_drop_target: bool,
117 },
118}
119 
120/// The axis to which a draggable is fixed, limiting it to only move in one direction
121#[derive(Clone, Copy)]
122pub enum DragAxis {
123 HorizontalOnly,
124 VerticalOnly,
125}
126 
127type BoundsCallback = Box<dyn FnMut(&PositionCache, Vector2F) -> Option<RectF>>;
128 
129/// The bounds for dragging this element.
130///
131/// This can be set to a fixed rectangle in the scene or to a callback that is used to calculate
132/// the bounds.
133enum DragBounds {
134 None,
135 Fixed(RectF),
136 Callback(BoundsCallback),
137}
138 
139impl DragBounds {
140 fn calculate(
141 &mut self,
142 position_cache: &PositionCache,
143 window_size: Vector2F,
144 ) -> Option<RectF> {
145 match self {
146 DragBounds::None => None,
147 DragBounds::Fixed(bounds) => Some(*bounds),
148 DragBounds::Callback(callback) => callback(position_cache, window_size),
149 }
150 }
151}
152 
153type Handler = Box<dyn FnMut(&mut EventContext, &AppContext, RectF)>;
154 
155/// Handler when a `Draggable` is dragged and dropped. Includes the data of a [`crate::elements::DropTarget`] if
156/// the `Draggable` was dropped on a `DropTarget`.
157type DragDropHandler =
158 Box<dyn FnMut(&mut EventContext, &AppContext, RectF, Option<&dyn DropTargetData>)>;
159 
160pub enum AcceptedByDropTarget {
161 Yes,
162 No,
163}
164 
165/// Callback that determines whether this [`Draggable`] can be dropped on a `DropTarget` that
166/// contains [`DropTargetData`].
167type AcceptedByDropTargetHandler =
168 Box<dyn Fn(&dyn DropTargetData, &AppContext) -> AcceptedByDropTarget>;
169 
170/// A container element that can be freely dragged and dropped around the screen
171///
172/// Dragging starts when the mouse is held down and dragged at least a threshold distance (default
173/// 5 pixels) away from where it started. Dragging stops when the mouse is released.
174///
175/// ## Layout and Painting
176///
177/// While dragging, the Element is still laid out using its original position in the Element tree,
178/// however it is painted at the location of the mouse. Once dragging stops, it returns to being
179/// painted in the normal tree element position (i.e. the dragged position is not maintained after
180/// dragging stops).
181///
182/// ## Limiting the Draggable Area
183///
184/// There are two complementary ways to limit the space that the Element can be dragged:
185///
186/// 1. Specifying a drag axis so that the Element can only be dragged in one direction.
187/// 2. Specifying bounds that limit the Element to only dragging within a specific rectangle.
188///
189/// ### Fixed Axis
190///
191/// To limit the Draggable to only move in a single direction, call `with_drag_axis` and pass it
192/// the appropriate direction (either `DragAxis::HorizontalOnly` or `DragAxis::VerticalOnly`). The
193/// Element will be able to freely move in the given direction, but will not move at all in the
194/// perpendicular direction.
195///
196/// ### Bounding Box
197///
198/// To specify a bounding box in which the Element is confined, call either:
199///
200/// * `with_drag_bounds` - Takes a fixed `RectF` value and uses that for the bounds.
201/// * `with_drag_bounds_callback` - Takes a callback—which accepts a `&StackContext` and the window
202/// size—that returns an `Option<RectF>` to indicate the bounds (or lack thereof).
203///
204/// In either case, if a bounding rectangle exists, the Element will be confined to only drag
205/// completely within that rectangle. If it happens that the bounding box is smaller than the
206/// Element, the top-left corner will be fixed within the bounding box and any overflow will happen
207/// to the right or below the bounds.
208///
209/// If you specify a callback for calculating the bounds, it will be called each time the Element
210/// is painted and the value will be cached for subsequent events.
211///
212/// ## Callbacks
213///
214/// There are three event callbacks that can be used to react to dragging:
215///
216/// - `on_drag_start`: Called when the `drag_threshold` is crossed and dragging begins.
217/// - `on_drag`: Called on mouse move while dragging.
218/// - `on_drop`: Called on mouse up when dragging stops. If the `Draggable` was dropped on
219/// a [`crate::elements::DropTarget`] the data of that `DropTarget` is passed as a parameter.
220///
221/// All of the callbacks receive three parameters:
222///
223/// - An `&mut EventContext`
224/// - An `&AppContext`
225/// - A `RectF` representing the current painted position and size of the Element.
226///
227/// Note: For `on_drag_start`, the `RectF` passed will be the original position of the Element when
228/// the mouse was first pressed, not the shifted position after crossing the threshold.
229///
230/// ## Child Events
231///
232/// While this element is actively being dragged, all events to the child Element will be
233/// suppressed, the drag behavior will take precedence over any other events.
234///
235///
236/// ## Drop Targets
237///
238/// `Draggable`s can optionally be dropped on [`crate::elements::DropTarget`]s. When dropped, the
239/// [`:DropTargetData`] of the `DropTarget` is included as a parameter to identify the `DropTarget`
240/// the element was dropped on.
241pub struct Draggable {
242 state: DraggableState,
243 child: Box<dyn Element>,
244 alternate_drag_element: Option<Box<dyn Element>>,
245 child_max_z_index: Option<ZIndex>,
246 unmodified_origin: Option<Vector2F>,
247 drag_threshold: f32,
248 drag_axis: Option<DragAxis>,
249 drag_bounds: DragBounds,
250 // Cache of the bounds value, used to avoid redundant calls to `DragBounds::Callback`. This is
251 // updated on every call to `paint` so that even if the Element is laid out again, the value is
252 // accurate for subsequent events.
253 bounds_cache: Option<RectF>,
254 /// If true, keeps the original element visible in its original position during drag.
255 keep_original_visible: bool,
256 
257 start_handler: Option<Handler>,
258 drag_handler: Option<DragDropHandler>,
259 is_accepted_by_drop_target_handler: Option<AcceptedByDropTargetHandler>,
260 drop_handler: Option<DragDropHandler>,
261 /// Whether to use the copy cursor while dragging on a valid drop target.
262 use_copy_cursor_when_dragging_over_drop_target: bool,
263}
264 
265impl Draggable {
266 pub fn new(state: DraggableState, child: Box<dyn Element>) -> Self {
267 Self {
268 state,
269 child,
270 alternate_drag_element: None,
271 child_max_z_index: None,
272 unmodified_origin: None,
273 drag_threshold: DEFAULT_DRAG_THRESHOLD,
274 drag_axis: None,
275 drag_bounds: DragBounds::None,
276 bounds_cache: None,
277 keep_original_visible: false,
278 start_handler: None,
279 drag_handler: None,
280 is_accepted_by_drop_target_handler: None,
281 drop_handler: None,
282 use_copy_cursor_when_dragging_over_drop_target: false,
283 }
284 }
285 
286 /// Set a custom drag threshold, in pixels.
287 pub fn with_drag_threshold(mut self, threshold: f32) -> Self {
288 self.drag_threshold = threshold;
289 self
290 }
291 
292 /// Set a custom drag axis.
293 pub fn with_drag_axis(mut self, axis: DragAxis) -> Self {
294 self.drag_axis = Some(axis);
295 self
296 }
297 
298 /// Sets an alternate element to be rendered while the drag is active
299 pub fn with_alternate_drag_element(mut self, element: Box<dyn Element>) -> Self {
300 self.alternate_drag_element = Some(element);
301 self
302 }
303 
304 /// When true, keeps the original element visible in its original position during drag,
305 /// showing both the original and the dragged copy.
306 pub fn with_keep_original_visible(mut self, keep_visible: bool) -> Self {
307 self.keep_original_visible = keep_visible;
308 self
309 }
310 
311 /// Set custom bounds to limit where the element can be dragged.
312 ///
313 /// Note: If the bounds are smaller than the element along either axis, the top-left corner
314 /// will be clamped to the minimum value of the bounds along that axis.
315 pub fn with_drag_bounds(mut self, bounds: RectF) -> Self {
316 self.drag_bounds = DragBounds::Fixed(bounds);
317 self
318 }
319 
320 /// Whether to use the copy cursor when dragging over a drop target.
321 pub fn use_copy_cursor_when_dragging_over_drop_target(mut self) -> Self {
322 self.use_copy_cursor_when_dragging_over_drop_target = true;
323 self
324 }
325 /// Set a custom bounds callback used to calculate the limits of dragging.
326 ///
327 /// The value will be calculated whenever the element is painted and cached for use in
328 /// subsequent events.
329 ///
330 /// Note: If the bounds are smaller than the element along either axis, the top-left corner
331 /// will be clamped to the minimum value of the bounds along that axis.
332 pub fn with_drag_bounds_callback<F>(mut self, callback: F) -> Self
333 where
334 F: FnMut(&PositionCache, Vector2F) -> Option<RectF> + 'static,
335 {
336 self.drag_bounds = DragBounds::Callback(Box::new(callback));
337 self
338 }
339 
340 /// Add a callback which will be called on mouse down when dragging starts.
341 pub fn on_drag_start<F>(mut self, callback: F) -> Self
342 where
343 F: FnMut(&mut EventContext, &AppContext, RectF) + 'static,
344 {
345 self.start_handler = Some(Box::new(callback));
346 self
347 }
348 
349 /// Add a callback which will be called on mouse move while dragging.
350 pub fn on_drag<F>(mut self, callback: F) -> Self
351 where
352 F: FnMut(&mut EventContext, &AppContext, RectF, Option<&dyn DropTargetData>) + 'static,
353 {
354 self.drag_handler = Some(Box::new(callback));
355 self
356 }
357 
358 /// Add a callback which will be called on mouse up when dragging ends.
359 pub fn on_drop<F>(mut self, callback: F) -> Self
360 where
361 F: FnMut(&mut EventContext, &AppContext, RectF, Option<&dyn DropTargetData>) + 'static,
362 {
363 self.drop_handler = Some(Box::new(callback));
364 self
365 }
366 
367 /// Add a callback to determine if a given DropTarget will accept this draggable
368 /// for drop or drag callbacks
369 pub fn with_accepted_by_drop_target_fn<F>(mut self, callback: F) -> Self
370 where
371 F: Fn(&dyn DropTargetData, &AppContext) -> AcceptedByDropTarget + 'static,
372 {
373 self.is_accepted_by_drop_target_handler = Some(Box::new(callback));
374 self
375 }
376 
377 /// Add a callback which will be called on mouse down when dragging starts.
378 pub fn set_on_drag_start<F>(&mut self, callback: F)
379 where
380 F: FnMut(&mut EventContext, &AppContext, RectF) + 'static,
381 {
382 self.start_handler = Some(Box::new(callback));
383 }
384 
385 /// Add a callback which will be called on mouse move while dragging.
386 pub fn set_on_drag<F>(&mut self, callback: F)
387 where
388 F: FnMut(&mut EventContext, &AppContext, RectF, Option<&dyn DropTargetData>) + 'static,
389 {
390 self.drag_handler = Some(Box::new(callback));
391 }
392 
393 /// Add a callback which will be called on mouse up when dragging ends.
394 pub fn set_on_drop<F>(&mut self, callback: F)
395 where
396 F: FnMut(&mut EventContext, &AppContext, RectF, Option<&dyn DropTargetData>) + 'static,
397 {
398 self.drop_handler = Some(Box::new(callback));
399 }
400 
401 /// Determine the drag origin based on the specified axis and any cached bounds.
402 fn drag_origin(&self, mouse_position: Vector2F, mouse_offset: Vector2F) -> Vector2F {
403 let unclamped_origin = match self.drag_axis {
404 // By default, we allow full drag in both directions, so we can use Vector addition
405 // to determine the appropriate origin.
406 None => mouse_position + mouse_offset,
407 Some(DragAxis::HorizontalOnly) => {
408 // For horizontal-only drag, we use the x value from the mouse position and keep
409 // the default y value from the laid-out element
410 let x = mouse_position.x() + mouse_offset.x();
411 let y = self.unmodified_origin.expect("origin should exist").y();
412 Vector2F::new(x, y)
413 }
414 Some(DragAxis::VerticalOnly) => {
415 // Similarly, for vertical-only drag, we use the x value from the laid-out element
416 // and the y value from the mouse
417 let x = self.unmodified_origin.expect("origin should exist").x();
418 let y = mouse_position.y() + mouse_offset.y();
419 Vector2F::new(x, y)
420 }
421 };
422 
423 match self.bounds_cache {
424 Some(bounds) => {
425 let size = self.size().expect("size should be set");
426 
427 let min_x = bounds.min_x();
428 let max_x = (bounds.max_x() - size.x()).max(min_x);
429 let x = unclamped_origin.x().clamp(min_x, max_x);
430 
431 let min_y = bounds.min_y();
432 let max_y = (bounds.max_y() - size.y()).max(min_y);
433 let y = unclamped_origin.y().clamp(min_y, max_y);
434 
435 Vector2F::new(x, y)
436 }
437 None => unclamped_origin,
438 }
439 }
440 
441 /// Returns the [`DropTargetData`] for a [`crate::elements::DropTarget`] that overlaps with
442 /// `rect`. Returns `None` if there is a no `DropTarget` at the location.
443 ///
444 /// If multiple `DropTarget`s are matched, the one with the smallest size (by area) is
445 /// returned. If the areas are the same, then we return the drop target with the closest center
446 /// to the drag position center.
447 fn compute_drop_target_data(
448 rect: RectF,
449 accepted_function: &AcceptedByDropTargetHandler,
450 ctx: &mut EventContext,
451 app: &AppContext,
452 ) -> Option<Arc<dyn DropTargetData>> {
453 ctx.drop_target_data()
454 .filter(|drop_target_position| {
455 drop_target_position.bounds().intersection(rect).is_some()
456 && match accepted_function(drop_target_position.data().as_ref(), app) {
457 AcceptedByDropTarget::Yes => true,
458 AcceptedByDropTarget::No => false,
459 }
460 })
461 .sorted_by(|position_a, position_b| {
462 if position_a.area().round() != position_b.area().round() {
463 position_a.area().cmp(&position_b.area())
464 } else {
465 let drag_center = rect.center();
466 let position_a_center_distance =
467 (drag_center - position_a.bounds().center()).length();
468 let position_b_center_distance =
469 (drag_center - position_b.bounds().center()).length();
470 if position_a_center_distance < position_b_center_distance {
471 Ordering::Less
472 } else {
473 Ordering::Greater
474 }
475 }
476 })
477 .map(|drop_target_position| drop_target_position.data().clone())
478 .next()
479 }
480 
481 /// Computes the mouse offset based on whether or not there's a specified alternate child. If there's
482 /// an alternate child, the calculated offset will be based on the ratio of the sizes to the base child
483 /// versus the alternate child.
484 ///
485 /// From the user's perspective, this ensures that the mouse position is in the same relative position
486 /// in the alternate element as where they started the drag.
487 fn compute_mouse_offset(&self, base_mouse_offset: Vector2F, child_size: Vector2F) -> Vector2F {
488 if let Some(alternate_child) = &self.alternate_drag_element {
489 let alternate_child_size = alternate_child.size().expect("size should exist");
490 
491 let size_difference_ratio = Vector2F::new(
492 alternate_child_size.x() / child_size.x(),
493 alternate_child_size.y() / child_size.y(),
494 );
495 Vector2F::new(
496 base_mouse_offset.x() * size_difference_ratio.x(),
497 base_mouse_offset.y() * size_difference_ratio.y(),
498 )
499 } else {
500 base_mouse_offset
501 }
502 }
503}
504 
505impl Element for Draggable {
506 fn layout(
507 &mut self,
508 constraint: SizeConstraint,
509 ctx: &mut LayoutContext,
510 app: &AppContext,
511 ) -> Vector2F {
512 if let Some(alternate_child) = &mut self.alternate_drag_element {
513 // For the alternate drag element, we ignore parent size contraints
514 alternate_child.layout(
515 SizeConstraint::new(vec2f(0.0, 0.0), vec2f(f32::INFINITY, f32::INFINITY)),
516 ctx,
517 app,
518 );
519 }
520 self.child.layout(constraint, ctx, app)
521 }
522 
523 fn after_layout(&mut self, ctx: &mut AfterLayoutContext, app: &AppContext) {
524 if let Some(alternate_child) = &mut self.alternate_drag_element {
525 alternate_child.after_layout(ctx, app);
526 }
527 self.child.after_layout(ctx, app)
528 }
529 
530 fn paint(&mut self, origin: Vector2F, ctx: &mut PaintContext, app: &AppContext) {
531 // We always cache the laid-out origin for the element, even if we are drawing it elsewhere
532 // for a drag. This allows us to look up the unmodified origin when calculating position
533 // for fixed-axis draggables
534 self.unmodified_origin = Some(origin);
535 
536 // Update the bounds cache based on the provided drag bounds, if necessary
537 self.bounds_cache = self
538 .drag_bounds
539 .calculate(ctx.position_cache, ctx.window_size);
540 
541 match self.state.read() {
542 DragState::None | DragState::WaitingToDrag { .. } => {
543 // If we aren't dragging or we haven't yet passed the drag threshold, we paint the
544 // element in its normal location.
545 self.child.paint(origin, ctx, app);
546 }
547 DragState::Dragging {
548 mouse_position,
549 mouse_offset,
550 ..
551 } => {
552 if self.keep_original_visible || self.state.suppress_overlay_paint() {
553 self.child.paint(origin, ctx, app);
554 }
555 // Paint the dragged element on an overlay layer so it appears
556 // above anything we drag over.
557 if !self.state.suppress_overlay_paint() {
558 ctx.scene.start_overlay_layer(ClipBounds::None);
559 let drag_origin = self.drag_origin(mouse_position, mouse_offset);
560 if let Some(alternate_child) = &mut self.alternate_drag_element {
561 alternate_child.paint(drag_origin, ctx, app);
562 } else {
563 self.child.paint(drag_origin, ctx, app);
564 }
565 ctx.scene.stop_layer();
566 }
567 }
568 }
569 // After drawing the child (and stopping the overlay layer, if appropriate), the max
570 // z-index in the scene will represent the highest point drawn by the child.
571 self.child_max_z_index = Some(ctx.scene.max_active_z_index());
572 }
573 
574 fn dispatch_event(
575 &mut self,
576 event: &DispatchedEvent,
577 ctx: &mut EventContext,
578 app: &AppContext,
579 ) -> bool {
580 let size = self.size().expect("size should exist");
581 let current_state = self.state.read();
582 
583 let handled = match current_state {
584 DragState::None | DragState::WaitingToDrag { .. } => {
585 // If we have not yet started dragging, then we always pass events to the child
586 self.child.dispatch_event(event, ctx, app)
587 }
588 DragState::Dragging { .. } => {
589 // If we are dragging, then we suppress all child events for the duration
590 false
591 }
592 };
593 
594 match event.raw_event() {
595 Event::LeftMouseDown { position, .. } => {
596 let origin = self.origin().expect("origin should exist");
597 if let Some(rect) = ctx.visible_rect(origin, size) {
598 let max_z_index = self.child_max_z_index.expect("child z index should exist");
599 // Only start dragging if the mouse is within the element and not covered by
600 // an element on a higher layer
601 if rect.contains_point(*position)
602 && !ctx.is_covered(Point::from_vec2f(*position, max_z_index))
603 {
604 let base_mouse_offset = origin.xy() - *position;
605 let mouse_down_offset = self.compute_mouse_offset(base_mouse_offset, size);
606 self.state.store(DragState::WaitingToDrag {
607 mouse_down_position: *position,
608 mouse_down_offset,
609 });
610 
611 ctx.set_cursor(Cursor::PointingHand, max_z_index);
612 return true;
613 }
614 }
615 handled
616 }
617 Event::LeftMouseUp { .. } => match current_state {
618 DragState::None => handled,
619 DragState::WaitingToDrag { .. } => {
620 self.state.store(DragState::None);
621 ctx.reset_cursor();
622 true
623 }
624 DragState::Dragging {
625 mouse_offset,
626 mouse_position,
627 ..
628 } => {
629 let origin = self.drag_origin(mouse_position, mouse_offset);
630 let rect = RectF::new(origin, size);
631 
632 self.state.store(DragState::None);
633 
634 let draggable_data = if let Some(accepted_fn) =
635 self.is_accepted_by_drop_target_handler.as_ref()
636 {
637 Self::compute_drop_target_data(rect, accepted_fn, ctx, app)
638 } else {
639 None
640 };
641 
642 if let Some(callback) = self.drop_handler.as_mut() {
643 callback(ctx, app, rect, draggable_data.as_deref());
644 }
645 
646 ctx.reset_cursor();
647 ctx.notify();
648 true
649 }
650 },
651 Event::LeftMouseDragged { position, .. } => match current_state {
652 DragState::None => handled,
653 DragState::WaitingToDrag {
654 mouse_down_position,
655 mouse_down_offset,
656 } => {
657 let drag_start_distance = (mouse_down_position - *position).length();
658 if drag_start_distance > self.drag_threshold {
659 // If the drag has moved beyond the `drag_threshold`, then we officially
660 // start the drag and fire the `on_drag_start` callback.
661 self.state.store(DragState::Dragging {
662 mouse_offset: mouse_down_offset,
663 mouse_position: *position,
664 is_on_accepted_drop_target: false,
665 });
666 
667 // Note: For the `on_drag_start` callback, we pass the position that the
668 // mouse down happened, since that is where the element was at the start
669 // of the drag.
670 let origin = self.drag_origin(mouse_down_position, mouse_down_offset);
671 let rect = RectF::new(origin, size);
672 dispatch_callback(self.start_handler.as_mut(), ctx, app, rect);
673 
674 ctx.notify();
675 true
676 } else {
677 handled
678 }
679 }
680 DragState::Dragging {
681 mouse_offset,
682 is_on_accepted_drop_target: was_on_accepted_drop_target,
683 ..
684 } => {
685 let origin = self.drag_origin(*position, mouse_offset);
686 let rect = RectF::new(origin, size);
687 
688 let draggable_data = if let Some(accepted_fn) =
689 self.is_accepted_by_drop_target_handler.as_ref()
690 {
691 Self::compute_drop_target_data(rect, accepted_fn, ctx, app)
692 } else {
693 None
694 };
695 
696 let is_on_accepted_drop_target = draggable_data.is_some();
697 
698 if self.use_copy_cursor_when_dragging_over_drop_target {
699 let max_z_index =
700 self.child_max_z_index.expect("child z index should exist");
701 match (was_on_accepted_drop_target, is_on_accepted_drop_target) {
702 (true, false) => {
703 ctx.set_cursor(Cursor::PointingHand, max_z_index);
704 }
705 (false, true) => {
706 ctx.set_cursor(Cursor::DragCopy, max_z_index);
707 }
708 _ => {}
709 }
710 }
711 
712 self.state.store(DragState::Dragging {
713 mouse_offset,
714 mouse_position: *position,
715 is_on_accepted_drop_target,
716 });
717 dispatch_drag_drop_callback(
718 self.drag_handler.as_mut(),
719 ctx,
720 app,
721 rect,
722 draggable_data.as_deref(),
723 );
724 
725 ctx.notify();
726 true
727 }
728 },
729 _ => handled,
730 }
731 }
732 
733 fn size(&self) -> Option<Vector2F> {
734 match self.state.read() {
735 DragState::None | DragState::WaitingToDrag { .. } => self.child.size(),
736 DragState::Dragging { .. } => {
737 if let Some(alternate_child) = &self.alternate_drag_element {
738 alternate_child.size()
739 } else {
740 self.child.size()
741 }
742 }
743 }
744 }
745 
746 fn origin(&self) -> Option<Point> {
747 match self.state.read() {
748 DragState::None | DragState::WaitingToDrag { .. } => self.child.origin(),
749 DragState::Dragging { .. } => {
750 if let Some(alternate_child) = &self.alternate_drag_element {
751 alternate_child.origin()
752 } else {
753 self.child.origin()
754 }
755 }
756 }
757 }
758}
759 
760fn dispatch_callback(
761 callback: Option<&mut Handler>,
762 ctx: &mut EventContext,
763 app: &AppContext,
764 rect: RectF,
765) {
766 if let Some(callback) = callback {
767 callback(ctx, app, rect);
768 }
769}
770 
771fn dispatch_drag_drop_callback(
772 callback: Option<&mut DragDropHandler>,
773 ctx: &mut EventContext,
774 app: &AppContext,
775 rect: RectF,
776 drop_target_data: Option<&dyn DropTargetData>,
777) {
778 if let Some(callback) = callback {
779 callback(ctx, app, rect, drop_target_data);
780 }
781}
782