Skip to main content

teksilo_data/
dnd_types.rs

1// SPDX-License-Identifier: MPL-2.0
2// SPDX-FileCopyrightText: 2026 FernTech
3
4//! Shared capability types for the data-source drag-and-drop + lazy protocol.
5//!
6//! These types are the Teksilo-shaped equivalent of Qt's
7//! `flags`/`canDropMimeData`/`dropMimeData` (DnD validation) and
8//! `canFetchMore`/`fetchMore` (lazy loading), expressed as defaulted methods on
9//! [`ListDataSource`](crate::ListDataSource) and
10//! [`TreeDataSource`](crate::TreeDataSource). A source *owns* the answer to
11//! "may this drop happen?" (`can_accept`) and "apply the move" (`accept_drop`);
12//! the view merely renders the source's verdict and routes the commit. This is
13//! what lets an external source of truth (e.g. a Qleany entity store) drive a
14//! view without the view ever mutating a mirror model.
15//!
16//! ## Key types
17//!
18//! - [`ItemKey`] — blanket identity trait for any `Clone + Eq + Hash + Debug + 'static` type.
19//! - [`RowState`] — whether a lazy row's data is resident (`Ready`) or still loading (`Loading`).
20//! - [`DragEligibility`] — per-row drag gate returned by `ListDataSource::drag`.
21//! - [`DropPosition`] — where a drop lands relative to the target row.
22//! - [`DragSource`] — who is dragging: the same view (intra-view reorder) or a foreign view/OS drop.
23//! - [`DropQuery`] / [`DropResponse`] — hover-time can-I-drop? query and verdict.
24//! - [`DropCommit`] — the committed drop handed to `accept_drop`.
25//!
26//! ```ignore
27//! // Example: implementing can_accept for a custom ListDataSource
28//! fn can_accept(&self, query: &teksilo_data::DropQuery<'_, usize>) -> teksilo_data::DropResponse {
29//!     match &query.source {
30//!         teksilo_data::DragSource::SameView { .. } => teksilo_data::DropResponse::Accept,
31//!         teksilo_data::DragSource::Foreign { .. } => teksilo_data::DropResponse::Reject,
32//!     }
33//! }
34//! ```
35
36use teksilo_core::DragPayload;
37
38/// A stable, hashable identity for a row/node. Blanket-implemented for every
39/// `Clone + Eq + Hash + Debug + 'static` type, so `usize`, `NodeId`, `i64`,
40/// `String`, `Uuid`, … all qualify with no extra work.
41///
42/// In-memory models use positional keys (`usize` for `ListModel`, `NodeId` for
43/// `TreeModel`); external sources use their own domain key (an entity id), which
44/// is exactly what removes the need to mirror them into a built-in model.
45pub trait ItemKey: Clone + Eq + std::hash::Hash + std::fmt::Debug + 'static {}
46impl<T: Clone + Eq + std::hash::Hash + std::fmt::Debug + 'static> ItemKey for T {}
47
48/// Whether a realized row's data is resident yet. A windowed/lazy source returns
49/// `Loading` for indices outside its resident window; the view renders a
50/// placeholder skeleton for those and calls `request_window` to pull them.
51#[derive(Debug, Clone, Copy, PartialEq, Eq)]
52pub enum RowState {
53    /// Item data is resident; `with_item`/`with_entry` returns `Some`.
54    Ready,
55    /// The row exists (counts against `len`/`visible_count`) but its data is not
56    /// yet loaded; `with_item`/`with_entry` returns `None`.
57    Loading,
58}
59
60/// Where, relative to a target row, a drop lands. `Into` (reparent) is only
61/// meaningful for trees; flat lists reject it.
62#[derive(Debug, Clone, Copy, PartialEq, Eq)]
63pub enum DropPosition {
64    /// Immediately before the target (sibling, same level).
65    Before,
66    /// As a child of the target (reparent — trees only).
67    Into,
68    /// Immediately after the target (sibling, same level).
69    After,
70}
71
72/// Whether a row may begin a drag at all (the per-item transferable gate, Qt's
73/// `Qt::ItemIsDragEnabled` / `TabBar`'s `with_transferable_predicate`).
74#[derive(Debug, Clone, Copy, PartialEq, Eq)]
75pub enum DragEligibility {
76    /// The row can be dragged.
77    CanDrag,
78    /// The row cannot be dragged (the gesture is suppressed).
79    NoDrag,
80}
81
82/// Who is dragging, from the receiving source's point of view.
83///
84/// `SameView` is an intra-view reorder identified by the dragged row's key.
85/// `Foreign` is everything else — an in-app drag from *another* view or an OS
86/// drop — carried as a type-erased [`DragPayload`] the source downcasts itself
87/// (e.g. a designer source downcasts to its palette-drop type, a list source to
88/// its item type, an OS drop to files). This single distinction is exactly what
89/// `TabBar` already encodes via its `source_bar_id`.
90pub enum DragSource<'a, K> {
91    /// An intra-view reorder; `key` identifies the dragged row.
92    SameView { key: K },
93    /// A drag from another view or the OS; downcast `payload` to interpret it.
94    Foreign { payload: &'a DragPayload },
95}
96
97/// A hover-time question posed to a source: "may `source` drop at `position`
98/// relative to `target`?" The source answers with a [`DropResponse`].
99pub struct DropQuery<'a, K> {
100    /// Who is dragging.
101    pub source: DragSource<'a, K>,
102    /// The row currently hovered.
103    pub target: K,
104    /// Where, relative to `target`, the drop would land.
105    pub position: DropPosition,
106}
107
108/// A source's verdict on a [`DropQuery`]. Drives the hover affordance and gates
109/// the commit.
110#[derive(Debug, Clone, Copy, PartialEq, Eq)]
111pub enum DropResponse {
112    /// Allowed: paint the insertion line / reparent box at this position.
113    Accept,
114    /// Forbidden: paint the no-drop affordance; the drop will be refused.
115    Reject,
116    /// Allowed, but only at a different position — the view snaps its indicator
117    /// to `.0` (e.g. a container that accepts children but not sibling reorder
118    /// redirects `Before`/`After` → `Into`).
119    Redirect(DropPosition),
120}
121
122/// A drop the user actually committed, handed to `accept_drop` to apply.
123pub struct DropCommit<'a, K> {
124    /// Who dragged.
125    pub source: DragSource<'a, K>,
126    /// The row dropped onto.
127    pub target: K,
128    /// Where, relative to `target`, the drop landed (after any `Redirect`).
129    pub position: DropPosition,
130}