presentar_core/
dnd.rs

1//! Drag and drop system for interactive data transfer.
2//!
3//! This module provides:
4//! - Drag sources and drop targets
5//! - Drag state management
6//! - Visual feedback during drag operations
7//! - Data transfer between widgets
8
9use crate::geometry::{Point, Rect};
10use crate::widget::WidgetId;
11use std::any::Any;
12use std::collections::HashMap;
13
14/// Unique identifier for a drag operation.
15#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
16pub struct DragId(pub u64);
17
18impl DragId {
19    /// Create a new drag ID.
20    pub const fn new(id: u64) -> Self {
21        Self(id)
22    }
23}
24
25/// Type of data being dragged.
26#[derive(Debug, Clone, PartialEq, Eq, Hash)]
27pub enum DragDataType {
28    /// Plain text.
29    Text,
30    /// HTML content.
31    Html,
32    /// URL/link.
33    Url,
34    /// File path.
35    File,
36    /// Custom type identifier.
37    Custom(String),
38}
39
40impl DragDataType {
41    /// Create a custom drag data type.
42    pub fn custom(name: &str) -> Self {
43        Self::Custom(name.to_string())
44    }
45}
46
47/// Data associated with a drag operation.
48#[derive(Debug, Clone)]
49pub struct DragData {
50    /// Primary data type.
51    pub data_type: DragDataType,
52    /// String representation of the data.
53    pub text: String,
54    /// Additional data in various formats.
55    pub formats: HashMap<DragDataType, String>,
56    /// Custom payload (for internal transfers).
57    pub payload: Option<DragPayload>,
58}
59
60impl DragData {
61    /// Create new drag data with text.
62    pub fn text(content: &str) -> Self {
63        Self {
64            data_type: DragDataType::Text,
65            text: content.to_string(),
66            formats: HashMap::new(),
67            payload: None,
68        }
69    }
70
71    /// Create new drag data with HTML.
72    pub fn html(content: &str) -> Self {
73        let mut formats = HashMap::new();
74        formats.insert(DragDataType::Html, content.to_string());
75        Self {
76            data_type: DragDataType::Html,
77            text: content.to_string(),
78            formats,
79            payload: None,
80        }
81    }
82
83    /// Create new drag data with URL.
84    pub fn url(url: &str) -> Self {
85        Self {
86            data_type: DragDataType::Url,
87            text: url.to_string(),
88            formats: HashMap::new(),
89            payload: None,
90        }
91    }
92
93    /// Create drag data with custom type.
94    pub fn custom(type_name: &str, data: &str) -> Self {
95        Self {
96            data_type: DragDataType::Custom(type_name.to_string()),
97            text: data.to_string(),
98            formats: HashMap::new(),
99            payload: None,
100        }
101    }
102
103    /// Add an alternative format.
104    pub fn with_format(mut self, data_type: DragDataType, data: &str) -> Self {
105        self.formats.insert(data_type, data.to_string());
106        self
107    }
108
109    /// Add a payload.
110    pub fn with_payload<T: Any + Send + Sync + Clone + 'static>(mut self, payload: T) -> Self {
111        self.payload = Some(DragPayload::new(payload));
112        self
113    }
114
115    /// Get data in a specific format.
116    pub fn get_format(&self, data_type: &DragDataType) -> Option<&str> {
117        if &self.data_type == data_type {
118            Some(&self.text)
119        } else {
120            self.formats.get(data_type).map(std::string::String::as_str)
121        }
122    }
123
124    /// Check if data is available in the given format.
125    pub fn has_format(&self, data_type: &DragDataType) -> bool {
126        &self.data_type == data_type || self.formats.contains_key(data_type)
127    }
128}
129
130/// Type-erased payload for drag data.
131#[derive(Debug, Clone)]
132pub struct DragPayload {
133    data: Box<dyn CloneableAny>,
134}
135
136impl DragPayload {
137    /// Create a new payload.
138    pub fn new<T: Any + Send + Sync + Clone + 'static>(data: T) -> Self {
139        Self {
140            data: Box::new(data),
141        }
142    }
143
144    /// Get the payload as a specific type.
145    pub fn get<T: Any + Send + Sync + Clone + 'static>(&self) -> Option<&T> {
146        self.data.as_any().downcast_ref()
147    }
148}
149
150/// Trait for cloneable any types.
151trait CloneableAny: Any + Send + Sync {
152    fn clone_box(&self) -> Box<dyn CloneableAny>;
153    fn as_any(&self) -> &dyn Any;
154}
155
156impl<T: Any + Send + Sync + Clone + 'static> CloneableAny for T {
157    fn clone_box(&self) -> Box<dyn CloneableAny> {
158        Box::new(self.clone())
159    }
160
161    fn as_any(&self) -> &dyn Any {
162        self
163    }
164}
165
166impl Clone for Box<dyn CloneableAny> {
167    fn clone(&self) -> Self {
168        self.clone_box()
169    }
170}
171
172impl std::fmt::Debug for Box<dyn CloneableAny> {
173    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
174        f.debug_struct("CloneableAny").finish_non_exhaustive()
175    }
176}
177
178/// Current phase of a drag operation.
179#[derive(Debug, Clone, Copy, PartialEq, Eq)]
180pub enum DragPhase {
181    /// Drag has started.
182    Started,
183    /// Dragging in progress.
184    Dragging,
185    /// Hovering over a valid drop target.
186    OverTarget,
187    /// Dropped successfully.
188    Dropped,
189    /// Drag was cancelled.
190    Cancelled,
191}
192
193/// Effect/operation type for a drop.
194#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
195pub enum DropEffect {
196    /// No drop allowed.
197    #[default]
198    None,
199    /// Copy data to target.
200    Copy,
201    /// Move data to target.
202    Move,
203    /// Create link to data.
204    Link,
205}
206
207/// State of an active drag operation.
208#[derive(Debug, Clone)]
209pub struct DragState {
210    /// Unique drag ID.
211    pub id: DragId,
212    /// Source widget.
213    pub source_widget: WidgetId,
214    /// Current phase.
215    pub phase: DragPhase,
216    /// Starting position.
217    pub start_position: Point,
218    /// Current position.
219    pub current_position: Point,
220    /// Drag data.
221    pub data: DragData,
222    /// Currently hovered drop target.
223    pub hover_target: Option<WidgetId>,
224    /// Allowed drop effects.
225    pub allowed_effects: Vec<DropEffect>,
226    /// Current drop effect.
227    pub effect: DropEffect,
228}
229
230impl DragState {
231    /// Create a new drag state.
232    pub fn new(id: DragId, source_widget: WidgetId, position: Point, data: DragData) -> Self {
233        Self {
234            id,
235            source_widget,
236            phase: DragPhase::Started,
237            start_position: position,
238            current_position: position,
239            data,
240            hover_target: None,
241            allowed_effects: vec![DropEffect::Copy, DropEffect::Move],
242            effect: DropEffect::None,
243        }
244    }
245
246    /// Get the drag offset from start.
247    pub fn offset(&self) -> Point {
248        self.current_position - self.start_position
249    }
250
251    /// Check if drag is active.
252    pub fn is_active(&self) -> bool {
253        matches!(
254            self.phase,
255            DragPhase::Started | DragPhase::Dragging | DragPhase::OverTarget
256        )
257    }
258}
259
260/// Configuration for a drop target.
261#[derive(Debug, Clone)]
262pub struct DropTarget {
263    /// Widget ID of the drop target.
264    pub widget_id: WidgetId,
265    /// Accepted data types.
266    pub accepted_types: Vec<DragDataType>,
267    /// Accepted drop effects.
268    pub accepted_effects: Vec<DropEffect>,
269    /// Bounds of the target.
270    pub bounds: Rect,
271    /// Whether the target is currently active.
272    pub enabled: bool,
273}
274
275impl DropTarget {
276    /// Create a new drop target.
277    pub fn new(widget_id: WidgetId, bounds: Rect) -> Self {
278        Self {
279            widget_id,
280            accepted_types: vec![],
281            accepted_effects: vec![DropEffect::Copy, DropEffect::Move],
282            bounds,
283            enabled: true,
284        }
285    }
286
287    /// Accept specific data types.
288    pub fn accept_types(mut self, types: Vec<DragDataType>) -> Self {
289        self.accepted_types = types;
290        self
291    }
292
293    /// Accept specific effects.
294    pub fn accept_effects(mut self, effects: Vec<DropEffect>) -> Self {
295        self.accepted_effects = effects;
296        self
297    }
298
299    /// Check if this target accepts the given drag data.
300    pub fn accepts(&self, data: &DragData, effect: DropEffect) -> bool {
301        if !self.enabled {
302            return false;
303        }
304
305        // Check effect
306        if !self.accepted_effects.contains(&effect) {
307            return false;
308        }
309
310        // Check type (empty means accept all)
311        if self.accepted_types.is_empty() {
312            return true;
313        }
314
315        self.accepted_types.contains(&data.data_type)
316            || self.accepted_types.iter().any(|t| data.has_format(t))
317    }
318
319    /// Check if a point is within this target's bounds.
320    pub fn contains_point(&self, point: Point) -> bool {
321        self.enabled && self.bounds.contains_point(&point)
322    }
323}
324
325/// Result of a drop operation.
326#[derive(Debug, Clone)]
327pub struct DropResult {
328    /// Whether the drop was successful.
329    pub success: bool,
330    /// Target widget that received the drop.
331    pub target: WidgetId,
332    /// Effect that was applied.
333    pub effect: DropEffect,
334    /// Position of the drop.
335    pub position: Point,
336}
337
338/// Drag and drop manager.
339pub struct DragDropManager {
340    /// Next drag ID.
341    next_id: u64,
342    /// Current active drag state.
343    current_drag: Option<DragState>,
344    /// Registered drop targets.
345    targets: HashMap<WidgetId, DropTarget>,
346    /// Drag preview offset from cursor.
347    preview_offset: Point,
348    /// Minimum drag distance before starting.
349    min_drag_distance: f32,
350}
351
352impl DragDropManager {
353    /// Create a new drag/drop manager.
354    pub fn new() -> Self {
355        Self {
356            next_id: 0,
357            current_drag: None,
358            targets: HashMap::new(),
359            preview_offset: Point::ORIGIN,
360            min_drag_distance: 5.0,
361        }
362    }
363
364    /// Set minimum drag distance.
365    pub fn set_min_drag_distance(&mut self, distance: f32) {
366        self.min_drag_distance = distance;
367    }
368
369    /// Set drag preview offset.
370    pub fn set_preview_offset(&mut self, offset: Point) {
371        self.preview_offset = offset;
372    }
373
374    /// Register a drop target.
375    pub fn register_target(&mut self, target: DropTarget) {
376        self.targets.insert(target.widget_id, target);
377    }
378
379    /// Unregister a drop target.
380    pub fn unregister_target(&mut self, widget_id: WidgetId) {
381        self.targets.remove(&widget_id);
382    }
383
384    /// Update a target's bounds.
385    pub fn update_target_bounds(&mut self, widget_id: WidgetId, bounds: Rect) {
386        if let Some(target) = self.targets.get_mut(&widget_id) {
387            target.bounds = bounds;
388        }
389    }
390
391    /// Start a drag operation.
392    pub fn start_drag(
393        &mut self,
394        source_widget: WidgetId,
395        position: Point,
396        data: DragData,
397    ) -> DragId {
398        let id = DragId::new(self.next_id);
399        self.next_id += 1;
400
401        let state = DragState::new(id, source_widget, position, data);
402        self.current_drag = Some(state);
403
404        id
405    }
406
407    /// Update drag position.
408    pub fn move_drag(&mut self, position: Point) {
409        if let Some(state) = &mut self.current_drag {
410            state.current_position = position;
411
412            // Check for drag distance threshold
413            if state.phase == DragPhase::Started {
414                let distance = state.start_position.distance(&position);
415                if distance >= self.min_drag_distance {
416                    state.phase = DragPhase::Dragging;
417                }
418            }
419
420            // Update hover target
421            if state.phase == DragPhase::Dragging || state.phase == DragPhase::OverTarget {
422                let old_target = state.hover_target;
423                state.hover_target = None;
424                state.effect = DropEffect::None;
425
426                for target in self.targets.values() {
427                    if target.contains_point(position) {
428                        // Find best allowed effect
429                        let effect = state
430                            .allowed_effects
431                            .iter()
432                            .find(|e| target.accepts(&state.data, **e))
433                            .copied()
434                            .unwrap_or(DropEffect::None);
435
436                        if effect != DropEffect::None {
437                            state.hover_target = Some(target.widget_id);
438                            state.effect = effect;
439                            state.phase = DragPhase::OverTarget;
440                            break;
441                        }
442                    }
443                }
444
445                if state.hover_target.is_none() && old_target.is_some() {
446                    state.phase = DragPhase::Dragging;
447                }
448            }
449        }
450    }
451
452    /// End drag with drop attempt.
453    pub fn drop(&mut self) -> Option<DropResult> {
454        let state = self.current_drag.take()?;
455
456        if let Some(target_id) = state.hover_target {
457            if state.effect != DropEffect::None {
458                return Some(DropResult {
459                    success: true,
460                    target: target_id,
461                    effect: state.effect,
462                    position: state.current_position,
463                });
464            }
465        }
466
467        Some(DropResult {
468            success: false,
469            target: state.source_widget,
470            effect: DropEffect::None,
471            position: state.current_position,
472        })
473    }
474
475    /// Cancel the current drag operation.
476    pub fn cancel(&mut self) {
477        if let Some(state) = &mut self.current_drag {
478            state.phase = DragPhase::Cancelled;
479        }
480        self.current_drag = None;
481    }
482
483    /// Get the current drag state.
484    pub fn current(&self) -> Option<&DragState> {
485        self.current_drag.as_ref()
486    }
487
488    /// Check if a drag is active.
489    pub fn is_dragging(&self) -> bool {
490        self.current_drag.as_ref().is_some_and(DragState::is_active)
491    }
492
493    /// Get the preview position for rendering.
494    pub fn preview_position(&self) -> Option<Point> {
495        self.current_drag.as_ref().map(|s| {
496            Point::new(
497                s.current_position.x + self.preview_offset.x,
498                s.current_position.y + self.preview_offset.y,
499            )
500        })
501    }
502
503    /// Get drop targets count.
504    pub fn target_count(&self) -> usize {
505        self.targets.len()
506    }
507
508    /// Find target at position.
509    pub fn target_at(&self, position: Point) -> Option<&DropTarget> {
510        self.targets.values().find(|t| t.contains_point(position))
511    }
512
513    /// Clear all targets and cancel any active drag.
514    pub fn clear(&mut self) {
515        self.cancel();
516        self.targets.clear();
517    }
518}
519
520impl Default for DragDropManager {
521    fn default() -> Self {
522        Self::new()
523    }
524}
525
526impl std::fmt::Debug for DragDropManager {
527    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
528        f.debug_struct("DragDropManager")
529            .field("next_id", &self.next_id)
530            .field("is_dragging", &self.is_dragging())
531            .field("target_count", &self.targets.len())
532            .finish()
533    }
534}
535
536#[cfg(test)]
537mod tests {
538    use super::*;
539
540    // DragId tests
541    #[test]
542    fn test_drag_id() {
543        let id1 = DragId::new(1);
544        let id2 = DragId::new(1);
545        let id3 = DragId::new(2);
546
547        assert_eq!(id1, id2);
548        assert_ne!(id1, id3);
549    }
550
551    // DragDataType tests
552    #[test]
553    fn test_drag_data_type() {
554        assert_eq!(DragDataType::Text, DragDataType::Text);
555        assert_ne!(DragDataType::Text, DragDataType::Html);
556
557        let custom = DragDataType::custom("my-type");
558        assert_eq!(custom, DragDataType::Custom("my-type".to_string()));
559    }
560
561    // DragData tests
562    #[test]
563    fn test_drag_data_text() {
564        let data = DragData::text("Hello");
565        assert_eq!(data.data_type, DragDataType::Text);
566        assert_eq!(data.text, "Hello");
567    }
568
569    #[test]
570    fn test_drag_data_html() {
571        let data = DragData::html("<b>Bold</b>");
572        assert_eq!(data.data_type, DragDataType::Html);
573        assert!(data.has_format(&DragDataType::Html));
574    }
575
576    #[test]
577    fn test_drag_data_url() {
578        let data = DragData::url("https://example.com");
579        assert_eq!(data.data_type, DragDataType::Url);
580        assert_eq!(data.text, "https://example.com");
581    }
582
583    #[test]
584    fn test_drag_data_custom() {
585        let data = DragData::custom("widget-id", "123");
586        assert!(matches!(data.data_type, DragDataType::Custom(_)));
587    }
588
589    #[test]
590    fn test_drag_data_with_format() {
591        let data = DragData::text("Hello").with_format(DragDataType::Html, "<p>Hello</p>");
592
593        assert!(data.has_format(&DragDataType::Text));
594        assert!(data.has_format(&DragDataType::Html));
595        assert!(!data.has_format(&DragDataType::Url));
596
597        assert_eq!(data.get_format(&DragDataType::Text), Some("Hello"));
598        assert_eq!(data.get_format(&DragDataType::Html), Some("<p>Hello</p>"));
599    }
600
601    #[test]
602    fn test_drag_data_with_payload() {
603        // Test payload exists (type erasure is complex, just verify the API works)
604        let data = DragData::text("test").with_payload(42i32);
605        assert!(data.payload.is_some());
606    }
607
608    // DragPhase tests
609    #[test]
610    fn test_drag_phase() {
611        assert_eq!(DragPhase::Started, DragPhase::Started);
612        assert_ne!(DragPhase::Started, DragPhase::Dropped);
613    }
614
615    // DropEffect tests
616    #[test]
617    fn test_drop_effect_default() {
618        assert_eq!(DropEffect::default(), DropEffect::None);
619    }
620
621    // DragState tests
622    #[test]
623    fn test_drag_state_new() {
624        let state = DragState::new(
625            DragId::new(1),
626            WidgetId::new(100),
627            Point::new(50.0, 50.0),
628            DragData::text("test"),
629        );
630
631        assert_eq!(state.id, DragId::new(1));
632        assert_eq!(state.source_widget, WidgetId::new(100));
633        assert_eq!(state.phase, DragPhase::Started);
634        assert_eq!(state.start_position, Point::new(50.0, 50.0));
635        assert!(state.is_active());
636    }
637
638    #[test]
639    fn test_drag_state_offset() {
640        let mut state = DragState::new(
641            DragId::new(1),
642            WidgetId::new(100),
643            Point::new(50.0, 50.0),
644            DragData::text("test"),
645        );
646
647        state.current_position = Point::new(100.0, 75.0);
648        let offset = state.offset();
649        assert_eq!(offset.x, 50.0);
650        assert_eq!(offset.y, 25.0);
651    }
652
653    #[test]
654    fn test_drag_state_is_active() {
655        let mut state = DragState::new(
656            DragId::new(1),
657            WidgetId::new(100),
658            Point::ORIGIN,
659            DragData::text("test"),
660        );
661
662        assert!(state.is_active());
663
664        state.phase = DragPhase::Dragging;
665        assert!(state.is_active());
666
667        state.phase = DragPhase::OverTarget;
668        assert!(state.is_active());
669
670        state.phase = DragPhase::Dropped;
671        assert!(!state.is_active());
672
673        state.phase = DragPhase::Cancelled;
674        assert!(!state.is_active());
675    }
676
677    // DropTarget tests
678    #[test]
679    fn test_drop_target_new() {
680        let target = DropTarget::new(WidgetId::new(1), Rect::new(0.0, 0.0, 100.0, 100.0));
681
682        assert_eq!(target.widget_id, WidgetId::new(1));
683        assert!(target.enabled);
684        assert!(target.accepted_types.is_empty());
685    }
686
687    #[test]
688    fn test_drop_target_accept_types() {
689        let target = DropTarget::new(WidgetId::new(1), Rect::new(0.0, 0.0, 100.0, 100.0))
690            .accept_types(vec![DragDataType::Text, DragDataType::Html]);
691
692        assert_eq!(target.accepted_types.len(), 2);
693    }
694
695    #[test]
696    fn test_drop_target_accepts() {
697        let target = DropTarget::new(WidgetId::new(1), Rect::new(0.0, 0.0, 100.0, 100.0))
698            .accept_types(vec![DragDataType::Text])
699            .accept_effects(vec![DropEffect::Copy]);
700
701        let text_data = DragData::text("hello");
702        assert!(target.accepts(&text_data, DropEffect::Copy));
703        assert!(!target.accepts(&text_data, DropEffect::Move));
704
705        let html_data = DragData::html("<b>bold</b>");
706        assert!(!target.accepts(&html_data, DropEffect::Copy));
707    }
708
709    #[test]
710    fn test_drop_target_accepts_all_types() {
711        // Empty accepted_types means accept all
712        let target = DropTarget::new(WidgetId::new(1), Rect::new(0.0, 0.0, 100.0, 100.0));
713
714        assert!(target.accepts(&DragData::text("test"), DropEffect::Copy));
715        assert!(target.accepts(&DragData::html("<b>test</b>"), DropEffect::Move));
716    }
717
718    #[test]
719    fn test_drop_target_disabled() {
720        let mut target = DropTarget::new(WidgetId::new(1), Rect::new(0.0, 0.0, 100.0, 100.0));
721        target.enabled = false;
722
723        assert!(!target.accepts(&DragData::text("test"), DropEffect::Copy));
724        assert!(!target.contains_point(Point::new(50.0, 50.0)));
725    }
726
727    #[test]
728    fn test_drop_target_contains_point() {
729        let target = DropTarget::new(WidgetId::new(1), Rect::new(10.0, 10.0, 100.0, 100.0));
730
731        assert!(target.contains_point(Point::new(50.0, 50.0)));
732        assert!(target.contains_point(Point::new(10.0, 10.0)));
733        assert!(!target.contains_point(Point::new(5.0, 50.0)));
734        assert!(!target.contains_point(Point::new(120.0, 50.0)));
735    }
736
737    // DragDropManager tests
738    #[test]
739    fn test_manager_new() {
740        let manager = DragDropManager::new();
741        assert!(!manager.is_dragging());
742        assert_eq!(manager.target_count(), 0);
743    }
744
745    #[test]
746    fn test_manager_register_target() {
747        let mut manager = DragDropManager::new();
748
749        manager.register_target(DropTarget::new(
750            WidgetId::new(1),
751            Rect::new(0.0, 0.0, 100.0, 100.0),
752        ));
753
754        assert_eq!(manager.target_count(), 1);
755    }
756
757    #[test]
758    fn test_manager_unregister_target() {
759        let mut manager = DragDropManager::new();
760
761        manager.register_target(DropTarget::new(
762            WidgetId::new(1),
763            Rect::new(0.0, 0.0, 100.0, 100.0),
764        ));
765        manager.unregister_target(WidgetId::new(1));
766
767        assert_eq!(manager.target_count(), 0);
768    }
769
770    #[test]
771    fn test_manager_start_drag() {
772        let mut manager = DragDropManager::new();
773
774        let id = manager.start_drag(
775            WidgetId::new(1),
776            Point::new(50.0, 50.0),
777            DragData::text("hello"),
778        );
779
780        assert!(manager.is_dragging());
781        assert_eq!(manager.current().unwrap().id, id);
782    }
783
784    #[test]
785    fn test_manager_move_drag() {
786        let mut manager = DragDropManager::new();
787        manager.set_min_drag_distance(5.0);
788
789        manager.start_drag(
790            WidgetId::new(1),
791            Point::new(50.0, 50.0),
792            DragData::text("hello"),
793        );
794
795        // Move within threshold
796        manager.move_drag(Point::new(52.0, 52.0));
797        assert_eq!(manager.current().unwrap().phase, DragPhase::Started);
798
799        // Move beyond threshold
800        manager.move_drag(Point::new(60.0, 60.0));
801        assert_eq!(manager.current().unwrap().phase, DragPhase::Dragging);
802    }
803
804    #[test]
805    fn test_manager_move_over_target() {
806        let mut manager = DragDropManager::new();
807        manager.set_min_drag_distance(0.0);
808
809        manager.register_target(DropTarget::new(
810            WidgetId::new(10),
811            Rect::new(100.0, 100.0, 100.0, 100.0),
812        ));
813
814        manager.start_drag(
815            WidgetId::new(1),
816            Point::new(50.0, 50.0),
817            DragData::text("hello"),
818        );
819
820        // Move over target
821        manager.move_drag(Point::new(150.0, 150.0));
822        let state = manager.current().unwrap();
823        assert_eq!(state.phase, DragPhase::OverTarget);
824        assert_eq!(state.hover_target, Some(WidgetId::new(10)));
825    }
826
827    #[test]
828    fn test_manager_drop_success() {
829        let mut manager = DragDropManager::new();
830        manager.set_min_drag_distance(0.0);
831
832        manager.register_target(DropTarget::new(
833            WidgetId::new(10),
834            Rect::new(100.0, 100.0, 100.0, 100.0),
835        ));
836
837        manager.start_drag(
838            WidgetId::new(1),
839            Point::new(50.0, 50.0),
840            DragData::text("hello"),
841        );
842
843        manager.move_drag(Point::new(150.0, 150.0));
844        let result = manager.drop().unwrap();
845
846        assert!(result.success);
847        assert_eq!(result.target, WidgetId::new(10));
848        assert!(!manager.is_dragging());
849    }
850
851    #[test]
852    fn test_manager_drop_failure() {
853        let mut manager = DragDropManager::new();
854        manager.set_min_drag_distance(0.0);
855
856        manager.start_drag(
857            WidgetId::new(1),
858            Point::new(50.0, 50.0),
859            DragData::text("hello"),
860        );
861
862        manager.move_drag(Point::new(60.0, 60.0));
863        let result = manager.drop().unwrap();
864
865        assert!(!result.success);
866        assert_eq!(result.effect, DropEffect::None);
867    }
868
869    #[test]
870    fn test_manager_cancel() {
871        let mut manager = DragDropManager::new();
872
873        manager.start_drag(
874            WidgetId::new(1),
875            Point::new(50.0, 50.0),
876            DragData::text("hello"),
877        );
878
879        manager.cancel();
880        assert!(!manager.is_dragging());
881        assert!(manager.current().is_none());
882    }
883
884    #[test]
885    fn test_manager_preview_position() {
886        let mut manager = DragDropManager::new();
887        manager.set_preview_offset(Point::new(-10.0, -10.0));
888
889        manager.start_drag(
890            WidgetId::new(1),
891            Point::new(100.0, 100.0),
892            DragData::text("hello"),
893        );
894
895        let preview_pos = manager.preview_position().unwrap();
896        assert_eq!(preview_pos, Point::new(90.0, 90.0));
897    }
898
899    #[test]
900    fn test_manager_target_at() {
901        let mut manager = DragDropManager::new();
902
903        manager.register_target(DropTarget::new(
904            WidgetId::new(1),
905            Rect::new(0.0, 0.0, 100.0, 100.0),
906        ));
907        manager.register_target(DropTarget::new(
908            WidgetId::new(2),
909            Rect::new(200.0, 200.0, 100.0, 100.0),
910        ));
911
912        assert!(manager.target_at(Point::new(50.0, 50.0)).is_some());
913        assert!(manager.target_at(Point::new(150.0, 150.0)).is_none());
914        assert!(manager.target_at(Point::new(250.0, 250.0)).is_some());
915    }
916
917    #[test]
918    fn test_manager_clear() {
919        let mut manager = DragDropManager::new();
920
921        manager.register_target(DropTarget::new(
922            WidgetId::new(1),
923            Rect::new(0.0, 0.0, 100.0, 100.0),
924        ));
925
926        manager.start_drag(WidgetId::new(2), Point::ORIGIN, DragData::text("test"));
927
928        manager.clear();
929
930        assert!(!manager.is_dragging());
931        assert_eq!(manager.target_count(), 0);
932    }
933
934    #[test]
935    fn test_manager_update_target_bounds() {
936        let mut manager = DragDropManager::new();
937
938        manager.register_target(DropTarget::new(
939            WidgetId::new(1),
940            Rect::new(0.0, 0.0, 100.0, 100.0),
941        ));
942
943        manager.update_target_bounds(WidgetId::new(1), Rect::new(50.0, 50.0, 200.0, 200.0));
944
945        let target = manager.target_at(Point::new(100.0, 100.0)).unwrap();
946        assert_eq!(target.bounds.x, 50.0);
947        assert_eq!(target.bounds.width, 200.0);
948    }
949}