teksilo_core/drag_state.rs
1// SPDX-License-Identifier: MPL-2.0
2// SPDX-FileCopyrightText: 2026 FernTech
3
4//! Drag session state and drop feedback types.
5//!
6//! A `DragSession` is created when a widget calls `EventContext::start_drag()`
7//! and lives on the `WidgetTree` until the drag completes or is cancelled.
8
9use teksilo_canvas::{Point, Rect};
10use teksilo_tokens::Color;
11
12use crate::drag_payload::DragPayload;
13use crate::widget_id::WidgetId;
14
15/// A drop target's response to a drag hovering over it.
16///
17/// The variant decides **hover bubbling**: a target that returns
18/// [`NoFeedback`](Self::NoFeedback) does **not** engage the payload, so the
19/// drag passes through to the next drop target up the tree. Any *engaging*
20/// variant ([`Accept`](Self::Accept), [`InsertionLine`](Self::InsertionLine),
21/// [`HighlightRect`](Self::HighlightRect)) stops the bubble and **settles the
22/// drop target** for the rest of the drag.
23///
24/// On release, `on_drop` runs once on that settled target; its returned `bool`
25/// reports acceptance to the source via `DropOutcome::InApp { accepted }` — it
26/// does **not** re-bubble to another target when it returns `false` (the target
27/// was already chosen during the hover phase).
28#[derive(Debug, Clone)]
29pub enum DropFeedback {
30 /// A horizontal line at the given Y coordinate, spanning the given width.
31 /// Used for insertion between list items. Engages the target.
32 InsertionLine { y: f32, width: f32 },
33 /// A highlighted rectangle. Used for container/folder drops. Engages the
34 /// target.
35 HighlightRect { rect: Rect, color: Color },
36 /// Accepted, but the target draws its own visual (signal-driven, e.g.
37 /// `DropTarget`'s `is_targeted` border) — the framework renders nothing
38 /// extra. Engages the target (stops bubbling), distinct from `NoFeedback`.
39 Accept,
40 /// The payload is not accepted by this target — the drag bubbles to the
41 /// next drop target up the tree (hover phase). The framework renders
42 /// nothing.
43 NoFeedback,
44}
45
46impl DropFeedback {
47 /// Whether this response engages the payload (stops drop-target bubbling).
48 /// Everything except [`NoFeedback`](Self::NoFeedback) engages.
49 pub fn is_engaged(&self) -> bool {
50 !matches!(self, DropFeedback::NoFeedback)
51 }
52}
53
54/// Active drag-and-drop session state, stored on the `WidgetTree`.
55pub(crate) struct DragSession {
56 /// The data being dragged.
57 pub payload: DragPayload,
58 /// The widget that initiated the drag. `None` for external (OS) drags,
59 /// which have no in-app source widget.
60 pub source_widget: Option<WidgetId>,
61 /// Whether this session was started by an external (OS) drag.
62 pub is_external: bool,
63 /// Current pointer position during drag.
64 pub current_position: Point,
65 /// The widget currently under the pointer that accepts this payload, if any.
66 pub current_target: Option<WidgetId>,
67 /// Visual feedback from the current drop target.
68 pub feedback: DropFeedback,
69 /// Widget ID of the preview overlay content (if any).
70 pub preview_content_id: Option<WidgetId>,
71 /// Overlay ID for the preview (if any).
72 pub preview_overlay_id: Option<crate::overlay::OverlayId>,
73}
74
75impl std::fmt::Debug for DragSession {
76 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
77 f.debug_struct("DragSession")
78 .field("source_widget", &self.source_widget)
79 .field("is_external", &self.is_external)
80 .field("current_position", &self.current_position)
81 .field("current_target", &self.current_target)
82 .field("feedback", &self.feedback)
83 .finish()
84 }
85}