Skip to content

拖放 ​

Drag(value) 让元素成为在窗口内拖动的值的来源:一旦按住它的指针移动几个 DIP,元素的半透明副本便跟随指针,这次按住不再是点击。ui.Drop[T](e) 让 e 接收类型 T 的值,返回拖放到它上面的值;ui.DragOver[T](e) 返回正拖过它上方的值,用于显示它可以接收:

go
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
}
  • 目标。 指针下方最内层、接收该值类型的元素获得它,因此嵌套在接收其他类型的目标内部的目标不会被阻挡。
  • 内部的控件。 在可拖动元素内,按住一个只接收点击的控件(如按钮),指针移动时即拖动该元素,且不再算作点击:由按钮组成的列表行可以通过拖动重排。没有移动的按住则点击该控件。接管指针移动的控件(如文本输入和滑块)保留这些移动。
  • 取消。 按 Escape 放弃拖动。
  • 滚动。 拖动接近滚动容器的顶部或底部时,容器随之滚动,越靠近边缘越快。
  • 拖动多个。 副本在指针旁显示该拖动包含多少个值,例如把列表中选中的多行一起拖动时。

Dragging 报告元素正在拖动它的值,以便把其原处变暗。

重排 ​

列表、表格和网格视图通过 ListState.Reorder 和 GridState.Reorder 支持拖动重排行:当被拖动的行是选中行之一时,所有选中行一起移动;列表用一条指示线显示它们将落往何处;最终行随指针所到之处落定。

go
app.list.Reorder = func(rows []int, to int) {
	app.items = move(app.items, rows, to) // before row to, len for the end
}

参见列表和网格视图。

从其他应用拖入的文件见拖入的文件。

跨窗口与跨应用 ​

DragData 使用 transfer 包提供的显式表示(representation)发起系统拖放。文件、文本、URL、HTML、PNG 和自定义序列化格式都可以离开应用,或来自其他应用。DropData 和 DataDragOver 声明元素接受的格式和操作:

go
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)
	}
}

最内层匹配且启用的目标在下一帧接收一次拖放。至少一个声明的格式必须匹配,且源与目标必须共有某种操作。悬停只声明格式而不读取字节。被接受的表示在拖放时被复制;得到的数据可以安全保留,并在原生会话结束后从 goroutine 读取。

表示与惰性提供者 ​

一个条目可以为同一内容提供多种替代形式。自定义格式使用 MIME 名称和显式编码,例如 JSON:

go
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 复制其输入。Lazy 声明一种格式,并在每次拖动会话中、当接收方请求时缓存其结果或错误。提供者运行在主线程上,必须迅速返回。开销大的数据应先在 goroutine 中准备好再提供;提供者绝不能等待需要主线程的工作。外部应用可能在释放之前就请求数据。

用 DragDataFrom(func() transfer.Data { ... }) 在手势开始时对不断变化的选择做快照。URL 和文件列表用 transfer.URLData(url) 和 transfer.FileData(absolutePaths...)。文件路径会被转义为 file URL;接收方的 Data.Files() 返回本地路径,而 Data.URLs() 返回 URI 列表条目。FileData 传输已存在的路径;它不创建承诺文件(promise file),也不流式传输文件内容。

单独的 Drag(value) 只在其窗口内有效。与 DragData 结合后,还会把其 Go 值提供给同一进程其他窗口中现有的 Drop[T] 和 DragOver[T] 目标。这些带类型的目标接受复制。一个临时的随机会话令牌标识该值;原生表示中不含 Go 指针,源结束后令牌即停止解析。其他进程使用序列化表示并显式解码。

复制、移动、完成与取消 ​

源和目标默认使用复制。要允许移动,双方都要给出 Operations: transfer.Copy | transfer.Move。当两端都允许时,以修饰键的建议为准;否则优先复制,其次移动。不兼容的提议会被拒绝。Option 在 macOS 上请求复制,其他平台上为 Ctrl;Cmd 在 macOS 上请求移动,其他平台上为 Shift。

go
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 在主线程上恰好运行一次,涵盖取消、原生启动失败、源移除和窗口销毁等情况。按 Escape 会通过系统拖动跟踪器取消。Window.CancelDataDrag() 可从任意 goroutine 取消属于该窗口的源。Window.StartDataDrag 直接从当前原生指针手势启动传输;UI 元素通常使用 DragData,从而应用移动阈值。

Result.Operation 是目标产生的最终效果。Canceled 表示未发生拖放;Escaped 区分用户主动放弃的拖动(Escape、CancelDataDrag 或窗口关闭)与在没有任何目标上方松手的拖动;Point 是拖动结束的位置,采用 Screen.CursorScreenPoint 的屏幕坐标。例如,在窗口之外松开一个标签页,其结果是 Point 处 Canceled && !Escaped。Err 报告原生层/提供者的失败。MyGo 从不删除文件。只在移动成功后才移除应用记录,并要考虑自行移动文件的文件管理器。现有的 DroppedFiles 和 OnFileDrop 监听器继续以复制语义接收文件。

默认预览是源可见元素的快照,尺寸上限为 512 DIP。要自定义预览,把 DragOptions.Preview 设为 image.Image,把 Hotspot 设为其指针偏移(每边至多 4096 像素)。图像在拖动开始时复制;一个图像像素代表一个 DIP。

原生格式映射 ​

平台原生传输表示
macOSAppKit 拖动会话与剪贴板条目提供者文本/HTML/PNG/file URL 使用系统 UTI。MIME 名称经 UTType 映射,并提供可逆的标识符回退。条目边界得以保留。
LinuxGTK 3 拖动上下文与异步选择MIME 目标、文本用 UTF8_STRING、URL 和文件用 text/uri-list。接受的格式在拖放后请求。
WindowsOLE IDataObject / IDropSource / IDropTargetUnicode 文本、CF_HDROP、URL、CF_HTML、PNG 及注册的自定义名称;延迟渲染与 Shell 拖动图像。流保留精确的二进制长度;HGLOBAL 来源可能包含填充。
浏览器拖放到画布上的文件,在拖放时读取(见浏览器)仅文件,位于 os 可读取的后端路径。其他拖动,以及拖出窗口的拖动,没有数据传输。

GTK 和 OLE 只有单一的选择/数据对象:多个文本条目以换行符连接,URI/文件列表以 CRLF 连接,其他格式使用第一个提供它的条目。同一 MyGo 进程内窗口之间的传输通过核心的本地注册表保留所有条目。传入的 GTK/OLE 表示每个上限 64 MiB;原生提供者必须提供内存或流形式的表示(不支持虚拟文件承诺)。

参见 examples/drag-drop:两个窗口交换便签、文件、文本和 URL,包括惰性 JSON 表示和移动完成。传输模型可脱离原生 UI 复用;现有的剪贴板便捷方法保持其签名。Clipboard.Write 和 Clipboard.Read 交换的正是同一种 transfer.Data;每次剪贴板写入和拖动都获得独立的提供者缓存。剪贴板协商、持久化与提供者生命周期参见剪贴板与拖放数据。