Files
8bitMyGoUI/gallery/demos_code.go
T
ZacharyZhang-NY 51af14709d Add code components 366-385; ScrollArea Follow, editor sync
A terminal with history, a Go-colored code block, a log viewer that
follows, a hex viewer, a memory map, a JSON tree, a line diff, a GAME
OVER crash screen, an FPS meter, profiler bars, breakpoints,
variables, command history, a shortcut list, a debug grid, a pixel
editor that exports string art, a palette editor, a sprite sheet, a
glyph map and a 6502 register view.

layout.ScrollArea takes Follow, and ChatLog now uses it. data.TreeNode
takes a Value drawn in its Slot. pixel gains Has, and Sprite.Art
writes MustSprite art. pixel.Editor takes the caller's value at each
input event, so a value changed between frames is never typed onto.
2026-10-12 07:18:30 -04:00

213 lines
7.5 KiB
Go

package gallery
import (
"fmt"
"math"
"strings"
"time"
"github.com/egoist/mygo/ui"
"git.zacharyzhang.com/ZacharyZhang-NY/8bitMyGoUI/code"
"git.zacharyzhang.com/ZacharyZhang-NY/8bitMyGoUI/input"
"git.zacharyzhang.com/ZacharyZhang-NY/8bitMyGoUI/pixel"
)
var codeEntries = []Entry{
{366, "Terminal", "终端", "code", "Type HELP. Up and Down bring back what you typed. XYZZY does nothing, as ever.", demoTerminal},
{367, "CodeBlock", "代码块", "code", "Go, colored by slot: keywords, strings, numbers, builtins. COPY takes it all.", demoCodeBlock},
{368, "LogViewer", "日志", "code", "Lines colored by level; pick the quietest worth seeing. It follows the newest.", demoLogViewer},
{369, "HexViewer", "十六进制", "code", "A save file laid bare: offset, hex and ASCII. Arrows walk the bytes.", demoHexViewer},
{370, "MemoryMap", "内存图", "code", "Each bank a block, filled as it fills. Red means start deleting sprites.", demoMemoryMap},
{371, "JSONTree", "JSON树", "code", "JSON in its own order, types in their own colors. Right opens, Left closes.", demoJSONTree},
{372, "DiffView", "差异", "code", "What changed between two versions, minus then plus.", demoDiffView},
{373, "CrashScreen", "崩溃画面", "code", "GAME OVER in a blinking red frame, as the old guru had. CONTINUE when ready.", demoCrashScreen},
{374, "FPSMeter", "帧率表", "code", "Frame times scroll by; green is on time, red is a stutter.", demoFPSMeter},
{375, "ProfilerBars", "性能条", "code", "Where a frame's 16.7 ms went, nested like a family tree.", demoProfilerBars},
}
type codeDemo struct {
shell []string
command string
least code.Level
logs []code.LogLine
byte, pick int
node, vr string
crashed bool
marks map[int]bool
art string
pal pixel.Palette
frame int
playing bool
glyph rune
step int
frames []time.Duration
last time.Time
history []string
}
func codeState(c *ui.Context) *codeDemo {
now := pixel.K(c).Now()
return ui.Local(c.Root(), demoKey("code"), func() codeDemo {
return codeDemo{
shell: []string{"8BIT-OS v1.0", "READY. TYPE HELP."}, least: code.Info,
logs: []code.LogLine{
{At: now.Add(-9 * time.Second), Level: code.Info, Text: "cartridge inserted"},
{At: now.Add(-8 * time.Second), Level: code.Debug, Text: "blew on the contacts"},
{At: now.Add(-6 * time.Second), Level: code.Warn, Text: "save battery low"},
{At: now.Add(-3 * time.Second), Level: code.Error, Text: "princess in another castle"},
},
marks: map[int]bool{4: true}, art: heartArt, pal: *pixel.Current(c).Palette, glyph: 'A',
history: []string{"load \"*\",8,1", "run", "help", "xyzzy"},
}
})
}
func demoTerminal(c *ui.Context, log func(string)) {
s := codeState(c)
r := code.Terminal(c, "8BIT-OS", s.shell, &s.command, code.TerminalOptions{Width: 260})
if r.Changed {
log(fmt.Sprintf("input %q", s.command))
}
if r.Ran != "" {
log("ran " + r.Ran)
s.shell = append(s.shell, "$ "+r.Ran)
switch strings.ToLower(strings.TrimSpace(r.Ran)) {
case "help":
s.shell = append(s.shell, "HELP LS CAT DATE CLEAR XYZZY")
case "ls":
s.shell = append(s.shell, "save.dat sprites/ music/ readme")
case "cat readme":
s.shell = append(s.shell, "IT'S DANGEROUS TO GO ALONE!")
case "date":
s.shell = append(s.shell, pixel.K(c).Now().Format("2006-01-02 15:04"))
case "clear":
s.shell = nil
case "xyzzy":
s.shell = append(s.shell, "NOTHING HAPPENS.")
default:
s.shell = append(s.shell, "SYNTAX ERROR")
}
}
}
const loop = `func main() {
// One frame at a time, forever.
hero := Hero{Name: "Aria", HP: 3}
for frame := 0; ; frame++ {
if hero.HP == 0 {
panic("GAME OVER")
}
draw(hero, frame%60, nil, true)
}
}`
func demoCodeBlock(c *ui.Context, log func(string)) {
if code.CodeBlock(c, "main.go", loop, code.CodeBlockOptions{}).Copied {
log("copied main.go")
}
}
func demoLogViewer(c *ui.Context, log func(string)) {
k := pixel.K(c)
s := codeState(c)
ui.Column(c).Gap(k.D(4)).Children(func() {
if code.LogViewer(c, "Log", s.logs, &s.least, code.LogViewerOptions{Height: 80}).Changed {
log(fmt.Sprintf("showing level %d and up", s.least))
}
if input.Button(c, "+ LINE", input.ButtonOptions{Size: input.Small}).Clicked {
lv := code.Level(len(s.logs) % 4)
s.logs = append(s.logs, code.LogLine{At: k.Now(), Level: lv, Text: fmt.Sprintf("tick %d", len(s.logs))})
log("added a line")
}
})
}
var saveFile = []byte("SAVE\x01\x00\x00\x03ARIA\x00\x00\x00\x00HP\x00\x2aGOLD\x03\xe7\x00\x00\xff\xfe\x00\x10")
func demoHexViewer(c *ui.Context, log func(string)) {
s := codeState(c)
if code.HexViewer(c, "save.dat", saveFile, &s.byte, code.HexViewerOptions{}).Changed {
log(fmt.Sprintf("offset %04X", s.byte))
}
}
func demoMemoryMap(c *ui.Context, log func(string)) {
code.MemoryMap(c, "Cartridge memory", []code.Bank{
{Name: "ROM0", Used: 15 << 10, Size: 16 << 10}, {Name: "ROM1", Used: 9 << 10, Size: 16 << 10},
{Name: "WRAM", Used: 3 << 10, Size: 8 << 10}, {Name: "VRAM", Used: 6 << 10, Size: 8 << 10},
}, code.MemoryMapOptions{})
}
const saveJSON = `{"player": {"name": "Aria", "level": 12, "alive": true,
"items": ["sword", "potion", "map"], "pet": null}, "checkpoint": 3, "flags": {}}`
func demoJSONTree(c *ui.Context, log func(string)) {
s := codeState(c)
if r := code.JSONTree(c, "save.json", saveJSON, &s.node, code.JSONTreeOptions{}); r.Changed || r.Toggled {
log("at " + s.node)
}
}
func demoDiffView(c *ui.Context, log func(string)) {
before := "speed = 4\njump = 2\nlives = 3\ncoins = 0"
after := "speed = 4\njump = 3\nlives = 3\ncoins = 0\nwarp = true"
code.DiffView(c, "settings.cfg", before, after, code.DiffViewOptions{})
}
func demoCrashScreen(c *ui.Context, log func(string)) {
k := pixel.K(c)
s := codeState(c)
if s.crashed {
if input.Button(c, "CRASH AGAIN", input.ButtonOptions{}).Clicked {
s.crashed = false
log("crashed again")
}
return
}
stack := []string{"main.loot(0x3)", "main.openChest(...)", "main.main()"}
if code.CrashScreen(c, "panic: index out of range [3] with length 3", stack, code.CrashScreenOptions{Width: 280}).Continue {
s.crashed = true
log("continued at " + k.Now().Format("15:04:05"))
}
}
func demoFPSMeter(c *ui.Context, log func(string)) {
k := pixel.K(c)
s := codeState(c)
// Made-up frames: mostly on time, now and then a hitch.
add := func(i int) {
ms := 9 + 4*math.Sin(float64(i)/5)
if i%23 == 7 {
ms = 38
}
s.frames = append(s.frames[max(0, len(s.frames)-200):], time.Duration(ms*float64(time.Millisecond)))
}
if len(s.frames) == 0 {
for i := range 56 {
add(i)
}
}
if !k.Reduce() {
if t := k.Tick(c, 12); s.last.IsZero() || k.Now().Sub(s.last) >= time.Second/12 {
s.last = k.Now()
add(t)
}
}
code.FPSMeter(c, "Frame times", s.frames, code.FPSMeterOptions{})
}
func demoProfilerBars(c *ui.Context, log func(string)) {
ms := func(f float64) time.Duration { return time.Duration(f * float64(time.Millisecond)) }
code.ProfilerBars(c, "One frame", code.Span{Name: "frame", Length: ms(16.7), Children: []code.Span{
{Name: "input", Length: ms(1)},
{Name: "update", Start: ms(1), Length: ms(6), Children: []code.Span{
{Name: "physics", Start: ms(1), Length: ms(3)}, {Name: "ai", Start: ms(4), Length: ms(2.5)},
}},
{Name: "render", Start: ms(7.5), Length: ms(8), Children: []code.Span{
{Name: "sprites", Start: ms(7.5), Length: ms(5), Children: []code.Span{{Name: "dither", Start: ms(9), Length: ms(2)}}},
{Name: "text", Start: ms(12.5), Length: ms(2)},
}},
}}, code.ProfilerBarsOptions{Width: 260})
}