Add fx components 291-320 and the home scene; Kit.Shot, Kit.Toward

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.
This commit is contained in:
ZacharyZhang-NY committed 2026-10-12 04:05:25 -04:00
1 parent 86a6901333
commit 8be66c09f2
56 files changed
+3324 -101

No files matched your search

+1 -1
View File
@@ -85,7 +85,7 @@ func TestSearch(t *testing.T) {
t.Fatal("/ did not focus search")
}
tt.Type("dith")
if !tt.HasText("1 results") || !tt.HasText("Dither") {
if !tt.HasText("2 results") || !tt.HasText("Dither") || !tt.HasText("FadeDither") {
t.Fatalf("results: %q", tt.Texts())
}
tt.Key(0, ui.KeyP)
+1 -1
View File
@@ -75,7 +75,7 @@ func (a *App) sidebar(c *ui.Context) {
g.VLine(g.W-2, 0, g.H, pixel.Shade)
g.VLine(g.W-1, 0, g.H, pixel.Edge)
}).Children(func() {
ui.Column(c).Padding(k.D(4)).Gap(k.D(1)).Children(func() {
ui.Column(c).Padding(k.D(4)).Gap(k.D(1)).FocusGroup(ui.Vertical).Children(func() {
for _, cat := range Categories {
n := count(cat.Key)
path := "/cat/" + cat.Key
+194
View File
@@ -0,0 +1,194 @@
package gallery
import (
"fmt"
"math"
"github.com/egoist/mygo/ui"
"git.zacharyzhang.com/ZacharyZhang-NY/8bitMyGoUI/fx"
"git.zacharyzhang.com/ZacharyZhang-NY/8bitMyGoUI/input"
"git.zacharyzhang.com/ZacharyZhang-NY/8bitMyGoUI/layout"
"git.zacharyzhang.com/ZacharyZhang-NY/8bitMyGoUI/pixel"
)
var fxEntries = []Entry{
{291, "PaletteCycle", "调色板循环", "fx", "Four slots take turns, so the rings seem to flow.", demoPaletteCycle},
{292, "Sky", "天空", "fx", "Blue by day; by night, stars that blink.", demoSky},
{293, "SunMoon", "日月", "fx", "Up at six, down at six. The moon takes the night shift.", demoSunMoon},
{294, "Clouds", "云", "fx", "Far clouds dawdle, near clouds hurry.", demoClouds},
{295, "Starfield", "星空", "fx", "Three layers of stars rush past. Hyperspace at 12 fps.", demoStarfield},
{296, "Rain", "雨", "fx", "Slanted streaks, each landing with a tiny splash.", demoRain},
{297, "Snow", "雪", "fx", "Flakes sway down; near ones are big and quick.", demoSnow},
{298, "Petals", "樱花瓣", "fx", "Petals ride the breeze, turning as they fall.", demoPetals},
{299, "Leaves", "落叶", "fx", "Leaves swing side to side on the way down.", demoLeaves},
{300, "Fireflies", "萤火虫", "fx", "Little lights wander, glow, and rest.", demoFireflies},
{301, "Waterfall", "瀑布", "fx", "Four slots cycling down make the water fall.", demoWaterfallFx},
{302, "Ocean", "海浪", "fx", "Wave bands roll in by cycling, with glints on top.", demoOcean},
{303, "Fire", "火焰", "fx", "Three flicker frames, heat rising through them.", demoFire},
{304, "Lava", "熔岩", "fx", "Hot bands flow; bubbles swell and pop. Do not touch.", demoLava},
{305, "Aurora", "极光", "fx", "Curtains of light, their colors sliding sideways.", demoAurora},
{306, "Lightning", "闪电", "fx", "A flash, then a forked bolt. No two strikes alike.", demoLightning},
{307, "Mountains", "山脉", "fx", "Three ridges scroll at three speeds. Parallax!", demoMountains},
{308, "SeasonScene", "四季场景", "fx", "Pick a season and an hour; the world repaints itself.", demoSeasonScene},
}
type fxDemo struct {
season, hour int
strike, shake, flash, mosaic, glitch, boom int
fade, iris, dissolve bool
}
func fxState(c *ui.Context) *fxDemo {
p := pixel.K(c).Palette()
return ui.Local(c.Root(), demoKey("fx"), func() fxDemo { return fxDemo{season: int(p.Season), hour: hourOf(p)} })
}
// hourOf suits p: morning by day, late evening by night.
func hourOf(p *pixel.Palette) int {
if p.Night {
return 21
}
return 10
}
// paletteOf is the settings in force with season's palette.
func paletteOf(c *ui.Context, s pixel.Season, night bool) pixel.Settings {
set := pixel.Current(c)
set.Palette = pixel.Find(s, night)
return set
}
// over stacks an effect on a same-sized sky.
func over(c *ui.Context, w, h int, effect func()) {
layout.Layers(c, func() { fx.Sky(c, fx.SkyOptions{Width: w, Height: h}) }, effect)
}
// portalBand rings the portal, outside in.
var portalBand = []pixel.Slot{pixel.Accent, pixel.AccentLit, pixel.Magic, pixel.Info}
var portal = pixel.SpriteOf(15, 15, func(x, y int) (pixel.Slot, bool) {
d := math.Hypot(float64(x-7), float64(y-7))
return portalBand[int(d)%len(portalBand)], d < 7.5
})
func demoPaletteCycle(c *ui.Context, log func(string)) {
k := pixel.K(c)
ui.Row(c).Gap(k.D(8)).AlignItems(ui.Center).Children(func() {
fx.PaletteCycle(c, portal, portalBand, fx.PaletteCycleOptions{Scale: 3, Label: "portal"})
fx.PaletteCycle(c, portal, portalBand, fx.PaletteCycleOptions{Scale: 2, FPS: 4, Label: "slow portal"})
})
}
func demoSky(c *ui.Context, log func(string)) {
k := pixel.K(c)
ui.Row(c).Gap(k.D(4)).Children(func() {
for _, night := range []bool{false, true} {
pixel.Scope(c, paletteOf(c, k.Palette().Season, night), func() {
fx.Sky(c, fx.SkyOptions{Width: 68, Height: 64, Label: map[bool]string{false: "day sky", true: "night sky"}[night]})
})
}
})
}
func demoSunMoon(c *ui.Context, log func(string)) {
k := pixel.K(c)
s := fxState(c)
ui.Column(c).Gap(k.D(6)).Children(func() {
pixel.Scope(c, paletteOf(c, k.Palette().Season, s.hour < 6 || s.hour >= 18), func() {
over(c, 140, 72, func() {
fx.SunMoon(c, s.hour, fx.SunMoonOptions{Width: 140, Height: 72, Label: fmt.Sprintf("%02d:00", s.hour)})
})
})
if input.Stepper(c, "Hour", &s.hour, input.StepperOptions{Max: 23}).Changed {
log(fmt.Sprintf("hour %02d", s.hour))
}
})
}
func demoClouds(c *ui.Context, log func(string)) {
over(c, 140, 80, func() { fx.Clouds(c, fx.CloudsOptions{Width: 140, Height: 80, Label: "clouds"}) })
}
func demoStarfield(c *ui.Context, log func(string)) {
fx.Starfield(c, fx.StarfieldOptions{Width: 140, Height: 80, Label: "stars"})
}
func demoRain(c *ui.Context, log func(string)) {
over(c, 140, 80, func() { fx.Rain(c, fx.RainOptions{Width: 140, Height: 80, Label: "rain"}) })
}
func demoSnow(c *ui.Context, log func(string)) {
over(c, 140, 80, func() { fx.Snow(c, fx.SnowOptions{Width: 140, Height: 80, Label: "snow"}) })
}
func demoPetals(c *ui.Context, log func(string)) {
over(c, 140, 80, func() { fx.Petals(c, fx.PetalsOptions{Width: 140, Height: 80, Label: "petals"}) })
}
func demoLeaves(c *ui.Context, log func(string)) {
over(c, 140, 80, func() { fx.Leaves(c, fx.LeavesOptions{Width: 140, Height: 80, Label: "leaves"}) })
}
func demoFireflies(c *ui.Context, log func(string)) {
pixel.Scope(c, paletteOf(c, pixel.K(c).Palette().Season, true), func() {
over(c, 140, 80, func() { fx.Fireflies(c, fx.FirefliesOptions{Width: 140, Height: 80, Label: "fireflies"}) })
})
}
func demoWaterfallFx(c *ui.Context, log func(string)) {
fx.Waterfall(c, fx.WaterfallOptions{Label: "waterfall"})
}
func demoOcean(c *ui.Context, log func(string)) {
fx.Ocean(c, fx.OceanOptions{Width: 140, Label: "ocean"})
}
func demoFire(c *ui.Context, log func(string)) {
fx.Fire(c, fx.FireOptions{Scale: 3, Label: "campfire"})
}
func demoLava(c *ui.Context, log func(string)) {
fx.Lava(c, fx.LavaOptions{Width: 140, Label: "lava"})
}
func demoAurora(c *ui.Context, log func(string)) {
pixel.Scope(c, paletteOf(c, pixel.K(c).Palette().Season, true), func() {
layout.Layers(c, func() { fx.Sky(c, fx.SkyOptions{Width: 140, Height: 80}) },
func() { fx.Aurora(c, fx.AuroraOptions{Width: 140, Height: 80, Label: "aurora"}) },
func() { fx.Mountains(c, fx.MountainsOptions{Width: 140, Height: 80}) })
})
}
func demoLightning(c *ui.Context, log func(string)) {
k := pixel.K(c)
s := fxState(c)
ui.Column(c).Gap(k.D(6)).Children(func() {
pixel.Scope(c, paletteOf(c, k.Palette().Season, true), func() {
over(c, 120, 80, func() { fx.Lightning(c, fx.LightningOptions{Cue: s.strike, Width: 120, Height: 80, Label: "storm"}) })
})
if small(c, "STRIKE", false) {
s.strike++
log(fmt.Sprintf("strike %d", s.strike))
}
})
}
func demoMountains(c *ui.Context, log func(string)) {
over(c, 140, 80, func() { fx.Mountains(c, fx.MountainsOptions{Width: 140, Height: 80, Label: "mountains"}) })
}
func demoSeasonScene(c *ui.Context, log func(string)) {
k := pixel.K(c)
s := fxState(c)
ui.Column(c).Gap(k.D(6)).Children(func() {
fx.SeasonScene(c, pixel.Season(s.season), s.hour, fx.SeasonSceneOptions{Width: 144, Height: 80, Label: "the world"})
seasons := []string{"Spring", "Summer", "Autumn", "Winter"}
if input.Segmented(c, "Season", seasons, &s.season, input.SegmentedOptions{}).Changed {
log("season " + seasons[s.season])
}
if input.Stepper(c, "Hour", &s.hour, input.StepperOptions{Max: 23}).Changed {
log(fmt.Sprintf("hour %02d", s.hour))
}
})
}
+156
View File
@@ -0,0 +1,156 @@
package gallery
import (
"fmt"
"github.com/egoist/mygo/ui"
"git.zacharyzhang.com/ZacharyZhang-NY/8bitMyGoUI/display"
"git.zacharyzhang.com/ZacharyZhang-NY/8bitMyGoUI/fx"
"git.zacharyzhang.com/ZacharyZhang-NY/8bitMyGoUI/input"
"git.zacharyzhang.com/ZacharyZhang-NY/8bitMyGoUI/layout"
"git.zacharyzhang.com/ZacharyZhang-NY/8bitMyGoUI/pixel"
)
var screenFxEntries = []Entry{
{309, "Scanlines", "扫描线", "fx", "Every other row, half shaded. Instant CRT.", demoScanlines},
{310, "Vignette", "暗角", "fx", "The corners dither into the dark.", demoVignette},
{311, "ScreenShake", "震屏", "fx", "Something big just landed.", demoScreenShake},
{312, "Flash", "闪白", "fx", "A step of light, then half a step. Never under Reduce Motion.", demoFlash},
{313, "FadeDither", "抖动淡出", "fx", "Fades through Bayer levels, never through opacity.", demoFadeDither},
{314, "IrisWipe", "光圈转场", "fx", "The circle closes on the middle. That's all, folks.", demoIrisWipe},
{315, "Dissolve", "溶解", "fx", "Dots give up one batch at a time, in shuffled order.", demoDissolve},
{316, "Mosaic", "马赛克", "fx", "Blocks grow to eight dots, then the picture comes back.", demoMosaic},
{317, "Glitch", "故障", "fx", "Rows slip and two colors trade places. Blow on the cartridge.", demoGlitch},
{318, "Sparkle", "闪光", "fx", "Four stars take turns around a shiny thing.", demoSparkle},
{319, "Explosion", "爆炸", "fx", "Fireball, smoke, debris. Stand back.", demoExplosion},
{320, "Confetti", "彩纸", "fx", "Bits of every color tumble down. You won!", demoConfetti},
}
// stage is a little world in the palette's season.
func stage(c *ui.Context) {
p := pixel.K(c).Palette()
fx.SeasonScene(c, p.Season, hourOf(p), fx.SeasonSceneOptions{Width: 128, Height: 72, Label: "stage"})
}
func demoScanlines(c *ui.Context, log func(string)) {
ui.Column(c).AlignItems(ui.Start).Children(func() {
fx.Scanlines(c, fx.ScanlinesOptions{}, func() { stage(c) })
})
}
func demoVignette(c *ui.Context, log func(string)) {
ui.Column(c).AlignItems(ui.Start).Children(func() {
fx.Vignette(c, fx.VignetteOptions{}, func() { stage(c) })
})
}
func demoScreenShake(c *ui.Context, log func(string)) {
k := pixel.K(c)
s := fxState(c)
ui.Column(c).Gap(k.D(6)).AlignItems(ui.Start).Children(func() {
fx.ScreenShake(c, fx.ScreenShakeOptions{Cue: s.shake}, func() { stage(c) })
if small(c, "SHAKE", false) {
s.shake++
log("rumble!")
}
})
}
func demoFlash(c *ui.Context, log func(string)) {
k := pixel.K(c)
s := fxState(c)
ui.Column(c).Gap(k.D(6)).AlignItems(ui.Start).Children(func() {
fx.Flash(c, fx.FlashOptions{Cue: s.flash}, func() { stage(c) })
if small(c, "FLASH", false) {
s.flash++
log("flash")
}
})
}
// toggled shows content under a transition, with its toggle.
func toggled(c *ui.Context, text string, on *bool, log func(string), effect func(content func())) {
k := pixel.K(c)
ui.Column(c).Gap(k.D(6)).AlignItems(ui.Start).Children(func() {
effect(func() { stage(c) })
if input.Toggle(c, text, on, input.ToggleOptions{Size: input.Small}).Changed {
log(fmt.Sprintf("%s %v", text, *on))
}
})
}
func demoFadeDither(c *ui.Context, log func(string)) {
s := fxState(c)
toggled(c, "FADE OUT", &s.fade, log, func(content func()) { fx.FadeDither(c, s.fade, fx.FadeDitherOptions{}, content) })
}
func demoIrisWipe(c *ui.Context, log func(string)) {
s := fxState(c)
toggled(c, "CLOSE", &s.iris, log, func(content func()) { fx.IrisWipe(c, s.iris, fx.IrisWipeOptions{}, content) })
}
func demoDissolve(c *ui.Context, log func(string)) {
s := fxState(c)
toggled(c, "DISSOLVE", &s.dissolve, log, func(content func()) { fx.Dissolve(c, s.dissolve, fx.DissolveOptions{}, content) })
}
func demoMosaic(c *ui.Context, log func(string)) {
k := pixel.K(c)
s := fxState(c)
ui.Column(c).Gap(k.D(6)).Children(func() {
ui.Row(c).Gap(k.D(8)).AlignItems(ui.Center).Children(func() {
fx.Mosaic(c, display.Reduce(sunset, 48, 32, k.Palette()), fx.MosaicOptions{Cue: s.mosaic, Scale: 2, Label: "sunset"})
fx.Mosaic(c, slime, fx.MosaicOptions{Cue: s.mosaic, Scale: 3, Label: "slime"})
})
if small(c, "PIXELATE", false) {
s.mosaic++
log("pixelate")
}
})
}
func demoGlitch(c *ui.Context, log func(string)) {
k := pixel.K(c)
s := fxState(c)
ui.Column(c).Gap(k.D(6)).Children(func() {
ui.Row(c).Gap(k.D(8)).AlignItems(ui.Center).Children(func() {
fx.Glitch(c, display.Reduce(sunset, 48, 32, k.Palette()), fx.GlitchOptions{Cue: s.glitch, Scale: 2, Label: "sunset"})
fx.Glitch(c, slime, fx.GlitchOptions{Cue: s.glitch, Scale: 3, Label: "slime"})
})
if small(c, "GLITCH", false) {
s.glitch++
log("glitch")
}
})
}
func demoSparkle(c *ui.Context, log func(string)) {
k := pixel.K(c)
layout.Layers(c, func() {
ui.Box(c).Size(k.D(48), k.D(48)).AlignItems(ui.Center).Justify(ui.Center).Children(func() {
display.Gem(c, display.Legendary, display.GemOptions{Scale: 2})
})
}, func() { fx.Sparkle(c, fx.SparkleOptions{Size: 48, Label: "sparkles"}) })
}
func demoExplosion(c *ui.Context, log func(string)) {
k := pixel.K(c)
s := fxState(c)
ui.Column(c).Gap(k.D(6)).AlignItems(ui.Center).Children(func() {
fx.Explosion(c, fx.ExplosionOptions{Cue: s.boom, Label: "blast"})
if small(c, "BOOM", false) {
s.boom++
log("boom")
}
})
}
func demoConfetti(c *ui.Context, log func(string)) {
k := pixel.K(c)
layout.Layers(c, func() {
ui.Box(c).Size(k.D(140), k.D(64)).AlignItems(ui.Center).Justify(ui.Center).Children(func() {
pixel.Text(c, "YOU WIN!", pixel.TextOptions{Scale: 2, Slot: pixel.Accent})
})
}, func() { fx.Confetti(c, fx.ConfettiOptions{Width: 140, Height: 64, Label: "confetti"}) })
}
+1 -1
View File
@@ -136,7 +136,7 @@ func demoLoadingTips(c *ui.Context, log func(string)) {
func demoLetterbox(c *ui.Context, log func(string)) {
k := pixel.K(c)
s := screenState(c)
ui.Column(c).Gap(k.D(6)).Children(func() {
ui.Column(c).Gap(k.D(6)).AlignItems(ui.Start).Children(func() {
game.Letterbox(c, s.cutscene, game.LetterboxOptions{}, func() {
ui.Box(c).Size(k.D(200), k.D(80)).Draw(func(p *ui.Painter, r ui.Rect) {
g := k.Canvas(p, r)
+86
View File
@@ -0,0 +1,86 @@
package gallery
import (
"testing"
"github.com/egoist/mygo/ui"
"git.zacharyzhang.com/ZacharyZhang-NY/8bitMyGoUI/pixel"
)
func TestFxDemosLogTheirActions(t *testing.T) {
for _, tc := range []struct {
path string
steps []string
}{
{"/c/293-sunmoon", []string{"Hour", "Right"}}, {"/c/306-lightning", []string{"STRIKE"}},
{"/c/308-seasonscene", []string{"Winter", "Hour", "Right"}}, {"/c/311-screenshake", []string{"SHAKE"}},
{"/c/312-flash", []string{"FLASH"}}, {"/c/313-fadedither", []string{"FADE OUT"}}, {"/c/314-iriswipe", []string{"CLOSE"}},
{"/c/315-dissolve", []string{"DISSOLVE"}}, {"/c/316-mosaic", []string{"PIXELATE"}}, {"/c/317-glitch", []string{"GLITCH"}},
{"/c/319-explosion", []string{"BOOM"}},
} {
a, tt := open(tc.path)
slug := tc.path[3:]
for _, step := range tc.steps {
before := len(a.logs[slug])
if step == "Right" {
tt.Key(0, ui.KeyRight)
} else {
click(t, tt, step)
}
// Focusing the stepper changes nothing worth a line.
if len(a.logs[slug]) <= before && step != "Hour" {
t.Errorf("%s: %s logged nothing: %q", slug, step, a.logs[slug])
}
}
}
}
func TestGroupsAreOneTabStopEach(t *testing.T) {
for _, page := range []struct{ path, query string }{{"/", ""}, {"/cat/fx", ""}, {"/", "e"}} {
_, tt := open(page.path)
tt.Key(0, ui.KeySlash)
tt.Type(page.query)
// Search, four top-bar controls, then one stop per group.
stops := 1
for tt.Key(0, ui.KeyTab); !tt.Focused("Search components"); tt.Key(0, ui.KeyTab) {
if stops++; stops > 12 {
t.Fatalf("%s %q: a Tab cycle passes over 12 stops", page.path, page.query)
}
}
}
_, tt := open("/")
for range 40 {
if tt.Focused("Spring Day, in use") {
break
}
tt.Key(0, ui.KeyTab)
}
tt.Key(0, ui.KeyRight)
if !tt.Focused("Spring Night") {
t.Fatal("Right did not move along the palette strip")
}
}
func TestHomeSceneFollowsThePaletteStrip(t *testing.T) {
a, tt := open("/")
if _, ok := tt.Find("Spring Day scene"); !ok {
t.Fatalf("no spring scene in %q", tt.Texts())
}
click(t, tt, "Winter Night")
if a.palette != 7 {
t.Fatalf("the strip chose palette %d", a.palette)
}
r, ok := tt.Find("Winter Night scene")
if !ok {
t.Fatalf("the scene kept its season: %q", tt.Texts())
}
// The banner's sky starts in Winter Night's darkest slot.
got, want := tt.Image().RGBAAt(int(r.X)+1, int(r.Y)+1), pixel.WinterNight.Colors[pixel.Well]
if got.R != want.R || got.G != want.G || got.B != want.B {
t.Fatalf("the banner's sky is %v, want %v", got, want)
}
if _, ok := tt.Find("Winter Night, in use"); !ok {
t.Fatal("the strip does not mark the palette in use")
}
}
+2 -1
View File
@@ -93,7 +93,8 @@ func TestInvariantsPages(t *testing.T) {
a.Go(path)
}
allowed := colors(pal)
if name == "entry" {
// Home shows every palette in its strip.
if name == "entry" || name == "home" {
allowed = colors(pixel.Palettes...)
}
img := render(a.View, 640, 400)
+48 -4
View File
@@ -8,6 +8,7 @@ import (
"git.zacharyzhang.com/ZacharyZhang-NY/8bitMyGoUI/display"
"git.zacharyzhang.com/ZacharyZhang-NY/8bitMyGoUI/feedback"
"git.zacharyzhang.com/ZacharyZhang-NY/8bitMyGoUI/fx"
"git.zacharyzhang.com/ZacharyZhang-NY/8bitMyGoUI/input"
"git.zacharyzhang.com/ZacharyZhang-NY/8bitMyGoUI/layout"
"git.zacharyzhang.com/ZacharyZhang-NY/8bitMyGoUI/pixel"
@@ -49,13 +50,23 @@ func page(c *ui.Context, fn func()) {
func (a *App) home(c *ui.Context) {
k := pixel.K(c)
text.ShadowText(c, logo, text.ShadowTextOptions{Slot: pixel.Accent, Scale: 3})
p := k.Palette()
// The banner lives in the palette's own season and hour.
layout.Layers(c, func() {
fx.SeasonScene(c, p.Season, hourOf(p), fx.SeasonSceneOptions{Height: 96, Label: p.Name + " scene"}).FillWidth()
}, func() {
ui.Column(c).Padding(k.D(10)).Children(func() {
text.OutlineText(c, logo, text.OutlineTextOptions{Slot: pixel.Accent, Scale: 3})
})
})
pixel.Text(c, "Pixel components for MyGo. Every dot sits on the grid; every color comes from sixteen.", pixel.TextOptions{Wrap: true})
pixel.Text(c, "为 MyGo 打造的像素组件。每个点落在点阵上,每种颜色来自十六格调色板。", pixel.TextOptions{Wrap: true, Slot: pixel.Faint})
comps := len(Entries()) - count("foundations")
stats := fmt.Sprintf("%d COMPONENTS %d CATEGORIES %d PALETTES", comps, len(Categories)-1, len(pixel.Palettes))
text.MicroText(c, stats, text.MicroTextOptions{Slot: pixel.Accent})
ui.Grid(c).Columns(3).Gap(k.D(4)).Children(func() {
a.palettes(c)
// Each grid is one Tab stop; arrows move inside it.
ui.Grid(c).Columns(3).Gap(k.D(4)).FocusGroup(ui.Horizontal | ui.Vertical).Children(func() {
for _, cat := range Categories {
n := count(cat.Key)
if press(c, 0, 40, cat.Name, func(g pixel.Canvas, st pixel.State) {
@@ -76,6 +87,39 @@ func (a *App) home(c *ui.Context) {
})
}
// palettes shows each palette in its colors; press to pick.
func (a *App) palettes(c *ui.Context) {
k := pixel.K(c)
ui.Grid(c).Columns(4).Gap(k.D(4)).FocusGroup(ui.Horizontal | ui.Vertical).Children(func() {
for i, pal := range pixel.Palettes {
name := pal.Name
if i == a.palette {
name += ", in use"
}
set := pixel.Current(c)
set.Palette = pal
pixel.Scope(c, set, func() {
if press(c, 0, 30, name, func(g pixel.Canvas, st pixel.State) {
fill := pixel.Face
if i == a.palette || st.Hover {
fill = pixel.Lit
}
g.Frame(0, 0, g.W, g.H, pixel.Window, fill)
for s := range pixel.Slots {
g.Rect(5+3*s, 4, 3, 8, pixel.Slot(s))
}
g.Text(5, 14, pal.Name+" "+pal.Zh, pixel.Ink, 1)
if i == a.palette {
g.Rect(g.W-9, 5, 4, 4, pixel.Accent)
}
}) {
a.setPalette(i)
}
})
}
})
}
func (a *App) category(c *ui.Context, key string) {
var cat Category
for _, x := range Categories {
@@ -93,7 +137,7 @@ func (a *App) category(c *ui.Context, key string) {
text.ShadowText(c, cat.Name, text.ShadowTextOptions{Scale: 2})
pixel.Text(c, cat.Zh, pixel.TextOptions{Slot: pixel.Faint})
})
ui.Grid(c).Columns(3).Gap(k.D(4)).Children(func() {
ui.Grid(c).Columns(3).Gap(k.D(4)).FocusGroup(ui.Horizontal | ui.Vertical).Children(func() {
for _, e := range Entries() {
if e.Category == key {
a.card(c, e)
@@ -180,7 +224,7 @@ func (a *App) results(c *ui.Context) {
found := matches(a.query)
pixel.Text(c, fmt.Sprintf("%d results for %q", len(found), strings.TrimSpace(a.query)), pixel.TextOptions{Slot: pixel.Faint})
k := pixel.K(c)
ui.Grid(c).Columns(3).Gap(k.D(4)).Children(func() {
ui.Grid(c).Columns(3).Gap(k.D(4)).FocusGroup(ui.Horizontal | ui.Vertical).Children(func() {
for _, e := range found {
a.card(c, e)
}
+1 -1
View File
@@ -56,7 +56,7 @@ var Categories = []Category{
{"tabletop", "Tabletop", "桌游", icon.Crown},
}
var entries = slices.Concat(foundationEntries, layoutEntries, textEntries, displayEntries, inputEntries, controlEntries, feedbackEntries, navigationEntries, dataEntries, recordEntries, overlayEntries, popupEntries, datetimeEntries, timeEntries, chartEntries, plotEntries, gameEntries, rpgEntries, screenEntries, lootEntries)
var entries = slices.Concat(foundationEntries, layoutEntries, textEntries, displayEntries, inputEntries, controlEntries, feedbackEntries, navigationEntries, dataEntries, recordEntries, overlayEntries, popupEntries, datetimeEntries, timeEntries, chartEntries, plotEntries, gameEntries, rpgEntries, screenEntries, lootEntries, fxEntries, screenFxEntries)
func Entries() []Entry { return entries }