Skip to content
This is the documentation for xc 0.8. The current documentation describes the newest version.

UXDragSession

UXDragSession is one drag in flight. It carries the payload, remembers what the source permits, and arbitrates the operation with whatever the pointer is over.

#use <UXKit> // or #import "UXDragSession.xc"
UXPasteboard* pb = new UXPasteboard();
pb.writeText(self.label());
UXDragSession* s = UXDragSession.begin((Object*)self, pb, UX_DRAG_COPY);

A session takes three things: who started it, what is being carried, and which operations are allowed.

#define UX_DRAG_NONE 0
#define UX_DRAG_COPY 1
#define UX_DRAG_MOVE 2
#define UX_DRAG_LINK 4

allowedOps is a bitmask, so a source that accepts either passes UX_DRAG_COPY | UX_DRAG_MOVE.

A drag does not use the clipboard. Dragging something never destroys what the user copied earlier.

i32 enter(UXDragDestination* target) {
i32 want = target.dragEntered(self);
operation = want & allowedOps; // masked to what the SOURCE permits
return operation;
}

The destination says what it wants, the source declared what it allows, and the result is the intersection. Neither end needs to know the other’s policy: a view that would move something still only copies if the source offered copy alone.

bool canDrop(void) // the negotiated op is non-empty
bool deliver(UXDragDestination* target) // refuses when it is not

deliver does not call dragPerform when the negotiated operation is UX_DRAG_NONE, so a destination is never asked to consume something it declined.

A toolkit-drawn drag is modal. The driver’s trackDragStep follows the pointer and returns when the button is released, emitting no events, so the source runs the loop:

void mouseDown(UXEvent* e) {
UXPasteboard* pb = new UXPasteboard();
pb.writeText(self.label());
UXDragSession* s = UXDragSession.begin((Object*)self, pb, UX_DRAG_COPY);
i32 x = e.x; i32 y = e.y;
while (gDriver.trackDragStep(&x, &y) != 0) {
well.hot = self.over(well, x, y) && (s.enter(well) != UX_DRAG_NONE);
self.setNeedsDisplay();
if (gApp != (UXApplication*)0) { gApp.displayIfNeeded(); }
}
if (self.over(well, x, y) && s.canDrop()) { s.deliver(well); }
}

In this loop:

  • enter is called per step, which keeps the highlight under the cursor accurate. For this reason dragEntered must be cheap.
  • The displayIfNeeded inside the loop makes the feedback appear, since no event arrives to trigger a redraw.
  • The drop is delivered after the loop, guarded by canDrop.

begin · enter · canDrop · deliver · hasType · stringForType · setHotSpot

static UXDragSession* begin(Object* source, UXPasteboard* pb, i32 allowedOps)
i32 enter(UXDragDestination* target)

Asks a candidate what it would do, masks the answer to the allowed set, remembers the result and returns it. Call it each step while the pointer is over a candidate.

bool canDrop(void)

Whether the last enter negotiated a non-empty operation. The drawing code uses it to decide whether to highlight.

bool deliver(UXDragDestination* target)

Performs the drop. Returns whether the destination accepted it. This distinguishes “dropped on something that declined” from “dropped successfully”, which matters for a UX_DRAG_MOVE that would otherwise delete the original.

bool hasType(u8* type)
u8* stringForType(u8* type)

Passthroughs to the carried pasteboard, so a destination does not have to reach through the session to get at it. Null-safe if there is no pasteboard.

void setHotSpot(i16 x, i16 y)

Where the pointer sits within the dragged image. A drag that began 12 pixels into a tile keeps that offset instead of snapping the corner to the cursor. A move in an editor needs the same correction. Without it the dragged item jumps on mouse-down, and the drag feels broken.

Object* source; // who started it
UXPasteboard* pasteboard; // what is being carried
i32 allowedOps; // the bitmask the source permits
i32 operation; // the last negotiated op, masked
i16 hotX; i16 hotY;