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.
This commit is contained in:
42 files changed
+3415
-37
No files matched your search
@@ -0,0 +1,177 @@
|
||||
package code_test
|
||||
|
||||
import (
|
||||
"image"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/egoist/mygo/ui"
|
||||
|
||||
"git.zacharyzhang.com/ZacharyZhang-NY/8bitMyGoUI/code"
|
||||
"git.zacharyzhang.com/ZacharyZhang-NY/8bitMyGoUI/pixel"
|
||||
)
|
||||
|
||||
type clock struct{ t time.Time }
|
||||
|
||||
func (c *clock) now() time.Time { return c.t }
|
||||
|
||||
// world is the settings a test view runs under.
|
||||
type world struct {
|
||||
clk *clock
|
||||
reduce, inert bool
|
||||
}
|
||||
|
||||
func newWorld() *world {
|
||||
return &world{clk: &clock{time.Date(2026, 9, 2, 10, 30, 0, 0, time.UTC)}}
|
||||
}
|
||||
|
||||
// view builds on a fake clock, two pixels a dot.
|
||||
func view(w, h int, wd *world, build func(c *ui.Context)) *ui.Tester {
|
||||
tt := ui.NewTester(func(c *ui.Context) {
|
||||
pixel.Use(c, pixel.Settings{Palette: pixel.SpringDay, Now: wd.clk.now, Inert: wd.inert})
|
||||
build(c)
|
||||
}, w, h)
|
||||
if wd.reduce {
|
||||
tt.SetPreferences(ui.Preferences{ReduceMotion: true, TextScale: 1})
|
||||
tt.Frame()
|
||||
}
|
||||
return tt
|
||||
}
|
||||
|
||||
// later moves the clock on and builds a frame.
|
||||
func later(tt *ui.Tester, wd *world, d time.Duration) {
|
||||
wd.clk.t = wd.clk.t.Add(d)
|
||||
tt.Frame()
|
||||
}
|
||||
|
||||
func click(t *testing.T, tt *ui.Tester, label string) {
|
||||
t.Helper()
|
||||
if err := tt.Click(label); err != nil {
|
||||
t.Fatalf("%v in %q", err, tt.Texts())
|
||||
}
|
||||
}
|
||||
|
||||
func find(t *testing.T, tt *ui.Tester, label string) ui.Rect {
|
||||
t.Helper()
|
||||
r, ok := tt.Find(label)
|
||||
if !ok {
|
||||
t.Fatalf("no %q in %q", label, tt.Texts())
|
||||
}
|
||||
return r
|
||||
}
|
||||
|
||||
// none is a color outside the palette.
|
||||
const none pixel.Slot = 255
|
||||
|
||||
// at is the slot at dot x, y, or none.
|
||||
func at(img *image.RGBA, x, y int) pixel.Slot {
|
||||
c := img.RGBAAt(2*x, 2*y)
|
||||
for s, want := range pixel.SpringDay.Colors {
|
||||
if c.R == want.R && c.G == want.G && c.B == want.B {
|
||||
return pixel.Slot(s)
|
||||
}
|
||||
}
|
||||
return none
|
||||
}
|
||||
|
||||
// count is how many dots in a box hold s.
|
||||
func count(img *image.RGBA, x, y, w, h int, s pixel.Slot) int {
|
||||
n := 0
|
||||
for j := y; j < y+h; j++ {
|
||||
for i := x; i < x+w; i++ {
|
||||
if at(img, i, j) == s {
|
||||
n++
|
||||
}
|
||||
}
|
||||
}
|
||||
return n
|
||||
}
|
||||
|
||||
// dots is a found box in dots.
|
||||
func dots(r ui.Rect) (x, y, w, h int) {
|
||||
return int(r.X / 2), int(r.Y / 2), int(r.W / 2), int(r.H / 2)
|
||||
}
|
||||
|
||||
func mustPanic(t *testing.T, name string, fn func()) {
|
||||
t.Helper()
|
||||
defer func() {
|
||||
if recover() == nil {
|
||||
t.Errorf("%s did not panic", name)
|
||||
}
|
||||
}()
|
||||
fn()
|
||||
}
|
||||
|
||||
func TestCodeBlockNumbersColorsAndCopies(t *testing.T) {
|
||||
wd := newWorld()
|
||||
copied := false
|
||||
tt := view(500, 300, wd, func(c *ui.Context) {
|
||||
copied = code.CodeBlock(c, "main.go", "package main\n\nfunc main() {\n\tprintln(1)\n}\n", code.CodeBlockOptions{}).Copied || copied
|
||||
})
|
||||
// Tabs become four spaces; each line is read alone.
|
||||
for _, l := range []string{"package main", "func main() {", " println(1)", "}"} {
|
||||
find(t, tt, l)
|
||||
}
|
||||
x, y, w, h := dots(find(t, tt, "package main"))
|
||||
img := tt.Image()
|
||||
if count(img, x, y, w, h, pixel.Accent) == 0 || count(img, x, y, 10, h, pixel.Faint) == 0 {
|
||||
t.Fatal("no Accent keyword or Faint line number on line 1")
|
||||
}
|
||||
click(t, tt, "COPY")
|
||||
if !copied || tt.Clipboard() != "package main\n\nfunc main() {\n println(1)\n}" {
|
||||
t.Fatalf("copied %v: %q", copied, tt.Clipboard())
|
||||
}
|
||||
find(t, tt, "COPIED!")
|
||||
later(tt, wd, 2*time.Second)
|
||||
find(t, tt, "COPY")
|
||||
}
|
||||
|
||||
func TestTerminalRunsAndRecalls(t *testing.T) {
|
||||
wd := newWorld()
|
||||
lines, input := []string{"READY."}, ""
|
||||
var ran []string
|
||||
tt := view(500, 300, wd, func(c *ui.Context) {
|
||||
if r := code.Terminal(c, "Shell", lines, &input, code.TerminalOptions{}); r.Ran != "" {
|
||||
ran = append(ran, r.Ran)
|
||||
lines = append(lines, "$ "+r.Ran)
|
||||
}
|
||||
})
|
||||
find(t, tt, "READY.")
|
||||
click(t, tt, "COMMAND")
|
||||
for _, cmd := range []string{"ls", "pwd", "pwd"} {
|
||||
tt.Type(cmd)
|
||||
tt.Key(0, ui.KeyEnter)
|
||||
}
|
||||
if len(ran) != 3 || ran[0] != "ls" || input != "" {
|
||||
t.Fatalf("ran %q, input %q", ran, input)
|
||||
}
|
||||
find(t, tt, "$ pwd")
|
||||
// A draft waits below the history; repeats are kept once.
|
||||
tt.Type("ec")
|
||||
steps := []struct {
|
||||
key ui.Key
|
||||
want string
|
||||
}{{ui.KeyUp, "pwd"}, {ui.KeyUp, "ls"}, {ui.KeyUp, "ls"}, {ui.KeyDown, "pwd"}, {ui.KeyDown, "ec"}, {ui.KeyDown, "ec"}}
|
||||
for i, s := range steps {
|
||||
tt.Key(0, s.key)
|
||||
if input != s.want {
|
||||
t.Fatalf("step %d: input %q, want %q", i, input, s.want)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestTerminalHoldsUnderInert(t *testing.T) {
|
||||
wd := newWorld()
|
||||
input := ""
|
||||
tt := view(500, 300, wd, func(c *ui.Context) {
|
||||
code.Terminal(c, "Shell", nil, &input, code.TerminalOptions{})
|
||||
})
|
||||
click(t, tt, "COMMAND")
|
||||
tt.Type("ls")
|
||||
tt.Key(0, ui.KeyEnter)
|
||||
wd.inert = true
|
||||
tt.Key(0, ui.KeyUp)
|
||||
if input != "" {
|
||||
t.Fatalf("an inert terminal recalled %q", input)
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user