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::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}