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>
160 lines
4.1 KiB
Go
160 lines
4.1 KiB
Go
// 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
|
||
}
|