feat(v2): Gio UI spike, cross-compiles CGo-free for Windows
Adds cmd/gio-spike: a minimal Gio window (dual tabs, an *image.RGBA video area, start/stop) that avoids the Walk TTM_ADDTOOL/comctl manifest problem entirely. Verified via GOOS=windows CGO_ENABLED=0 go build (11MB single exe). Windows visual smoke pending. Existing Walk ui-spike left untouched. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -532,3 +532,12 @@
|
||||
- 阻塞:Gio 依赖需在能联网且 Go≥1.24 的环境 `go get gioui.org`;真实窗口显示需 Windows 目视。
|
||||
- 决策:Gio 不用 Win32 common controls,从根上不存在 `TTM_ADDTOOL`/manifest 问题;`internal/render.Render` 已返回 `*image.RGBA`,Gio 用 `paint.NewImageOp` 直接贴,UI 层很薄;纯 Go 单 exe 符合"无 Go 环境运行"目标且不带 WebView2。新建 `cmd/gio-spike`,不动现有 Walk `cmd/ui-spike`(保留对照)。
|
||||
- 下一步:写 `cmd/gio-spike` 最小窗口(双 Tab + 贴一张 RGBA + 开始/停止按钮),给 Windows 运行命令;据结果决定修 Walk 还是迁 Gio。
|
||||
|
||||
## 【2026-07-23】T-305 V2 UI Gio Spike(交叉编译验证通过,待 Windows 目视)
|
||||
|
||||
- 状态:DOING(代码完成 + 交叉编译验证;窗口目视需 Windows)
|
||||
- 变更:新增 `v2/cmd/gio-spike/main.go`——Gio 最小窗口:浅灰蓝底、顶部双 Tab(实时监控/设置)、贴一张合成 `*image.RGBA`(模拟 render 输出,带 6px 红边)、开始/停止按钮,关闭即退出。不动现有 Walk `cmd/ui-spike`(保留对照)。`go.mod` 加 `gioui.org v0.10.1`,`go.sum` 由 `go mod tidy` 补全。
|
||||
- 验证:本环境为 Go 1.21/无显示,无法运行 Gio;改用 `GOOS=windows GOARCH=amd64 CGO_ENABLED=0 go build ./cmd/gio-spike` **交叉编译到 Windows** 对真实 Gio Windows API 做类型检查——`BUILD_EXIT=0`,产出 11 MB 纯 Go 单 exe(CGO_ENABLED=0 通过,证明 Gio 的 Windows 版无需 CGo,符合"无 Go 环境单文件运行"目标)。因此代码对 Windows API 编译无误,`go run ./cmd/gio-spike` 在 Windows 上有高把握能起窗。
|
||||
- 阻塞:窗口能否真正显示/关闭、无 `TTM_ADDTOOL`,需用户在 Windows 目视一次(Gio 不用 Win32 common controls,理论上不会出该错)。
|
||||
- 决策:Gio 从根上规避 Walk 的 comctl/manifest 问题;`internal/render` 已出 `*image.RGBA`,Gio 用 `paint.NewImageOp` 直接贴,UI 层很薄。若 Windows 目视通过,则 V2 UI 由 Walk 迁 Gio;否则回退"给 Walk 嵌 Common Controls v6 清单"方案。
|
||||
- 下一步:用户在 `v2/` 下 `go run ./cmd/gio-spike` 确认起窗;通过则据此把 `internal/ui` 迁 Gio、更新架构文档。
|
||||
|
||||
@@ -0,0 +1,159 @@
|
||||
// Command gio-spike validates a Gio window for the V2 UI, bypassing the Walk
|
||||
// TTM_ADDTOOL startup failure (Gio uses no Win32 common controls, so the
|
||||
// manifest/comctl issue cannot occur). It shows a synthetic RGBA frame — the
|
||||
// same *image.RGBA type internal/render produces — a top dual-tab bar and a
|
||||
// start/stop button, then closes cleanly.
|
||||
//
|
||||
// Run on Windows: go run ./cmd/gio-spike
|
||||
package main
|
||||
|
||||
import (
|
||||
"image"
|
||||
"image/color"
|
||||
"log"
|
||||
"os"
|
||||
|
||||
"gioui.org/app"
|
||||
"gioui.org/font/gofont"
|
||||
"gioui.org/layout"
|
||||
"gioui.org/op"
|
||||
"gioui.org/op/paint"
|
||||
"gioui.org/text"
|
||||
"gioui.org/unit"
|
||||
"gioui.org/widget"
|
||||
"gioui.org/widget/material"
|
||||
)
|
||||
|
||||
func main() {
|
||||
go func() {
|
||||
w := new(app.Window)
|
||||
w.Option(
|
||||
app.Title("Silver Pose V2 · Gio Spike"),
|
||||
app.Size(unit.Dp(960), unit.Dp(600)),
|
||||
)
|
||||
if err := loop(w); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
os.Exit(0)
|
||||
}()
|
||||
app.Main()
|
||||
}
|
||||
|
||||
type spikeUI struct {
|
||||
theme *material.Theme
|
||||
frame paint.ImageOp
|
||||
tabs []string
|
||||
tabButton []widget.Clickable
|
||||
active int
|
||||
startStop widget.Clickable
|
||||
running bool
|
||||
}
|
||||
|
||||
func newSpikeUI() *spikeUI {
|
||||
theme := material.NewTheme()
|
||||
theme.Shaper = text.NewShaper(text.WithCollection(gofont.Collection()))
|
||||
ui := &spikeUI{
|
||||
theme: theme,
|
||||
frame: paint.NewImageOp(syntheticFrame(640, 360)),
|
||||
tabs: []string{"实时监控", "设置"},
|
||||
}
|
||||
ui.tabButton = make([]widget.Clickable, len(ui.tabs))
|
||||
return ui
|
||||
}
|
||||
|
||||
func loop(w *app.Window) error {
|
||||
ui := newSpikeUI()
|
||||
var ops op.Ops
|
||||
for {
|
||||
switch e := w.Event().(type) {
|
||||
case app.DestroyEvent:
|
||||
return e.Err
|
||||
case app.FrameEvent:
|
||||
gtx := app.NewContext(&ops, e)
|
||||
ui.layout(gtx)
|
||||
e.Frame(gtx.Ops)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (ui *spikeUI) layout(gtx layout.Context) layout.Dimensions {
|
||||
paint.Fill(gtx.Ops, color.NRGBA{R: 0xEA, G: 0xF1, B: 0xF8, A: 0xFF})
|
||||
return layout.Flex{Axis: layout.Vertical}.Layout(gtx,
|
||||
layout.Rigid(ui.layoutTabs),
|
||||
layout.Flexed(1, ui.layoutBody),
|
||||
layout.Rigid(ui.layoutControls),
|
||||
)
|
||||
}
|
||||
|
||||
func (ui *spikeUI) layoutTabs(gtx layout.Context) layout.Dimensions {
|
||||
for i := range ui.tabButton {
|
||||
if ui.tabButton[i].Clicked(gtx) {
|
||||
ui.active = i
|
||||
}
|
||||
}
|
||||
children := make([]layout.FlexChild, 0, len(ui.tabs))
|
||||
for i := range ui.tabs {
|
||||
i := i
|
||||
children = append(children, layout.Rigid(func(gtx layout.Context) layout.Dimensions {
|
||||
button := material.Button(ui.theme, &ui.tabButton[i], ui.tabs[i])
|
||||
if i != ui.active {
|
||||
button.Background = color.NRGBA{R: 0xFF, G: 0xFF, B: 0xFF, A: 0xFF}
|
||||
button.Color = color.NRGBA{R: 0x52, G: 0x65, B: 0x7C, A: 0xFF}
|
||||
}
|
||||
return layout.UniformInset(unit.Dp(6)).Layout(gtx, button.Layout)
|
||||
}))
|
||||
}
|
||||
return layout.Flex{}.Layout(gtx, children...)
|
||||
}
|
||||
|
||||
func (ui *spikeUI) layoutBody(gtx layout.Context) layout.Dimensions {
|
||||
if ui.active == 0 {
|
||||
return layout.UniformInset(unit.Dp(8)).Layout(gtx, func(gtx layout.Context) layout.Dimensions {
|
||||
return widget.Image{
|
||||
Src: ui.frame,
|
||||
Fit: widget.Contain,
|
||||
Position: layout.Center,
|
||||
}.Layout(gtx)
|
||||
})
|
||||
}
|
||||
return layout.Center.Layout(gtx, material.Body1(ui.theme, "设置页占位(spike)").Layout)
|
||||
}
|
||||
|
||||
func (ui *spikeUI) layoutControls(gtx layout.Context) layout.Dimensions {
|
||||
if ui.startStop.Clicked(gtx) {
|
||||
ui.running = !ui.running
|
||||
}
|
||||
label := "开始监控"
|
||||
if ui.running {
|
||||
label = "停止监控"
|
||||
}
|
||||
return layout.UniformInset(unit.Dp(8)).Layout(gtx, material.Button(ui.theme, &ui.startStop, label).Layout)
|
||||
}
|
||||
|
||||
// syntheticFrame stands in for internal/render's *image.RGBA output: a gradient
|
||||
// with a 6px red border, mimicking a CONFIRMED overlay, to prove image display.
|
||||
func syntheticFrame(width, height int) *image.RGBA {
|
||||
img := image.NewRGBA(image.Rect(0, 0, width, height))
|
||||
for y := 0; y < height; y++ {
|
||||
for x := 0; x < width; x++ {
|
||||
img.SetRGBA(x, y, color.RGBA{
|
||||
R: uint8(x * 255 / width),
|
||||
G: uint8(y * 255 / height),
|
||||
B: 90,
|
||||
A: 255,
|
||||
})
|
||||
}
|
||||
}
|
||||
red := color.RGBA{R: 198, G: 40, B: 40, A: 255}
|
||||
for t := 0; t < 6; t++ {
|
||||
for x := 0; x < width; x++ {
|
||||
img.SetRGBA(x, t, red)
|
||||
img.SetRGBA(x, height-1-t, red)
|
||||
}
|
||||
for y := 0; y < height; y++ {
|
||||
img.SetRGBA(t, y, red)
|
||||
img.SetRGBA(width-1-t, y, red)
|
||||
}
|
||||
}
|
||||
return img
|
||||
}
|
||||
@@ -3,13 +3,19 @@ module silverpose/v2
|
||||
go 1.24.0
|
||||
|
||||
require (
|
||||
gioui.org v0.10.1
|
||||
github.com/lxn/walk v0.0.0-20210112085537-c389da54e794
|
||||
github.com/lxn/win v0.0.0-20210218163916-a377121e959e
|
||||
github.com/yalue/onnxruntime_go v1.31.0
|
||||
golang.org/x/image v0.24.0
|
||||
golang.org/x/image v0.26.0
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/lxn/win v0.0.0-20210218163916-a377121e959e // indirect
|
||||
golang.org/x/sys v0.30.0 // indirect
|
||||
gioui.org/shader v1.0.8 // indirect
|
||||
github.com/go-text/typesetting v0.3.4 // indirect
|
||||
golang.org/x/exp/shiny v0.0.0-20250408133849-7e4ce0ab07d0 // indirect
|
||||
golang.org/x/net v0.48.0 // indirect
|
||||
golang.org/x/sys v0.39.0 // indirect
|
||||
golang.org/x/text v0.32.0 // indirect
|
||||
gopkg.in/Knetic/govaluate.v3 v3.0.0 // indirect
|
||||
)
|
||||
|
||||
@@ -1,13 +1,32 @@
|
||||
eliasnaur.com/font v0.0.0-20230308162249-dd43949cb42d h1:ARo7NCVvN2NdhLlJE9xAbKweuI9L6UgfTbYb0YwPacY=
|
||||
eliasnaur.com/font v0.0.0-20230308162249-dd43949cb42d/go.mod h1:OYVuxibdk9OSLX8vAqydtRPP87PyTFcT9uH3MlEGBQA=
|
||||
gioui.org v0.10.1 h1:Dvp6iDk9RKuZk19jxhOmb4p673CLVvb656LyMxQ+uO0=
|
||||
gioui.org v0.10.1/go.mod h1:MZJZsdEPkTBzChdqeE8CiiQhreUQBj43qusDxQNDf7k=
|
||||
gioui.org/cpu v0.0.0-20210808092351-bfe733dd3334/go.mod h1:A8M0Cn5o+vY5LTMlnRoK3O5kG+rH0kWfJjeKd9QpBmQ=
|
||||
gioui.org/shader v1.0.8 h1:6ks0o/A+b0ne7RzEqRZK5f4Gboz2CfG+mVliciy6+qA=
|
||||
gioui.org/shader v1.0.8/go.mod h1:mWdiME581d/kV7/iEhLmUgUK5iZ09XR5XpduXzbePVM=
|
||||
github.com/go-text/typesetting v0.3.4 h1:YYurUOtEb9kGSOz4uE3k4OpBGsp1dDL8+fjCeaFamAU=
|
||||
github.com/go-text/typesetting v0.3.4/go.mod h1:4qZCQphq4KSgGTAeI0uMEkVbROgfah8BuyF5LRYr7XY=
|
||||
github.com/go-text/typesetting-utils v0.0.0-20260223113751-2d88ac90dae3 h1:drBZzMgdYPbmyXqOto4YhhJGrFIQCX94FpR4MzTCsos=
|
||||
github.com/go-text/typesetting-utils v0.0.0-20260223113751-2d88ac90dae3/go.mod h1:3/62I4La/HBRX9TcTpBj4eipLiwzf+vhI+7whTc9V7o=
|
||||
github.com/lxn/walk v0.0.0-20210112085537-c389da54e794 h1:NVRJ0Uy0SOFcXSKLsS65OmI1sgCCfiDUPj+cwnH7GZw=
|
||||
github.com/lxn/walk v0.0.0-20210112085537-c389da54e794/go.mod h1:E23UucZGqpuUANJooIbHWCufXvOcT6E7Stq81gU+CSQ=
|
||||
github.com/lxn/win v0.0.0-20210218163916-a377121e959e h1:H+t6A/QJMbhCSEH5rAuRxh+CtW96g0Or0Fxa9IKr4uc=
|
||||
github.com/lxn/win v0.0.0-20210218163916-a377121e959e/go.mod h1:KxxjdtRkfNoYDCUP5ryK7XJJNTnpC8atvtmTheChOtk=
|
||||
github.com/yalue/onnxruntime_go v1.31.0 h1:1ln4YW1SFOFfGJZXe3jNOb2JUSt+l2pEneZfV8HdtFA=
|
||||
github.com/yalue/onnxruntime_go v1.31.0/go.mod h1:b4X26A8pekNb1ACJ58wAXgNKeUCGEAQ9dmACut9Sm/4=
|
||||
golang.org/x/image v0.24.0 h1:AN7zRgVsbvmTfNyqIbbOraYL8mSwcKncEj8ofjgzcMQ=
|
||||
golang.org/x/image v0.24.0/go.mod h1:4b/ITuLfqYq1hqZcjofwctIhi7sZh2WaCjvsBNjjya8=
|
||||
golang.org/x/exp v0.0.0-20250408133849-7e4ce0ab07d0 h1:R84qjqJb5nVJMxqWYb3np9L5ZsaDtB+a39EqjV0JSUM=
|
||||
golang.org/x/exp v0.0.0-20250408133849-7e4ce0ab07d0/go.mod h1:S9Xr4PYopiDyqSyp5NjCrhFrqg6A5zA2E/iPHPhqnS8=
|
||||
golang.org/x/exp/shiny v0.0.0-20250408133849-7e4ce0ab07d0 h1:tMSqXTK+AQdW3LpCbfatHSRPHeW6+2WuxaVQuHftn80=
|
||||
golang.org/x/exp/shiny v0.0.0-20250408133849-7e4ce0ab07d0/go.mod h1:ygj7T6vSGhhm/9yTpOQQNvuAUFziTH7RUiH74EoE2C8=
|
||||
golang.org/x/image v0.26.0 h1:4XjIFEZWQmCZi6Wv8BoxsDhRU3RVnLX04dToTDAEPlY=
|
||||
golang.org/x/image v0.26.0/go.mod h1:lcxbMFAovzpnJxzXS3nyL83K27tmqtKzIJpctK8YO5c=
|
||||
golang.org/x/net v0.48.0 h1:zyQRTTrjc33Lhh0fBgT/H3oZq9WuvRR5gPC70xpDiQU=
|
||||
golang.org/x/net v0.48.0/go.mod h1:+ndRgGjkh8FGtu1w1FGbEC31if4VrNVMuKTgcAAnQRY=
|
||||
golang.org/x/sys v0.0.0-20201018230417-eeed37f84f13/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc=
|
||||
golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.39.0 h1:CvCKL8MeisomCi6qNZ+wbb0DN9E5AATixKsvNtMoMFk=
|
||||
golang.org/x/sys v0.39.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
||||
golang.org/x/text v0.32.0 h1:ZD01bjUt1FQ9WJ0ClOL5vxgxOI/sVCNgX1YtKwcY0mU=
|
||||
golang.org/x/text v0.32.0/go.mod h1:o/rUWzghvpD5TXrTIBuJU77MTaN0ljMWE47kxGJQ7jY=
|
||||
gopkg.in/Knetic/govaluate.v3 v3.0.0 h1:18mUyIt4ZlRlFZAAfVetz4/rzlJs9yhN+U02F4u1AOc=
|
||||
gopkg.in/Knetic/govaluate.v3 v3.0.0/go.mod h1:csKLBORsPbafmSCGTEh3U7Ozmsuq8ZSIlKk1bcqph0E=
|
||||
|
||||
Reference in New Issue
Block a user