Guides
Drag and drop
Values dragged within the window, and rows reordered by dragging.
Drag(value) makes an element the source of a value dragged within the
window: once the pointer pressing it moves a few DIPs, a translucent copy
of the element follows the pointer, and the press is no click.
ui.Drop[T](e) makes e take values of type T, returning the one
dropped on it, and ui.DragOver[T](e) the one over it, for showing it
would take it:
for _, task := range app.todo {
ui.Row(c).Key(task.ID).Drag(task).Children(func() {
ui.Text(c, task.Name)
})
}
done := ui.Column(c).Grow(1).Padding(12).Radius(8).Border(1, t.Border)
if _, over := ui.DragOver[*Task](done); over {
done.Border(2, t.Accent)
}
if task, ok := ui.Drop[*Task](done); ok {
task.Done = true
}- Targets. The innermost element under the pointer that takes the value's type gets it, so a target inside another that takes other types does not block it.
- Controls inside. A press on a control that only takes clicks, as a button, inside an element that drags drags that element once the pointer moves, and is no click: a list's rows made of buttons reorder. A press that does not move clicks the control. Controls that take the pointer's moves, as text inputs and sliders, keep them.
- Canceling. Escape gives up a drag.
- Scrolling. Scroll containers scroll as a drag nears their top or bottom, faster nearer to the edge.
- Dragging several. The copy shows how many values a drag holds, beside the pointer, as when the rows chosen in a list are dragged together.
Dragging reports that an element is dragging its value, to dim it where
it was.
DragMissed reports, in the frame after, that the value was let go over
no element taking it, and where, in the window's coordinates: outside the
window when it was let go past its edge, as to tear a tab out into a
window of its own. A drag given up with Escape is not missed.
tab := ui.Row(c).Drag(t.id)
w, h := c.Size()
if at, ok := tab.DragMissed(); ok && (at.X < 0 || at.Y < 0 || at.X > w || at.Y > h) {
app.tearOut(t, at)
}Reordering
Lists, tables and grid views reorder their rows by dragging with
ListState.Reorder and GridState.Reorder: the rows chosen move together
when the row dragged is one of them, the list shows where they would go
with a line, and the rows go where the pointer let them go.
app.list.Reorder = func(rows []int, to int) {
app.items = move(app.items, rows, to) // before row to, len for the end
}A list's rows can also leave it, as files dragged into a folder: with
ListState.DragRows they drag a value of the app's, for ui.Drop to take
anywhere. See List.
Files dragged from other apps are dropped files.
Between windows and applications
DragData starts a system drag with explicit representations from package
transfer. Files, text, URLs, HTML, PNG and custom serialized formats can
leave the app or come from another app. DropData and DataDragOver declare
the formats and operations an element accepts:
data := transfer.TextData("Hello from MyGo")
ui.Text(c, "Drag me").Padding(12).DragData(data)
zone := ui.Box(c).Size(260, 140).Border(1, c.Theme().Border)
options := transfer.DropOptions{Formats: []transfer.Format{transfer.Text}}
if _, over := ui.DataDragOver(zone, options); over {
zone.Border(2, c.Theme().Accent)
}
if drop, ok := ui.DropData(zone, options); ok {
text, err := drop.Data.Read(transfer.Text)
if err == nil {
app.message = string(text)
}
}The innermost matching enabled target receives the drop once, in the next frame. At least one advertised format must match, and the source and target must share an operation. Hover advertises formats without reading bytes. Accepted representations are copied on drop; the resulting data is safe to keep and read from a goroutine after the native session ends.
Representations and lazy providers
An item can offer alternatives to the same content. Custom formats use MIME names and explicit encodings, such as JSON:
const taskFormat transfer.Format = "application/vnd.example.task+json"
frozen := *task // snapshot the value that all representations describe
data := transfer.New(transfer.NewItem(
transfer.Bytes(transfer.Text, []byte(frozen.Title)),
transfer.Lazy(taskFormat, func() ([]byte, error) {
return json.Marshal(frozen)
}),
))
row.Drag(task).DragData(data)Bytes copies its input. Lazy advertises a format and caches its result
or error once per drag session, when requested by a receiver. Providers
run on the main thread and must return promptly. Prepare expensive data on
a goroutine before offering it; a provider must never wait for work needing
the main thread. External applications can request data before release.
Use DragDataFrom(func() transfer.Data { ... }) to snapshot a changing
selection when the gesture starts. Use transfer.URLData(url) and
transfer.FileData(absolutePaths...) for URLs and file lists. File paths
are escaped as file URLs; the receiver's Data.Files() returns local
paths, while Data.URLs() returns URI-list entries. FileData transfers
existing paths; it does not create promised files or stream file contents.
Drag(value) alone stays within its window. Combining it with DragData
also offers its Go value to existing Drop[T] and DragOver[T] targets in
other windows of the same process. Those typed targets accept copy. A
temporary random session token identifies the value; native representations
contain no Go pointer and the token stops resolving when the source ends.
Other processes use the serialized representations and decode them explicitly.
Copy, move, completion and cancellation
Sources and destinations default to copy. To allow moves, give both
Operations: transfer.Copy | transfer.Move. The modifier-key suggestion
wins when both ends allow it; otherwise copy is preferred, then move.
An incompatible offer is rejected. Option requests copy on macOS and Ctrl
elsewhere; Cmd requests move on macOS and Shift elsewhere.
row.DragData(data, transfer.DragOptions{
Operations: transfer.Copy | transfer.Move,
Done: func(result transfer.Result) {
if result.Err != nil {
app.report(result.Err)
} else if result.Operation == transfer.Move {
app.removeOriginal(task.ID)
}
},
})Done runs exactly once on the main thread, including cancellation,
native startup failure, source removal and window destruction. Escape
cancels through the system drag tracker. Window.CancelDataDrag() cancels
a source owned by that window, from any goroutine. Window.StartDataDrag
starts a transfer directly from a current native pointer gesture; UI
elements normally use DragData so the movement threshold is applied.
Result.Operation is the destination's final effect. Canceled means no
drop occurred; Escaped tells a drag the user gave up (Escape,
CancelDataDrag or the window closing) from one let go over no target, and
Point is where the drag ended, in the screen coordinates of
Screen.CursorScreenPoint. A tab let go outside every window, for example,
is Canceled && !Escaped at Point. Err reports a native/provider
failure. MyGo never deletes
files. Remove application records only after a successful move, and account
for file managers that move a file themselves. Existing DroppedFiles and
OnFileDrop listeners keep accepting files with copy semantics.
The default preview is a snapshot of the source's visible element, bounded
to 512 DIPs. Set DragOptions.Preview to an image.Image and Hotspot to
its pointer offset for a custom preview (up to 4096 pixels per dimension).
The image is copied at drag start; one image pixel represents one DIP.
Native format mapping
| Platform | Native transfer | Representations |
|---|---|---|
| macOS | AppKit dragging sessions and pasteboard item providers | Text/HTML/PNG/file URLs use system UTIs. MIME names map through UTType, with a reversible identifier fallback. Item boundaries are preserved. |
| Linux | GTK 3 drag contexts and asynchronous selections | MIME targets, UTF8_STRING for text, text/uri-list for URLs and files. Accepted formats are requested after drop. |
| Windows | OLE IDataObject / IDropSource / IDropTarget | Unicode text, CF_HDROP, URL, CF_HTML, PNG and registered custom names; delayed rendering and Shell drag images. Streams preserve exact binary lengths; HGLOBAL sources may include padding. |
| Browsers | Files dropped on the canvas, read as they drop (see browsers) | Files only, at paths of the backend's that os reads. Other drags, and drags out of the window, have no transfer. |
GTK and OLE have a single selection/data object: multiple text items are joined by newlines, URI/file lists by CRLF, and other formats use the first item offering them. Transfers between windows in the same MyGo process preserve all items through the core's local registry. Incoming GTK/OLE representations are limited to 64 MiB each; native providers must supply memory or stream representations (virtual-file promises are not supported).
See examples/drag-drop for two windows exchanging
notes, files, text and URLs, including a lazy JSON representation and move
completion. The transfer model is reusable without native UI; existing
clipboard convenience methods keep their signatures. Clipboard.Write and
Clipboard.Read exchange this same transfer.Data; each clipboard write and
drag gets an independent provider cache. See Clipboard and drag data
for clipboard negotiation, persistence, and provider lifetimes.