One component per file; GAME OVER offers CONTINUE
PRD G1 asks for one component per file. Eight files held two or three; their eleven extra components move to files of their own, helpers stay where both sides use them, and an archtest now fails any file that holds two. PRD 4.4 and the request say an error always offers CONTINUE. ErrorState's button read RETRY; it now reads CONTINUE, under the CONTINUE? countdown, on the crash screen and the lost page alike.
This commit is contained in:
25 files changed
+439
-359
No files matched your search
@@ -54,7 +54,7 @@ Imports point down only. Same-tier imports are forbidden. `internal/archtest` en
|
||||
|
||||
## Component rules
|
||||
|
||||
- Signatures and results: PRD §5.1, held by archtest `TestComponentsTakeTheirOptions`: `Name` takes `NameOptions` and returns `NameResult`. Zero options give the default look; required values are parameters. Sizes in options are dots.
|
||||
- Signatures and results: PRD §5.1, held by archtest `TestComponentsTakeTheirOptions`: `Name` takes `NameOptions` and returns `NameResult`, one component per file. Zero options give the default look; required values are parameters. Sizes in options are dots.
|
||||
- `k := pixel.K(c)` once per component; layout sizes through `k.D(n)`. Integer options default through `pixel.Or`.
|
||||
- Custom-drawn controls end their Draw with `g.Finish(st)`. Grids of cells moved by arrows use `pixel.Pad`; custom-drawn sliders use `input.SlideBase`; every clock runs at S5's 4–15 fps (`checkFPS` panics otherwise): a blink is `g.Step(4)%4 < 2`, a timer counts quarter seconds, and a view that changes on whole seconds asks `k.At`; one-shot timing steps with `k.Since`, which holds at zero when the clock steps back; a stored start is read through `k.Elapsed(&t)`, which moves it to now when the clock steps back; drawing from time quantizes it to whole frames (`Truncate(time.Second / 15)`); a decorative one-shot on a cue steps with `k.Shot`, which Reduce Motion spends whole; a view that changes at a known moment asks `k.At`; a two-way slide (bars, fades, wipes) steps with `k.Toward`, which turns from where it is.
|
||||
- A one-shot effect inside a lasting view starts when its cue changes (a `Cue` option read through `k.Cue`, or a value that rises), never on its first frame. A state screen (`ErrorState`, `SuccessState`) starts when it appears. Decorative motion jumps to its end under Reduce Motion; countdowns and loaders keep time.
|
||||
|
||||
@@ -110,40 +110,3 @@ func Face(g pixel.Canvas, x, y int, name string, sc int) {
|
||||
g.Sprite(x, y+8*sc, eyes[pick(len(eyes))], 0, pixel.Ink, sc)
|
||||
g.Sprite(x, y+11*sc, mouths[pick(len(mouths))], 0, pixel.Ink, sc)
|
||||
}
|
||||
|
||||
type AvatarGroupOptions struct {
|
||||
// Max faces shown before +N; zero means 4.
|
||||
Max int
|
||||
Scale int
|
||||
}
|
||||
|
||||
// AvatarGroup overlaps faces and counts the rest in a chip.
|
||||
func AvatarGroup(c *ui.Context, names []string, o AvatarGroupOptions) *ui.Element {
|
||||
k, sc := pixel.K(c), scale(o.Scale)
|
||||
shown := names[:min(len(names), pixel.Or("AvatarGroup Max", o.Max, 4))]
|
||||
rest := len(names) - len(shown)
|
||||
more := ""
|
||||
if rest > 0 {
|
||||
more = "+" + itoa(rest)
|
||||
}
|
||||
step := 12 * sc
|
||||
w := max(0, len(shown)-1)*step + 16*sc
|
||||
if more != "" {
|
||||
w += pixel.MicroWidth(more) + 8
|
||||
}
|
||||
return art(c, k, strings.Join(names, ", "), w, 16*sc, func(g pixel.Canvas) {
|
||||
for i, name := range shown {
|
||||
x := i * step
|
||||
Face(g, x, 0, name, sc)
|
||||
if i > 0 {
|
||||
g.VLine(x, 0, 16*sc, pixel.Back)
|
||||
}
|
||||
}
|
||||
if more != "" {
|
||||
x := len(shown)*step + 4*sc
|
||||
cw := pixel.MicroWidth(more) + 4
|
||||
g.Frame(x, (16*sc-9)/2, cw, 9, pixel.Notched, pixel.Face)
|
||||
g.Micro(x+2, (16*sc-9)/2+2, more, pixel.Ink, 1)
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
package display
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/egoist/mygo/ui"
|
||||
|
||||
"git.zacharyzhang.com/ZacharyZhang-NY/8bitMyGoUI/pixel"
|
||||
)
|
||||
|
||||
type AvatarGroupOptions struct {
|
||||
// Max faces shown before +N; zero means 4.
|
||||
Max int
|
||||
Scale int
|
||||
}
|
||||
|
||||
// AvatarGroup overlaps faces and counts the rest in a chip.
|
||||
func AvatarGroup(c *ui.Context, names []string, o AvatarGroupOptions) *ui.Element {
|
||||
k, sc := pixel.K(c), scale(o.Scale)
|
||||
shown := names[:min(len(names), pixel.Or("AvatarGroup Max", o.Max, 4))]
|
||||
rest := len(names) - len(shown)
|
||||
more := ""
|
||||
if rest > 0 {
|
||||
more = "+" + itoa(rest)
|
||||
}
|
||||
step := 12 * sc
|
||||
w := max(0, len(shown)-1)*step + 16*sc
|
||||
if more != "" {
|
||||
w += pixel.MicroWidth(more) + 8
|
||||
}
|
||||
return art(c, k, strings.Join(names, ", "), w, 16*sc, func(g pixel.Canvas) {
|
||||
for i, name := range shown {
|
||||
x := i * step
|
||||
Face(g, x, 0, name, sc)
|
||||
if i > 0 {
|
||||
g.VLine(x, 0, 16*sc, pixel.Back)
|
||||
}
|
||||
}
|
||||
if more != "" {
|
||||
x := len(shown)*step + 4*sc
|
||||
cw := pixel.MicroWidth(more) + 4
|
||||
g.Frame(x, (16*sc-9)/2, cw, 9, pixel.Notched, pixel.Face)
|
||||
g.Micro(x+2, (16*sc-9)/2+2, more, pixel.Ink, 1)
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -6,24 +6,6 @@ import (
|
||||
"git.zacharyzhang.com/ZacharyZhang-NY/8bitMyGoUI/pixel"
|
||||
)
|
||||
|
||||
type DotsLoaderOptions struct{}
|
||||
|
||||
// DotsLoader bounces three dots in turn.
|
||||
func DotsLoader(c *ui.Context, text string, o DotsLoaderOptions) *ui.Element {
|
||||
k := pixel.K(c)
|
||||
return ui.Box(c).Role(ui.RoleProgress).Label(text).Size(k.D(18), k.D(8)).Draw(func(p *ui.Painter, r ui.Rect) {
|
||||
g := k.Canvas(p, r)
|
||||
up := g.Busy(6) % 4
|
||||
for i := range 3 {
|
||||
y, ink := 4, pixel.Faint
|
||||
if i == up {
|
||||
y, ink = 0, pixel.Accent
|
||||
}
|
||||
g.Rect(i*7, y, 4, 4, ink)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
type BarsLoaderOptions struct{}
|
||||
|
||||
// heights rise and fall; each bar runs a step behind.
|
||||
@@ -0,0 +1,25 @@
|
||||
package feedback
|
||||
|
||||
import (
|
||||
"github.com/egoist/mygo/ui"
|
||||
|
||||
"git.zacharyzhang.com/ZacharyZhang-NY/8bitMyGoUI/pixel"
|
||||
)
|
||||
|
||||
type DotsLoaderOptions struct{}
|
||||
|
||||
// DotsLoader bounces three dots in turn.
|
||||
func DotsLoader(c *ui.Context, text string, o DotsLoaderOptions) *ui.Element {
|
||||
k := pixel.K(c)
|
||||
return ui.Box(c).Role(ui.RoleProgress).Label(text).Size(k.D(18), k.D(8)).Draw(func(p *ui.Painter, r ui.Rect) {
|
||||
g := k.Canvas(p, r)
|
||||
up := g.Busy(6) % 4
|
||||
for i := range 3 {
|
||||
y, ink := 4, pixel.Faint
|
||||
if i == up {
|
||||
y, ink = 0, pixel.Accent
|
||||
}
|
||||
g.Rect(i*7, y, 4, 4, ink)
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
package feedback
|
||||
|
||||
import (
|
||||
"github.com/egoist/mygo/ui"
|
||||
|
||||
"git.zacharyzhang.com/ZacharyZhang-NY/8bitMyGoUI/display"
|
||||
"git.zacharyzhang.com/ZacharyZhang-NY/8bitMyGoUI/input"
|
||||
"git.zacharyzhang.com/ZacharyZhang-NY/8bitMyGoUI/pixel"
|
||||
"git.zacharyzhang.com/ZacharyZhang-NY/8bitMyGoUI/text"
|
||||
)
|
||||
|
||||
var openChest = pixel.MustSprite(`
|
||||
..eeeeeeeeeeee..
|
||||
.esssssssssssse.
|
||||
.elllllllllllle.
|
||||
..eeeeeeeeeeee..
|
||||
................
|
||||
eeeeeeeeeeeeeeee
|
||||
ewwwwwwwwwwwwwwe
|
||||
ewwwwwwwwwwwwwwe
|
||||
eeeeeeeyyeeeeeee
|
||||
essssssyysssssse
|
||||
esssssssssssssse
|
||||
eeeeeeeeeeeeeeee
|
||||
`)
|
||||
|
||||
type EmptyStateOptions struct {
|
||||
// Action labels a button; Acted reports it.
|
||||
Action string
|
||||
Disabled bool
|
||||
}
|
||||
|
||||
type EmptyStateResult struct {
|
||||
Element *ui.Element
|
||||
Acted bool
|
||||
}
|
||||
|
||||
// EmptyState shows NO ITEMS over an empty chest.
|
||||
func EmptyState(c *ui.Context, message string, o EmptyStateOptions) EmptyStateResult {
|
||||
k := pixel.K(c)
|
||||
res := EmptyStateResult{}
|
||||
res.Element = ui.Column(c).Gap(k.D(6)).Padding(k.D(12)).AlignItems(ui.Center).Role(ui.RoleStatus).Label(k.Msg("empty")).Children(func() {
|
||||
display.Sprite(c, openChest, display.SpriteOptions{Scale: 2})
|
||||
text.ShadowText(c, k.Msg("empty"), text.ShadowTextOptions{Slot: pixel.Faint, Scale: 2})
|
||||
pixel.Text(c, message, pixel.TextOptions{Slot: pixel.Faint})
|
||||
if o.Action != "" {
|
||||
res.Acted = input.Button(c, o.Action, input.ButtonOptions{Variant: input.Primary, Disabled: o.Disabled}).Clicked
|
||||
}
|
||||
})
|
||||
return res
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
package feedback
|
||||
|
||||
import (
|
||||
"log/slog"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/egoist/mygo/ui"
|
||||
|
||||
"git.zacharyzhang.com/ZacharyZhang-NY/8bitMyGoUI/display"
|
||||
"git.zacharyzhang.com/ZacharyZhang-NY/8bitMyGoUI/icon"
|
||||
"git.zacharyzhang.com/ZacharyZhang-NY/8bitMyGoUI/input"
|
||||
"git.zacharyzhang.com/ZacharyZhang-NY/8bitMyGoUI/pixel"
|
||||
"git.zacharyzhang.com/ZacharyZhang-NY/8bitMyGoUI/text"
|
||||
)
|
||||
|
||||
type ErrorStateOptions struct {
|
||||
Disabled bool
|
||||
}
|
||||
|
||||
type ErrorStateResult struct {
|
||||
Element *ui.Element
|
||||
Continue bool
|
||||
}
|
||||
|
||||
type startKey struct{}
|
||||
|
||||
// ErrorState is GAME OVER, a CONTINUE? countdown, and CONTINUE.
|
||||
func ErrorState(c *ui.Context, message string, o ErrorStateOptions) ErrorStateResult {
|
||||
k := pixel.K(c)
|
||||
col := ui.Column(c).Gap(k.D(8)).Padding(k.D(16)).AlignItems(ui.Center).Role(ui.RoleGroup).Label(k.Msg("game_over"))
|
||||
start := ui.Local(col, startKey{}, func() time.Time { return k.Now() })
|
||||
left := max(0, 9-max(0, int(k.Now().Sub(*start)/time.Second)))
|
||||
if left > 0 {
|
||||
k.Tick(c, 10)
|
||||
}
|
||||
res := ErrorStateResult{Element: col}
|
||||
col.Children(func() {
|
||||
text.ShadowText(c, k.Msg("game_over"), text.ShadowTextOptions{Slot: pixel.Bad, Scale: 3})
|
||||
pixel.Text(c, message, pixel.TextOptions{Slot: pixel.Faint, Wrap: true, MaxLines: 6}).Width(k.D(min(pixel.Measure(message), 280)))
|
||||
display.Icon(c, icon.Skull, display.IconOptions{Scale: 3})
|
||||
text.ShadowText(c, k.Msg("continue")+" "+strconv.Itoa(left), text.ShadowTextOptions{Slot: pixel.Accent, Scale: 2})
|
||||
res.Continue = input.Button(c, k.Msg("continue_game"), input.ButtonOptions{Variant: input.Primary, Disabled: o.Disabled}).Clicked
|
||||
})
|
||||
if res.Continue {
|
||||
slog.Debug("8bit: continue", "left", left)
|
||||
}
|
||||
return res
|
||||
}
|
||||
@@ -1,127 +0,0 @@
|
||||
package feedback
|
||||
|
||||
import (
|
||||
"log/slog"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/egoist/mygo/ui"
|
||||
|
||||
"git.zacharyzhang.com/ZacharyZhang-NY/8bitMyGoUI/display"
|
||||
"git.zacharyzhang.com/ZacharyZhang-NY/8bitMyGoUI/icon"
|
||||
"git.zacharyzhang.com/ZacharyZhang-NY/8bitMyGoUI/input"
|
||||
"git.zacharyzhang.com/ZacharyZhang-NY/8bitMyGoUI/pixel"
|
||||
"git.zacharyzhang.com/ZacharyZhang-NY/8bitMyGoUI/text"
|
||||
)
|
||||
|
||||
var openChest = pixel.MustSprite(`
|
||||
..eeeeeeeeeeee..
|
||||
.esssssssssssse.
|
||||
.elllllllllllle.
|
||||
..eeeeeeeeeeee..
|
||||
................
|
||||
eeeeeeeeeeeeeeee
|
||||
ewwwwwwwwwwwwwwe
|
||||
ewwwwwwwwwwwwwwe
|
||||
eeeeeeeyyeeeeeee
|
||||
essssssyysssssse
|
||||
esssssssssssssse
|
||||
eeeeeeeeeeeeeeee
|
||||
`)
|
||||
|
||||
type EmptyStateOptions struct {
|
||||
// Action labels a button; Acted reports it.
|
||||
Action string
|
||||
Disabled bool
|
||||
}
|
||||
|
||||
type EmptyStateResult struct {
|
||||
Element *ui.Element
|
||||
Acted bool
|
||||
}
|
||||
|
||||
// EmptyState shows NO ITEMS over an empty chest.
|
||||
func EmptyState(c *ui.Context, message string, o EmptyStateOptions) EmptyStateResult {
|
||||
k := pixel.K(c)
|
||||
res := EmptyStateResult{}
|
||||
res.Element = ui.Column(c).Gap(k.D(6)).Padding(k.D(12)).AlignItems(ui.Center).Role(ui.RoleStatus).Label(k.Msg("empty")).Children(func() {
|
||||
display.Sprite(c, openChest, display.SpriteOptions{Scale: 2})
|
||||
text.ShadowText(c, k.Msg("empty"), text.ShadowTextOptions{Slot: pixel.Faint, Scale: 2})
|
||||
pixel.Text(c, message, pixel.TextOptions{Slot: pixel.Faint})
|
||||
if o.Action != "" {
|
||||
res.Acted = input.Button(c, o.Action, input.ButtonOptions{Variant: input.Primary, Disabled: o.Disabled}).Clicked
|
||||
}
|
||||
})
|
||||
return res
|
||||
}
|
||||
|
||||
type ErrorStateOptions struct {
|
||||
Disabled bool
|
||||
}
|
||||
|
||||
type ErrorStateResult struct {
|
||||
Element *ui.Element
|
||||
Continue bool
|
||||
}
|
||||
|
||||
type startKey struct{}
|
||||
|
||||
// ErrorState is GAME OVER with a CONTINUE? countdown and RETRY.
|
||||
func ErrorState(c *ui.Context, message string, o ErrorStateOptions) ErrorStateResult {
|
||||
k := pixel.K(c)
|
||||
col := ui.Column(c).Gap(k.D(8)).Padding(k.D(16)).AlignItems(ui.Center).Role(ui.RoleGroup).Label(k.Msg("game_over"))
|
||||
start := ui.Local(col, startKey{}, func() time.Time { return k.Now() })
|
||||
left := max(0, 9-max(0, int(k.Now().Sub(*start)/time.Second)))
|
||||
if left > 0 {
|
||||
k.Tick(c, 10)
|
||||
}
|
||||
res := ErrorStateResult{Element: col}
|
||||
col.Children(func() {
|
||||
text.ShadowText(c, k.Msg("game_over"), text.ShadowTextOptions{Slot: pixel.Bad, Scale: 3})
|
||||
pixel.Text(c, message, pixel.TextOptions{Slot: pixel.Faint, Wrap: true, MaxLines: 6}).Width(k.D(min(pixel.Measure(message), 280)))
|
||||
display.Icon(c, icon.Skull, display.IconOptions{Scale: 3})
|
||||
text.ShadowText(c, k.Msg("continue")+" "+strconv.Itoa(left), text.ShadowTextOptions{Slot: pixel.Accent, Scale: 2})
|
||||
res.Continue = input.Button(c, k.Msg("retry"), input.ButtonOptions{Variant: input.Primary, Disabled: o.Disabled}).Clicked
|
||||
})
|
||||
if res.Continue {
|
||||
slog.Debug("8bit: continue", "left", left)
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
type SuccessStateOptions struct {
|
||||
// Stars pop in one by one; zero means 3.
|
||||
Stars int
|
||||
}
|
||||
|
||||
// SuccessState is STAGE CLEAR! with stars popping in steps.
|
||||
func SuccessState(c *ui.Context, message string, o SuccessStateOptions) *ui.Element {
|
||||
k := pixel.K(c)
|
||||
n := pixel.Or("SuccessState Stars", o.Stars, 3)
|
||||
col := ui.Column(c).Gap(k.D(8)).Padding(k.D(16)).AlignItems(ui.Center).Role(ui.RoleStatus).Label(k.Msg("stage_clear"))
|
||||
start := ui.Local(col, startKey{}, func() time.Time { return k.Now() })
|
||||
// Stars come four a second; Reduce Motion shows them all.
|
||||
shown := n + 1
|
||||
if !k.Reduce() {
|
||||
shown = min(n+1, max(0, int(k.Now().Sub(*start)*4/time.Second)))
|
||||
if shown <= n {
|
||||
k.Tick(c, 8)
|
||||
}
|
||||
}
|
||||
return col.Children(func() {
|
||||
text.ShadowText(c, k.Msg("stage_clear"), text.ShadowTextOptions{Slot: pixel.Good, Scale: 3})
|
||||
w := icon.Star.W*2 + 4
|
||||
ui.Box(c).Size(k.D(n*w), k.D(icon.Star.H*2+4)).Draw(func(p *ui.Painter, r ui.Rect) {
|
||||
g := k.Canvas(p, r)
|
||||
for i := range min(shown, n) {
|
||||
if i == shown-1 {
|
||||
for _, d := range [][2]int{{-1, 0}, {1, 0}, {0, -1}, {0, 1}} {
|
||||
g.Sprite(i*w+2+d[0], 2+d[1], icon.Star, 0, pixel.Lit, 2)
|
||||
}
|
||||
}
|
||||
g.Sprite(i*w+2, 2, icon.Star, 0, pixel.Warn, 2)
|
||||
}
|
||||
})
|
||||
pixel.Text(c, message, pixel.TextOptions{})
|
||||
})
|
||||
}
|
||||
@@ -70,9 +70,9 @@ func TestErrorStateCountsDownAndContinues(t *testing.T) {
|
||||
find(t, tt, "CONTINUE? 6")
|
||||
wait(tt, clk, 10*time.Second)
|
||||
find(t, tt, "CONTINUE? 0")
|
||||
click(t, tt, "RETRY")
|
||||
click(t, tt, "CONTINUE")
|
||||
if cont != 1 {
|
||||
t.Fatal("RETRY did not report Continue")
|
||||
t.Fatal("CONTINUE did not report Continue")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -212,7 +212,7 @@ func TestDisabledFeedbackButtonsDoNothing(t *testing.T) {
|
||||
on(feedback.ContinuePrompt(c, feedback.ContinuePromptOptions{Disabled: true}).Yes)
|
||||
on(feedback.Toasts(c, &q, feedback.ToastsOptions{Disabled: true}).Acted != nil)
|
||||
})
|
||||
for _, l := range []string{"CLOSE", "GO", "SHOP", "RETRY", "YES", "UNDO"} {
|
||||
for _, l := range []string{"CLOSE", "GO", "SHOP", "CONTINUE", "YES", "UNDO"} {
|
||||
click(t, tt, l)
|
||||
}
|
||||
if events != 0 {
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
package feedback
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/egoist/mygo/ui"
|
||||
|
||||
"git.zacharyzhang.com/ZacharyZhang-NY/8bitMyGoUI/icon"
|
||||
"git.zacharyzhang.com/ZacharyZhang-NY/8bitMyGoUI/pixel"
|
||||
"git.zacharyzhang.com/ZacharyZhang-NY/8bitMyGoUI/text"
|
||||
)
|
||||
|
||||
type SuccessStateOptions struct {
|
||||
// Stars pop in one by one; zero means 3.
|
||||
Stars int
|
||||
}
|
||||
|
||||
// SuccessState is STAGE CLEAR! with stars popping in steps.
|
||||
func SuccessState(c *ui.Context, message string, o SuccessStateOptions) *ui.Element {
|
||||
k := pixel.K(c)
|
||||
n := pixel.Or("SuccessState Stars", o.Stars, 3)
|
||||
col := ui.Column(c).Gap(k.D(8)).Padding(k.D(16)).AlignItems(ui.Center).Role(ui.RoleStatus).Label(k.Msg("stage_clear"))
|
||||
start := ui.Local(col, startKey{}, func() time.Time { return k.Now() })
|
||||
// Stars come four a second; Reduce Motion shows them all.
|
||||
shown := n + 1
|
||||
if !k.Reduce() {
|
||||
shown = min(n+1, max(0, int(k.Now().Sub(*start)*4/time.Second)))
|
||||
if shown <= n {
|
||||
k.Tick(c, 8)
|
||||
}
|
||||
}
|
||||
return col.Children(func() {
|
||||
text.ShadowText(c, k.Msg("stage_clear"), text.ShadowTextOptions{Slot: pixel.Good, Scale: 3})
|
||||
w := icon.Star.W*2 + 4
|
||||
ui.Box(c).Size(k.D(n*w), k.D(icon.Star.H*2+4)).Draw(func(p *ui.Painter, r ui.Rect) {
|
||||
g := k.Canvas(p, r)
|
||||
for i := range min(shown, n) {
|
||||
if i == shown-1 {
|
||||
for _, d := range [][2]int{{-1, 0}, {1, 0}, {0, -1}, {0, 1}} {
|
||||
g.Sprite(i*w+2+d[0], 2+d[1], icon.Star, 0, pixel.Lit, 2)
|
||||
}
|
||||
}
|
||||
g.Sprite(i*w+2, 2, icon.Star, 0, pixel.Warn, 2)
|
||||
}
|
||||
})
|
||||
pixel.Text(c, message, pixel.TextOptions{})
|
||||
})
|
||||
}
|
||||
+2
-2
@@ -123,10 +123,10 @@ func TestLostPageAndLocale(t *testing.T) {
|
||||
t.Fatalf("lost page texts %q", tt.Texts())
|
||||
}
|
||||
click(t, tt, "language")
|
||||
if a.locale != pixel.Zh || !tt.HasText("重试") {
|
||||
if _, ok := tt.Find("继续"); a.locale != pixel.Zh || !ok {
|
||||
t.Fatal("language toggle did not switch built-in copy")
|
||||
}
|
||||
click(t, tt, "重试")
|
||||
click(t, tt, "继续")
|
||||
if a.Path() != "/" {
|
||||
t.Fatalf("continue went to %q", a.Path())
|
||||
}
|
||||
|
||||
@@ -20,7 +20,7 @@ var feedbackEntries = []Entry{
|
||||
{110, "Alert", "提示框", "feedback", "A framed callout: info, success, warning or danger, with an optional ×.", demoAlert},
|
||||
{111, "Banner", "横幅", "feedback", "A full-width strip with actions and a ×.", demoBanner},
|
||||
{112, "EmptyState", "空状态", "feedback", "NO ITEMS over an empty chest, and something to do about it.", demoEmptyState},
|
||||
{113, "ErrorState", "错误态", "feedback", "GAME OVER, a CONTINUE? countdown and RETRY. The gallery's own errors use it.", demoErrorState},
|
||||
{113, "ErrorState", "错误态", "feedback", "GAME OVER, a CONTINUE? countdown, and CONTINUE. The gallery's own errors use it.", demoErrorState},
|
||||
{114, "SuccessState", "通关", "feedback", "STAGE CLEAR! The stars pop in one by one.", demoSuccessState},
|
||||
{115, "LoadingScreen", "加载画面", "feedback", "NOW LOADING with stepping dots and a tip every four seconds.", demoLoadingScreen},
|
||||
{116, "DotsLoader", "点点加载", "feedback", "Three dots hopping in turn.", demoDotsLoader},
|
||||
|
||||
@@ -76,19 +76,3 @@ func Button(c *ui.Context, text string, o ButtonOptions) ButtonResult {
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
type IconButtonOptions struct {
|
||||
Variant Variant
|
||||
Size Size
|
||||
Disabled bool
|
||||
}
|
||||
|
||||
type IconButtonResult = ButtonResult
|
||||
|
||||
// IconButton is a square button named for assistive technology.
|
||||
func IconButton(c *ui.Context, icon *pixel.Sprite, name string, o IconButtonOptions) IconButtonResult {
|
||||
if name == "" {
|
||||
panic("input: IconButton needs a name")
|
||||
}
|
||||
return Button(c, "", ButtonOptions{Variant: o.Variant, Size: o.Size, Icon: icon, Disabled: o.Disabled, Label: name})
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
package input
|
||||
|
||||
import (
|
||||
"github.com/egoist/mygo/ui"
|
||||
|
||||
"git.zacharyzhang.com/ZacharyZhang-NY/8bitMyGoUI/pixel"
|
||||
)
|
||||
|
||||
type IconButtonOptions struct {
|
||||
Variant Variant
|
||||
Size Size
|
||||
Disabled bool
|
||||
}
|
||||
|
||||
type IconButtonResult = ButtonResult
|
||||
|
||||
// IconButton is a square button named for assistive technology.
|
||||
func IconButton(c *ui.Context, icon *pixel.Sprite, name string, o IconButtonOptions) IconButtonResult {
|
||||
if name == "" {
|
||||
panic("input: IconButton needs a name")
|
||||
}
|
||||
return Button(c, "", ButtonOptions{Variant: o.Variant, Size: o.Size, Icon: icon, Disabled: o.Disabled, Label: name})
|
||||
}
|
||||
@@ -274,19 +274,24 @@ func TestWebScripts(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// TestComponentsTakeTheirOptions holds PRD 5.1's shape: Name takes NameOptions.
|
||||
// TestComponentsTakeTheirOptions holds PRD 5.1's shape, one per file.
|
||||
func TestComponentsTakeTheirOptions(t *testing.T) {
|
||||
for _, f := range files(t) {
|
||||
tier, ok := tiers[f.pkg]
|
||||
if !ok || tier < 2 || tier > 6 || strings.HasSuffix(f.path, "_test.go") {
|
||||
continue
|
||||
}
|
||||
var found []string
|
||||
for _, d := range f.ast.Decls {
|
||||
fn, ok := d.(*ast.FuncDecl)
|
||||
// Undrawn bases, as MyGo's own, are parts, not components.
|
||||
if !ok || fn.Recv != nil || !fn.Name.IsExported() || strings.HasSuffix(fn.Name.Name, "Base") || len(fn.Type.Params.List) == 0 || !isContext(fn.Type.Params.List[0].Type) {
|
||||
continue
|
||||
}
|
||||
// PRD G1: one component per file.
|
||||
if found = append(found, fn.Name.Name); len(found) > 1 {
|
||||
t.Errorf("%s holds %v; one component per file", f.path, found)
|
||||
}
|
||||
want := fn.Name.Name + "Options"
|
||||
if !slices.ContainsFunc(fn.Type.Params.List, func(p *ast.Field) bool { return typeName(p.Type) == want }) {
|
||||
t.Errorf("%s: %s takes no %s", f.path, fn.Name.Name, want)
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
package navigation
|
||||
|
||||
import (
|
||||
"log/slog"
|
||||
|
||||
"github.com/egoist/mygo/ui"
|
||||
|
||||
"git.zacharyzhang.com/ZacharyZhang-NY/8bitMyGoUI/icon"
|
||||
"git.zacharyzhang.com/ZacharyZhang-NY/8bitMyGoUI/input"
|
||||
"git.zacharyzhang.com/ZacharyZhang-NY/8bitMyGoUI/pixel"
|
||||
)
|
||||
|
||||
type BackForwardOptions struct {
|
||||
Disabled bool
|
||||
}
|
||||
|
||||
type BackForwardResult struct {
|
||||
Element *ui.Element
|
||||
// Moved reports a step through the history.
|
||||
Moved bool
|
||||
}
|
||||
|
||||
// BackForward walks a router's history with ◀ and ▶.
|
||||
func BackForward(c *ui.Context, r *ui.Router, o BackForwardOptions) BackForwardResult {
|
||||
k := pixel.K(c)
|
||||
res := BackForwardResult{}
|
||||
res.Element = ui.Row(c).Gap(k.D(2)).FocusGroup(ui.Horizontal).Role(ui.RoleToolbar).Label(k.Msg("history")).Children(func() {
|
||||
back := input.IconButtonOptions{Size: input.Small, Disabled: o.Disabled || !r.CanGoBack()}
|
||||
if input.IconButton(c, icon.ChevronLeft, k.Msg("back"), back).Clicked {
|
||||
r.Back()
|
||||
res.Moved = true
|
||||
}
|
||||
forth := input.IconButtonOptions{Size: input.Small, Disabled: o.Disabled || !r.CanGoForward()}
|
||||
if input.IconButton(c, icon.ChevronRight, k.Msg("forward"), forth).Clicked {
|
||||
r.Forward()
|
||||
res.Moved = true
|
||||
}
|
||||
})
|
||||
if res.Moved {
|
||||
slog.Debug("8bit: history", "path", r.Path())
|
||||
}
|
||||
return res
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
package navigation
|
||||
|
||||
import (
|
||||
"log/slog"
|
||||
|
||||
"github.com/egoist/mygo/ui"
|
||||
|
||||
"git.zacharyzhang.com/ZacharyZhang-NY/8bitMyGoUI/pixel"
|
||||
)
|
||||
|
||||
type BottomNavOptions struct {
|
||||
Disabled bool
|
||||
}
|
||||
|
||||
type BottomNavResult struct {
|
||||
Element *ui.Element
|
||||
Changed bool
|
||||
}
|
||||
|
||||
// BottomNav is a bar of labeled icons for narrow screens.
|
||||
func BottomNav(c *ui.Context, text string, items []NavItem, selected *int, o BottomNavOptions) BottomNavResult {
|
||||
k := pixel.K(c)
|
||||
res := BottomNavResult{}
|
||||
// The bases turn before they learn they are off.
|
||||
v := *selected
|
||||
res.Element = ui.RadioGroup(c, func() {
|
||||
for i, it := range items {
|
||||
shell, _, st := pixel.Shell(c, func() *ui.Element {
|
||||
return ui.RadioBase(c, &v, i).Label(it.Label).Disabled(o.Disabled)
|
||||
})
|
||||
w := max(pixel.Measure(it.Label), 12) + 12
|
||||
shell.Size(k.D(w), k.D(16+pixel.LineHeight)).Draw(func(p *ui.Painter, r ui.Rect) {
|
||||
g := k.Canvas(p, r)
|
||||
ink := pixel.Faint
|
||||
if i == *selected {
|
||||
ink = pixel.Accent
|
||||
g.HLine(2, 0, g.W-4, pixel.Accent)
|
||||
} else if st.Hover {
|
||||
ink = pixel.Ink
|
||||
}
|
||||
g.Sprite((g.W-it.Icon.W)/2, 2, it.Icon, 0, ink, 1)
|
||||
g.Text((g.W-pixel.Measure(it.Label))/2, 14, it.Label, ink, 1)
|
||||
g.Finish(st)
|
||||
})
|
||||
}
|
||||
}).Row().Gap(k.D(4)).Padding(k.D(2), k.D(6)).Label(text).Draw(func(p *ui.Painter, r ui.Rect) {
|
||||
g := k.Canvas(p, r)
|
||||
g.Rect(0, 0, g.W, g.H, pixel.Face)
|
||||
g.HLine(0, 0, g.W, pixel.Edge)
|
||||
})
|
||||
if res.Changed = keep(c, v, selected, o.Disabled); res.Changed {
|
||||
slog.Debug("8bit: bottom nav", "label", text, "selected", *selected)
|
||||
}
|
||||
return res
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
package navigation
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log/slog"
|
||||
|
||||
"github.com/egoist/mygo/ui"
|
||||
|
||||
"git.zacharyzhang.com/ZacharyZhang-NY/8bitMyGoUI/pixel"
|
||||
)
|
||||
|
||||
type PageDotsOptions struct {
|
||||
Disabled bool
|
||||
}
|
||||
|
||||
type PageDotsResult struct {
|
||||
Element *ui.Element
|
||||
Changed bool
|
||||
}
|
||||
|
||||
// PageDots shows a dot per page; clicks jump, arrows move.
|
||||
func PageDots(c *ui.Context, text string, page *int, total int, o PageDotsOptions) PageDotsResult {
|
||||
k := pixel.K(c)
|
||||
check(*page, total)
|
||||
shell, base, st := pixel.Shell(c, func() *ui.Element {
|
||||
return ui.Box(c).Focusable().Role(ui.RoleSlider).Label(text).Description(fmt.Sprintf("%d/%d", *page, total)).Disabled(o.Disabled)
|
||||
})
|
||||
old := *page
|
||||
for _, m := range []struct {
|
||||
key ui.Key
|
||||
to func() int
|
||||
}{
|
||||
{ui.KeyLeft, func() int { return *page - 1 }}, {ui.KeyRight, func() int { return *page + 1 }},
|
||||
{ui.KeyHome, func() int { return 1 }}, {ui.KeyEnd, func() int { return total }},
|
||||
} {
|
||||
if pixel.Shortcut(base, 0, m.key) {
|
||||
*page = max(1, min(total, m.to()))
|
||||
}
|
||||
}
|
||||
// Taking Enter and Space keeps keys from clicking.
|
||||
pixel.Shortcut(base, 0, ui.KeyEnter)
|
||||
pixel.Shortcut(base, 0, ui.KeySpace)
|
||||
if base.Clicked() {
|
||||
x, _, _ := base.PointerPosition()
|
||||
*page = max(1, min(total, int(x/k.Dot())/8+1))
|
||||
}
|
||||
shell.Size(k.D(total*8), k.D(8)).Draw(func(p *ui.Painter, r ui.Rect) {
|
||||
g := k.Canvas(p, r)
|
||||
for i := range total {
|
||||
if i+1 == *page {
|
||||
g.Rect(i*8+1, 1, 6, 6, pixel.Accent)
|
||||
} else {
|
||||
g.Rect(i*8+2, 2, 4, 4, pixel.Shade)
|
||||
}
|
||||
}
|
||||
g.Finish(st)
|
||||
})
|
||||
res := PageDotsResult{Element: shell, Changed: *page != old}
|
||||
if res.Changed {
|
||||
slog.Debug("8bit: page dots", "label", text, "page", *page)
|
||||
}
|
||||
return res
|
||||
}
|
||||
@@ -84,56 +84,3 @@ func check(page, total int) {
|
||||
panic(fmt.Sprintf("navigation: page %d of %d", page, total))
|
||||
}
|
||||
}
|
||||
|
||||
type PageDotsOptions struct {
|
||||
Disabled bool
|
||||
}
|
||||
|
||||
type PageDotsResult struct {
|
||||
Element *ui.Element
|
||||
Changed bool
|
||||
}
|
||||
|
||||
// PageDots shows a dot per page; clicks jump, arrows move.
|
||||
func PageDots(c *ui.Context, text string, page *int, total int, o PageDotsOptions) PageDotsResult {
|
||||
k := pixel.K(c)
|
||||
check(*page, total)
|
||||
shell, base, st := pixel.Shell(c, func() *ui.Element {
|
||||
return ui.Box(c).Focusable().Role(ui.RoleSlider).Label(text).Description(fmt.Sprintf("%d/%d", *page, total)).Disabled(o.Disabled)
|
||||
})
|
||||
old := *page
|
||||
for _, m := range []struct {
|
||||
key ui.Key
|
||||
to func() int
|
||||
}{
|
||||
{ui.KeyLeft, func() int { return *page - 1 }}, {ui.KeyRight, func() int { return *page + 1 }},
|
||||
{ui.KeyHome, func() int { return 1 }}, {ui.KeyEnd, func() int { return total }},
|
||||
} {
|
||||
if pixel.Shortcut(base, 0, m.key) {
|
||||
*page = max(1, min(total, m.to()))
|
||||
}
|
||||
}
|
||||
// Taking Enter and Space keeps keys from clicking.
|
||||
pixel.Shortcut(base, 0, ui.KeyEnter)
|
||||
pixel.Shortcut(base, 0, ui.KeySpace)
|
||||
if base.Clicked() {
|
||||
x, _, _ := base.PointerPosition()
|
||||
*page = max(1, min(total, int(x/k.Dot())/8+1))
|
||||
}
|
||||
shell.Size(k.D(total*8), k.D(8)).Draw(func(p *ui.Painter, r ui.Rect) {
|
||||
g := k.Canvas(p, r)
|
||||
for i := range total {
|
||||
if i+1 == *page {
|
||||
g.Rect(i*8+1, 1, 6, 6, pixel.Accent)
|
||||
} else {
|
||||
g.Rect(i*8+2, 2, 4, 4, pixel.Shade)
|
||||
}
|
||||
}
|
||||
g.Finish(st)
|
||||
})
|
||||
res := PageDotsResult{Element: shell, Changed: *page != old}
|
||||
if res.Changed {
|
||||
slog.Debug("8bit: page dots", "label", text, "page", *page)
|
||||
}
|
||||
return res
|
||||
}
|
||||
@@ -73,49 +73,3 @@ func tip(c *ui.Context, k pixel.Kit, at ui.Rect, s string) {
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
type BottomNavOptions struct {
|
||||
Disabled bool
|
||||
}
|
||||
|
||||
type BottomNavResult struct {
|
||||
Element *ui.Element
|
||||
Changed bool
|
||||
}
|
||||
|
||||
// BottomNav is a bar of labeled icons for narrow screens.
|
||||
func BottomNav(c *ui.Context, text string, items []NavItem, selected *int, o BottomNavOptions) BottomNavResult {
|
||||
k := pixel.K(c)
|
||||
res := BottomNavResult{}
|
||||
// The bases turn before they learn they are off.
|
||||
v := *selected
|
||||
res.Element = ui.RadioGroup(c, func() {
|
||||
for i, it := range items {
|
||||
shell, _, st := pixel.Shell(c, func() *ui.Element {
|
||||
return ui.RadioBase(c, &v, i).Label(it.Label).Disabled(o.Disabled)
|
||||
})
|
||||
w := max(pixel.Measure(it.Label), 12) + 12
|
||||
shell.Size(k.D(w), k.D(16+pixel.LineHeight)).Draw(func(p *ui.Painter, r ui.Rect) {
|
||||
g := k.Canvas(p, r)
|
||||
ink := pixel.Faint
|
||||
if i == *selected {
|
||||
ink = pixel.Accent
|
||||
g.HLine(2, 0, g.W-4, pixel.Accent)
|
||||
} else if st.Hover {
|
||||
ink = pixel.Ink
|
||||
}
|
||||
g.Sprite((g.W-it.Icon.W)/2, 2, it.Icon, 0, ink, 1)
|
||||
g.Text((g.W-pixel.Measure(it.Label))/2, 14, it.Label, ink, 1)
|
||||
g.Finish(st)
|
||||
})
|
||||
}
|
||||
}).Row().Gap(k.D(4)).Padding(k.D(2), k.D(6)).Label(text).Draw(func(p *ui.Painter, r ui.Rect) {
|
||||
g := k.Canvas(p, r)
|
||||
g.Rect(0, 0, g.W, g.H, pixel.Face)
|
||||
g.HLine(0, 0, g.W, pixel.Edge)
|
||||
})
|
||||
if res.Changed = keep(c, v, selected, o.Disabled); res.Changed {
|
||||
slog.Debug("8bit: bottom nav", "label", text, "selected", *selected)
|
||||
}
|
||||
return res
|
||||
}
|
||||
@@ -23,20 +23,6 @@ func Tabs(c *ui.Context, text string, labels []string, selected *int, o TabsOpti
|
||||
return tabs(c, text, labels, selected, false, o, panel)
|
||||
}
|
||||
|
||||
type VerticalTabsOptions struct {
|
||||
Disabled bool
|
||||
}
|
||||
|
||||
type VerticalTabsResult struct {
|
||||
Element *ui.Element
|
||||
Changed bool
|
||||
}
|
||||
|
||||
// VerticalTabs are tabs down the panel's left edge.
|
||||
func VerticalTabs(c *ui.Context, text string, labels []string, selected *int, o VerticalTabsOptions, panel func()) VerticalTabsResult {
|
||||
return VerticalTabsResult(tabs(c, text, labels, selected, true, TabsOptions(o), panel))
|
||||
}
|
||||
|
||||
// tabs lays tabs beside a panel; side means left.
|
||||
func tabs(c *ui.Context, text string, labels []string, selected *int, side bool, o TabsOptions, panel func()) TabsResult {
|
||||
k := pixel.K(c)
|
||||
|
||||
@@ -1,12 +1,8 @@
|
||||
package navigation
|
||||
|
||||
import (
|
||||
"log/slog"
|
||||
|
||||
"github.com/egoist/mygo/ui"
|
||||
|
||||
"git.zacharyzhang.com/ZacharyZhang-NY/8bitMyGoUI/icon"
|
||||
"git.zacharyzhang.com/ZacharyZhang-NY/8bitMyGoUI/input"
|
||||
"git.zacharyzhang.com/ZacharyZhang-NY/8bitMyGoUI/pixel"
|
||||
)
|
||||
|
||||
@@ -20,35 +16,3 @@ func Toolbar(c *ui.Context, text string, o ToolbarOptions, content func()) *ui.E
|
||||
g.Frame(0, 0, g.W, g.H, pixel.Raised, pixel.Face)
|
||||
}).Children(content)
|
||||
}
|
||||
|
||||
type BackForwardOptions struct {
|
||||
Disabled bool
|
||||
}
|
||||
|
||||
type BackForwardResult struct {
|
||||
Element *ui.Element
|
||||
// Moved reports a step through the history.
|
||||
Moved bool
|
||||
}
|
||||
|
||||
// BackForward walks a router's history with ◀ and ▶.
|
||||
func BackForward(c *ui.Context, r *ui.Router, o BackForwardOptions) BackForwardResult {
|
||||
k := pixel.K(c)
|
||||
res := BackForwardResult{}
|
||||
res.Element = ui.Row(c).Gap(k.D(2)).FocusGroup(ui.Horizontal).Role(ui.RoleToolbar).Label(k.Msg("history")).Children(func() {
|
||||
back := input.IconButtonOptions{Size: input.Small, Disabled: o.Disabled || !r.CanGoBack()}
|
||||
if input.IconButton(c, icon.ChevronLeft, k.Msg("back"), back).Clicked {
|
||||
r.Back()
|
||||
res.Moved = true
|
||||
}
|
||||
forth := input.IconButtonOptions{Size: input.Small, Disabled: o.Disabled || !r.CanGoForward()}
|
||||
if input.IconButton(c, icon.ChevronRight, k.Msg("forward"), forth).Clicked {
|
||||
r.Forward()
|
||||
res.Moved = true
|
||||
}
|
||||
})
|
||||
if res.Moved {
|
||||
slog.Debug("8bit: history", "path", r.Path())
|
||||
}
|
||||
return res
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
package navigation
|
||||
|
||||
import (
|
||||
"github.com/egoist/mygo/ui"
|
||||
)
|
||||
|
||||
type VerticalTabsOptions struct {
|
||||
Disabled bool
|
||||
}
|
||||
|
||||
type VerticalTabsResult struct {
|
||||
Element *ui.Element
|
||||
Changed bool
|
||||
}
|
||||
|
||||
// VerticalTabs are tabs down the panel's left edge.
|
||||
func VerticalTabs(c *ui.Context, text string, labels []string, selected *int, o VerticalTabsOptions, panel func()) VerticalTabsResult {
|
||||
return VerticalTabsResult(tabs(c, text, labels, selected, true, TabsOptions(o), panel))
|
||||
}
|
||||
+2
-2
@@ -13,7 +13,7 @@ var messages = map[Locale]map[string]string{
|
||||
"gold": "GOLD", "silver": "SILVER", "bronze": "BRONZE",
|
||||
"common": "COMMON", "uncommon": "UNCOMMON", "rare": "RARE", "epic": "EPIC", "legendary": "LEGENDARY",
|
||||
"hold": "HOLD TO CONFIRM", "del": "DEL", "end": "END", "lives": "+30 LIVES!",
|
||||
"retry": "RETRY", "stage_clear": "STAGE CLEAR!", "level_up": "LEVEL UP!", "achievement": "ACHIEVEMENT UNLOCKED",
|
||||
"stage_clear": "STAGE CLEAR!", "level_up": "LEVEL UP!", "achievement": "ACHIEVEMENT UNLOCKED",
|
||||
"combo": "COMBO!", "saving": "SAVING", "saved": "SAVED", "go": "GO!", "wait": "PLEASE WAIT",
|
||||
"prev": "PREVIOUS", "next": "NEXT", "back": "BACK", "forward": "FORWARD", "history": "HISTORY",
|
||||
"on": "ON", "off": "OFF", "clear": "CLEAR", "per_page": "PER PAGE", "range": "%d–%d OF %d", "found": "FOUND: %d",
|
||||
@@ -91,7 +91,7 @@ var messages = map[Locale]map[string]string{
|
||||
"gold": "金", "silver": "银", "bronze": "铜",
|
||||
"common": "普通", "uncommon": "优秀", "rare": "稀有", "epic": "史诗", "legendary": "传说",
|
||||
"hold": "长按确认", "del": "删", "end": "完", "lives": "命数 +30!",
|
||||
"retry": "重试", "stage_clear": "过关!", "level_up": "升级!", "achievement": "成就解锁",
|
||||
"stage_clear": "过关!", "level_up": "升级!", "achievement": "成就解锁",
|
||||
"combo": "连击!", "saving": "存档中", "saved": "已存档", "go": "开始!", "wait": "请稍候",
|
||||
"prev": "上一页", "next": "下一页", "back": "后退", "forward": "前进", "history": "历史",
|
||||
"on": "开", "off": "关", "clear": "清除", "per_page": "每页", "range": "%d–%d / 共 %d", "found": "找到:%d",
|
||||
|
||||
@@ -238,13 +238,13 @@ test('resize keeps whole pixels per dot', async ({ page }) => {
|
||||
expect(Number.isInteger(s.zoom)).toBe(true)
|
||||
})
|
||||
|
||||
test('a Go panic shows GAME OVER in pixels; RETRY reloads', async ({ page }) => {
|
||||
test('a Go panic shows GAME OVER in pixels; CONTINUE reloads', async ({ page }) => {
|
||||
test.info().annotations.push({ type: 'console errors', description: 'the panic is on purpose' })
|
||||
await open(page, '#/f/icons')
|
||||
await page.evaluate(() => window.__8bitGo.pointer('bogus', 0, 0))
|
||||
expect(await texts(page)).toContain('web: unknown pointer kind bogus')
|
||||
await expect(page.locator('#over')).toBeHidden()
|
||||
await click(page, 'RETRY')
|
||||
await click(page, 'CONTINUE')
|
||||
await page.waitForFunction(() => window.__8bit?.ready && window.__8bit.page() === '/f/icons')
|
||||
expect(await texts(page)).toContain('COPY CODE')
|
||||
})
|
||||
|
||||
Reference in new issue
Block a user