Drag, drop & DOBJ¶
The picture under the pointer is feedback. The typed object is the event.
Good drag and drop feels physical, but its real job is semantic: move this paragraph, copy that chart, link this live range, put this file in the Trash. D/BASIC treats those as object operations rather than coordinates followed by a hopeful paste.
The same model works at three distances:
- inside one application, where stable object identities can stay local;
- between applications, where a typed transactional pasteboard carries representations; and
- across the host boundary, where the runner bridges those representations to macOS, Windows, Linux, or a browser.
At the rich end of that path is DOBJ, the D/OS object representation. DOBJ is the premier representation for structured domain objects: first in the producer's preference order when it preserves the real meaning, followed by honest fallbacks such as styled or plain text.
The NeXTSTEP lesson¶
NeXTSTEP made drag and drop feel deep because it was not a screenshot-moving trick. The dragging session was built around a pasteboard: a source could offer typed representations, a destination declared what it understood, and the destination acted on the data—not on the drag image.
D/OS carries that lineage forward in four rules:
- clipboard and drag/drop use one typed pasteboard substrate with different verbs;
- one item may offer several representations of the same meaning;
- source and destination negotiate type and operation before a drop commits; and
- object graphs cross as portable values, never as process-local pointers.
The historical through-line is visible in the original NeXTSTEP Concepts manual and AppKit's later documentation of pasteboard items with multiple representations and dragging sources. D/OS adds exact schema identity, revision binding, bounded carriers, transactional receipts, and fail-closed admission.
One pasteboard, two verbs¶
Copy/paste and drag/drop enter the same typed exchange system, but they do not trample each other:
- the General pasteboard holds ordinary clipboard publication;
- every drag gets a separate transient pasteboard identity;
- a committed item is immutable and generation-stamped; and
- ending a drag retires its transient item without replacing the clipboard.
An item may carry up to four producer-ordered representations. Common tags are:
| Tag | Meaning | Typical use |
|---|---|---|
DOBJ |
package-qualified D/OS object | range, chart, document block, style, scene object |
STYL |
canonical styled text | rich text that does not need a domain schema |
TEXT |
plain text | universal readable fallback |
PATH |
host-governed path reference | Filer and native OS exchange |
FILE |
file/content representation | import, attach, snapshot, or conversion |
Order is meaningful. A d/Calc selection might offer DOBJ first, then STYL, then tab-delimited TEXT. A target that understands the exact range schema receives the living structure; a plain editor receives the truthful text. The target cannot reorder the producer's declaration to smuggle in a weaker interpretation.
PATH and FILE enter the governed Files & File Manipulation boundary. Importing their bytes as a durable, revisioned document crosses deliberately into Database & Data Intents; a drag tag alone grants neither authority.
DOBJ: the premier domain object¶
DOBJ means D/OS object, but it is not “dump this class's memory.” It is a system-wide, language-neutral interchange contract shared by D/BASIC, native applications, floor runtimes, packages, pasteboards, and operating-system bridges.
Three related forms do the work:
| Form | Carries |
|---|---|
DSCH |
the canonical schema: fields, kinds, widths, and referenced types |
DOBJ |
a 64-byte envelope, canonical type name, identity, and value payload |
DGRF |
a bounded class-object graph with stable serialization identities and relationships |
A DOBJ type is identified by the installed package ID, kind, canonical type name, and SHA-256 of its canonical schema. The receiver resolves that exact identity before interpreting a byte. A four-letter DOBJ tag alone proves nothing.
Values, not machine artifacts¶
DOBJ preserves language meaning while removing runtime accidents:
INTEGER,LONG,DECIMAL,SINGLE, andDOUBLEhave canonical widths and byte order;- a
TYPEbecomes an exact packed value image; - a
CLASSbecomes a graph whose references are serialization IDs; - strings are counted bytes, not native pointers or NUL-terminated buffers;
- shared and cyclic relationships can be represented without duplicating an object; and
- no DBC handle, implementation reference, allocator token, window ID, callback, or host pointer crosses the boundary.
One admitted representation is bounded to 1 MiB, and one class graph to 256 objects. A smaller system may advertise a lower capacity. It may not silently truncate an object or reinterpret its schema.
Premier does not mean compulsory¶
DOBJ should be first when it is the most truthful expression of the selection. It should not be manufactured when the data is simply text, nor forced on a destination that does not know the schema.
| Source | Premier representation | Honest fallbacks |
|---|---|---|
| d/Calc range | range DOBJ | styled table, TSV text, snapshot |
| d/Write block | rich-block DOBJ | STYL, then TEXT |
| d/Present object | scene-fragment DOBJ | image snapshot, accessible text |
| d/File object set | typed object-set DOBJ | projected table or readable text |
| style | style DOBJ | style name/summary text |
| ordinary sentence | STYL or TEXT |
TEXT |
This is the useful NeXTSTEP idea, made stricter: offer the richest object the receiver can genuinely understand, and always keep a human-readable fallback when one is truthful.
The three D/BASIC drag constructs¶
The source-level surface stays small:
WINDOW ACCEPTS "DOBJ", "STYL", "TEXT"
ON DROP SUB (D AS DROPITEM)
SELECT CASE D.KIND$
CASE "DOBJ"
CALL ImportDomainObject(D.HANDLE)
CASE "STYL"
CALL InsertStyledText(D.HANDLE)
CASE "TEXT"
CALL InsertText(D.TEXT$)
END SELECT
END SUB
DRAG Selection
WINDOW ACCEPTSdeclares the target's supported representations. It is admission data, so drag-over can answer without repeatedly waking the application.ON DROPruns once for a negotiated, committed drop.DROPITEMprojects the accepted representation into a small, bounded language value. Operation and source settlement remain session metadata bound into the host-authored receipt rather than extra fields an application can forge.DRAG valuebegins at the source. ATYPEorCLASSvalue uses its governed DOBJ mapping; standard values use their natural representation.
DROPITEM has four governed fields:
| Field | Meaning |
|---|---|
KIND$ |
negotiated tag: TEXT, STYL, PATH, FILE, or DOBJ |
TEXT$ |
complete text value when KIND$ = "TEXT" |
PATH$ |
governed path/reference when the accepted representation is PATH |
HANDLE |
opaque callback-scoped payload lease for structured or binary data; pass it to the matching typed reader, never perform pointer arithmetic or retain it |
A window that accepts drops but has no drop handler is a compile-time contradiction. The compiler rejects it instead of advertising a target that cannot finish the operation.
For exact transactional control, import the D/Works adapters:
dworks.interchange owns checked wrappers for pasteboard publication, listing, negotiation, reading, status, acknowledgement, and DOBJ schema lookup. dworks.drag adds the host-owned drag-session calls without making ordinary copy/paste require a drag provider.
The low-level entry points are grouped by the transaction they advance:
| Stage | D/Works procedures |
|---|---|
| publish an item | WorksPasteboardQueryCaps, WorksPasteboardBegin, WorksPasteboardWriteExact, WorksPasteboardCommit, WorksPasteboardAbort |
| discover and read | WorksPasteboardInspect, WorksPasteboardList, WorksPasteboardNegotiate, WorksPasteboardRead |
| settle publication | WorksPasteboardStatus, WorksPasteboardAcknowledge |
| resolve DOBJ schema | WorksDobjSchemaLength, WorksDobjSchemaRead |
| open and negotiate drag | WorksDragQueryCaps, WorksDragBegin, WorksDragNegotiate, WorksDragAccept, WorksDragReject |
| settle a drop | WorksDragDestinationCommit, WorksDragAuthorizeMove, WorksDragSourceCommit, WorksDragStatus, WorksDragCancel, WorksDragAcknowledge |
These wrappers use rank-one INTEGER arrays as checked byte carriers; each word holds two little-endian bytes. UUIDs, revisions, digests, and receipts remain exact word tuples instead of being squeezed into a LONG. Higher-level domain libraries build and decode the carriers so application code can speak in objects.
The transaction in calls¶
When an adapter needs to publish several representations explicitly, the orchestration is short even though the carriers are exact. This sequence assumes the domain adapter has already prepared each checked request-and-chunk array:
CALL WorksPasteboardBegin(BeginItem(), Transaction())
CALL WorksPasteboardWriteExact(DobjBytes, DobjCarrier())
CALL WorksPasteboardWriteExact(StyledBytes, StyledCarrier())
CALL WorksPasteboardWriteExact(TextBytes, TextCarrier())
CALL WorksPasteboardCommit(Transaction())
CALL WorksPasteboardStatus(Transaction(), PublicationReceipt())
' DRAG_BEGIN freezes the committed item into the new session.
CALL WorksDragBegin(Opening(), Session())
CALL WorksPasteboardAcknowledge(PublicationReceipt())
Each write carrier names the transaction, representation index, and chunk offset; a chunk carries at most 256 logical bytes, so a large DOBJ uses repeated writes. Keep the publication receipt outstanding until WorksDragBegin has frozen the item. Acknowledging it earlier retires the readable publication; holding it after a successful begin needlessly retains the board slot.
Inside one application¶
An in-app reorder does not need to serialize a card merely to move it between two panels. Keep the model operation local:
SUB Board.MoveCard (CardId AS LONG, ExpectedRevision AS LONG, NewColumn AS INTEGER)
IF THIS.Revision <> ExpectedRevision THEN EXIT SUB
' Validate the stable identity, record undo, and commit one model change.
END SUB
The application owns object identity, insertion rules, undo, and the meaning of the drop. The compositor still owns pointer capture, the drag ghost, target routing, and accept/refuse feedback.
A runner may optimize this same-app path, but it must preserve the externally visible contract: the same offered types, copy/move decision, validation, undo result, and failure behavior as the cross-app route.
Between D/OS applications¶
A cross-app drag is a small transaction, not a long callback:
- Stage. The source creates a transient pasteboard item with identity, revision, allowed
COPY/MOVE, and ordered representations. - Begin. The host stamps the source endpoint; an application cannot impersonate another run.
- Negotiate. A candidate target supplies accepted types, operations, drop zone, base revision, and interpretation. The first producer-ordered compatible representation wins; when both sides admit both operations,
COPYwins so deletion is never selected by ambiguity. - Show. The compositor displays a ghost and honest accept, insertion, or refusal feedback.
- Commit destination. The target validates the complete representation privately, applies one model transaction, and returns its exact result and revision against the frozen binding.
- Finish move. Only after destination success does the host issue a one-use authorization. The source application deletes its own exact selection and returns the post-revision and deletion digest.
- Acknowledge. Terminal receipts are acknowledged exactly once; cancellation retires the session without a half-drop.
For COPY, step 6 disappears. For MOVE, it is the safety hinge: dropping a file, paragraph, or mail message cannot erase the source merely because a destination looked willing for one frame.
The terminal is a delivery receipt. It proves the exact destination endpoint received the negotiated bytes and the move had source consent; it does not pretend the host understands a d/Write document well enough to certify that the target inserted a paragraph. Each app remains authority for its own model. When source and target are different endpoints, both acknowledge the terminal before the session slot is reclaimed.
LINK is not a secret third low-level operation. It is a destination interpretation of an accepted copy: the destination commits a durable link plus a usable snapshot.
Out into macOS, Windows, Linux, and the browser¶
Native interoperability belongs to the runner. A D/BASIC program never imports AppKit, COM, Wayland, X11, or a browser DataTransfer object.
| Host | Runner bridge |
|---|---|
| macOS | projects one D/OS item onto one AppKit pasteboard item, with native text/file types and a declared DOBJ type; verifies its native change marker before acknowledging publication |
| Windows | maps representations onto the native data-object/clipboard contract while retaining D/OS type order and copy/move meaning |
| Linux | maps onto the active Wayland or X11 drag/data-offer route; session mechanics stay outside BASIC |
| browser | offers only sandbox-admitted web data types and permissions; unsupported export or import refuses honestly |
| D/OS head | keeps the typed pasteboard native and lets the composed scene own ghost, cursor, and target feedback |
On a Mac, for example, a D/Works rich block can publish DOBJ, styled text, and plain text on one NSPasteboardItem. Another D/OS application may choose DOBJ. A native text editor may choose public.utf8-plain-text. Finder may offer a file URL or file promise that the runner translates into a governed PATH/FILE representation before D/BASIC sees it. The canonical private types remain distinct—org.d-os.pasteboard.text.v1, org.d-os.pasteboard.styl.v1, and org.d-os.pasteboard.dobj.v1—so a native label cannot silently change D/OS meaning.
The bridge is intentionally asymmetric when necessary. The Mac import path accepts a stable complete UTF-8 string; it does not relabel RTF, an arbitrary archive, or private native bytes as STYL or DOBJ. Export re-authenticates the committed D/OS bytes, publishes all present representations together, and correlates the final AppKit change marker before acknowledging the D/OS item. AppKit does not promise atomic ownership replacement across every process, so the marker and durable correlation tell the exact truth the host can prove.
This is the same upgrade/lower rule as other Intents: use the richest honest local route, preserve meaning, and refuse what cannot be represented safely.
A real DOBJ in D/BASIC¶
The standard d/Write fragment library is a compact example of the pattern:
IMPORT dworks.dwrite_dobj
DIM Fragment AS WorksDwriteFragment
DIM Encoded(0 TO (WORKS_DWRITE_FRAGMENT_DOBJ_BYTES \ 2) - 1) AS INTEGER
Fragment = NEW WorksDwriteFragment()
Fragment.Text = "A SMALL OBJECT WITH A LARGE TRAVEL ITINERARY."
IF WorksDwriteFragmentEncode(Fragment, Encoded()) = 0 THEN STOP
' Encoded() is now the complete canonical DOBJ representation to stage.
That fragment contains text and bounded formatting spans. It contains no document handle, selection, caret, window state, callback, or native object. The pasteboard does not need to understand d/Write; the installed schema and receiving adapter do.
Trash is a serious drop target¶
Trash is the canonical test of honest move semantics. A drop into Trash is staged deletion, not immediate destruction:
- the target accepts a type and move operation;
- destination staging succeeds first;
- source deletion follows the terminal authorization;
- the trashed object remains restorable; and
- emptying Trash purges each object only after its own backend operation succeeds.
If restoring or purging one item fails, that item and the unvisited suffix remain recoverable. A mail message, Filer object, and app-defined DOBJ can all use the same target contract because Trash negotiates typed objects rather than memorizing every application.
Rules that keep a drop honest¶
- Use stable object IDs and exact source revisions.
- Put DOBJ first only when it is the premier truthful representation.
- Offer
TEXTor another readable fallback when it preserves meaning. - Never treat a tag, filename, or successful parse as schema authority.
- Validate a complete DOBJ in private staging before exposing typed objects.
- Never serialize handles, pointers, callbacks, or native object identities.
- Commit the destination before authorizing source deletion.
- Keep drag pasteboards transient and the General clipboard intact.
- Let the compositor own capture and feedback; let the application own meaning and undo.
- Let the runner bridge native systems without leaking their APIs into BASIC.
Next: Imports & Libraries →
See also: Widgets · Intents & Services · Imports & Libraries · Bytecode & Delivery