Skip to main content

tui_lipan/widgets/drag_drop/
drop_target.rs

1use std::hash::Hash;
2use std::rc::Rc;
3use std::sync::Arc;
4
5use crate::callback::Callback;
6use crate::core::element::{Element, ElementKind};
7use crate::layout::hash::LayoutHash;
8use crate::style::{LayoutConstraints, Length, Style, StyleSlot};
9
10use super::drop_target_layout::measure_drop_target;
11use super::payload::{DragLeaveEvent, DragOverEvent, DragPayload, DropEvent, DropSlot};
12
13/// Predicate used to accept or reject payloads.
14pub type PayloadAcceptFn = Rc<dyn Fn(&dyn DragPayload) -> bool>;
15
16/// How to visualize a compatible drag hovering a `DropTarget`.
17#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
18pub enum DropHighlight {
19    /// No highlight layer (callbacks still run).
20    #[default]
21    None,
22    /// Solid fill using [`DropTarget`](DropTarget) `highlight_style`.
23    Fill,
24    /// Bordered placeholder frame (same helper as Image pending decode).
25    Placeholder,
26    /// Tint drawn **after** the child so content remains visible underneath.
27    Overlay,
28}
29
30#[derive(Clone)]
31/// Wrapper widget that marks its child as a generic drop target.
32pub struct DropTarget {
33    pub(crate) child: Option<Box<Element>>,
34    pub(crate) on_drag_over: Option<Callback<DragOverEvent>>,
35    pub(crate) on_drag_leave: Option<Callback<DragLeaveEvent>>,
36    pub(crate) on_drop: Option<Callback<DropEvent>>,
37    pub(crate) accept_group: Option<Arc<str>>,
38    pub(crate) can_accept: Option<PayloadAcceptFn>,
39    pub(crate) highlight: DropHighlight,
40    pub(crate) highlight_style: StyleSlot,
41    pub(crate) drop_slot: DropSlot,
42    pub(crate) enabled: bool,
43}
44
45impl Default for DropTarget {
46    fn default() -> Self {
47        Self {
48            child: None,
49            on_drag_over: None,
50            on_drag_leave: None,
51            on_drop: None,
52            accept_group: None,
53            can_accept: None,
54            highlight: DropHighlight::None,
55            highlight_style: StyleSlot::Inherit,
56            drop_slot: DropSlot::Child,
57            enabled: true,
58        }
59    }
60}
61
62impl DropTarget {
63    /// Create an empty drop target wrapper.
64    pub fn new() -> Self {
65        Self::default()
66    }
67
68    /// Set the wrapped child element.
69    pub fn child(mut self, child: impl Into<Element>) -> Self {
70        self.child = Some(Box::new(child.into()));
71        self
72    }
73
74    /// Callback emitted when a compatible payload hovers this target.
75    pub fn on_drag_over(mut self, cb: Callback<DragOverEvent>) -> Self {
76        self.on_drag_over = Some(cb);
77        self
78    }
79
80    /// Callback emitted when an active drag leaves this target.
81    pub fn on_drag_leave(mut self, cb: Callback<DragLeaveEvent>) -> Self {
82        self.on_drag_leave = Some(cb);
83        self
84    }
85
86    /// Callback emitted when payload is dropped on this target.
87    pub fn on_drop(mut self, cb: Callback<DropEvent>) -> Self {
88        self.on_drop = Some(cb);
89        self
90    }
91
92    /// Restrict this target to a compatibility group.
93    pub fn accept_group(mut self, group: impl Into<Arc<str>>) -> Self {
94        self.accept_group = Some(group.into());
95        self
96    }
97
98    /// Remove group restriction; target accepts all source groups.
99    pub fn clear_accept_group(mut self) -> Self {
100        self.accept_group = None;
101        self
102    }
103
104    /// Set payload acceptance predicate.
105    pub fn can_accept(mut self, accept: PayloadAcceptFn) -> Self {
106        self.can_accept = Some(accept);
107        self
108    }
109
110    /// Set payload acceptance predicate from closure.
111    pub fn can_accept_with(mut self, accept: impl Fn(&dyn DragPayload) -> bool + 'static) -> Self {
112        self.can_accept = Some(Rc::new(accept));
113        self
114    }
115
116    /// Remove payload acceptance predicate.
117    pub fn clear_can_accept(mut self) -> Self {
118        self.can_accept = None;
119        self
120    }
121
122    /// How to paint a compatible hover highlight (default: [`DropHighlight::None`]).
123    pub fn highlight(mut self, highlight: DropHighlight) -> Self {
124        self.highlight = highlight;
125        self
126    }
127
128    /// Style used by [`DropHighlight::Fill`], [`DropHighlight::Placeholder`], and [`DropHighlight::Overlay`].
129    pub fn highlight_style(mut self, style: Style) -> Self {
130        self.highlight_style = StyleSlot::Replace(style);
131        self
132    }
133
134    /// Extend the themed highlight style with the given style.
135    pub fn extend_highlight_style(mut self, style: Style) -> Self {
136        self.highlight_style = StyleSlot::Extend(style);
137        self
138    }
139
140    /// Inherit highlight style from the active theme.
141    pub fn inherit_highlight_style(mut self) -> Self {
142        self.highlight_style = StyleSlot::Inherit;
143        self
144    }
145
146    /// Set the highlight style slot directly.
147    pub fn highlight_style_slot(mut self, slot: StyleSlot) -> Self {
148        self.highlight_style = slot;
149        self
150    }
151
152    /// [`DropHighlight::Fill`] with the given style.
153    pub fn highlight_fill(mut self, style: Style) -> Self {
154        self.highlight = DropHighlight::Fill;
155        self.highlight_style = StyleSlot::Replace(style);
156        self
157    }
158
159    /// [`DropHighlight::Placeholder`] with the given style.
160    pub fn highlight_placeholder(mut self, style: Style) -> Self {
161        self.highlight = DropHighlight::Placeholder;
162        self.highlight_style = StyleSlot::Replace(style);
163        self
164    }
165
166    /// [`DropHighlight::Overlay`] with the given style (drawn after children).
167    pub fn highlight_overlay(mut self, style: Style) -> Self {
168        self.highlight = DropHighlight::Overlay;
169        self.highlight_style = StyleSlot::Replace(style);
170        self
171    }
172
173    /// What to render at this target while a compatible [`crate::prelude::DragPreview::SourceSnapshot`] drag hovers.
174    pub fn drop_slot(mut self, slot: DropSlot) -> Self {
175        self.drop_slot = slot;
176        self
177    }
178
179    /// Render the dragged source's snapshot in-place; floating cursor preview is suppressed.
180    /// Shorthand for `drop_slot(DropSlot::SourcePreview)`.
181    pub fn drop_slot_source_preview(mut self) -> Self {
182        self.drop_slot = DropSlot::SourcePreview;
183        self
184    }
185
186    /// Enable or disable this drop target.
187    pub fn enabled(mut self, enabled: bool) -> Self {
188        self.enabled = enabled;
189        self
190    }
191}
192
193impl From<DropTarget> for Element {
194    fn from(value: DropTarget) -> Self {
195        let (min_w, min_h) = measure_drop_target(&value, None, None);
196        Element::new(ElementKind::DropTarget(value)).with_layout(
197            LayoutConstraints::default()
198                .min_width(Length::Px(min_w))
199                .min_height(Length::Px(min_h)),
200        )
201    }
202}
203
204impl LayoutHash for DropTarget {
205    fn layout_hash(
206        &self,
207        hasher: &mut impl std::hash::Hasher,
208        recurse: &dyn Fn(&Element) -> Option<u64>,
209    ) -> Option<()> {
210        self.enabled.hash(hasher);
211        self.on_drag_over.is_some().hash(hasher);
212        self.on_drag_leave.is_some().hash(hasher);
213        self.on_drop.is_some().hash(hasher);
214        self.accept_group.hash(hasher);
215        self.can_accept.is_some().hash(hasher);
216        self.highlight.hash(hasher);
217        self.highlight_style.hash(hasher);
218        self.drop_slot.hash(hasher);
219        if let Some(child) = self.child.as_ref() {
220            recurse(child.as_ref())?.hash(hasher);
221        }
222        Some(())
223    }
224}