1use crate::{Element, Renderer};
5use iced::{Alignment, Pixels, alignment};
6use iced_core::event::Event;
7use iced_core::layout::{self, Layout};
8use iced_core::mouse::{self, Cursor};
9use iced_core::widget::Operation;
10use iced_core::widget::tree::{self, Tree};
11#[cfg(feature = "wgpu")]
12use iced_core::{Background, Border, Shadow};
13use iced_core::{
14 Clipboard, Length, Padding, Point, Rectangle, Renderer as _, Shell, Size, Vector, Widget,
15 overlay, renderer, window,
16};
17use std::collections::{HashMap, HashSet};
18use std::hash::Hash;
19use std::time::{Duration, Instant};
20
21const DEFAULT_ANIMATION_DURATION: Duration = Duration::from_millis(180);
22const DEFAULT_DRAG_LIFT: f32 = 10.0;
23const DEFAULT_DRAG_THRESHOLD: f32 = 6.0;
24const SHADOW_BLUR_RADIUS: f32 = 20.0;
25const POSITION_EPSILON: f32 = 0.5;
26
27#[derive(Debug, Clone)]
28struct SlotAnimation {
29 from: Point,
30 to: Point,
31 started_at: Option<Instant>,
32}
33
34impl SlotAnimation {
35 fn new(position: Point) -> Self {
36 Self {
37 from: position,
38 to: position,
39 started_at: None,
40 }
41 }
42
43 fn current_position(&self, duration: Duration) -> Point {
44 let Some(started_at) = self.started_at else {
45 return self.to;
46 };
47
48 let duration_secs = duration.as_secs_f32();
49 if duration_secs <= f32::EPSILON {
50 return self.to;
51 }
52
53 let progress = (started_at.elapsed().as_secs_f32() / duration_secs).clamp(0.0, 1.0);
54 let eased = 1.0 - (1.0 - progress).powi(3);
55
56 Point::new(
57 self.from.x + (self.to.x - self.from.x) * eased,
58 self.from.y + (self.to.y - self.from.y) * eased,
59 )
60 }
61
62 fn retarget(&mut self, new_target: Point, duration: Duration) {
63 if approx_eq_point(self.to, new_target) {
64 if !self.is_animating(duration) {
65 self.from = new_target;
66 self.to = new_target;
67 self.started_at = None;
68 }
69 return;
70 }
71
72 self.from = self.current_position(duration);
73 self.to = new_target;
74 self.started_at = Some(Instant::now());
75 }
76
77 fn is_animating(&self, duration: Duration) -> bool {
78 self.started_at
79 .is_some_and(|started_at| started_at.elapsed() < duration)
80 }
81
82 fn finish_if_done(&mut self, duration: Duration) {
83 if self
84 .started_at
85 .is_some_and(|started_at| started_at.elapsed() >= duration)
86 {
87 self.from = self.to;
88 self.started_at = None;
89 }
90 }
91}
92
93#[derive(Debug, Clone)]
94struct PendingDragState<Key>
95where
96 Key: Clone + Eq + Hash + 'static,
97{
98 key: Key,
99 item_index: usize,
100 original_index: usize,
101 press_local: Point,
102 pointer_offset: Vector,
103}
104
105#[derive(Debug, Clone)]
106struct DragState<Key>
107where
108 Key: Clone + Eq + Hash + 'static,
109{
110 key: Key,
111 item_index: usize,
112 original_index: usize,
113 current_index: usize,
114 cursor_local: Point,
115 pointer_offset: Vector,
116}
117
118#[derive(Debug, Clone)]
119struct State<Key>
120where
121 Key: Clone + Eq + Hash + 'static,
122{
123 keys: Vec<Key>,
124 slot_positions: HashMap<Key, SlotAnimation>,
125 pending_drag: Option<PendingDragState<Key>>,
126 drag: Option<DragState<Key>>,
127 wrap_width: f32,
128 initialized: bool,
129}
130
131impl<Key> Default for State<Key>
132where
133 Key: Clone + Eq + Hash + 'static,
134{
135 fn default() -> Self {
136 Self {
137 keys: Vec::new(),
138 slot_positions: HashMap::new(),
139 pending_drag: None,
140 drag: None,
141 wrap_width: f32::INFINITY,
142 initialized: false,
143 }
144 }
145}
146
147impl<Key> State<Key>
148where
149 Key: Clone + Eq + Hash + 'static,
150{
151 fn retain_keys(&mut self, keys: &[Key]) {
152 let keep: HashSet<_> = keys.iter().cloned().collect();
153 self.slot_positions.retain(|key, _| keep.contains(key));
154
155 if self
156 .pending_drag
157 .as_ref()
158 .is_some_and(|drag| !keep.contains(&drag.key))
159 {
160 self.pending_drag = None;
161 }
162
163 if self
164 .drag
165 .as_ref()
166 .is_some_and(|drag| !keep.contains(&drag.key))
167 {
168 self.drag = None;
169 }
170 }
171
172 fn finish_animations(&mut self, duration: Duration) {
173 self.slot_positions
174 .values_mut()
175 .for_each(|slot| slot.finish_if_done(duration));
176 }
177
178 fn is_animating(&self, duration: Duration) -> bool {
179 self.slot_positions
180 .values()
181 .any(|slot| slot.is_animating(duration))
182 }
183
184 fn current_slot_position(&self, key: &Key, fallback: Point, duration: Duration) -> Point {
185 self.slot_positions
186 .get(key)
187 .map(|slot| slot.current_position(duration))
188 .unwrap_or(fallback)
189 }
190
191 fn retarget_slot(&mut self, key: &Key, target: Point, duration: Duration) {
192 self.slot_positions
193 .entry(key.clone())
194 .or_insert_with(|| SlotAnimation::new(target))
195 .retarget(target, duration);
196 }
197
198 fn snap_slot(&mut self, key: &Key, target: Point) {
199 self.slot_positions
200 .insert(key.clone(), SlotAnimation::new(target));
201 }
202
203 fn apply_layout_position(&mut self, key: &Key, target: Point, duration: Duration) {
204 if self.initialized {
205 self.retarget_slot(key, target, duration);
206 } else {
207 self.snap_slot(key, target);
208 }
209 }
210}
211
212#[derive(Debug, Clone)]
213struct LocalSlot<Key>
214where
215 Key: Clone + Eq + Hash + 'static,
216{
217 key: Key,
218 locked: bool,
219 bounds: Rectangle,
220}
221
222#[must_use]
224pub struct ReorderableFlexRow<'a, Key, Message>
225where
226 Key: Clone + Eq + Hash + 'static,
227{
228 spacing: f32,
229 padding: Padding,
230 width: Length,
231 height: Length,
232 align: Alignment,
233 clip: bool,
234 drag_lift: f32,
235 animation_duration: Duration,
236 on_reorder: Box<dyn Fn(Vec<Key>) -> Message + 'a>,
237 keys: Vec<Key>,
238 locked: Vec<bool>,
239 children: Vec<Element<'a, Message>>,
240}
241
242impl<'a, Key, Message> ReorderableFlexRow<'a, Key, Message>
243where
244 Key: Clone + Eq + Hash + 'static,
245{
246 pub fn new(on_reorder: impl Fn(Vec<Key>) -> Message + 'a) -> Self {
247 Self {
248 spacing: 0.0,
249 padding: Padding::ZERO,
250 width: Length::Shrink,
251 height: Length::Shrink,
252 align: Alignment::Start,
253 clip: false,
254 drag_lift: DEFAULT_DRAG_LIFT,
255 animation_duration: DEFAULT_ANIMATION_DURATION,
256 on_reorder: Box::new(on_reorder),
257 keys: Vec::new(),
258 locked: Vec::new(),
259 children: Vec::new(),
260 }
261 }
262
263 pub fn with_capacity(capacity: usize, on_reorder: impl Fn(Vec<Key>) -> Message + 'a) -> Self {
264 let mut row = Self::new(on_reorder);
265 row.keys = Vec::with_capacity(capacity);
266 row.locked = Vec::with_capacity(capacity);
267 row.children = Vec::with_capacity(capacity);
268 row
269 }
270
271 pub fn spacing(mut self, amount: impl Into<Pixels>) -> Self {
272 self.spacing = amount.into().0;
273 self
274 }
275
276 pub fn padding<P: Into<Padding>>(mut self, padding: P) -> Self {
277 self.padding = padding.into();
278 self
279 }
280
281 pub fn width(mut self, width: impl Into<Length>) -> Self {
282 self.width = width.into();
283 self
284 }
285
286 pub fn height(mut self, height: impl Into<Length>) -> Self {
287 self.height = height.into();
288 self
289 }
290
291 pub fn align_y(mut self, align: impl Into<alignment::Vertical>) -> Self {
292 self.align = Alignment::from(align.into());
293 self
294 }
295
296 pub fn clip(mut self, clip: bool) -> Self {
298 self.clip = clip;
299 self
300 }
301
302 pub fn drag_lift(mut self, lift: f32) -> Self {
303 self.drag_lift = lift.max(0.0);
304 self
305 }
306
307 pub fn animation_duration(mut self, duration: Duration) -> Self {
308 self.animation_duration = duration;
309 self
310 }
311
312 pub fn push(self, key: Key, child: impl Into<Element<'a, Message>>) -> Self {
313 self.push_with_lock(key, false, child)
314 }
315
316 pub fn push_locked(self, key: Key, child: impl Into<Element<'a, Message>>) -> Self {
318 self.push_with_lock(key, true, child)
319 }
320
321 fn push_with_lock(
322 mut self,
323 key: Key,
324 locked: bool,
325 child: impl Into<Element<'a, Message>>,
326 ) -> Self {
327 let child = child.into();
328 let child_size = child.as_widget().size_hint();
329
330 if !child_size.is_void() {
331 self.width = self.width.enclose(child_size.width);
332 self.height = self.height.enclose(child_size.height);
333 self.keys.push(key);
334 self.locked.push(locked);
335 self.children.push(child);
336 }
337
338 self
339 }
340
341 pub fn extend(self, items: impl IntoIterator<Item = (Key, Element<'a, Message>)>) -> Self {
342 items
343 .into_iter()
344 .fold(self, |row, (key, child)| row.push(key, child))
345 }
346
347 pub fn extend_locked(
348 self,
349 items: impl IntoIterator<Item = (Key, Element<'a, Message>)>,
350 ) -> Self {
351 items
352 .into_iter()
353 .fold(self, |row, (key, child)| row.push_locked(key, child))
354 }
355
356 fn item_local_slots_from_layout(
357 &self,
358 bounds: Rectangle,
359 child_layouts: &[Layout<'_>],
360 ) -> Vec<LocalSlot<Key>> {
361 self.keys
362 .iter()
363 .zip(self.locked.iter())
364 .zip(child_layouts.iter())
365 .map(|((key, locked), child_layout)| {
366 let child_bounds = child_layout.bounds();
367 LocalSlot {
368 key: key.clone(),
369 locked: *locked,
370 bounds: Rectangle {
371 x: child_bounds.x - bounds.x,
372 y: child_bounds.y - bounds.y,
373 width: child_bounds.width,
374 height: child_bounds.height,
375 },
376 }
377 })
378 .collect()
379 }
380
381 fn sync_slot_positions(&self, state: &mut State<Key>, slots: &[LocalSlot<Key>]) {
382 let ordered_keys: Vec<Key> = slots.iter().map(|slot| slot.key.clone()).collect();
383 state.retain_keys(&ordered_keys);
384
385 let size_by_key: HashMap<Key, Size> = slots
386 .iter()
387 .map(|slot| {
388 (
389 slot.key.clone(),
390 Size::new(slot.bounds.width, slot.bounds.height),
391 )
392 })
393 .collect();
394 let locked_by_key: HashMap<Key, bool> = slots
395 .iter()
396 .map(|slot| (slot.key.clone(), slot.locked))
397 .collect();
398
399 let Some(drag_snapshot) = state.drag.as_ref().map(|drag| {
400 (
401 drag.key.clone(),
402 drag.cursor_local,
403 drag.pointer_offset,
404 drag.item_index,
405 )
406 }) else {
407 for slot in slots {
408 state.apply_layout_position(
409 &slot.key,
410 Point::new(slot.bounds.x, slot.bounds.y),
411 self.animation_duration,
412 );
413 }
414 return;
415 };
416
417 let (drag_key, cursor_local, pointer_offset, drag_item_index) = drag_snapshot;
418
419 let Some(dragged_slot) = slots.iter().find(|slot| slot.key == drag_key) else {
420 state.drag = None;
421 for slot in slots {
422 state.apply_layout_position(
423 &slot.key,
424 Point::new(slot.bounds.x, slot.bounds.y),
425 self.animation_duration,
426 );
427 }
428 return;
429 };
430
431 if dragged_slot.locked {
432 state.drag = None;
433 return;
434 }
435
436 let dragged_center = Point::new(
437 cursor_local.x - pointer_offset.x + dragged_slot.bounds.width / 2.0,
438 cursor_local.y - pointer_offset.y + dragged_slot.bounds.height / 2.0,
439 );
440 let target_index = target_index_for_drag(slots, &drag_key, dragged_center);
441 let prior_index = state.drag.as_ref().map(|drag| drag.current_index);
442
443 if let Some(drag) = state.drag.as_mut() {
444 drag.current_index = target_index;
445 drag.item_index = drag_item_index;
446 }
447
448 if prior_index == Some(target_index) {
449 return;
450 }
451
452 let reordered_keys =
453 reordered_keys_for_drag(&ordered_keys, &locked_by_key, &drag_key, target_index);
454 let (target_slots, _) = compute_wrapped_slots(
455 &reordered_keys,
456 &locked_by_key,
457 &size_by_key,
458 state.wrap_width,
459 self.padding,
460 self.spacing,
461 self.align,
462 );
463 let target_positions: HashMap<Key, Point> = target_slots
464 .iter()
465 .map(|slot| (slot.key.clone(), Point::new(slot.bounds.x, slot.bounds.y)))
466 .collect();
467
468 for slot in slots {
469 let target = target_positions
470 .get(&slot.key)
471 .copied()
472 .unwrap_or(Point::new(slot.bounds.x, slot.bounds.y));
473 state.retarget_slot(&slot.key, target, self.animation_duration);
474 }
475 }
476}
477
478impl<'a, Key, Message> Widget<Message, crate::Theme, Renderer>
479 for ReorderableFlexRow<'a, Key, Message>
480where
481 Key: Clone + Eq + Hash + 'static,
482 Message: 'a,
483{
484 fn tag(&self) -> tree::Tag {
485 tree::Tag::of::<State<Key>>()
486 }
487
488 fn state(&self) -> tree::State {
489 tree::State::new(State {
490 keys: self.keys.clone(),
491 ..State::default()
492 })
493 }
494
495 fn children(&self) -> Vec<Tree> {
496 self.children.iter().map(Tree::new).collect()
497 }
498
499 fn diff(&mut self, tree: &mut Tree) {
500 let Tree {
501 state, children, ..
502 } = tree;
503 let state = state.downcast_mut::<State<Key>>();
504 let previous_keys = state.keys.clone();
505 let previous_children = std::mem::take(children);
506 let mut previous_by_key = HashMap::with_capacity(previous_keys.len());
507
508 for (key, child_tree) in previous_keys.into_iter().zip(previous_children) {
509 previous_by_key.insert(key, child_tree);
510 }
511
512 children.reserve(self.children.len());
513
514 for (key, child) in self.keys.iter().cloned().zip(self.children.iter_mut()) {
515 let mut child_tree = previous_by_key
516 .remove(&key)
517 .unwrap_or_else(|| Tree::new(child.as_widget()));
518 child.as_widget_mut().diff(&mut child_tree);
519 children.push(child_tree);
520 }
521
522 if state.keys != self.keys {
523 state.keys.clone_from(&self.keys);
524 }
525 state.retain_keys(&self.keys);
526 }
527
528 fn size(&self) -> Size<Length> {
529 Size {
530 width: self.width,
531 height: self.height,
532 }
533 }
534
535 fn layout(
536 &mut self,
537 tree: &mut Tree,
538 renderer: &Renderer,
539 limits: &layout::Limits,
540 ) -> layout::Node {
541 let limits = limits
542 .width(self.width)
543 .height(self.height)
544 .shrink(self.padding);
545 let child_limits = limits.loose();
546 let wrap_width = limits.max().width;
547
548 let mut nodes = Vec::with_capacity(self.children.len());
549 let mut size_by_key = HashMap::with_capacity(self.children.len());
550 let locked_by_key: HashMap<Key, bool> = self
551 .keys
552 .iter()
553 .cloned()
554 .zip(self.locked.iter().copied())
555 .collect();
556
557 for ((key, child), child_tree) in self
558 .keys
559 .iter()
560 .zip(self.children.iter_mut())
561 .zip(tree.children.iter_mut())
562 {
563 let node = child
564 .as_widget_mut()
565 .layout(child_tree, renderer, &child_limits);
566 size_by_key.insert(key.clone(), node.size());
567 nodes.push(node);
568 }
569
570 let (slots, intrinsic_size) = compute_wrapped_slots(
571 &self.keys,
572 &locked_by_key,
573 &size_by_key,
574 wrap_width,
575 self.padding,
576 self.spacing,
577 self.align,
578 );
579
580 for (node, slot) in nodes.iter_mut().zip(&slots) {
581 node.move_to_mut(Point::new(slot.bounds.x, slot.bounds.y));
582 }
583
584 let size = limits.resolve(self.width, self.height, intrinsic_size);
585 let node = layout::Node::with_children(size.expand(self.padding), nodes);
586 let state = tree.state.downcast_mut::<State<Key>>();
587 state.wrap_width = wrap_width;
588 self.sync_slot_positions(state, &slots);
589
590 node
591 }
592
593 fn operate(
594 &mut self,
595 tree: &mut Tree,
596 layout: Layout<'_>,
597 renderer: &Renderer,
598 operation: &mut dyn Operation,
599 ) {
600 operation.container(None, layout.bounds());
601 operation.traverse(&mut |operation| {
602 self.children
603 .iter_mut()
604 .zip(&mut tree.children)
605 .zip(layout.children())
606 .for_each(|((child, state), child_layout)| {
607 child.as_widget_mut().operate(
608 state,
609 child_layout.with_virtual_offset(layout.virtual_offset()),
610 renderer,
611 operation,
612 );
613 });
614 });
615 }
616
617 fn update(
618 &mut self,
619 tree: &mut Tree,
620 event: &Event,
621 layout: Layout<'_>,
622 cursor: Cursor,
623 renderer: &Renderer,
624 clipboard: &mut dyn Clipboard,
625 shell: &mut Shell<'_, Message>,
626 viewport: &Rectangle,
627 ) {
628 let bounds = layout.bounds();
629 let state = tree.state.downcast_mut::<State<Key>>();
630 let child_layouts: Vec<_> = layout.children().collect();
631
632 if let Event::Window(window::Event::RedrawRequested(_)) = event {
633 state.initialized = true;
634 state.finish_animations(self.animation_duration);
635 if state.drag.is_some() || state.is_animating(self.animation_duration) {
636 shell.request_redraw();
637 }
638 }
639
640 match event {
641 Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left)) => {
642 if state.drag.is_none()
643 && state.pending_drag.is_none()
644 && let Some(cursor_pos) = cursor.position()
645 && let Some((index, child_layout)) = child_layouts
646 .iter()
647 .enumerate()
648 .find(|(_, child_layout)| child_layout.bounds().contains(cursor_pos))
649 && !self.locked.get(index).copied().unwrap_or(false)
650 && let Some(reorder_index) = reorderable_index_for_child(&self.locked, index)
651 {
652 let child_bounds = child_layout.bounds();
653 state.pending_drag = Some(PendingDragState {
654 key: self.keys[index].clone(),
655 item_index: index,
656 original_index: reorder_index,
657 press_local: Point::new(cursor_pos.x - bounds.x, cursor_pos.y - bounds.y),
658 pointer_offset: Vector::new(
659 cursor_pos.x - child_bounds.x,
660 cursor_pos.y - child_bounds.y,
661 ),
662 });
663 }
664 }
665 Event::Mouse(mouse::Event::CursorMoved { .. }) => {
666 if let Some(pending) = state.pending_drag.clone()
667 && let Some(cursor_pos) = cursor.position()
668 {
669 let cursor_local = Point::new(cursor_pos.x - bounds.x, cursor_pos.y - bounds.y);
670 let delta = Vector::new(
671 cursor_local.x - pending.press_local.x,
672 cursor_local.y - pending.press_local.y,
673 );
674 let distance = (delta.x.powi(2) + delta.y.powi(2)).sqrt();
675
676 if distance >= DEFAULT_DRAG_THRESHOLD {
677 if let (Some(child), Some(child_tree), Some(child_layout)) = (
678 self.children.get_mut(pending.item_index),
679 tree.children.get_mut(pending.item_index),
680 child_layouts.get(pending.item_index),
681 ) {
682 let cursor_left = Event::Mouse(mouse::Event::CursorLeft);
683 child.as_widget_mut().update(
684 child_tree,
685 &cursor_left,
686 child_layout.with_virtual_offset(layout.virtual_offset()),
687 cursor,
688 renderer,
689 clipboard,
690 shell,
691 viewport,
692 );
693 }
694
695 state.pending_drag = None;
696 state.drag = Some(DragState {
697 key: pending.key,
698 item_index: pending.item_index,
699 original_index: pending.original_index,
700 current_index: pending.original_index,
701 cursor_local,
702 pointer_offset: pending.pointer_offset,
703 });
704 let slots = self.item_local_slots_from_layout(bounds, &child_layouts);
705 self.sync_slot_positions(state, &slots);
706 shell.capture_event();
707 shell.request_redraw();
708 return;
709 }
710 }
711
712 if let Some(drag) = state.drag.as_mut()
713 && let Some(cursor_pos) = cursor.position()
714 {
715 drag.cursor_local =
716 Point::new(cursor_pos.x - bounds.x, cursor_pos.y - bounds.y);
717 let slots = self.item_local_slots_from_layout(bounds, &child_layouts);
718 self.sync_slot_positions(state, &slots);
719 shell.capture_event();
720 shell.request_redraw();
721 return;
722 }
723 }
724 Event::Mouse(mouse::Event::ButtonReleased(mouse::Button::Left)) => {
725 state.pending_drag = None;
726
727 if state.drag.is_some() {
728 let slots = self.item_local_slots_from_layout(bounds, &child_layouts);
729 self.sync_slot_positions(state, &slots);
730 }
731 if let Some(drag) = state.drag.take() {
732 if drag.current_index != drag.original_index {
733 let locked_by_key: HashMap<Key, bool> = self
734 .keys
735 .iter()
736 .cloned()
737 .zip(self.locked.iter().copied())
738 .collect();
739 let new_order = reordered_keys_for_drag(
740 &self.keys,
741 &locked_by_key,
742 &drag.key,
743 drag.current_index,
744 );
745 shell.publish((self.on_reorder)(new_order));
746 }
747 shell.capture_event();
748 shell.request_redraw();
749 return;
750 }
751 }
752 _ => {}
753 }
754
755 if state.drag.is_some() {
756 return;
757 }
758
759 for ((item, tree), child_layout) in self
760 .children
761 .iter_mut()
762 .zip(&mut tree.children)
763 .zip(child_layouts.into_iter())
764 {
765 item.as_widget_mut().update(
766 tree,
767 event,
768 child_layout.with_virtual_offset(layout.virtual_offset()),
769 cursor,
770 renderer,
771 clipboard,
772 shell,
773 viewport,
774 );
775 }
776 }
777
778 fn mouse_interaction(
779 &self,
780 tree: &Tree,
781 layout: Layout<'_>,
782 cursor: Cursor,
783 viewport: &Rectangle,
784 renderer: &Renderer,
785 ) -> mouse::Interaction {
786 let state = tree.state.downcast_ref::<State<Key>>();
787
788 if state.drag.is_some() {
789 return mouse::Interaction::Grabbing;
790 }
791
792 if let Some(cursor_pos) = cursor.position()
793 && self
794 .locked
795 .iter()
796 .zip(layout.children())
797 .any(|(locked, child_layout)| {
798 !*locked && child_layout.bounds().contains(cursor_pos)
799 })
800 {
801 return mouse::Interaction::Grab;
802 }
803
804 self.children
805 .iter()
806 .zip(&tree.children)
807 .zip(layout.children())
808 .map(|((child, tree), child_layout)| {
809 child.as_widget().mouse_interaction(
810 tree,
811 child_layout.with_virtual_offset(layout.virtual_offset()),
812 cursor,
813 viewport,
814 renderer,
815 )
816 })
817 .max()
818 .unwrap_or_default()
819 }
820
821 fn draw(
822 &self,
823 tree: &Tree,
824 renderer: &mut Renderer,
825 theme: &crate::Theme,
826 style: &renderer::Style,
827 layout: Layout<'_>,
828 cursor: Cursor,
829 viewport: &Rectangle,
830 ) {
831 let state = tree.state.downcast_ref::<State<Key>>();
832 let bounds = layout.bounds();
833
834 let Some(clipped_viewport) = bounds.intersection(viewport) else {
835 return;
836 };
837
838 let viewport = if self.clip {
839 &clipped_viewport
840 } else {
841 viewport
842 };
843 let drag_key = state.drag.as_ref().map(|drag| &drag.key);
844 let drag_item = state.drag.as_ref().and_then(|drag| {
845 self.keys
846 .iter()
847 .zip(&self.children)
848 .zip(&tree.children)
849 .zip(layout.children())
850 .find_map(|(((key, child), state), child_layout)| {
851 (key == &drag.key).then_some((key, child, state, child_layout, drag))
852 })
853 });
854
855 for (((key, child), child_tree), child_layout) in self
856 .keys
857 .iter()
858 .zip(&self.children)
859 .zip(&tree.children)
860 .zip(layout.children())
861 {
862 if drag_key.is_some_and(|drag_key| drag_key == key) {
863 continue;
864 }
865
866 let child_layout = child_layout.with_virtual_offset(layout.virtual_offset());
867 let child_bounds = child_layout.bounds();
868 let base_local = Point::new(child_bounds.x - bounds.x, child_bounds.y - bounds.y);
869 let target_local =
870 state.current_slot_position(key, base_local, self.animation_duration);
871 let translation =
872 Vector::new(target_local.x - base_local.x, target_local.y - base_local.y);
873 let translated_bounds = translate_rect(child_bounds, translation);
874
875 if translated_bounds.intersects(viewport) {
876 renderer.with_translation(translation, |renderer| {
877 child.as_widget().draw(
878 child_tree,
879 renderer,
880 theme,
881 style,
882 child_layout,
883 cursor,
884 viewport,
885 );
886 });
887 }
888 }
889
890 if let Some((_key, child, child_tree, child_layout, drag)) = drag_item {
891 let child_layout = child_layout.with_virtual_offset(layout.virtual_offset());
892 let child_bounds = child_layout.bounds();
893 let base_local = Point::new(child_bounds.x - bounds.x, child_bounds.y - bounds.y);
894 let drag_local = Point::new(
895 drag.cursor_local.x - drag.pointer_offset.x,
896 drag.cursor_local.y - drag.pointer_offset.y - self.drag_lift,
897 );
898 let translation = Vector::new(drag_local.x - base_local.x, drag_local.y - base_local.y);
899
900 #[cfg(feature = "wgpu")]
901 {
902 let translated_bounds = translate_rect(child_bounds, translation);
903 draw_drag_backdrop(renderer, theme, translated_bounds);
904 }
905
906 renderer.with_translation(translation, |renderer| {
907 child.as_widget().draw(
908 child_tree,
909 renderer,
910 theme,
911 style,
912 child_layout,
913 cursor,
914 viewport,
915 );
916 });
917 }
918 }
919
920 fn overlay<'b>(
921 &'b mut self,
922 tree: &'b mut Tree,
923 layout: Layout<'b>,
924 renderer: &Renderer,
925 viewport: &Rectangle,
926 translation: Vector,
927 ) -> Option<overlay::Element<'b, Message, crate::Theme, Renderer>> {
928 overlay::from_children(
929 &mut self.children,
930 tree,
931 layout,
932 renderer,
933 viewport,
934 translation,
935 )
936 }
937
938 fn drag_destinations(
939 &self,
940 state: &Tree,
941 layout: Layout<'_>,
942 renderer: &Renderer,
943 dnd_rectangles: &mut iced_core::clipboard::DndDestinationRectangles,
944 ) {
945 for ((item, child_layout), child_state) in self
946 .children
947 .iter()
948 .zip(layout.children())
949 .zip(state.children.iter())
950 {
951 item.as_widget().drag_destinations(
952 child_state,
953 child_layout.with_virtual_offset(layout.virtual_offset()),
954 renderer,
955 dnd_rectangles,
956 );
957 }
958 }
959}
960
961impl<'a, Key, Message> From<ReorderableFlexRow<'a, Key, Message>> for Element<'a, Message>
962where
963 Key: Clone + Eq + Hash + 'static,
964 Message: 'a,
965{
966 fn from(row: ReorderableFlexRow<'a, Key, Message>) -> Self {
967 Element::new(row)
968 }
969}
970
971pub fn reorderable_flex_row<'a, Key, Message>(
973 on_reorder: impl Fn(Vec<Key>) -> Message + 'a,
974) -> ReorderableFlexRow<'a, Key, Message>
975where
976 Key: Clone + Eq + Hash + 'static,
977{
978 ReorderableFlexRow::new(on_reorder)
979}
980
981fn compute_wrapped_slots<Key>(
982 ordered_keys: &[Key],
983 locked_by_key: &HashMap<Key, bool>,
984 size_by_key: &HashMap<Key, Size>,
985 wrap_width: f32,
986 padding: Padding,
987 spacing: f32,
988 align: Alignment,
989) -> (Vec<LocalSlot<Key>>, Size)
990where
991 Key: Clone + Eq + Hash + 'static,
992{
993 let wrap_width = if wrap_width.is_finite() {
994 wrap_width.max(0.0)
995 } else {
996 f32::INFINITY
997 };
998
999 let mut slots = Vec::with_capacity(ordered_keys.len());
1000 let mut intrinsic_size = Size::ZERO;
1001 let mut row_start = 0;
1002 let mut row_height = 0.0;
1003 let mut x = 0.0;
1004 let mut y = 0.0;
1005
1006 let align_factor = match align {
1007 Alignment::Start => 0.0,
1008 Alignment::Center => 2.0,
1009 Alignment::End => 1.0,
1010 };
1011
1012 let align_row =
1013 |range: std::ops::Range<usize>, row_height: f32, slots: &mut [LocalSlot<Key>]| {
1014 if align_factor == 0.0 {
1015 return;
1016 }
1017
1018 for slot in &mut slots[range] {
1019 slot.bounds.y += (row_height - slot.bounds.height) / align_factor;
1020 }
1021 };
1022
1023 for (index, key) in ordered_keys.iter().enumerate() {
1024 let size = size_by_key.get(key).copied().unwrap_or(Size::ZERO);
1025
1026 if x != 0.0 && x + size.width > wrap_width {
1027 intrinsic_size.width = intrinsic_size.width.max(x - spacing);
1028 align_row(row_start..index, row_height, &mut slots);
1029 y += row_height + spacing;
1030 x = 0.0;
1031 row_start = index;
1032 row_height = 0.0;
1033 }
1034
1035 row_height = row_height.max(size.height);
1036
1037 slots.push(LocalSlot {
1038 key: key.clone(),
1039 locked: locked_by_key.get(key).copied().unwrap_or(false),
1040 bounds: Rectangle {
1041 x: x + padding.left,
1042 y: y + padding.top,
1043 width: size.width,
1044 height: size.height,
1045 },
1046 });
1047
1048 x += size.width + spacing;
1049 }
1050
1051 if x != 0.0 {
1052 intrinsic_size.width = intrinsic_size.width.max(x - spacing);
1053 }
1054
1055 intrinsic_size.height = y + row_height;
1056 align_row(row_start..slots.len(), row_height, &mut slots);
1057
1058 (slots, intrinsic_size)
1059}
1060
1061fn reordered_keys_for_drag<Key>(
1062 ordered_keys: &[Key],
1063 locked_by_key: &HashMap<Key, bool>,
1064 dragged_key: &Key,
1065 target_index: usize,
1066) -> Vec<Key>
1067where
1068 Key: Clone + Eq + Hash + 'static,
1069{
1070 let movable_keys: Vec<Key> = ordered_keys
1071 .iter()
1072 .filter(|key| !locked_by_key.get(*key).copied().unwrap_or(false))
1073 .cloned()
1074 .collect();
1075 let mut remaining: Vec<Key> = movable_keys
1076 .iter()
1077 .filter(|key| *key != dragged_key)
1078 .cloned()
1079 .collect();
1080
1081 remaining.insert(target_index.min(remaining.len()), dragged_key.clone());
1082 merge_locked_and_reordered_keys(ordered_keys, locked_by_key, &remaining)
1083}
1084
1085fn target_index_for_drag<Key>(
1091 slots: &[LocalSlot<Key>],
1092 dragged_key: &Key,
1093 dragged_center: Point,
1094) -> usize
1095where
1096 Key: Clone + Eq + Hash + 'static,
1097{
1098 let mut target = 0;
1099 let mut passed_movable: usize = 0;
1100 let mut found_target = false;
1101
1102 let mut i = 0;
1103 while i < slots.len() {
1104 let slot = &slots[i];
1105
1106 if slot.locked || &slot.key == dragged_key {
1107 i += 1;
1108 continue;
1109 }
1110
1111 let row_top = slot.bounds.y;
1112 let row_bottom = row_top + slot.bounds.height;
1113
1114 if !found_target && dragged_center.y < row_top {
1115 target = passed_movable;
1116 found_target = true;
1117 break;
1118 }
1119
1120 if dragged_center.y > row_bottom {
1121 passed_movable += 1;
1122 i += 1;
1123 continue;
1124 }
1125
1126 let center_x = slot.bounds.x + slot.bounds.width / 2.0;
1127 if dragged_center.x < center_x {
1128 target = passed_movable;
1129 found_target = true;
1130 break;
1131 }
1132
1133 passed_movable += 1;
1134 i += 1;
1135 }
1136
1137 if !found_target {
1138 target = passed_movable;
1139 }
1140
1141 target
1142}
1143
1144fn reorderable_index_for_child(locked: &[bool], item_index: usize) -> Option<usize> {
1145 (!locked.get(item_index).copied().unwrap_or(false)).then(|| {
1146 locked[..item_index]
1147 .iter()
1148 .filter(|is_locked| !**is_locked)
1149 .count()
1150 })
1151}
1152
1153fn merge_locked_and_reordered_keys<Key>(
1154 ordered_keys: &[Key],
1155 locked_by_key: &HashMap<Key, bool>,
1156 reordered_movable_keys: &[Key],
1157) -> Vec<Key>
1158where
1159 Key: Clone + Eq + Hash + 'static,
1160{
1161 let mut movable = reordered_movable_keys.iter();
1162
1163 ordered_keys
1164 .iter()
1165 .map(|key| {
1166 if locked_by_key.get(key).copied().unwrap_or(false) {
1167 key.clone()
1168 } else {
1169 movable.next().cloned().unwrap_or_else(|| key.clone())
1170 }
1171 })
1172 .collect()
1173}
1174
1175fn approx_eq_point(a: Point, b: Point) -> bool {
1176 (a.x - b.x).abs() <= POSITION_EPSILON && (a.y - b.y).abs() <= POSITION_EPSILON
1177}
1178
1179fn translate_rect(bounds: Rectangle, translation: Vector) -> Rectangle {
1180 Rectangle {
1181 x: bounds.x + translation.x,
1182 y: bounds.y + translation.y,
1183 width: bounds.width,
1184 height: bounds.height,
1185 }
1186}
1187
1188#[cfg(feature = "wgpu")]
1189fn draw_drag_backdrop(renderer: &mut Renderer, theme: &crate::Theme, bounds: Rectangle) {
1190 let cosmic = theme.cosmic();
1191 let backdrop = iced::Color {
1192 a: 0.08,
1193 ..iced::Color::from(cosmic.bg_component_color())
1194 };
1195
1196 renderer.fill_quad(
1197 renderer::Quad {
1198 bounds,
1199 border: Border {
1200 radius: cosmic.corner_radii.radius_m.into(),
1201 ..Border::default()
1202 },
1203 shadow: Shadow {
1204 color: cosmic.shade.into(),
1205 offset: Vector::new(0.0, 8.0),
1206 blur_radius: SHADOW_BLUR_RADIUS,
1207 },
1208 snap: true,
1209 },
1210 Background::Color(backdrop),
1211 );
1212}
1213
1214#[cfg(test)]
1215mod tests {
1216 use super::{compute_wrapped_slots, reordered_keys_for_drag, target_index_for_drag};
1217 use iced::{Alignment, Padding, Point, Size};
1218 use std::collections::HashMap;
1219
1220 fn size_map(keys: &[usize], width: f32, height: f32) -> HashMap<usize, Size> {
1221 keys.iter()
1222 .copied()
1223 .map(|key| (key, Size::new(width, height)))
1224 .collect()
1225 }
1226
1227 fn locked_map(keys: &[usize], locked_keys: &[usize]) -> HashMap<usize, bool> {
1228 keys.iter()
1229 .copied()
1230 .map(|key| (key, locked_keys.contains(&key)))
1231 .collect()
1232 }
1233
1234 #[test]
1235 fn compute_wrapped_slots_creates_new_rows() {
1236 let ordered_keys = vec![0, 1, 2];
1237 let locked_by_key = locked_map(&ordered_keys, &[]);
1238 let size_by_key = size_map(&ordered_keys, 100.0, 40.0);
1239 let (slots, intrinsic_size) = compute_wrapped_slots(
1240 &ordered_keys,
1241 &locked_by_key,
1242 &size_by_key,
1243 220.0,
1244 Padding::ZERO,
1245 10.0,
1246 Alignment::Start,
1247 );
1248
1249 assert_eq!(slots[0].bounds.x, 0.0);
1250 assert_eq!(slots[0].bounds.y, 0.0);
1251 assert_eq!(slots[1].bounds.x, 110.0);
1252 assert_eq!(slots[1].bounds.y, 0.0);
1253 assert_eq!(slots[2].bounds.x, 0.0);
1254 assert_eq!(slots[2].bounds.y, 50.0);
1255 assert_eq!(intrinsic_size.width, 210.0);
1256 assert_eq!(intrinsic_size.height, 90.0);
1257 }
1258
1259 #[test]
1260 fn reordered_keys_for_drag_inserts_key_at_target_index() {
1261 let keys = [0, 1, 2, 3];
1262 let locked_by_key = locked_map(&keys, &[]);
1263 let reordered = reordered_keys_for_drag(&keys, &locked_by_key, &0, 3);
1264 assert_eq!(reordered, vec![1, 2, 3, 0]);
1265 }
1266
1267 #[test]
1268 fn target_index_tracks_wrapped_drop_positions() {
1269 let ordered_keys = vec![0, 1, 2, 3];
1270 let locked_by_key = locked_map(&ordered_keys, &[]);
1271 let size_by_key = size_map(&ordered_keys, 100.0, 40.0);
1272
1273 let (slots, _) = compute_wrapped_slots(
1274 &ordered_keys,
1275 &locked_by_key,
1276 &size_by_key,
1277 220.0,
1278 Padding::ZERO,
1279 10.0,
1280 Alignment::Start,
1281 );
1282
1283 let target_index = target_index_for_drag(&slots, &0, Point::new(160.0, 70.0));
1284
1285 assert_eq!(target_index, 3);
1286 }
1287
1288 #[test]
1289 fn reordered_keys_for_drag_preserves_locked_positions() {
1290 let keys = [10, 11, 12, 13];
1291 let locked_by_key = locked_map(&keys, &[10, 13]);
1292 let reordered = reordered_keys_for_drag(&keys, &locked_by_key, &11, 1);
1293
1294 assert_eq!(reordered, vec![10, 12, 11, 13]);
1295 }
1296}