Add canvas, dither, sprites, clock and pixel elements
This commit is contained in:
11 files changed
+1274
-11
No files matched your search
@@ -0,0 +1,242 @@
|
||||
package pixel_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/egoist/mygo/ui"
|
||||
|
||||
"git.zacharyzhang.com/ZacharyZhang-NY/8bitMyGoUI/pixel"
|
||||
)
|
||||
|
||||
func TestSpriteLettersMatchSlots(t *testing.T) {
|
||||
sp := pixel.MustSprite("kbflsenwaAigyrcm")
|
||||
d := canvas(t, 16, 1, func(g pixel.Canvas) { g.Sprite(0, 0, sp, 0, pixel.Ink, 1) })
|
||||
for i := range 16 {
|
||||
if got := d.at(i, 0); got != pixel.Slot(i) {
|
||||
t.Fatalf("letter %d drew slot %d", i, got)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestSpriteFramesMirrorScale(t *testing.T) {
|
||||
sp := pixel.MustSprite(`
|
||||
#a.
|
||||
..g
|
||||
|
||||
.r.
|
||||
.r.
|
||||
`)
|
||||
if sp.W != 3 || sp.H != 2 || sp.Frames() != 2 {
|
||||
t.Fatalf("sprite %d×%d with %d frames", sp.W, sp.H, sp.Frames())
|
||||
}
|
||||
d := canvas(t, 6, 4, func(g pixel.Canvas) {
|
||||
g.Sprite(0, 0, sp, 0, pixel.Magic, 1)
|
||||
g.Sprite(3, 0, sp, 3, pixel.Ink, 1)
|
||||
g.Sprite(0, 2, sp.Mirror(), 0, pixel.Magic, 1)
|
||||
})
|
||||
checks := map[[2]int]pixel.Slot{
|
||||
{0, 0}: pixel.Magic, {1, 0}: pixel.Accent, {2, 0}: pixel.Back, {2, 1}: pixel.Good,
|
||||
{4, 0}: pixel.Bad, {4, 1}: pixel.Bad, {3, 0}: pixel.Back,
|
||||
{2, 2}: pixel.Magic, {1, 2}: pixel.Accent, {0, 3}: pixel.Good,
|
||||
}
|
||||
for p, want := range checks {
|
||||
if got := d.at(p[0], p[1]); got != want {
|
||||
t.Fatalf("dot %v = %d, want %d", p, got, want)
|
||||
}
|
||||
}
|
||||
big := canvas(t, 6, 4, func(g pixel.Canvas) { g.Sprite(0, 0, sp, 0, pixel.Magic, 2) })
|
||||
if big.count(0, 0, 2, 2, pixel.Magic) != 4 || big.count(2, 0, 2, 2, pixel.Accent) != 4 {
|
||||
t.Fatal("scale 2 must fill 2×2 dots per art pixel")
|
||||
}
|
||||
for name, art := range map[string]string{"ragged": "ab\nabc", "letter": "az", "empty": " ", "frames": "aa\n\naa\naa"} {
|
||||
mustPanic(t, name, func() { pixel.MustSprite(art) })
|
||||
}
|
||||
}
|
||||
|
||||
func TestClock(t *testing.T) {
|
||||
at := time.Unix(1_700_000_001, 250_000_000)
|
||||
var step, busy, tick int
|
||||
tt := ui.NewTester(func(c *ui.Context) {
|
||||
pixel.Use(c, pixel.Settings{Palette: pixel.SpringDay, Now: func() time.Time { return at }})
|
||||
k := pixel.K(c)
|
||||
tick = k.Tick(c, 8)
|
||||
ui.Box(c).Size(4, 4).Draw(func(p *ui.Painter, r ui.Rect) {
|
||||
g := k.Canvas(p, r)
|
||||
step, busy = g.Step(4), g.Busy(4)
|
||||
})
|
||||
}, 10, 10)
|
||||
if want := int(at.UnixNano() / int64(time.Second/4)); step != want || busy != want {
|
||||
t.Fatalf("Step %d Busy %d, want %d", step, busy, want)
|
||||
}
|
||||
if want := int(at.UnixNano() / int64(time.Second/8)); tick != want {
|
||||
t.Fatalf("Tick %d, want %d", tick, want)
|
||||
}
|
||||
pixel.ResetDue()
|
||||
tt.SetPreferences(ui.Preferences{ReduceMotion: true, TextScale: 1})
|
||||
if step != 0 || busy == 0 {
|
||||
t.Fatalf("under Reduce Motion: Step %d (want 0), Busy %d", step, busy)
|
||||
}
|
||||
if due := time.Until(pixel.Due()); due <= 0 || due > 125*time.Millisecond {
|
||||
t.Fatalf("Due in %v, want the 8 fps wait on the real clock", due)
|
||||
}
|
||||
pixel.ResetDue()
|
||||
if !pixel.Due().IsZero() {
|
||||
t.Fatal("ResetDue left a deadline")
|
||||
}
|
||||
mustPanic(t, "fps 0", func() {
|
||||
ui.NewTester(func(c *ui.Context) {
|
||||
pixel.Use(c, pixel.Settings{Palette: pixel.SpringDay})
|
||||
pixel.K(c).Tick(c, 0)
|
||||
}, 10, 10)
|
||||
})
|
||||
mustPanic(t, "Step 0 under Reduce Motion", func() {
|
||||
on := false
|
||||
tt := ui.NewTester(func(c *ui.Context) {
|
||||
pixel.Use(c, pixel.Settings{Palette: pixel.SpringDay})
|
||||
k := pixel.K(c)
|
||||
ui.Box(c).Size(4, 4).Draw(func(p *ui.Painter, r ui.Rect) {
|
||||
if on {
|
||||
k.Canvas(p, r).Step(0)
|
||||
}
|
||||
})
|
||||
}, 10, 10)
|
||||
tt.SetPreferences(ui.Preferences{ReduceMotion: true, TextScale: 1})
|
||||
on = true
|
||||
tt.Frame()
|
||||
})
|
||||
}
|
||||
|
||||
func TestWrapFollowsSameFrameResize(t *testing.T) {
|
||||
const s = "像素风格组件库,每个点都经过认真摆放,在限制中创造丰富。"
|
||||
width := float32(120)
|
||||
tt := ui.NewTester(func(c *ui.Context) {
|
||||
pixel.Use(c, pixel.Settings{Palette: pixel.SpringDay, Dot: 2})
|
||||
ui.Column(c).Width(width).Children(func() {
|
||||
pixel.Text(c, s, pixel.TextOptions{Wrap: true})
|
||||
})
|
||||
}, 400, 800)
|
||||
for _, w := range []float32{60, 200, 120} {
|
||||
width = w
|
||||
tt.Frame()
|
||||
r, _ := tt.Find(s)
|
||||
if want := float32(len(pixel.Lines(s, int(w/2))) * 16 * 2); r.H != want {
|
||||
t.Fatalf("width %v: text height %v, want %v", w, r.H, want)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestTextElement(t *testing.T) {
|
||||
var width float32
|
||||
tt := ui.NewTester(func(c *ui.Context) {
|
||||
pixel.Use(c, pixel.Settings{Palette: pixel.SpringDay, Dot: 2})
|
||||
pixel.Text(c, "HELLO", pixel.TextOptions{Scale: 2})
|
||||
ui.Column(c).Width(120).Children(func() {
|
||||
pixel.Text(c, "像素风格组件库,每个点都经过认真摆放。", pixel.TextOptions{Wrap: true})
|
||||
})
|
||||
pixel.Text(c, "one\ntwo\nthree", pixel.TextOptions{MaxLines: 2})
|
||||
width = float32(pixel.Measure("HELLO") * 2 * 2)
|
||||
}, 400, 400)
|
||||
r, ok := tt.Find("HELLO")
|
||||
if !ok || r.W != width || r.H != 2*16*2 {
|
||||
t.Fatalf("HELLO box %v (found %v), want %v×64", r, ok, width)
|
||||
}
|
||||
r, _ = tt.Find("像素风格组件库,每个点都经过认真摆放。")
|
||||
if lines := len(pixel.Lines("像素风格组件库,每个点都经过认真摆放。", 60)); r.H != float32(lines*16*2) || lines < 3 {
|
||||
t.Fatalf("wrapped box %v, want %d lines", r, lines)
|
||||
}
|
||||
if r, _ = tt.Find("one\ntwo\nthree"); r.H != 2*16*2 {
|
||||
t.Fatalf("MaxLines box height %v, want 64", r.H)
|
||||
}
|
||||
}
|
||||
|
||||
func TestIconElement(t *testing.T) {
|
||||
sp := pixel.MustSprite("aa\naa\naa")
|
||||
tt := ui.NewTester(func(c *ui.Context) {
|
||||
pixel.Use(c, pixel.Settings{Palette: pixel.SpringDay, Dot: 2})
|
||||
pixel.Icon(c, sp, pixel.IconOptions{Scale: 3, Label: "block"})
|
||||
pixel.Icon(c, sp, pixel.IconOptions{})
|
||||
}, 100, 100)
|
||||
r, ok := tt.Find("block")
|
||||
if !ok || r.W != 2*3*2 || r.H != 3*3*2 {
|
||||
t.Fatalf("icon box %v found %v, want 12×18", r, ok)
|
||||
}
|
||||
if len(tt.Texts()) != 1 {
|
||||
t.Fatalf("unlabeled icon is visible to assistive technology: %q", tt.Texts())
|
||||
}
|
||||
}
|
||||
|
||||
func shellView(disabled *bool, clicks *int, st *pixel.State) func(c *ui.Context) {
|
||||
at := time.Unix(1_700_000_001, 250_000_000)
|
||||
return func(c *ui.Context) {
|
||||
pixel.Use(c, pixel.Settings{Palette: pixel.SummerDay, Dot: 2, Now: func() time.Time { return at }})
|
||||
k := pixel.K(c)
|
||||
ui.Box(c).Size(k.D(16), k.D(4))
|
||||
ui.Row(c).Children(func() {
|
||||
ui.Box(c).Size(k.D(16), k.D(8))
|
||||
shell, base, s := pixel.Shell(c, func() *ui.Element {
|
||||
return ui.ButtonBase(c).Label("go").Disabled(*disabled)
|
||||
})
|
||||
*st = s
|
||||
if base.Clicked() {
|
||||
*clicks++
|
||||
}
|
||||
shell.Size(k.D(12), k.D(8)).Draw(func(p *ui.Painter, r ui.Rect) {
|
||||
g := k.Canvas(p, r)
|
||||
g.Rect(0, 0, 12, 8, pixel.Accent)
|
||||
if s.Focus {
|
||||
g.FocusMark(0, 0, 12, 8)
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestShellKeepsLookAndBehavior(t *testing.T) {
|
||||
disabled, clicks, st := false, 0, pixel.State{}
|
||||
tt := ui.NewTester(shellView(&disabled, &clicks, &st), 100, 100)
|
||||
tt.ClickAt(40, 16)
|
||||
if clicks != 1 {
|
||||
t.Fatalf("enabled shell: %d clicks", clicks)
|
||||
}
|
||||
tt.Key(0, ui.KeyTab)
|
||||
tt.Frame()
|
||||
if !st.Focus {
|
||||
t.Fatal("Tab did not give the base visible focus")
|
||||
}
|
||||
d := dots{t: t, img: tt.Image(), pal: pixel.SummerDay}
|
||||
if d.at(14, 2) != pixel.Accent || d.at(15, 2) != pixel.Back || d.at(29, 13) != pixel.Accent {
|
||||
t.Fatal("focus ring dots missing around the shell")
|
||||
}
|
||||
if d.count(3, 5, 12, 6, pixel.Edge) == 0 || d.count(3, 5, 12, 6, pixel.Lit) == 0 {
|
||||
t.Fatal("hand cursor missing left of the shell")
|
||||
}
|
||||
disabled = true
|
||||
tt.Frame()
|
||||
tt.ClickAt(40, 16)
|
||||
if clicks != 1 || !st.Disabled || st.Focus {
|
||||
t.Fatalf("disabled: clicks %d, state %+v", clicks, st)
|
||||
}
|
||||
d = dots{t: t, img: tt.Image(), pal: pixel.SummerDay}
|
||||
if d.count(16, 4, 12, 8, pixel.Accent) != 96 {
|
||||
t.Fatal("disabled base dimmed the shell's look")
|
||||
}
|
||||
}
|
||||
|
||||
func TestVeilAnchorsAcrossElements(t *testing.T) {
|
||||
tt := ui.NewTester(func(c *ui.Context) {
|
||||
pixel.Use(c, pixel.Settings{Palette: pixel.SummerDay, Dot: 2})
|
||||
k := pixel.K(c)
|
||||
ui.Box(c).Size(k.D(1), k.D(1))
|
||||
ui.Row(c).Children(func() {
|
||||
ui.Box(c).Size(k.D(3), k.D(2))
|
||||
ui.Box(c).Size(k.D(4), k.D(2)).Draw(func(p *ui.Painter, r ui.Rect) {
|
||||
k.Canvas(p, r).Checker(0, 0, 4, 2, pixel.Ink)
|
||||
})
|
||||
})
|
||||
}, 14, 6)
|
||||
d := dots{t: t, img: tt.Image(), pal: pixel.SummerDay}
|
||||
if d.at(3, 1) != pixel.Ink || d.at(4, 1) != pixel.Back || d.at(3, 2) != pixel.Back || d.at(4, 2) != pixel.Ink {
|
||||
t.Fatal("checker phase follows the element, not the window")
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user