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:
@@ -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
|
||||
}
|
||||
Reference in New Issue
Block a user