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:
ila
2026-07-23 08:45:21 +08:00
co-authored by Claude Opus 4.8
parent 677a0d587b
commit 43ec154075
4 changed files with 200 additions and 7 deletions
+159
View File
@@ -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
}