Skip to main content

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}