Pulp 1.88.0
Cross-platform audio plugin framework
Loading...
Searching...
No Matches
drag_drop.hpp
Go to the documentation of this file.
1#pragma once
2
4#include <pulp/view/view.hpp>
5#include <cstdint>
6#include <functional>
7#include <string>
8#include <vector>
9
10namespace pulp::view {
11
12// ── Drop data types ──────────────────────────────────────────────────────────
13
14struct DropData {
15 enum class Type { files, text, custom };
16
18 std::vector<std::string> file_paths; // For file drops
19 std::string text; // For text drops
20 std::string custom_type; // For custom data type
21 std::vector<std::uint8_t> custom_data; // For custom binary data
22};
23
24// ── Drop receiver (view-tree drop handler) ───────────────────────────────────
25//
26// The extensible interface a View subclass mixes in to accept native drops
27// routed by the dispatch_* functions below:
28//
29// class MyZone : public View, public DropReceiver { ... };
30//
31// The dispatch core finds receivers via dynamic_cast as it walks the view tree,
32// so a new drop-aware widget needs NO change to the dispatch core — that is the
33// extension point. This is the rich, typed surface (FileDropZone implements it).
34// For lightweight cases a view can instead set the View::on_drop std::function
35// (the JS-bridge convenience surface). See the dispatch contract below for how
36// the two interact.
38public:
39 virtual ~DropReceiver() = default;
40
41 // A drag carrying `data` is hovering at `local` (this receiver's local
42 // coordinates). Return true to claim the hover (drives highlight state).
43 // Called on enter and on each move while over this receiver, so it must be
44 // idempotent. Default: decline.
45 virtual bool accept_drag(const DropData& data, Point local) {
46 (void)data;
47 (void)local;
48 return false;
49 }
50
51 // The drag left this receiver (moved away, exited the window, or dropped).
52 // Clear any highlight set by accept_drag.
53 virtual void leave_drag() {}
54
55 // Commit a drop at `local`. Return true if handled — a true return CONSUMES
56 // the drop (dispatch stops bubbling); false lets it fall through to an outer
57 // receiver / View::on_drop.
58 virtual bool accept_drop(const DropData& data, Point local) = 0;
59};
60
61// ── Legacy platform-registration drop target (macOS NSView) ──────────────────
62//
63// SEPARATE CONCERN from DropReceiver above. This is the platform-side
64// registration layer: register_drop_target() tells a native OS view (today only
65// the macOS NSView path in drag_drop_mac.mm) to advertise dragged types. It does
66// NOT route into the Pulp view tree. The dispatch_* functions below are the
67// convergence point for native delivery and DropReceiver. New code should
68// implement DropReceiver, not DropTarget.
70public:
71 virtual ~DropTarget() = default;
72
73 // Called when a drag enters the view. Return true to accept.
74 virtual bool on_drag_enter(const DropData& data, Point position) { (void)data; (void)position; return false; }
75
76 // Called as the drag moves over the view
77 virtual void on_drag_move(Point position) { (void)position; }
78
79 // Called when the drag leaves the view
80 virtual void on_drag_exit() {}
81
82 // Called when the user drops. Return true if handled.
83 virtual bool on_drop(const DropData& data, Point position) { (void)data; (void)position; return false; }
84};
85
86// ── Drag source ──────────────────────────────────────────────────────────────
87
88// Initiate a drag operation from a view
89struct DragSource {
91 // Platform-specific drag initiation (macOS: NSDraggingSession)
92 // Called by the view system when the user starts dragging
93};
94
95// ── Outbound file drag (view-tree → host) ────────────────────────────────────
96//
97// A request to drag one or more EXISTING on-disk files out of a Pulp view and
98// into another application (e.g. drop a rendered/bounced .wav onto a DAW
99// timeline). The files must already exist at `file_paths` when the drag begins
100// — this is a plain file-URL drag, not a lazy file-promise, so the caller
101// writes the file (typically to a temp dir) first.
102//
103// Driven from a pointer interaction: `View::start_file_drag()` reaches the
104// view's attached host, hands the request to the platform backend
105// (`begin_file_drag` below), and the OS takes over the drag. On macOS the
106// backend begins an `NSDraggingSession` on the host NSView using the current
107// mouse event, so this must be called synchronously from within a
108// mouse-down/drag handler (where `NSApp.currentEvent` is the initiating event).
110 std::vector<std::string> file_paths; // existing files to drag (>= 1 required)
111 Point root_position{}; // drag origin in root/view coords
112 std::string display_name; // optional label for the drag image
113};
114
115// Begin a native outbound file drag from `native_view` (macOS: an NSView*).
116// Returns true if the platform started a drag session. Returns false when the
117// platform is unsupported, the handle is null, there are no files, or there is
118// no active mouse event to anchor the drag. Implemented per-platform alongside
119// register_drop_target (macOS, Windows, and Linux/X11 today; a no-op stub
120// elsewhere).
121bool begin_file_drag(void* native_view, const FileDragRequest& request);
122
123// Process-global outbound file-drag backend for HOSTLESS platforms — Android,
124// where the view tree is a bare root View with no WindowHost / PluginViewHost,
125// so neither the host virtual nor the native-handle free function above can be
126// reached. The platform layer registers a backend (Android: a JNI up-call into
127// Kotlin's View.startDragAndDrop); View::start_file_drag() invokes it only as a
128// last resort, after the host paths decline. Returns the previous backend so a
129// caller can restore it. Pass nullptr to clear.
130using FileDragBackend = std::function<bool(const FileDragRequest&)>;
132
133// Invoke the registered global backend; returns false if none is set. Used by
134// View::start_file_drag() — application code should call View::start_file_drag.
136
137// ── Drag-drop registration ──────────────────────────────────────────────────
138
139// Register/unregister a native view for file drops (macOS NSView). See the
140// DropTarget note above — this is the platform-registration layer, not the
141// view-tree dispatch.
142bool register_drop_target(void* native_view, DropTarget& target);
143void unregister_drop_target(void* native_view);
144
145// ── Native → view-tree drop dispatch ─────────────────────────────────────────
146//
147// The bridge a platform drag-drop backend (SDL3 standalone drop events, Windows
148// IDropTarget, Linux XDND, macOS NSDraggingDestination) calls to route a native
149// drop into a Pulp view tree. Each function hit-tests `root` at the given
150// root-space point, then walks from the hit target up to `root` — exactly like
151// View::simulate_click resolves a click target.
152//
153// Dispatch contract (first-handler-wins, no double-dispatch): at each view from
154// the deepest hit up to the root, in order:
155// 1. if the view is a DropReceiver and accept_drop() returns true → consumed.
156// 2. else if the view has a View::on_drop std::function set → fire it,
157// consumed. on_drop is the JS-bridge convenience surface; `type` is
158// "file"/"text"/"custom" and a multi-file drop fires it once per path,
159// with x/y in that view's local coordinates.
160// The first view that consumes the drop ends the walk; a view never receives the
161// drop through both surfaces.
162//
163// Coordinate space: `root_pos` is in `root`'s local (window/root) coordinates;
164// the platform backend owns any window→root viewport transform (e.g. HiDPI /
165// design-viewport scale) before calling in.
166//
167// Threading: UI thread only (mirrors the rest of the view input path).
168//
169// Hover state lives in a DragSession the *caller* owns — one per platform
170// backend / window, NOT a process global — so concurrent drags on separate
171// windows can't corrupt each other and the tracked pointer's lifetime is scoped
172// to the backend that brackets the drag (enter … (move)* … exit|drop).
173
174// Per-drag hover state owned by the platform backend (a member of the window
175// host / drop target). Reset between drags happens automatically via exit/drop.
176namespace detail {
177struct DragSessionAccess;
178
180private:
181 ViewCapture identity_;
182 // Last compatibility-pointer value imported into identity_. A pointer that
183 // still equals this value after identity_ expires is stale and must not be
184 // rebound merely because an allocator reused the address.
185 DropReceiver* observed_public_hover_ = nullptr;
186 std::uint64_t generation_ = 0;
187
188 friend struct DragSessionAccess;
189};
190}
191
193 // Preserve the installed public pointer surface exactly. Dispatch resolves
194 // this compatibility pointer through the current root before every
195 // dereference. A newly observed value is imported once; an expired capture
196 // is never rebound from an unchanged address.
197 DropReceiver* hover = nullptr;
198 // Public only so DragSession remains an aggregate and preserves designated
199 // initialization of `.hover`; bookkeeping is private to dispatch.
201
202};
203
204// Hover lifecycle for visual feedback (DropReceiver highlight). Returns true if a
205// receiver claims the drag, or a View::on_drop handler is under the point.
206bool dispatch_drag_enter(View& root, DragSession& session, const DropData& data,
207 Point root_pos);
208void dispatch_drag_move(View& root, DragSession& session, const DropData& data,
209 Point root_pos);
210void dispatch_drag_exit(View& root, DragSession& session);
211
212// Commit a drop. Returns true if a view consumed it. Also clears any hover state.
213bool dispatch_drop(View& root, DragSession& session, const DropData& data,
214 Point root_pos);
215
216// ── In-tree drag source ──────────────────────────────────────────────────────
217//
218// Everything above routes a drag that ORIGINATED OUTSIDE the view tree (the OS
219// handed it to a platform backend). An ActiveDrag is the other direction: a drag
220// that starts INSIDE the tree and stays there.
221//
222// The payload never touches the OS pasteboard, so it can be anything a DropData
223// can carry — including a `custom_data` blob — and it arrives at the receiver
224// byte-for-byte. (An OS-mediated drag can only carry serialized types, which is
225// why code that needs to hand a live object across a drag usually ends up
226// parking it in a side channel and dragging a token. An in-tree drag does not
227// need that.)
228//
229// The SOURCE view drives the gesture, because the source view is the one holding
230// the pointer capture: it calls start_drag() on press-and-move, drag_to() on each
231// motion tick, and drop_here() on release. The tree does the rest — hit-testing
232// receivers under the pointer, firing accept_drag()/leave_drag() for highlight,
233// and committing through the same dispatch core a native drop uses.
236 View* source = nullptr;
238 bool active = false;
239};
240
241} // namespace pulp::view
Definition drag_drop.hpp:37
virtual bool accept_drag(const DropData &data, Point local)
Definition drag_drop.hpp:45
virtual ~DropReceiver()=default
virtual bool accept_drop(const DropData &data, Point local)=0
virtual void leave_drag()
Definition drag_drop.hpp:53
Definition drag_drop.hpp:69
virtual ~DropTarget()=default
virtual bool on_drop(const DropData &data, Point position)
Definition drag_drop.hpp:83
virtual void on_drag_move(Point position)
Definition drag_drop.hpp:77
virtual void on_drag_exit()
Definition drag_drop.hpp:80
virtual bool on_drag_enter(const DropData &data, Point position)
Definition drag_drop.hpp:74
Identity-safe reference to a View captured across native event boundaries.
Definition view.hpp:56
Definition view.hpp:73
Definition drag_drop.hpp:179
friend struct DragSessionAccess
Definition drag_drop.hpp:188
Definition accessibility_provider.hpp:65
Hover-cursor tracking for platform window hosts.
Definition fwd.hpp:27
bool dispatch_drop(View &root, DragSession &session, const DropData &data, Point root_pos)
bool dispatch_drag_enter(View &root, DragSession &session, const DropData &data, Point root_pos)
FileDragBackend set_file_drag_backend(FileDragBackend backend)
void unregister_drop_target(void *native_view)
std::function< bool(const FileDragRequest &)> FileDragBackend
Definition drag_drop.hpp:130
bool begin_file_drag(void *native_view, const FileDragRequest &request)
void dispatch_drag_exit(View &root, DragSession &session)
bool invoke_file_drag_backend(const FileDragRequest &request)
bool register_drop_target(void *native_view, DropTarget &target)
void dispatch_drag_move(View &root, DragSession &session, const DropData &data, Point root_pos)
Definition drag_drop.hpp:234
DropData data
Definition drag_drop.hpp:235
DragSession session
Definition drag_drop.hpp:237
bool active
Definition drag_drop.hpp:238
View * source
Definition drag_drop.hpp:236
Definition drag_drop.hpp:192
DropReceiver * hover
Definition drag_drop.hpp:197
detail::DragSessionState state_
Definition drag_drop.hpp:200
Definition drag_drop.hpp:89
DropData data
Definition drag_drop.hpp:90
Definition drag_drop.hpp:14
std::vector< std::string > file_paths
Definition drag_drop.hpp:18
Type
Definition drag_drop.hpp:15
@ files
Definition drag_drop.hpp:15
@ custom
Definition drag_drop.hpp:15
std::vector< std::uint8_t > custom_data
Definition drag_drop.hpp:21
Type type
Definition drag_drop.hpp:17
std::string text
Definition drag_drop.hpp:19
std::string custom_type
Definition drag_drop.hpp:20
Definition drag_drop.hpp:109
std::vector< std::string > file_paths
Definition drag_drop.hpp:110
std::string display_name
Definition drag_drop.hpp:112
Point root_position
Definition drag_drop.hpp:111
Definition geometry.hpp:20