mirror of
https://github.com/mineracks/seedhammer-v1-companion.git
synced 2026-06-26 22:01:05 +10:00
The Phase 2 scaffolding boot screen is replaced with the actual
upstream v1.3.0 gui package running in browser. cmd/emulator now
calls gui.NewApp(plat, version) + drives a.Frame() in a goroutine.
Lifts:
gui/ (top-level UI + state machine)
gui/assets (icons, fonts, 9-patch images)
gui/layout (constraint-based UI layout)
gui/op (drawing op primitives)
gui/saver (screensaver + idle timeout)
gui/text (text shaping)
gui/widget (button/menu/keyboard widgets)
All 11 .go files lifted verbatim from seedhammer/seedhammer @ v1.3.0
(commit 2f071c1d...), import paths rewritten seedhammer.com → mineracks.
browserPlatform now implements gui.Platform (12 methods):
Events(deadline) — drains v1.Event chan, maps to gui.ButtonEvent
(gui.Button enum order matches platform/v1.Button,
so the conversion is a direct uint cast)
Wakeup() — no-op (no sleep state in browser)
PlateSizes() — backup.SquarePlate, backup.LargePlate
Engraver() — nullEngraver stub (browser can't punch metal)
EngraverParams() — copy of mjolnir.Params {StrokeWidth: 38,
Millimeter: 126}. Inlined because tarm/serial
(mjolnir's USB dep) doesn't compile to js/wasm.
CameraFrame(size) — emits FrameEvent{Error: stubbed} so QR-scan
screens fall through cleanly; real handoff
lands when SeedSigner sim wires up (Phase 2.5)
Now() — time.Now()
DisplaySize() — 240×240
Dirty(r) — records rect, resets chunk cursor
NextChunk() — one-shot: returns full sub-image once per
Dirty cycle, then flushes the whole frame
buffer to JS via emulatorPaint()
ScanQR() — returns nil decodes (stub)
Debug() — false
The Engraver interface is satisfied by nullEngraver — Engrave() returns
"engraver not connected" so the GUI's engrave flow fails cleanly
instead of looking like it's working.
WASM grows 2.7MB → 8.0MB (gui package + btcd + crypto deps + fonts
all linked in). Acceptable for the v1 emulator one-time cache.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
152 lines
4.2 KiB
Go
152 lines
4.2 KiB
Go
//go:build ignore
|
|
|
|
// gen converts the PNG assets to embedded image literals.
|
|
package main
|
|
|
|
import (
|
|
"bytes"
|
|
"fmt"
|
|
"go/format"
|
|
"image"
|
|
"image/color"
|
|
"image/draw"
|
|
_ "image/png"
|
|
"log"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
"unicode"
|
|
|
|
simage "github.com/mineracks/seedhammer-v1-companion/image"
|
|
"github.com/mineracks/seedhammer-v1-companion/image/rgb565"
|
|
)
|
|
|
|
func main() {
|
|
pngs, err := filepath.Glob("*.png")
|
|
if err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
// out is the generated embed.go file.
|
|
out := new(bytes.Buffer)
|
|
// data is the binary embed.bin containing image data.
|
|
fmt.Fprintf(out, "// Code generated by gui/assets/gen.go; DO NOT EDIT.\n")
|
|
fmt.Fprintf(out, "package assets\n\n")
|
|
fmt.Fprintf(out, "import (\n")
|
|
fmt.Fprintf(out, "_ \"embed\"\n")
|
|
fmt.Fprintf(out, "\"unsafe\"\n\n")
|
|
fmt.Fprintf(out, "\"github.com/mineracks/seedhammer-v1-companion/image/ninepatch\"\n")
|
|
fmt.Fprintf(out, "\"github.com/mineracks/seedhammer-v1-companion/image/paletted\"\n\n")
|
|
fmt.Fprintf(out, ")\n\n")
|
|
fmt.Fprintf(out, "var (\n")
|
|
for _, p := range pngs {
|
|
r, err := os.Open(p)
|
|
if err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
img, _, err := image.Decode(r)
|
|
r.Close()
|
|
if err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
// Convert RGBA images to image.Paletted.
|
|
if nrgba, ok := img.(*image.NRGBA); ok {
|
|
// Convert to alpha pre-multiplied RGBA.
|
|
rgba := image.NewRGBA(nrgba.Bounds())
|
|
draw.Draw(rgba, rgba.Bounds(), nrgba, nrgba.Bounds().Min, draw.Src)
|
|
paletteMap := make(map[color.RGBA64]uint8)
|
|
b := rgba.Bounds()
|
|
index := uint8(0)
|
|
for y := b.Min.Y; y < b.Max.Y; y++ {
|
|
for x := b.Min.X; x < b.Max.X; x++ {
|
|
c := rgba.RGBA64At(x, y)
|
|
idx, ok := paletteMap[c]
|
|
if !ok {
|
|
idx = index
|
|
paletteMap[c] = idx
|
|
index++
|
|
if index < idx {
|
|
log.Fatalf("too many colors in %q:", p)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
palette := make([]color.Color, len(paletteMap))
|
|
for col, idx := range paletteMap {
|
|
palette[idx] = col
|
|
}
|
|
pimg := image.NewPaletted(rgba.Bounds(), palette)
|
|
for y := b.Min.Y; y < b.Max.Y; y++ {
|
|
for x := b.Min.X; x < b.Max.X; x++ {
|
|
c := rgba.RGBA64At(x, y)
|
|
idx := paletteMap[c]
|
|
pimg.SetColorIndex(x, y, idx)
|
|
}
|
|
}
|
|
img = pimg
|
|
}
|
|
name := p[:len(p)-len(filepath.Ext(p))]
|
|
ninePatchPrefix, ninePatchSuffix := "", ""
|
|
if ext := filepath.Ext(name); ext == ".9" {
|
|
name = name[:len(name)-len(ext)]
|
|
ninePatchPrefix, ninePatchSuffix = "ninepatch.New(", ")"
|
|
}
|
|
goName := filenameToGoName(name)
|
|
fmt.Fprintf(out, "%s = %s&", goName, ninePatchPrefix)
|
|
data := new(bytes.Buffer)
|
|
switch img := img.(type) {
|
|
case *image.Paletted:
|
|
r := simage.Crop(img)
|
|
crop := image.NewPaletted(r, img.Palette)
|
|
draw.Draw(crop, crop.Rect, img, crop.Rect.Min, draw.Src)
|
|
img = crop
|
|
data.Write(img.Pix)
|
|
start := data.Len()
|
|
// Write palette.
|
|
for _, c := range img.Palette {
|
|
r, g, b, a := c.RGBA()
|
|
rgb565 := rgb565.RGB888ToRGB565(uint8(r>>8), uint8(g>>8), uint8(b>>8))
|
|
data.Write([]byte{rgb565[0], rgb565[1], uint8(a >> 8)})
|
|
}
|
|
b := img.Rect
|
|
fmt.Fprintf(out, "paletted.Image{\n")
|
|
fmt.Fprintf(out, "Pix: unsafe.Slice(unsafe.StringData(%sData[:%d]), len(%[1]sData[:%[2]d])),\n", goName, start)
|
|
fmt.Fprintf(out, "Rect: paletted.Rectangle{MinX: %d, MinY: %d, MaxX: %d, MaxY: %d},\n", b.Min.X, b.Min.Y, b.Max.X, b.Max.Y)
|
|
fmt.Fprintf(out, "Palette: paletted.Palette(unsafe.Slice(unsafe.StringData(%sData[%d:]), len(%[1]sData[%[2]d:]))),\n", goName, start)
|
|
default:
|
|
log.Fatalf("unsupported image format for %q: %T\n", p, img)
|
|
}
|
|
fmt.Fprintf(out, "}%s\n", ninePatchSuffix)
|
|
binName := fmt.Sprintf("%s.bin", name)
|
|
fmt.Fprintf(out, "//go:embed %s\n", binName)
|
|
fmt.Fprintf(out, "%sData string\n\n", goName)
|
|
if err := os.WriteFile(binName, data.Bytes(), 0o644); err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
}
|
|
fmt.Fprintf(out, ")\n\n")
|
|
src, err := format.Source(out.Bytes())
|
|
if err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
if err := os.WriteFile("embed.go", src, 0o644); err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
}
|
|
|
|
func filenameToGoName(n string) string {
|
|
var name strings.Builder
|
|
toTitle := true
|
|
for _, ch := range n {
|
|
if !unicode.IsLetter(ch) && !unicode.IsDigit(ch) {
|
|
toTitle = true
|
|
continue
|
|
}
|
|
if toTitle {
|
|
toTitle = false
|
|
ch = unicode.ToTitle(ch)
|
|
}
|
|
name.WriteRune(ch)
|
|
}
|
|
return name.String()
|
|
}
|