Add layout components 001-020 with demos and behavior tests

This commit is contained in:
ZacharyZhang-NY committed 2026-10-11 18:02:26 -04:00
1 parent 08e19833a2
commit 9a7f593892
29 files changed
+2110 -9

No files matched your search

+322
View File
@@ -0,0 +1,322 @@
package gallery
import (
"fmt"
"github.com/egoist/mygo/ui"
"git.zacharyzhang.com/ZacharyZhang-NY/8bitMyGoUI/icon"
"git.zacharyzhang.com/ZacharyZhang-NY/8bitMyGoUI/layout"
"git.zacharyzhang.com/ZacharyZhang-NY/8bitMyGoUI/pixel"
)
var layoutEntries = []Entry{
{1, "Screen", "屏幕", "layout", "The root of a screen: Back fills the window, with an optional dithered vignette.", demoScreen},
{2, "Window", "窗口", "layout", "A beveled window with a title tab, the shape of every RPG menu.", demoWindow},
{3, "Panel", "面板", "layout", "A box framed raised, sunken, flat, double, window or notched.", demoPanel},
{4, "Card", "卡片", "layout", "A raised panel with a header strip, a body and a footer.", demoCard},
{5, "Section", "分节", "layout", "A titled group inside a dotted rule.", demoSection},
{6, "Divider", "分隔线", "layout", "Rules across or down: solid, dotted, dashed, double, ornament.", demoDivider},
{7, "ScrollArea", "滚动区", "layout", "A scroll view with a pixel scrollbar: arrows step, the track pages, the thumb drags.", demoScrollArea},
{8, "Split", "分栏", "layout", "Two panes and a grip. Drag it, or focus it and use the arrow keys.", demoSplit},
{9, "Grid", "网格", "layout", "Equal columns with solid or dotted lines between the cells.", demoGrid},
{10, "Tiles", "平铺", "layout", "Equal tiles that wrap into rows as the width allows.", demoTiles},
{11, "Masonry", "瀑布流", "layout", "Each item drops into the shortest column.", demoMasonry},
{12, "AspectBox", "比例框", "layout", "Keeps a ratio in whole dots at whatever width it gets.", demoAspectBox},
{13, "Layers", "图层", "layout", "Layers stacked in one box, each over the one before.", demoLayers},
{14, "Viewport", "视口", "layout", "A fixed virtual resolution, drawn at the largest Dot that fits and letterboxed.", demoViewport},
{15, "TitleBar", "标题栏", "layout", "Stripes, a title, a close box and a minimize box.", demoTitleBar},
{16, "StatusBar", "状态栏", "layout", "Segments split by sunken dividers; the first one grows.", demoStatusBar},
{17, "Resizable", "可调尺寸", "layout", "A box with a corner grip. Drag it, or focus it and use the arrow keys.", demoResizable},
{18, "Handheld", "掌机", "layout", "A pocket console: a screen, a power LED, a D-pad, A and B.", demoHandheld},
{19, "CRTFrame", "显像管", "layout", "A monitor bezel with scanlines over the picture.", demoCRTFrame},
{20, "AppFrame", "应用框架", "layout", "Header, sidebar, main and footer; on a narrow screen the sidebar folds above.", demoAppFrame},
}
var monsters = []string{"Slime", "Bat", "Goblin", "Mimic", "Ghost", "Golem", "Drake", "Imp"}
func demoScreen(c *ui.Context, log func(string)) {
k := pixel.K(c)
on := ui.Local(c.Root(), demoKey("screen"), func() bool { return true })
ui.Column(c).Gap(k.D(6)).Children(func() {
ui.Box(c).FillWidth().Height(k.D(96)).Children(func() {
layout.Screen(c, layout.ScreenOptions{Vignette: *on, Padding: 12}, func() {
label(c, "A NEW SAVE FILE", pixel.Ink)
label(c, "Day 1. The cartridge starts.", pixel.Faint)
})
})
if button(c, "VIGNETTE") {
*on = !*on
log(fmt.Sprintf("vignette %v", *on))
}
})
}
func demoWindow(c *ui.Context, log func(string)) {
k := pixel.K(c)
ui.Row(c).Gap(k.D(8)).AlignItems(ui.Start).Children(func() {
layout.Window(c, layout.WindowOptions{Title: "ITEMS"}, func() {
for _, s := range []string{"Potion x3", "Ether x1", "Key x2"} {
label(c, s, pixel.Ink)
}
})
layout.Window(c, layout.WindowOptions{}, func() {
label(c, "HP 32/40", pixel.Ink)
label(c, "MP 12/18", pixel.Ink)
})
})
}
func demoPanel(c *ui.Context, log func(string)) {
k := pixel.K(c)
ui.Row(c).Wrap().Gap(k.D(6)).Children(func() {
for i, name := range []string{"RAISED", "SUNKEN", "FLAT", "DOUBLE", "WINDOW", "NOTCHED"} {
layout.Panel(c, layout.PanelOptions{Style: pixel.FrameStyle(i)}, func() { label(c, name, pixel.Ink) })
}
})
}
func demoCard(c *ui.Context, log func(string)) {
k := pixel.K(c)
ui.Box(c).Width(k.D(200)).Children(func() {
layout.Card(c, layout.CardOptions{Title: "QUEST", Icon: icon.Scroll, Footer: func() {
if button(c, "ACCEPT") {
log("quest accepted")
}
if button(c, "LATER") {
log("quest saved for later")
}
}}, func() {
label(c, "Find the lost cartridge.", pixel.Ink)
label(c, "Reward: 300 G", pixel.Faint)
})
})
}
func demoSection(c *ui.Context, log func(string)) {
k := pixel.K(c)
ui.Box(c).Width(k.D(220)).Children(func() {
layout.Section(c, "Sound", layout.SectionOptions{}, func() {
label(c, "Music 8", pixel.Ink)
label(c, "Effects 5", pixel.Ink)
})
})
}
func demoDivider(c *ui.Context, log func(string)) {
k := pixel.K(c)
names := []string{"SOLID", "DOTTED", "DASHED", "DOUBLE", "ORNAMENT"}
ui.Row(c).Gap(k.D(12)).AlignItems(ui.Stretch).Children(func() {
ui.Column(c).Grow(1).Gap(k.D(4)).Children(func() {
for i, n := range names {
label(c, n, pixel.Faint)
layout.Divider(c, layout.DividerOptions{Style: layout.DividerStyle(i)})
}
})
for i := range names {
layout.Divider(c, layout.DividerOptions{Vertical: true, Style: layout.DividerStyle(i), Slot: pixel.Accent})
}
})
}
func demoScrollArea(c *ui.Context, log func(string)) {
k := pixel.K(c)
s := ui.Local(c.Root(), demoKey("scroll"), func() ui.ScrollState { return ui.ScrollState{} })
ui.Column(c).Gap(k.D(4)).Children(func() {
r := layout.ScrollArea(c, s, layout.ScrollAreaOptions{Label: "Bestiary"}, func() {
for i := range 40 {
label(c, fmt.Sprintf("No.%03d %s", i+1, monsters[i%len(monsters)]), pixel.Ink)
}
})
r.Element.Size(k.D(200), k.D(112))
if r.Changed {
log(fmt.Sprintf("scrolled to %d dots", int(s.Y/k.Dot())))
}
label(c, fmt.Sprintf("Y %d of %d dots", int(s.Y/k.Dot()), int(s.MaxY/k.Dot())), pixel.Faint)
})
}
func demoSplit(c *ui.Context, log func(string)) {
k := pixel.K(c)
size := ui.Local(c.Root(), demoKey("split"), func() int { return 120 })
pane := func(name string) func() {
return func() {
layout.Panel(c, layout.PanelOptions{Style: pixel.Sunken, Fill: pixel.Well}, func() { label(c, name, pixel.Ink) }).Fill()
}
}
ui.Box(c).FillWidth().Height(k.D(96)).Children(func() {
r := layout.Split(c, size, layout.SplitOptions{Label: "Map and log"}, pane("MAP"), pane("LOG"))
r.Element.Fill()
if r.Changed {
log(fmt.Sprintf("split %d dots", *size))
}
})
}
func demoGrid(c *ui.Context, log func(string)) {
k := pixel.K(c)
items := []*pixel.Sprite{icon.Sword, icon.Shield, icon.Potion, icon.Gem, icon.Key, icon.Coin}
ui.Row(c).Gap(k.D(12)).AlignItems(ui.Start).Children(func() {
for _, lines := range []layout.GridLines{layout.SolidLines, layout.DottedLines} {
layout.Grid(c, len(items), layout.GridOptions{Columns: 3, Lines: lines}, func(i int) {
pixel.Icon(c, items[i], pixel.IconOptions{Scale: 2})
})
}
})
}
func demoTiles(c *ui.Context, log func(string)) {
k := pixel.K(c)
items := []*pixel.Sprite{icon.Sun, icon.Moon, icon.Cloud, icon.Snowflake, icon.Leaf, icon.Flower, icon.Flame, icon.Drop, icon.Bolt, icon.Star}
layout.Tiles(c, len(items), layout.TilesOptions{W: 28}, func(i int) {
ui.Box(c).Fill().Center().Draw(func(p *ui.Painter, r ui.Rect) {
k.Canvas(p, r).Frame(0, 0, 28, 28, pixel.Raised, pixel.Face)
}).Children(func() { pixel.Icon(c, items[i], pixel.IconOptions{}) })
}).Width(k.D(160))
}
func demoMasonry(c *ui.Context, log func(string)) {
k := pixel.K(c)
heights := []int{40, 24, 56, 32, 24, 48, 32, 40}
slots := []pixel.Slot{pixel.Accent, pixel.Good, pixel.Info, pixel.Warn, pixel.Magic}
layout.Masonry(c, heights, layout.MasonryOptions{}, func(i int) {
ui.Box(c).Fill().Draw(func(p *ui.Painter, r ui.Rect) {
g := k.Canvas(p, r)
g.Frame(0, 0, g.W, g.H, pixel.Flat, slots[i%len(slots)])
g.Micro(3, 3, fmt.Sprint(i+1), pixel.Ink, 1)
})
}).Width(k.D(180))
}
func demoAspectBox(c *ui.Context, log func(string)) {
k := pixel.K(c)
ui.Box(c).Width(k.D(160)).Children(func() {
layout.AspectBox(c, layout.AspectBoxOptions{W: 16, H: 9}, func() {
ui.Box(c).Fill().Draw(func(p *ui.Painter, r ui.Rect) {
g := k.Canvas(p, r)
for i := range 17 {
g.Dither(0, i*g.H/17, g.W, g.H/17+1, pixel.Back, pixel.Info, i)
}
g.Text(4, 2, fmt.Sprintf("16:9 %d×%d", g.W, g.H), pixel.Ink, 1)
})
})
})
}
func demoLayers(c *ui.Context, log func(string)) {
k := pixel.K(c)
layout.Layers(c, func() {
ui.Box(c).Fill().Draw(func(p *ui.Painter, r ui.Rect) {
g := k.Canvas(p, r)
g.Dither(0, 0, g.W, g.H, pixel.Well, pixel.Info, 6)
})
}, func() {
ui.Box(c).Fill().Center().Children(func() { pixel.Icon(c, icon.Chest, pixel.IconOptions{Scale: 3}) })
}, func() {
ui.Box(c).Padding(k.D(3)).Children(func() { label(c, "LAYER 3", pixel.Ink) })
}).Size(k.D(120), k.D(64))
}
func demoViewport(c *ui.Context, log func(string)) {
k := pixel.K(c)
ui.Box(c).FillWidth().Height(k.D(100)).Children(func() {
layout.Viewport(c, layout.ViewportOptions{W: 64, H: 40}, func() {
kv := pixel.K(c)
ui.Box(c).Fill().Draw(func(p *ui.Painter, r ui.Rect) {
g := kv.Canvas(p, r)
g.Rect(0, 0, g.W, g.H, pixel.Info)
g.Rect(0, 30, g.W, 10, pixel.Good)
g.Disc(50, 10, 5, pixel.Warn)
g.Micro(3, 3, "64X40", pixel.Ink, 1)
})
})
})
}
func demoTitleBar(c *ui.Context, log func(string)) {
k := pixel.K(c)
ui.Box(c).Width(k.D(220)).Children(func() {
r := layout.TitleBar(c, "Untitled", layout.TitleBarOptions{})
if r.Close {
log("close")
}
if r.Minimize {
log("minimize")
}
})
}
func demoStatusBar(c *ui.Context, log func(string)) {
k := pixel.K(c)
ui.Box(c).Width(k.D(260)).Children(func() {
layout.StatusBar(c, "Ready", "Ln 1, Col 1", "UTF-8")
})
}
func demoResizable(c *ui.Context, log func(string)) {
w := ui.Local(c.Root(), demoKey("resize-w"), func() int { return 120 })
h := ui.Local(c.Root(), demoKey("resize-h"), func() int { return 56 })
r := layout.Resizable(c, w, h, layout.ResizableOptions{MinW: 48, MinH: 32, MaxW: 240, MaxH: 120}, func() {
layout.Panel(c, layout.PanelOptions{Style: pixel.Sunken, Fill: pixel.Well}, func() {
label(c, fmt.Sprintf("%d×%d", *w, *h), pixel.Ink)
}).Fill()
})
if r.Changed {
log(fmt.Sprintf("size %d×%d", *w, *h))
}
}
func demoHandheld(c *ui.Context, log func(string)) {
k := pixel.K(c)
at := ui.Local(c.Root(), demoKey("handheld"), func() [2]int { return [2]int{36, 20} })
r := layout.Handheld(c, layout.HandheldOptions{ScreenW: 80, ScreenH: 56, Brand: "8BIT POCKET", Label: "Pocket console"}, func() {
ui.Box(c).Fill().Draw(func(p *ui.Painter, r ui.Rect) {
g := k.Canvas(p, r)
g.Rect(0, 0, g.W, g.H, pixel.Well)
g.Sprite(at[0], at[1], slime, g.Step(2), pixel.Ink, 1)
})
})
moves := map[string][2]int{"up": {0, -4}, "down": {0, 4}, "left": {-4, 0}, "right": {4, 0}}
for name, on := range map[string]bool{"up": r.Up, "down": r.Down, "left": r.Left, "right": r.Right} {
if on {
at[0] = max(0, min(at[0]+moves[name][0], 80-slime.W))
at[1] = max(0, min(at[1]+moves[name][1], 56-slime.H))
log(name)
}
}
if r.A {
log("A: jump")
}
if r.B {
log("B: run")
}
}
func demoCRTFrame(c *ui.Context, log func(string)) {
k := pixel.K(c)
layout.CRTFrame(c, layout.CRTFrameOptions{Brand: "8BIT CRT"}, func() {
ui.Column(c).Size(k.D(180), k.D(72)).Padding(k.D(4)).Children(func() {
label(c, "READY.", pixel.Good)
label(c, "10 PRINT \"HI\"", pixel.Good)
})
}).AlignSelf(ui.Start)
}
func demoAppFrame(c *ui.Context, log func(string)) {
k := pixel.K(c)
ui.Box(c).FillWidth().Height(k.D(150)).Children(func() {
layout.AppFrame(c, layout.AppFrameOptions{SidebarWidth: 72}, func() {
label(c, "8BIT MAIL", pixel.Ink)
}, func() {
for _, s := range []string{"Inbox", "Sent", "Spam"} {
if button(c, s) {
log("open " + s)
}
}
}, func() {
label(c, "No new mail. Go outside.", pixel.Ink)
}, func() {
label(c, "3 folders", pixel.Faint)
})
})
}
+1 -1
View File
@@ -56,7 +56,7 @@ var Categories = []Category{
{"tabletop", "Tabletop", "桌游", icon.Crown},
}
var entries = slices.Concat(foundationEntries)
var entries = slices.Concat(foundationEntries, layoutEntries)
func Entries() []Entry { return entries }