Palette cycling, sky, sun and moon, clouds, starfield, rain, snow, petals, leaves, fireflies, waterfall, ocean, campfire, lava, aurora, lightning, mountains and a season scene; scanlines, vignette, screen shake, flash, dither fades, iris wipes, dissolves, mosaics, glitches, sparkles, explosions and confetti. Home opens on a live scene in the palette's season and a strip of all eight palettes. pixel adds Canvas.Cycled, Sprite.At, SpriteOf, Kit.Toward and Kit.Shot, which spends a cue under Reduce Motion so it never replays; display exports Reduce. Gallery grids and the sidebar are one Tab stop each.
149 lines
4.9 KiB
Go
149 lines
4.9 KiB
Go
package fx_test
|
||
|
||
import (
|
||
"image"
|
||
"testing"
|
||
"time"
|
||
|
||
"github.com/egoist/mygo/ui"
|
||
|
||
"git.zacharyzhang.com/ZacharyZhang-NY/8bitMyGoUI/fx"
|
||
"git.zacharyzhang.com/ZacharyZhang-NY/8bitMyGoUI/pixel"
|
||
)
|
||
|
||
type clock struct{ t time.Time }
|
||
|
||
func (c *clock) now() time.Time { return c.t }
|
||
|
||
func newClock() *clock { return &clock{time.Unix(1_800_000_000, 0)} }
|
||
|
||
// step is the clock's frame at fps, as pixel counts.
|
||
func (c *clock) step(fps int64) int { return int(c.t.UnixNano() / (int64(time.Second) / fps)) }
|
||
|
||
// tick is one step at fps, just past the edge.
|
||
func tick(fps int) time.Duration { return time.Second/time.Duration(fps) + time.Microsecond }
|
||
|
||
// view builds w×h dots on a fake clock, 2px each.
|
||
func view(w, h int, pal *pixel.Palette, clk *clock, reduce bool, build func(c *ui.Context)) *ui.Tester {
|
||
tt := ui.NewTester(func(c *ui.Context) {
|
||
pixel.Use(c, pixel.Settings{Palette: pal, Now: clk.now})
|
||
build(c)
|
||
}, 2*w, 2*h)
|
||
if reduce {
|
||
tt.SetPreferences(ui.Preferences{ReduceMotion: true, TextScale: 1})
|
||
tt.Frame()
|
||
}
|
||
return tt
|
||
}
|
||
|
||
// dots reads w×h dots as slots; -1 is off-palette.
|
||
func dots(img *image.RGBA, pal *pixel.Palette, w, h int) [][]int {
|
||
out := make([][]int, h)
|
||
for y := range h {
|
||
out[y] = make([]int, w)
|
||
for x := range w {
|
||
c, s := img.RGBAAt(2*x, 2*y), -1
|
||
for i, pc := range pal.Colors {
|
||
if pc.R == c.R && pc.G == c.G && pc.B == c.B {
|
||
s = i
|
||
break
|
||
}
|
||
}
|
||
out[y][x] = s
|
||
}
|
||
}
|
||
return out
|
||
}
|
||
|
||
// count is how many dots of a box hold s.
|
||
func count(d [][]int, 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 d[j][i] == int(s) {
|
||
n++
|
||
}
|
||
}
|
||
}
|
||
return n
|
||
}
|
||
|
||
func same(a, b [][]int) bool {
|
||
for y := range a {
|
||
for x := range a[y] {
|
||
if a[y][x] != b[y][x] {
|
||
return false
|
||
}
|
||
}
|
||
}
|
||
return true
|
||
}
|
||
|
||
func mustPanic(t *testing.T, name string, fn func()) {
|
||
t.Helper()
|
||
defer func() {
|
||
if recover() == nil {
|
||
t.Errorf("%s did not panic", name)
|
||
}
|
||
}()
|
||
fn()
|
||
}
|
||
|
||
// solid is a one-slot w×h box under effects.
|
||
func solid(c *ui.Context, w, h int, s pixel.Slot) {
|
||
k := pixel.K(c)
|
||
ui.Box(c).Size(k.D(w), k.D(h)).Label("content").Draw(func(p *ui.Painter, r ui.Rect) {
|
||
g := k.Canvas(p, r)
|
||
g.Rect(0, 0, g.W, g.H, s)
|
||
})
|
||
}
|
||
|
||
var portal = pixel.MustSprite("agcagc\ncagcag")
|
||
|
||
func TestAmbientEffectsMoveButHoldStillUnderReduceMotion(t *testing.T) {
|
||
for name, build := range map[string]func(c *ui.Context){
|
||
"PaletteCycle": func(c *ui.Context) {
|
||
fx.PaletteCycle(c, portal, []pixel.Slot{pixel.Accent, pixel.Good, pixel.Info}, fx.PaletteCycleOptions{})
|
||
},
|
||
"SunMoon": func(c *ui.Context) { fx.SunMoon(c, 12, fx.SunMoonOptions{Width: 80, Height: 48}) },
|
||
"Clouds": func(c *ui.Context) { fx.Clouds(c, fx.CloudsOptions{Width: 80, Height: 48}) },
|
||
"Starfield": func(c *ui.Context) { fx.Starfield(c, fx.StarfieldOptions{Width: 80, Height: 48}) },
|
||
"Rain": func(c *ui.Context) { fx.Rain(c, fx.RainOptions{Width: 80, Height: 48}) },
|
||
"Snow": func(c *ui.Context) { fx.Snow(c, fx.SnowOptions{Width: 80, Height: 48}) },
|
||
"Petals": func(c *ui.Context) { fx.Petals(c, fx.PetalsOptions{Width: 80, Height: 48}) },
|
||
"Leaves": func(c *ui.Context) { fx.Leaves(c, fx.LeavesOptions{Width: 80, Height: 48}) },
|
||
"Fireflies": func(c *ui.Context) { fx.Fireflies(c, fx.FirefliesOptions{Width: 80, Height: 48}) },
|
||
"Waterfall": func(c *ui.Context) { fx.Waterfall(c, fx.WaterfallOptions{Width: 40, Height: 48}) },
|
||
"Ocean": func(c *ui.Context) { fx.Ocean(c, fx.OceanOptions{Width: 80, Height: 48}) },
|
||
"Fire": func(c *ui.Context) { fx.Fire(c, fx.FireOptions{}) },
|
||
"Lava": func(c *ui.Context) { fx.Lava(c, fx.LavaOptions{Width: 80, Height: 48}) },
|
||
"Aurora": func(c *ui.Context) { fx.Aurora(c, fx.AuroraOptions{Width: 80, Height: 48}) },
|
||
"Mountains": func(c *ui.Context) { fx.Mountains(c, fx.MountainsOptions{Width: 80, Height: 48}) },
|
||
"Season": func(c *ui.Context) { fx.SeasonScene(c, pixel.Winter, 22, fx.SeasonSceneOptions{Width: 80, Height: 48}) },
|
||
"Sparkle": func(c *ui.Context) { fx.Sparkle(c, fx.SparkleOptions{}) },
|
||
"Confetti": func(c *ui.Context) { fx.Confetti(c, fx.ConfettiOptions{Width: 80, Height: 48}) },
|
||
"NightSky": func(c *ui.Context) { fx.Sky(c, fx.SkyOptions{Width: 80, Height: 48}) },
|
||
"NightSnows": func(c *ui.Context) { fx.Snow(c, fx.SnowOptions{Width: 80, Height: 48}) },
|
||
} {
|
||
// A day sky holds still: only stars move.
|
||
pal := pixel.SpringDay
|
||
if name == "NightSky" || name == "NightSnows" || name == "Season" {
|
||
pal = pixel.WinterNight
|
||
}
|
||
for _, reduce := range []bool{false, true} {
|
||
clk := newClock()
|
||
tt := view(80, 48, pal, clk, reduce, build)
|
||
first := dots(tt.Image(), pal, 80, 48)
|
||
moved := false
|
||
for _, d := range []time.Duration{300, 700, 1300} {
|
||
clk.t = clk.t.Add(d * time.Millisecond)
|
||
tt.Frame()
|
||
moved = moved || !same(first, dots(tt.Image(), pal, 80, 48))
|
||
}
|
||
if moved == reduce {
|
||
t.Errorf("%s, reduce %v: moved %v", name, reduce, moved)
|
||
}
|
||
}
|
||
}
|
||
}
|