Files
8bitMyGoUI/gallery/demos_display.go
T
ZacharyZhang-NY b4b4d67b92 Audit keyboard, focus, veils, motion and clocks across all 500
A cross-cutting pass, each fix with a test that failed first.

Veils: MyGo still hands a key to a base that turned off this frame,
so every element key now goes through pixel.Shortcut, which an
archtest enforces. Input handlers record and the build applies them
only while enabled; the editor turns away a veil-frame letter or
copy; value bases get a copy and keep only enabled turns. Popups
give a held focus back to their opener when the veil lifts.

Motion: S5 is enforced at the clock. Every Tick, Since, Step,
Toward and Shot runs at 4-15 fps; blinks keep 1 Hz on 4 fps steps,
fast animations step at 15, and drawn time is quantized to frames.

Clocks stepping back: Since holds at zero without overflow, and
k.Elapsed moves a stored start to now, so nothing goes negative,
runs backward or indexes out of range.

Also: logs count typed text instead of holding it, gallery names
use glyphs the font has, removable rows are keyed, components take
their own Options (archtest), and sweeps over all 508 demos check
veiled keys, clicks, wheels, the clock stepping back and names.
2026-10-12 16:03:04 -04:00

274 lines
10 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package gallery
import (
"fmt"
"image"
"image/color"
"math"
"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"
)
var displayEntries = []Entry{
{41, "Icon", "图标", "display", "A sprite in one ink slot at ×1 to ×4, named for screen readers or hidden.", demoIcon},
{42, "Sprite", "精灵", "display", "A multi-slot sprite that plays its frames and can face the other way.", demoSprite},
{43, "Picture", "像素图", "display", "Any image reduced to the palette with Bayer dither. Press P: it recolors.", demoPicture},
{44, "Avatar", "头像", "display", "A face built from a name: skin, hair, eyes, mouth and background.", demoAvatar},
{45, "AvatarGroup", "头像组", "display", "Overlapping faces and a chip for the rest.", demoAvatarGroup},
{46, "Badge", "角标", "display", "A count or a word in a notched pill; counts stop at 99+.", demoBadge},
{47, "Tag", "标签", "display", "A chip with text, an icon and a remove box.", demoTag},
{48, "StatusDot", "状态点", "display", "Online, away, busy or offline; a live online dot blinks.", demoStatusDot},
{49, "Medal", "勋章", "display", "Gold, silver and bronze on ribbons, rank on the face.", demoMedal},
{50, "Trophy", "金杯", "display", "A gold cup over an engraved plaque.", demoTrophy},
{51, "Stat", "数据", "display", "A big value, its label and a change up or down.", demoStat},
{52, "KeyValue", "键值", "display", "A label and a value joined by a dotted leader.", demoKeyValue},
{53, "Portrait", "立绘框", "display", "A character in a frame over a nameplate.", demoPortrait},
{54, "Ribbon", "绶带", "display", "A title banner with folded, notched tails.", demoRibbon},
{55, "Stamp", "印章", "display", "A rubber stamp whose border wore unevenly.", demoStamp},
{56, "Emoji", "表情", "display", "Sixteen faces placed dot by dot.", demoEmoji},
{57, "Flag", "旗帜", "display", "A flag on a pole, waving in steps.", demoFlag},
{58, "Gem", "宝石", "display", "A cut gem for each rarity, with a stepped sparkle.", demoGem},
{59, "RarityFrame", "稀有度框", "display", "A border by rarity; a legendary one carries a running glint.", demoRarityFrame},
{60, "Bubble", "气泡", "display", "A speech bubble with its tail on any side.", demoBubble},
{61, "Swatch", "色板", "display", "The sixteen slots of the palette in use, by name.", demoSwatch},
{62, "Placeholder", "占位图", "display", "A dithered box with a cross and its size, for art to come.", demoPlaceholder},
{63, "Figure", "插图", "display", "Content over a caption in Faint.", demoFigure},
{64, "QRCode", "二维码", "display", "A scannable QR code in the darkest and lightest slots.", demoQRCode},
{65, "Signpost", "路牌", "display", "A post with up to three boards. Each board is a button.", demoSignpost},
}
// sunset is a smooth picture for Picture to reduce.
var sunset = func() image.Image {
img := image.NewRGBA(image.Rect(0, 0, 96, 64))
for y := range 64 {
for x := range 96 {
t := float64(y) / 63
c := color.RGBA{uint8(250 - 120*t), uint8(170 - 120*t), uint8(90 + 90*t), 255}
if dx, dy := float64(x-60), float64(y-34); dx*dx+dy*dy < 14*14 {
c = color.RGBA{255, 220, 120, 255}
}
if float64(y) > 44+6*math.Sin(float64(x)/9) {
c = color.RGBA{50, 40, 70, 255}
}
img.SetRGBA(x, y, c)
}
}
return img
}()
func demoIcon(c *ui.Context, log func(string)) {
k := pixel.K(c)
ui.Row(c).Gap(k.D(8)).AlignItems(ui.End).Children(func() {
for sc := 1; sc <= 4; sc++ {
display.Icon(c, icon.Heart, display.IconOptions{Slot: pixel.Bad, Scale: sc, Label: fmt.Sprintf("heart ×%d", sc)})
}
})
}
func demoSprite(c *ui.Context, log func(string)) {
k := pixel.K(c)
ui.Row(c).Gap(k.D(12)).Children(func() {
display.Sprite(c, slime, display.SpriteOptions{Scale: 3, FPS: 4, Label: "slime"})
display.Sprite(c, slime, display.SpriteOptions{Scale: 3, FPS: 4, Mirror: true, Label: "slime, mirrored"})
})
}
func demoPicture(c *ui.Context, log func(string)) {
k := pixel.K(c)
ui.Row(c).Gap(k.D(8)).AlignItems(ui.End).Children(func() {
display.Picture(c, sunset, display.PictureOptions{Label: "sunset"})
display.Picture(c, sunset, display.PictureOptions{W: 48, H: 32, Label: "small sunset"})
})
}
var party = []string{"Aria", "Brom", "Cass", "Dax", "Eve", "Finn", "Gus"}
func demoAvatar(c *ui.Context, log func(string)) {
k := pixel.K(c)
ui.Row(c).Gap(k.D(6)).Children(func() {
for _, n := range party[:5] {
display.Avatar(c, n, display.AvatarOptions{Scale: 2})
}
display.Avatar(c, "Sunny", display.AvatarOptions{Scale: 2, Picture: sunset})
})
}
func demoAvatarGroup(c *ui.Context, log func(string)) {
display.AvatarGroup(c, party, display.AvatarGroupOptions{Scale: 2})
}
func demoBadge(c *ui.Context, log func(string)) {
k := pixel.K(c)
ui.Row(c).Gap(k.D(6)).AlignItems(ui.Center).Children(func() {
for _, n := range []int{3, 42, 128} {
display.Badge(c, n, display.BadgeOptions{})
}
display.Badge(c, 0, display.BadgeOptions{Text: "NEW", Slot: pixel.Good})
display.Badge(c, 0, display.BadgeOptions{Text: "HOT", Slot: pixel.Warn})
})
}
func demoTag(c *ui.Context, log func(string)) {
k := pixel.K(c)
tags := ui.Local(c.Root(), demoKey("tags"), func() []string { return []string{"fire", "ice", "thunder", "wind"} })
ui.Column(c).Gap(k.D(6)).Children(func() {
ui.Row(c).Wrap().Gap(k.D(4)).Children(func() {
for i, t := range *tags {
if display.Tag(c, t, display.TagOptions{Icon: icon.Sparkle, Removable: true}).Removed {
*tags = append((*tags)[:i:i], (*tags)[i+1:]...)
log("removed " + t)
}
}
display.Tag(c, "locked", display.TagOptions{Icon: icon.Lock, Removable: true, Disabled: true})
})
if input.Button(c, "RESET", input.ButtonOptions{}).Clicked {
*tags = []string{"fire", "ice", "thunder", "wind"}
log("tags reset")
}
})
}
func demoStatusDot(c *ui.Context, log func(string)) {
k := pixel.K(c)
ui.Column(c).Gap(k.D(4)).Children(func() {
for _, s := range []display.Status{display.Online, display.Away, display.Busy, display.Offline} {
ui.Row(c).Gap(k.D(4)).AlignItems(ui.Center).Children(func() {
display.StatusDot(c, s, display.StatusDotOptions{Live: true})
pixel.Text(c, []string{"Player 1", "Player 2", "Player 3", "Player 4"}[s], pixel.TextOptions{})
})
}
})
}
func demoMedal(c *ui.Context, log func(string)) {
k := pixel.K(c)
ui.Row(c).Gap(k.D(10)).Children(func() {
for _, r := range []display.Rank{display.Gold, display.Silver, display.Bronze} {
display.Medal(c, r, display.MedalOptions{Scale: 2})
}
})
}
func demoTrophy(c *ui.Context, log func(string)) {
display.Trophy(c, "Champion 2026", display.TrophyOptions{Scale: 2})
}
func demoStat(c *ui.Context, log func(string)) {
k := pixel.K(c)
ui.Row(c).Gap(k.D(24)).Children(func() {
display.Stat(c, "SCORE", "42,000", display.StatOptions{Delta: "12%"})
display.Stat(c, "LIVES", "2", display.StatOptions{Delta: "1", Down: true})
})
}
func demoKeyValue(c *ui.Context, log func(string)) {
k := pixel.K(c)
ui.Column(c).Width(k.D(160)).Gap(k.D(2)).Children(func() {
for _, kv := range [][2]string{{"STR", "18"}, {"DEX", "12"}, {"INT", "7"}, {"LUCK", "99"}} {
display.KeyValue(c, kv[0], kv[1], display.KeyValueOptions{})
}
})
}
func demoPortrait(c *ui.Context, log func(string)) {
display.Portrait(c, "Sir Brom", display.PortraitOptions{Size: 48}, func() {
display.Avatar(c, "Brom", display.AvatarOptions{Scale: 3})
})
}
func demoRibbon(c *ui.Context, log func(string)) {
k := pixel.K(c)
ui.Column(c).Gap(k.D(6)).Children(func() {
display.Ribbon(c, "STAGE 1-1", display.RibbonOptions{})
display.Ribbon(c, "BONUS ROUND", display.RibbonOptions{Slot: pixel.Info})
})
}
func demoStamp(c *ui.Context, log func(string)) {
k := pixel.K(c)
ui.Row(c).Gap(k.D(10)).Children(func() {
display.Stamp(c, "APPROVED", display.StampOptions{Slot: pixel.Good, Seed: 3})
display.Stamp(c, "VOID", display.StampOptions{Seed: 9})
})
}
func demoEmoji(c *ui.Context, log func(string)) {
k := pixel.K(c)
ui.Row(c).Wrap().Gap(k.D(6)).Width(k.D(240)).Children(func() {
for _, name := range display.EmojiNames {
display.Emoji(c, name, display.EmojiOptions{Scale: 2})
}
})
}
func demoFlag(c *ui.Context, log func(string)) {
k := pixel.K(c)
ui.Row(c).Gap(k.D(12)).Children(func() {
display.Flag(c, display.FlagOptions{Scale: 2, Label: "red flag"})
display.Flag(c, display.FlagOptions{Slot: pixel.Info, Scale: 2, Label: "blue flag"})
})
}
func demoGem(c *ui.Context, log func(string)) {
k := pixel.K(c)
ui.Row(c).Gap(k.D(8)).Children(func() {
for r := display.Common; r <= display.Legendary; r++ {
display.Gem(c, r, display.GemOptions{Scale: 2})
}
})
}
func demoRarityFrame(c *ui.Context, log func(string)) {
k := pixel.K(c)
items := []string{"Stick", "Bow", "Rapier", "Dragon Lance", "Excalibur"}
ui.Row(c).Wrap().Gap(k.D(6)).Children(func() {
for r := display.Common; r <= display.Legendary; r++ {
display.RarityFrame(c, r, display.RarityFrameOptions{}, func() { pixel.Text(c, items[r], pixel.TextOptions{}) })
}
})
}
func demoBubble(c *ui.Context, log func(string)) {
k := pixel.K(c)
lines := []string{"Hey!", "Look up.", "Psst.", "Over here."}
ui.Row(c).Wrap().Gap(k.D(8)).AlignItems(ui.Center).Children(func() {
for i, side := range []display.Side{display.Bottom, display.Top, display.Left, display.Right} {
display.Bubble(c, display.BubbleOptions{Tail: side}, func() { pixel.Text(c, lines[i], pixel.TextOptions{}) })
}
})
}
func demoSwatch(c *ui.Context, log func(string)) {
display.Swatch(c, display.SwatchOptions{})
}
func demoPlaceholder(c *ui.Context, log func(string)) {
k := pixel.K(c)
ui.Row(c).Gap(k.D(8)).Children(func() {
display.Placeholder(c, display.PlaceholderOptions{})
display.Placeholder(c, display.PlaceholderOptions{W: 120, H: 40})
})
}
func demoFigure(c *ui.Context, log func(string)) {
display.Figure(c, "Fig. 1 — a slime, at rest", display.FigureOptions{}, func() {
display.Sprite(c, slime, display.SpriteOptions{Scale: 4, Label: "slime"})
})
}
func demoQRCode(c *ui.Context, log func(string)) {
display.QRCode(c, "https://mygo.egoist.dev/docs/ui", display.QRCodeOptions{})
}
func demoSignpost(c *ui.Context, log func(string)) {
boards := []string{"Town", "Dungeon", "Shop"}
if r := display.Signpost(c, boards, display.SignpostOptions{}); r.Clicked >= 0 {
log("to the " + boards[r.Clicked])
}
}