外观
拖放
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。
原生格式映射
| 平台 | 原生传输 | 表示 |
|---|---|---|
| macOS | AppKit 拖动会话与剪贴板条目提供者 | 文本/HTML/PNG/file URL 使用系统 UTI。MIME 名称经 UTType 映射,并提供可逆的标识符回退。条目边界得以保留。 |
| Linux | GTK 3 拖动上下文与异步选择 | MIME 目标、文本用 UTF8_STRING、URL 和文件用 text/uri-list。接受的格式在拖放后请求。 |
| Windows | OLE IDataObject / IDropSource / IDropTarget | Unicode 文本、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;每次剪贴板写入和拖动都获得独立的提供者缓存。剪贴板协商、持久化与提供者生命周期参见剪贴板与拖放数据。