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::pointer::PointerInfo;
14use crate::widget_id::WidgetId;
15
16/// A drop target's response to a drag hovering over it.
17///
18/// The variant decides **hover bubbling**: a target that returns
19/// [`NoFeedback`](Self::NoFeedback) does **not** engage the payload, so the
20/// drag passes through to the next drop target up the tree. Any *engaging*
21/// variant ([`Accept`](Self::Accept), [`InsertionLine`](Self::InsertionLine),
22/// [`HighlightRect`](Self::HighlightRect)) stops the bubble and **settles the
23/// drop target** for the rest of the drag.
24///
25/// On release, `on_drop` runs once on that settled target; its returned `bool`
26/// reports acceptance to the source via `DropOutcome::InApp { accepted }` — it
27/// does **not** re-bubble to another target when it returns `false` (the target
28/// was already chosen during the hover phase).
29#[derive(Debug, Clone)]
30pub enum DropFeedback {
31 /// A horizontal line at the given Y coordinate, spanning the given width.
32 /// Used for insertion between list items. Engages the target.
33 InsertionLine { y: f32, width: f32 },
34 /// A highlighted rectangle. Used for container/folder drops. Engages the
35 /// target.
36 HighlightRect { rect: Rect, color: Color },
37 /// Accepted, but the target draws its own visual (signal-driven, e.g.
38 /// `DropTarget`'s `is_targeted` border) — the framework renders nothing
39 /// extra. Engages the target (stops bubbling), distinct from `NoFeedback`.
40 Accept,
41 /// The payload is not accepted by this target — the drag bubbles to the
42 /// next drop target up the tree (hover phase). The framework renders
43 /// nothing.
44 NoFeedback,
45}
46
47impl DropFeedback {
48 /// Whether this response engages the payload (stops drop-target bubbling).
49 /// Everything except [`NoFeedback`](Self::NoFeedback) engages.
50 pub fn is_engaged(&self) -> bool {
51 !matches!(self, DropFeedback::NoFeedback)
52 }
53}
54
55/// Active drag-and-drop session state, stored on the `WidgetTree`.
56pub(crate) struct DragSession {
57 /// The data being dragged.
58 pub payload: DragPayload,
59 /// The pointer carrying the drag.
60 ///
61 /// Recorded at the drag's start and never revised, because a drag belongs
62 /// to one pointer for its whole life: the press that armed it is the press
63 /// that ends it.
64 ///
65 /// It is here because **most of a drag runs outside any pointer dispatch**.
66 /// `on_drag_tick` fires from `WidgetTree::layout`, and an OS drag's phases
67 /// arrive from the platform's own thread — at both of those points
68 /// `current_input` holds its default, a mouse, so a handler asking the
69 /// context which device it was serving got the wrong answer for the whole
70 /// of a finger drag. The tree installs this pointer as the input snapshot
71 /// around those dispatches instead. See
72 /// `WidgetTree::process_drag_tick`.
73 pub pointer: PointerInfo,
74 /// The widget that initiated the drag. `None` for external (OS) drags,
75 /// which have no in-app source widget.
76 pub source_widget: Option<WidgetId>,
77 /// Whether this session was started by an external (OS) drag.
78 pub is_external: bool,
79 /// Current pointer position during drag.
80 pub current_position: Point,
81 /// The widget currently under the pointer that accepts this payload, if any.
82 pub current_target: Option<WidgetId>,
83 /// Visual feedback from the current drop target.
84 pub feedback: DropFeedback,
85 /// Widget ID of the preview overlay content (if any).
86 pub preview_content_id: Option<WidgetId>,
87 /// Overlay ID for the preview (if any).
88 pub preview_overlay_id: Option<crate::overlay::OverlayId>,
89}
90
91impl std::fmt::Debug for DragSession {
92 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
93 f.debug_struct("DragSession")
94 .field("pointer", &self.pointer.kind)
95 .field("source_widget", &self.source_widget)
96 .field("is_external", &self.is_external)
97 .field("current_position", &self.current_position)
98 .field("current_target", &self.current_target)
99 .field("feedback", &self.feedback)
100 .finish()
101 }
102}