1use crate::geometry::{Point, Rect};
10use crate::widget::WidgetId;
11use std::any::Any;
12use std::collections::HashMap;
13
14#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
16pub struct DragId(pub u64);
17
18impl DragId {
19 pub const fn new(id: u64) -> Self {
21 Self(id)
22 }
23}
24
25#[derive(Debug, Clone, PartialEq, Eq, Hash)]
27pub enum DragDataType {
28 Text,
30 Html,
32 Url,
34 File,
36 Custom(String),
38}
39
40impl DragDataType {
41 pub fn custom(name: &str) -> Self {
43 Self::Custom(name.to_string())
44 }
45}
46
47#[derive(Debug, Clone)]
49pub struct DragData {
50 pub data_type: DragDataType,
52 pub text: String,
54 pub formats: HashMap<DragDataType, String>,
56 pub payload: Option<DragPayload>,
58}
59
60impl DragData {
61 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 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 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 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 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 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 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 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#[derive(Debug, Clone)]
132pub struct DragPayload {
133 data: Box<dyn CloneableAny>,
134}
135
136impl DragPayload {
137 pub fn new<T: Any + Send + Sync + Clone + 'static>(data: T) -> Self {
139 Self {
140 data: Box::new(data),
141 }
142 }
143
144 pub fn get<T: Any + Send + Sync + Clone + 'static>(&self) -> Option<&T> {
146 self.data.as_any().downcast_ref()
147 }
148}
149
150trait 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#[derive(Debug, Clone, Copy, PartialEq, Eq)]
180pub enum DragPhase {
181 Started,
183 Dragging,
185 OverTarget,
187 Dropped,
189 Cancelled,
191}
192
193#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
195pub enum DropEffect {
196 #[default]
198 None,
199 Copy,
201 Move,
203 Link,
205}
206
207#[derive(Debug, Clone)]
209pub struct DragState {
210 pub id: DragId,
212 pub source_widget: WidgetId,
214 pub phase: DragPhase,
216 pub start_position: Point,
218 pub current_position: Point,
220 pub data: DragData,
222 pub hover_target: Option<WidgetId>,
224 pub allowed_effects: Vec<DropEffect>,
226 pub effect: DropEffect,
228}
229
230impl DragState {
231 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 pub fn offset(&self) -> Point {
248 self.current_position - self.start_position
249 }
250
251 pub fn is_active(&self) -> bool {
253 matches!(
254 self.phase,
255 DragPhase::Started | DragPhase::Dragging | DragPhase::OverTarget
256 )
257 }
258}
259
260#[derive(Debug, Clone)]
262pub struct DropTarget {
263 pub widget_id: WidgetId,
265 pub accepted_types: Vec<DragDataType>,
267 pub accepted_effects: Vec<DropEffect>,
269 pub bounds: Rect,
271 pub enabled: bool,
273}
274
275impl DropTarget {
276 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 pub fn accept_types(mut self, types: Vec<DragDataType>) -> Self {
289 self.accepted_types = types;
290 self
291 }
292
293 pub fn accept_effects(mut self, effects: Vec<DropEffect>) -> Self {
295 self.accepted_effects = effects;
296 self
297 }
298
299 pub fn accepts(&self, data: &DragData, effect: DropEffect) -> bool {
301 if !self.enabled {
302 return false;
303 }
304
305 if !self.accepted_effects.contains(&effect) {
307 return false;
308 }
309
310 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 pub fn contains_point(&self, point: Point) -> bool {
321 self.enabled && self.bounds.contains_point(&point)
322 }
323}
324
325#[derive(Debug, Clone)]
327pub struct DropResult {
328 pub success: bool,
330 pub target: WidgetId,
332 pub effect: DropEffect,
334 pub position: Point,
336}
337
338pub struct DragDropManager {
340 next_id: u64,
342 current_drag: Option<DragState>,
344 targets: HashMap<WidgetId, DropTarget>,
346 preview_offset: Point,
348 min_drag_distance: f32,
350}
351
352impl DragDropManager {
353 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 pub fn set_min_drag_distance(&mut self, distance: f32) {
366 self.min_drag_distance = distance;
367 }
368
369 pub fn set_preview_offset(&mut self, offset: Point) {
371 self.preview_offset = offset;
372 }
373
374 pub fn register_target(&mut self, target: DropTarget) {
376 self.targets.insert(target.widget_id, target);
377 }
378
379 pub fn unregister_target(&mut self, widget_id: WidgetId) {
381 self.targets.remove(&widget_id);
382 }
383
384 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 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 pub fn move_drag(&mut self, position: Point) {
409 if let Some(state) = &mut self.current_drag {
410 state.current_position = position;
411
412 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 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 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 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 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 pub fn current(&self) -> Option<&DragState> {
485 self.current_drag.as_ref()
486 }
487
488 pub fn is_dragging(&self) -> bool {
490 self.current_drag.as_ref().is_some_and(DragState::is_active)
491 }
492
493 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 pub fn target_count(&self) -> usize {
505 self.targets.len()
506 }
507
508 pub fn target_at(&self, position: Point) -> Option<&DropTarget> {
510 self.targets.values().find(|t| t.contains_point(position))
511 }
512
513 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 #[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 #[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 #[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 let data = DragData::text("test").with_payload(42i32);
605 assert!(data.payload.is_some());
606 }
607
608 #[test]
610 fn test_drag_phase() {
611 assert_eq!(DragPhase::Started, DragPhase::Started);
612 assert_ne!(DragPhase::Started, DragPhase::Dropped);
613 }
614
615 #[test]
617 fn test_drop_effect_default() {
618 assert_eq!(DropEffect::default(), DropEffect::None);
619 }
620
621 #[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 #[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 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 #[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 manager.move_drag(Point::new(52.0, 52.0));
797 assert_eq!(manager.current().unwrap().phase, DragPhase::Started);
798
799 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 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}