Elements are now pass-scoped value handles: tests that read bounds after a frame bind a ui.Handle. pixel tells frames apart by the build pass's root, since the Tester clock no longer moves on its own. Fix the regressions the upgrade brought: - Clipboard reads in input handlers go through c.Services(). - MyGo notes an overlay's opener after building it, so AutoFocus inside lost it; pixel.AutoFocus focuses through a Handle, and archtest bans the raw call. Dialogs hand the focus back again. - Elements around a press stay hovered: Tooltip tells a press-given focus by focus arriving under the pointer. - The web host moves the Tester clock with real time, at most 500 ms a call, and gives components the real clock. The gallery's source index also accepts CRLF checkouts. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
159 lines
3.9 KiB
Go
159 lines
3.9 KiB
Go
package account
|
|
|
|
import (
|
|
"log/slog"
|
|
"strings"
|
|
|
|
"github.com/egoist/mygo/ui"
|
|
|
|
"git.zacharyzhang.com/ZacharyZhang-NY/8bitMyGoUI/pixel"
|
|
)
|
|
|
|
// Look is a character's parts and colors, each an index.
|
|
type Look struct {
|
|
Hair, Eyes, Outfit, HairColor, OutfitColor int
|
|
}
|
|
|
|
// hues color hair and outfits, as "hues" names them.
|
|
var hues = []pixel.Slot{pixel.Ink, pixel.Warn, pixel.Bad, pixel.Magic, pixel.Info}
|
|
|
|
var (
|
|
head = pixel.MustSprite(`
|
|
................
|
|
................
|
|
................
|
|
.....eeeeee.....
|
|
....elllllle....
|
|
....elllllle....
|
|
....elllllle....
|
|
....elllllle....
|
|
.....elllle.....
|
|
......eeee......
|
|
................
|
|
................
|
|
................
|
|
................
|
|
................
|
|
................
|
|
`)
|
|
// hair is SPIKY, BOB, BALD; # takes the hair color.
|
|
hair = []*pixel.Sprite{pixel.MustSprite(`
|
|
....#..#..#.....
|
|
....##.##.##....
|
|
....########....
|
|
...##########...
|
|
...##......##...
|
|
...#........#...
|
|
`), pixel.MustSprite(`
|
|
................
|
|
................
|
|
.....######.....
|
|
....########....
|
|
...##########...
|
|
...##......##...
|
|
...##......##...
|
|
...##......##...
|
|
...##......##...
|
|
`), pixel.MustSprite(`
|
|
................
|
|
`)}
|
|
// eyes are DOTS, WIDE, SLEEPY, from the face's fifth column.
|
|
eyes = []*pixel.Sprite{pixel.MustSprite(`
|
|
.......
|
|
.......
|
|
.......
|
|
.......
|
|
.......
|
|
.......
|
|
..k..k.
|
|
`), pixel.MustSprite(`
|
|
.......
|
|
.......
|
|
.......
|
|
.......
|
|
.......
|
|
..k..k.
|
|
..k..k.
|
|
`), pixel.MustSprite(`
|
|
.......
|
|
.......
|
|
.......
|
|
.......
|
|
.......
|
|
.......
|
|
.kk..kk
|
|
`)}
|
|
// outfits are TUNIC, ROBE, ARMOR; # takes the outfit color.
|
|
outfits = []*pixel.Sprite{pixel.MustSprite(`
|
|
.....e####e.....
|
|
....e######e....
|
|
...e########e...
|
|
...l########l...
|
|
....########....
|
|
....##....##....
|
|
`), pixel.MustSprite(`
|
|
.....e####e.....
|
|
....e######e....
|
|
...e########e...
|
|
...l########l...
|
|
...##########...
|
|
..############..
|
|
`), pixel.MustSprite(`
|
|
.....e####e.....
|
|
....e#ssss#e....
|
|
...e#ssssss#e...
|
|
...l#ssssss#l...
|
|
....#ssssss#....
|
|
....##....##....
|
|
`)}
|
|
)
|
|
|
|
type CharacterCreatorOptions struct {
|
|
Disabled bool
|
|
}
|
|
|
|
type CharacterCreatorResult struct {
|
|
Element ui.Element
|
|
Changed bool
|
|
}
|
|
|
|
// CharacterCreator cycles hair, eyes, outfit and colors; a live avatar.
|
|
func CharacterCreator(c *ui.Context, text string, look *Look, o CharacterCreatorOptions) CharacterCreatorResult {
|
|
k := pixel.K(c)
|
|
styles, looks, wears, hueNames := words(k, "styles"), words(k, "looks"), words(k, "outfits"), words(k, "hues")
|
|
var res CharacterCreatorResult
|
|
res.Element = ui.Row(c).Gap(k.D(8)).AlignItems(ui.Center).Role(ui.RoleGroup).Label(text).Children(func() {
|
|
shown := *look
|
|
say := strings.Join([]string{styles[look.Hair], looks[look.Eyes], wears[look.Outfit], hueNames[look.HairColor], hueNames[look.OutfitColor]}, ", ")
|
|
ui.Box(c).Size(k.D(56), k.D(56)).Role(ui.RoleImage).Label(say).Draw(func(p *ui.Painter, r ui.Rect) {
|
|
g := k.Canvas(p, r)
|
|
g.Frame(0, 0, 56, 56, pixel.Sunken, pixel.Well)
|
|
g.Sprite(4, 4, head, 0, pixel.Ink, 3)
|
|
g.Sprite(4+3*4, 4, eyes[shown.Eyes], 0, pixel.Ink, 3)
|
|
g.Sprite(4, 4, hair[shown.Hair], 0, hues[shown.HairColor], 3)
|
|
g.Sprite(4, 4+3*10, outfits[shown.Outfit], 0, hues[shown.OutfitColor], 3)
|
|
})
|
|
ui.Column(c).Gap(k.D(2)).Children(func() {
|
|
for _, row := range []struct {
|
|
key string
|
|
names []string
|
|
at *int
|
|
}{
|
|
{"hair", styles, &look.Hair}, {"eyes", looks, &look.Eyes}, {"outfit", wears, &look.Outfit},
|
|
{"hair_color", hueNames, &look.HairColor}, {"outfit_color", hueNames, &look.OutfitColor},
|
|
} {
|
|
ui.Row(c).Gap(k.D(4)).AlignItems(ui.Center).Children(func() {
|
|
mark(c, k, k.Msg(row.key), pixel.Faint, 64)
|
|
if OptionCycler(c, k.Msg(row.key), row.names, row.at, OptionCyclerOptions{Width: 48, Disabled: o.Disabled}).Changed {
|
|
res.Changed = true
|
|
}
|
|
})
|
|
}
|
|
})
|
|
})
|
|
if res.Changed {
|
|
slog.Debug("8bit: character creator", "label", text, "look", *look)
|
|
}
|
|
return res
|
|
}
|