Fix drag-out giving live drags to OLE: prove the pointer left REAPER
OffReaper leaves the surface vocabulary; OsHandoff now gates on a window-ownership proof resolved in the shell, so no in-REAPER surface can reach DoDragDrop.
This commit is contained in:
@@ -88,7 +88,9 @@ L7 sub-pass, 2026-07-27):
|
||||
between evaluations. That is what makes every transition reversible and what
|
||||
makes drag speed (WM_MOUSEMOVE coalescing) unable to change an outcome. Leaving
|
||||
REAPER entirely is the one irreversible transition, because the OS hand-off goes
|
||||
modal. A first-move class lock and a drag-lifetime "cannot hand off" latch both
|
||||
modal — so it must be PROVEN (a window-ownership test in the shell), never inferred
|
||||
from a hit-test token REAPER could not name; an unrecognised surface *inside* REAPER
|
||||
refuses. A first-move class lock and a drag-lifetime "cannot hand off" latch both
|
||||
existed here and were removed — do not reintroduce either.
|
||||
|
||||
## Modules
|
||||
@@ -97,7 +99,7 @@ L7 sub-pass, 2026-07-27):
|
||||
- `bank_grid` — REAPER-free grid layout, selection, keyboard-nav, and thumbnail-cache-key logic for the docked bank panel.
|
||||
- `tab_strip` — REAPER-free scrollable tab-strip layout + hit-test for the named-banks strip.
|
||||
- `prune_button` — pure layout/hit-test for the `bank_panel` footer Prune button.
|
||||
- `drag_out` — the pure drag-out gesture law plus path-list assembly. Owns the `ReaperSurface` vocabulary (OffReaper / TrackPanel / FxSurface / FxEmbed / Arrange / Other — `core/wire/instrument_drop` classifies REAPER's info token INTO it), `decideDropClass` (surface × single-vs-multi payload → Internal / InstrumentDrop / ArrangeInsert / Refuse / OsHandoff / None), and `cueForDropClass`. **No `DropClass` means "nothing happens"**: a surface with no defined outcome for the payload resolves to `Refuse`, which the shell shows as a cursor, so "no silent no-op release" is a property of the enumeration rather than of any call site.
|
||||
- `drag_out` — the pure drag-out gesture law plus path-list assembly. Owns the `ReaperSurface` vocabulary (TrackPanel / FxSurface / FxEmbed / Arrange / Other — `core/wire/instrument_drop` classifies REAPER's info token INTO it; there is deliberately no off-REAPER member, since no token can carry that fact), `decideDropClass` (surface × single-vs-multi payload → Internal / InstrumentDrop / ArrangeInsert / Refuse / OsHandoff / None), and `cueForDropClass`. `OsHandoff` is reachable ONLY through `DropContext::pointerOffHost`, a positive window-ownership proof the shell owes the law (`drag_out_win::pointerOverHostWindow`), gated ahead of the surface switch — so no reading of REAPER's hit-test can give a live drag away to OLE. **No `DropClass` means "nothing happens"**: a surface with no defined outcome for the payload resolves to `Refuse`, which the shell shows as a cursor, so "no silent no-op release" is a property of the enumeration rather than of any call site.
|
||||
- `theme` — pure palette module: role→color mapping, REAPER-grey neutral ladder + the pastel accent system, the keyboard strip's spectral ramp, WCAG contrast-floor helpers + `compositeOver` (the effective color of a translucent fill, so alpha overlays are testable). Only the ramp's MID stop is its own constant; lo/hi are still aliases of `accent/primary`/`accent/tertiary`, so a categorical accent move CAN still reorder the ramp — `testSpectralRampLuminanceIsMonotonic` is the build-time catch, not the structure.
|
||||
- `component_geometry` — pure button/slider/list-row geometry + hover hit-test helpers.
|
||||
- `action_bar` — pure task-grouped action-bar layout/hit-test: clusters (Capture / Placement / Maintenance / Tagging / Switching).
|
||||
|
||||
@@ -24,10 +24,12 @@ DropClass decideDropClass(int px, int py, const PanelClientRect& client,
|
||||
if (!ctx.drag.dragging || !ctx.drag.hasArmedSamples) return DropClass::None;
|
||||
if (insideClient(px, py, client)) return DropClass::Internal;
|
||||
|
||||
switch (ctx.surface) {
|
||||
case ReaperSurface::OffReaper:
|
||||
return DropClass::OsHandoff;
|
||||
// The hand-off gate sits AHEAD of the surface switch and is the only route to OsHandoff, so
|
||||
// no reading of REAPER's hit-test — recognised, unrecognised, or empty — can reach the modal
|
||||
// OLE loop while the pointer is still over a host window.
|
||||
if (ctx.pointerOffHost) return DropClass::OsHandoff;
|
||||
|
||||
switch (ctx.surface) {
|
||||
case ReaperSurface::TrackPanel:
|
||||
case ReaperSurface::FxSurface:
|
||||
// One instance holds one capture, so a multi payload names no instrument to build —
|
||||
|
||||
+18
-7
@@ -6,8 +6,10 @@
|
||||
//
|
||||
// THE LAW: the class is resolved from what is under the cursor on EVERY move; every transition
|
||||
// is reversible until release or until the pointer leaves REAPER entirely; the OS hand-off is
|
||||
// reserved for leaving REAPER, and every REAPER-internal target executes natively on release.
|
||||
// Do not reintroduce a first-move class lock or a drag-lifetime "blocked" latch.
|
||||
// reserved for leaving REAPER — which the shell must PROVE (DropContext::pointerOffHost), never
|
||||
// infer from a hit-test token it could not name — and every REAPER-internal target executes
|
||||
// natively on release. Do not reintroduce a first-move class lock or a drag-lifetime "blocked"
|
||||
// latch.
|
||||
//
|
||||
// Path-list assembly: turns armed sample ids into the absolute path list an OS drop carries
|
||||
// (Windows CF_HDROP / macOS file-list pasteboard) — set algebra only; the shell resolves each id
|
||||
@@ -32,16 +34,18 @@ struct DragState {
|
||||
};
|
||||
|
||||
// What REAPER reports under the pointer, reduced to the surfaces the law distinguishes.
|
||||
// Produced from GetThingFromPoint's info token by wire::classifyReaperSurface.
|
||||
// Produced from GetThingFromPoint's info token by wire::classifyReaperSurface. There is
|
||||
// deliberately NO off-REAPER member: this vocabulary describes what REAPER's own hit-test
|
||||
// named, and "the pointer left REAPER" is a window-ownership fact no token can carry —
|
||||
// it enters through DropContext::pointerOffHost instead.
|
||||
enum class ReaperSurface {
|
||||
OffReaper, // not over REAPER at all — the one irreversible exit
|
||||
TrackPanel, // TCP/MCP, ANY sub-element: the WHOLE panel is the instrument hotspot
|
||||
FxSurface, // fx_* — the FX chain and floating-FX windows
|
||||
FxEmbed, // tcp.fxembed / mcp.fxembed — an instance already draws there
|
||||
Arrange, // the timeline
|
||||
Other, // ruler, transport, spacers, docker chrome, unknown future tokens
|
||||
Other, // ruler, transport, spacers, docker chrome, unknown/empty future tokens
|
||||
Count, // sentinel, NOT a real surface — tests/test_drag_out.cpp's kAllSurfaces is
|
||||
// pinned against this via static_assert so a 7th surface can't silently skip
|
||||
// pinned against this via static_assert so a 6th surface can't silently skip
|
||||
// the exhaustiveness matrix
|
||||
};
|
||||
|
||||
@@ -62,8 +66,15 @@ enum class DropClass {
|
||||
struct DropContext {
|
||||
DragState drag;
|
||||
bool singlePayload = false; // payload holds EXACTLY one capture (arms InstrumentDrop)
|
||||
ReaperSurface surface = ReaperSurface::OffReaper; // only read outside the client rect
|
||||
ReaperSurface surface = ReaperSurface::Other; // only read outside the client rect
|
||||
bool haveTrack = false; // GetThingFromPoint returned a non-null MediaTrack*
|
||||
// The ONE gate on the irreversible OS hand-off, and the only member whose true value can
|
||||
// produce it. It is a POSITIVE proof the shell owes the law — a window-ownership test
|
||||
// (drag_out_win::pointerOverHostWindow), not "the hit-test named nothing I recognise",
|
||||
// which is equally true of the transport, the toolbar and the docker chrome. Defaults
|
||||
// false so an unset context stays inside REAPER: the failure mode of forgetting to
|
||||
// resolve it must be a refusal, never a drag given away to OLE.
|
||||
bool pointerOffHost = false;
|
||||
};
|
||||
|
||||
// Resolves the class for a drag at pointer (px, py) over `client`. Position-and-context only,
|
||||
|
||||
Reference in New Issue
Block a user