Skip to main content

tui_lipan/widgets/drag_drop/
drag_source.rs

1use std::hash::Hash;
2use std::sync::Arc;
3
4use crate::core::element::{Element, ElementKind};
5use crate::layout::drag_source_layout_hint::drag_source_snapshot_collapse_key;
6use crate::layout::hash::LayoutHash;
7use crate::style::{LayoutConstraints, Length, Style, StyleSlot};
8
9use super::drag_source_layout::measure_drag_source;
10use super::payload::{
11    DragCancelEvent, DragPayload, DragPreview, DragSlot, DragSlotAxis, DragStartEvent,
12    DragStartedEvent,
13};
14
15/// Callback used to start a drag and produce its payload.
16/// Handler used to start drag and produce payload.
17pub type DragStartHandler = Arc<dyn Fn(DragStartEvent) -> Option<Box<dyn DragPayload>>>;
18
19/// Wrapper widget that turns its child into a drag source.
20#[derive(Clone)]
21pub struct DragSource {
22    pub(crate) child: Option<Box<Element>>,
23    pub(crate) on_drag_start: Option<DragStartHandler>,
24    pub(crate) on_drag_cancel: Option<crate::callback::Callback<DragCancelEvent>>,
25    pub(crate) on_drag_started: Option<crate::callback::Callback<DragStartedEvent>>,
26    pub(crate) drag_group: Option<Arc<str>>,
27    pub(crate) preview: DragPreview,
28    pub(crate) dragging_style: StyleSlot,
29    pub(crate) drag_slot: DragSlot,
30    pub(crate) drag_slot_axis: DragSlotAxis,
31    pub(crate) preview_max_width: Option<u16>,
32    pub(crate) preview_max_height: Option<u16>,
33    pub(crate) threshold: u16,
34    pub(crate) enabled: bool,
35}
36
37impl Default for DragSource {
38    fn default() -> Self {
39        Self {
40            child: None,
41            on_drag_start: None,
42            on_drag_cancel: None,
43            on_drag_started: None,
44            drag_group: None,
45            preview: DragPreview::None,
46            dragging_style: StyleSlot::Inherit,
47            drag_slot: DragSlot::Collapse,
48            drag_slot_axis: DragSlotAxis::default(),
49            preview_max_width: None,
50            preview_max_height: None,
51            threshold: 3,
52            enabled: true,
53        }
54    }
55}
56
57impl DragSource {
58    /// Create an empty drag source wrapper.
59    pub fn new() -> Self {
60        Self::default()
61    }
62
63    /// Set the wrapped child element.
64    pub fn child(mut self, child: impl Into<Element>) -> Self {
65        self.child = Some(Box::new(child.into()));
66        self
67    }
68
69    /// Set drag-start callback that returns the payload for this drag.
70    pub fn on_drag_start(
71        mut self,
72        cb: impl Fn(DragStartEvent) -> Option<Box<dyn DragPayload>> + 'static,
73    ) -> Self {
74        self.on_drag_start = Some(Arc::new(cb));
75        self
76    }
77
78    /// Set cancellation callback fired when drop fails or is canceled.
79    pub fn on_drag_cancel(mut self, cb: crate::callback::Callback<DragCancelEvent>) -> Self {
80        self.on_drag_cancel = Some(cb);
81        self
82    }
83
84    /// Callback fired once when the drag activates (after the movement threshold).
85    pub fn on_drag_started(mut self, cb: crate::callback::Callback<DragStartedEvent>) -> Self {
86        self.on_drag_started = Some(cb);
87        self
88    }
89
90    /// Restrict this source to a compatibility group.
91    pub fn drag_group(mut self, group: impl Into<Arc<str>>) -> Self {
92        self.drag_group = Some(group.into());
93        self
94    }
95
96    /// Remove any compatibility group restriction.
97    pub fn clear_drag_group(mut self) -> Self {
98        self.drag_group = None;
99        self
100    }
101
102    /// Configure drag preview behavior.
103    pub fn preview(mut self, preview: DragPreview) -> Self {
104        self.preview = preview;
105        self
106    }
107
108    /// Configure a simple text preview label.
109    pub fn preview_label(mut self, label: impl Into<Arc<str>>) -> Self {
110        self.preview = DragPreview::Label(label.into());
111        self
112    }
113
114    /// Render a snapshot of the drag source's cells near the cursor as a drag preview.
115    pub fn preview_snapshot(mut self) -> Self {
116        self.preview = DragPreview::SourceSnapshot;
117        self
118    }
119
120    /// Disable preview rendering.
121    pub fn no_preview(mut self) -> Self {
122        self.preview = DragPreview::None;
123        self
124    }
125
126    /// Style overlay while this source is actively dragging (tint, reserved slot, label preview).
127    pub fn dragging_style(mut self, style: Style) -> Self {
128        self.dragging_style = StyleSlot::Replace(style);
129        self
130    }
131
132    /// Extend the themed dragging style with the given style.
133    pub fn extend_dragging_style(mut self, style: Style) -> Self {
134        self.dragging_style = StyleSlot::Extend(style);
135        self
136    }
137
138    /// Inherit dragging style from the active theme.
139    pub fn inherit_dragging_style(mut self) -> Self {
140        self.dragging_style = StyleSlot::Inherit;
141        self
142    }
143
144    /// Set the dragging style slot directly.
145    pub fn dragging_style_slot(mut self, slot: StyleSlot) -> Self {
146        self.dragging_style = slot;
147        self
148    }
149
150    /// Main-axis space reserved at the source while dragging with [`DragPreview::SourceSnapshot`].
151    pub fn drag_slot(mut self, slot: DragSlot) -> Self {
152        self.drag_slot = slot;
153        self
154    }
155
156    /// Shorthand for `drag_slot(DragSlot::Collapse)`.
157    pub fn drag_slot_collapse(mut self) -> Self {
158        self.drag_slot = DragSlot::Collapse;
159        self
160    }
161
162    /// Same as `drag_slot(DragSlot::Specified(len))`.
163    pub fn drag_slot_length(mut self, len: Length) -> Self {
164        self.drag_slot = DragSlot::Specified(len);
165        self
166    }
167
168    /// Which axis [`DragSlot`] sizes apply on when not inside a `VStack` / `HStack`.
169    pub fn drag_slot_axis(mut self, axis: DragSlotAxis) -> Self {
170        self.drag_slot_axis = axis;
171        self
172    }
173
174    /// Cap floating `SourceSnapshot` width. `None` paints the full source width.
175    pub fn preview_max_width(mut self, max_width: Option<u16>) -> Self {
176        self.preview_max_width = max_width;
177        self
178    }
179
180    /// Cap floating `SourceSnapshot` height. `None` paints the full source height.
181    pub fn preview_max_height(mut self, max_height: Option<u16>) -> Self {
182        self.preview_max_height = max_height;
183        self
184    }
185
186    /// Cap both floating preview axes. `None` per axis means no cap on that axis.
187    pub fn preview_max_size(mut self, max_width: Option<u16>, max_height: Option<u16>) -> Self {
188        self.preview_max_width = max_width;
189        self.preview_max_height = max_height;
190        self
191    }
192
193    /// Set pointer movement threshold (cells) before drag starts.
194    pub fn threshold(mut self, threshold: u16) -> Self {
195        self.threshold = threshold;
196        self
197    }
198
199    /// Enable or disable this drag source.
200    pub fn enabled(mut self, enabled: bool) -> Self {
201        self.enabled = enabled;
202        self
203    }
204}
205
206impl From<DragSource> for Element {
207    fn from(value: DragSource) -> Self {
208        let (min_w, min_h) = measure_drag_source(&value, None, None, None);
209        // SourceSnapshot sources may collapse to zero height during a drag,
210        // so their min_height must be 0 to allow that.
211        let min_h = if matches!(value.preview, DragPreview::SourceSnapshot) {
212            0
213        } else {
214            min_h
215        };
216        Element::new(ElementKind::DragSource(value)).with_layout(
217            LayoutConstraints::default()
218                .min_width(Length::Px(min_w))
219                .min_height(Length::Px(min_h)),
220        )
221    }
222}
223
224impl LayoutHash for DragSource {
225    fn layout_hash(
226        &self,
227        hasher: &mut impl std::hash::Hasher,
228        recurse: &dyn Fn(&Element) -> Option<u64>,
229    ) -> Option<()> {
230        self.enabled.hash(hasher);
231        self.on_drag_start.is_some().hash(hasher);
232        self.on_drag_cancel.is_some().hash(hasher);
233        self.on_drag_started.is_some().hash(hasher);
234        self.drag_group.hash(hasher);
235        self.preview.hash(hasher);
236        self.dragging_style.hash(hasher);
237        self.drag_slot.hash(hasher);
238        self.drag_slot_axis.hash(hasher);
239        self.preview_max_width.hash(hasher);
240        self.preview_max_height.hash(hasher);
241        self.threshold.hash(hasher);
242        // Include the collapse hint so the global measure cache invalidates
243        // when a SourceSnapshot drag starts or ends.
244        if matches!(self.preview, DragPreview::SourceSnapshot) {
245            drag_source_snapshot_collapse_key().hash(hasher);
246        }
247        if let Some(child) = self.child.as_ref() {
248            recurse(child.as_ref())?.hash(hasher);
249        }
250        Some(())
251    }
252}