Add data components 151-175; clear MyGo row highlights

Lists, tables, trees, grids, inventories and records in data/.
Outline and GridView build on ui.List: ui.Outline strokes its arrows
and ui.GridView spaces and tints items. The pixel theme keeps MyGo's
row highlights clear so scrolled rows stay on whole dots; off lists
take a veil against the wheel. Adds Canvas.Silhouette, SwitchOptions
Label and exports Rarity.Slot.
This commit is contained in:
ZacharyZhang-NY committed 2026-10-11 22:29:07 -04:00
1 parent f587d25a5a
commit 03680980da
47 files changed
+3379 -17

No files matched your search

+42
View File
@@ -238,6 +238,25 @@ func TestControlDemosLogTheirEvents(t *testing.T) {
}
}
func TestDataDemosLogSubmits(t *testing.T) {
for slug, row := range map[string]string{"152-table": "Brom, Monk, 5", "154-outline": "Region 3", "174-searchresults": "Fira"} {
a, tt := open("/c/" + slug)
click(t, tt, row)
n := len(a.logs[slug])
tt.Key(0, ui.KeyEnter)
if len(a.logs[slug]) == n {
t.Errorf("%s logged no submit: %q", slug, a.logs[slug])
}
}
a, tt := open("/c/153-tree")
click(t, tt, "Items")
n := len(a.logs["153-tree"])
tt.Key(0, ui.KeyRight)
if len(a.logs["153-tree"]) == n {
t.Errorf("opening a branch logged nothing: %q", a.logs["153-tree"])
}
}
func TestFieldDemoLogsSubmit(t *testing.T) {
a, tt := open("/c/100-field")
// The group shares its name; the input sits below.
@@ -250,3 +269,26 @@ func TestFieldDemoLogsSubmit(t *testing.T) {
t.Fatal("Enter in the field logged nothing")
}
}
func TestDataDemosLogTheirEvents(t *testing.T) {
for slug, act := range map[string]func(tt *ui.Tester){
"151-list": func(tt *ui.Tester) { click(t, tt, "Bat") },
"152-table": func(tt *ui.Tester) { click(t, tt, "LV") },
"153-tree": func(tt *ui.Tester) { click(t, tt, "Items"); tt.Key(0, ui.KeyRight) },
"154-outline": func(tt *ui.Tester) { click(t, tt, "Region 2") },
"174-searchresults": func(tt *ui.Tester) { click(t, tt, "Fira") },
"155-gridview": func(tt *ui.Tester) { click(t, tt, "loot 3") },
"160-accordion": func(tt *ui.Tester) { click(t, tt, "Where is the key?") },
"162-carousel": func(tt *ui.Tester) { click(t, tt, "NEXT") },
"167-pager": func(tt *ui.Tester) { click(t, tt, "NEXT") },
"170-sortable": func(tt *ui.Tester) { click(t, tt, "Quest order"); tt.Key(ui.Alt, ui.KeyDown) },
"171-transfer": func(tt *ui.Tester) { click(t, tt, "RIGHT") },
"173-filterbar": func(tt *ui.Tester) { click(t, tt, "FIRE") },
} {
a, tt := open("/c/" + slug)
act(tt)
if len(a.logs[slug]) == 0 {
t.Errorf("%s logged nothing", slug)
}
}
}
+264
View File
@@ -0,0 +1,264 @@
package gallery
import (
"cmp"
"fmt"
"slices"
"strings"
"time"
"github.com/egoist/mygo/ui"
"git.zacharyzhang.com/ZacharyZhang-NY/8bitMyGoUI/data"
"git.zacharyzhang.com/ZacharyZhang-NY/8bitMyGoUI/display"
"git.zacharyzhang.com/ZacharyZhang-NY/8bitMyGoUI/icon"
"git.zacharyzhang.com/ZacharyZhang-NY/8bitMyGoUI/pixel"
"git.zacharyzhang.com/ZacharyZhang-NY/8bitMyGoUI/text"
)
var dataEntries = []Entry{
{151, "List", "列表", "data", "Thousands of rows, built as they scroll in. ▶ marks the chosen one.", demoList},
{152, "Table", "表格", "data", "Click a header to sort ▲, again for ▼. Odd rows are dithered.", demoTable},
{153, "Tree", "树", "data", "▸ opens, ▾ closes. Right opens, Left climbs back out.", demoTree},
{154, "Outline", "大纲", "data", "A tree of 2,000 floors that only builds what shows.", demoOutline},
{155, "GridView", "网格视图", "data", "Whole-dot cells in as many columns as fit; arrows go both ways.", demoGridView},
{156, "DescriptionList", "描述列表", "data", "Terms on the left, what they mean on the right.", demoDescriptionList},
{157, "Leaderboard", "排行榜", "data", "Medals for the top three; your row lights up.", demoLeaderboard},
{158, "Inventory", "物品栏", "data", "Arrows pick a slot. Drag one item onto another to swap them.", demoInventory},
{159, "ItemSlot", "物品格", "data", "An item with its stack, rarity edge and cooldown veil.", demoItemSlot},
{160, "Accordion", "手风琴", "data", "Open one answer and the others fold away.", demoAccordion},
{161, "Collapsible", "折叠区", "data", "One header that shows and hides what is under it.", demoCollapsible},
{162, "Carousel", "轮播", "data", "◀ ▶ turn the pages; it turns by itself unless you point at it.", demoCarousel},
{163, "Timeline", "时间线", "data", "Dated events down a line of markers.", demoTimeline},
}
type dataDemo struct {
list, row, tile, slot, page, size, carousel, hit int
sort data.Sort
tree, floor string
faq []bool
secret bool
quests, bench, party []string
bag []data.Item
rights [][]bool
filters []bool
name string
level float64
hard bool
logs int
loading time.Time
query string
}
func dataState(c *ui.Context) *dataDemo {
return ui.Local(c.Root(), demoKey("data"), func() dataDemo {
return dataDemo{size: 20, tree: "0", floor: "R1", faq: []bool{true, false, false},
quests: []string{"Find the key", "Save the cat", "Beat the boss", "Buy a potion"},
bench: []string{"Dax", "Eko", "Fay"}, party: []string{"Aria", "Brom"},
bag: []data.Item{{Sprite: icon.Potion, Name: "Potion", Count: 5}, {Sprite: icon.Drop, Name: "Ether", Count: 2, Rarity: display.Uncommon},
{Sprite: icon.Sword, Name: "Sword", Rarity: display.Rare}, {Sprite: icon.Shield, Name: "Shield", Rarity: display.Epic},
{Sprite: icon.Crown, Name: "Crown", Rarity: display.Legendary}, {Sprite: icon.Gem, Name: "Gem", Count: 12, Rarity: display.Rare},
{}, {Sprite: icon.Key, Name: "Key"}, {}, {}, {Sprite: icon.Scroll, Name: "Map scroll"}, {}},
rights: [][]bool{{true, false, false}, {false, true, false}, {true, true, true}},
filters: []bool{false, true, false, false}, name: "Aria", level: 7, logs: 20}
})
}
var horde = strings.Fields("Slime Bat Goblin Ghost Golem Imp Kobold Lich Mimic Ogre Orc Pixie Rat Skeleton Snake Spider Troll Wisp Wolf Wyrm Zombie Harpy Hydra Kraken Basilisk Djinn Gargoyle Manticore Wyvern Yeti")
func demoList(c *ui.Context, log func(string)) {
s := dataState(c)
r := data.List(c, "Monsters", len(horde), &s.list, data.ListOptions{}, func(i int) {
pixel.Text(c, horde[i], pixel.TextOptions{})
})
r.Element.Width(pixel.K(c).D(140))
if r.Changed {
log("chose " + horde[s.list])
}
if r.Submitted {
log("opened " + horde[s.list])
}
}
var roster = []struct {
name, class string
lv int
}{{"Aria", "Knight", 7}, {"Brom", "Monk", 5}, {"Cyra", "Mage", 9}, {"Dax", "Thief", 4}, {"Eko", "Bard", 6},
{"Fay", "Cleric", 8}, {"Gus", "Ranger", 3}, {"Hal", "Knight", 10}, {"Ivy", "Mage", 2}, {"Jun", "Monk", 11}}
func demoTable(c *ui.Context, log func(string)) {
s := dataState(c)
order := make([]int, len(roster))
for i := range order {
order[i] = i
}
slices.SortStableFunc(order, func(a, b int) int {
x, y := roster[a], roster[b]
d := [3]int{cmp.Compare(x.name, y.name), cmp.Compare(x.class, y.class), cmp.Compare(x.lv, y.lv)}[s.sort.Column]
if s.sort.Desc {
return -d
}
return d
})
cols := []data.Column{{Title: "Name", Width: 50}, {Title: "Class", Width: 56}, {Title: "LV", Width: 30}}
r := data.Table(c, "Roster", cols, len(roster), &s.sort, &s.row, data.TableOptions{}, func(row, col int) string {
h := roster[order[row]]
return [3]string{h.name, h.class, fmt.Sprint(h.lv)}[col]
})
if r.Sorted {
log(fmt.Sprintf("sorted by %s, desc %v", cols[s.sort.Column].Title, s.sort.Desc))
}
if r.Changed {
log("chose row " + fmt.Sprint(s.row))
}
if r.Submitted {
log("opened " + roster[order[s.row]].name)
}
}
func demoTree(c *ui.Context, log func(string)) {
s := dataState(c)
roots := []data.TreeNode{
{Label: "Bag", Children: []data.TreeNode{{Label: "Potions", Children: []data.TreeNode{{Label: "Potion ×5"}, {Label: "Ether ×2"}}}, {Label: "Tent"}}},
{Label: "Key items", Children: []data.TreeNode{{Label: "Old key"}, {Label: "Map scroll"}}},
{Label: "Gold: 120"},
}
r := data.Tree(c, "Items", roots, &s.tree, data.TreeOptions{})
if r.Changed {
log("chose " + s.tree)
}
if r.Toggled {
log("opened or closed " + s.tree)
}
}
func demoOutline(c *ui.Context, log func(string)) {
s := dataState(c)
roots := make([]string, 8)
for i := range roots {
roots[i] = fmt.Sprintf("R%d", i+1)
}
children := func(k string) []string {
n := map[int]int{0: 12, 1: 20}[strings.Count(k, "/")]
var out []string
for i := range n {
out = append(out, fmt.Sprintf("%s/%d", k, i+1))
}
return out
}
label := func(k string) string {
parts := strings.Split(k, "/")
name := []string{"Region " + parts[0][1:], "Dungeon ", "Floor B"}[len(parts)-1]
if len(parts) > 1 {
name += parts[len(parts)-1]
}
return name
}
r := data.Outline(c, "World", roots, children, label, &s.floor, data.OutlineOptions{})
r.Element.Width(pixel.K(c).D(160))
if r.Changed {
log("chose " + label(s.floor))
}
if r.Submitted {
log("entered " + label(s.floor))
}
}
var tiles = []*pixel.Sprite{icon.Coin, icon.Gem, icon.Potion, icon.Sword, icon.Shield, icon.Chest, icon.Crown, icon.Scroll, icon.Flame, icon.Leaf}
func demoGridView(c *ui.Context, log func(string)) {
k := pixel.K(c)
s := dataState(c)
r := data.GridView(c, "Loot", 200, &s.tile, data.GridViewOptions{CellWidth: 24, CellHeight: 24, Height: 100}, func(i int) {
ui.Box(c).Grow(1).AlignItems(ui.Center).Justify(ui.Center).Children(func() {
display.Icon(c, tiles[i%len(tiles)], display.IconOptions{Label: fmt.Sprintf("loot %d", i)})
})
})
r.Element.Width(k.D(200))
if r.Changed {
log(fmt.Sprintf("chose loot %d", s.tile))
}
if r.Submitted {
log(fmt.Sprintf("took loot %d", s.tile))
}
}
func demoDescriptionList(c *ui.Context, log func(string)) {
ui.Box(c).Width(pixel.K(c).D(220)).Children(func() {
data.DescriptionList(c, [][2]string{{"Class", "Knight"}, {"Weapon", "Rusty sword"},
{"Home", "Castle Town, two screens west of the cave"}, {"Motto", "It is dangerous to go alone."}}, data.DescriptionListOptions{TermWidth: 50})
})
}
func demoLeaderboard(c *ui.Context, log func(string)) {
scores := []data.Score{{Name: "ZAK", Score: 98400}, {Name: "MOM", Score: 87150}, {Name: "AAA", Score: 80000}, {Name: "BOB", Score: 64200}, {Name: "YOU", Score: 51300}, {Name: "KEN", Score: 32000}}
ui.Box(c).Width(pixel.K(c).D(160)).Children(func() {
data.Leaderboard(c, scores, data.LeaderboardOptions{Player: 4})
})
}
func demoInventory(c *ui.Context, log func(string)) {
s := dataState(c)
r := data.Inventory(c, "Bag", s.bag, &s.slot, data.InventoryOptions{})
if r.Swapped {
log("swapped slots")
}
if r.Changed {
log(fmt.Sprintf("slot %d: %s", s.slot, s.bag[s.slot].Name))
}
}
func demoItemSlot(c *ui.Context, log func(string)) {
ui.Row(c).Gap(pixel.K(c).D(6)).Children(func() {
data.ItemSlot(c, data.Item{Sprite: icon.Potion, Name: "Potion", Count: 3}, data.ItemSlotOptions{})
data.ItemSlot(c, data.Item{Sprite: icon.Crown, Name: "Crown", Rarity: display.Legendary}, data.ItemSlotOptions{Selected: true})
data.ItemSlot(c, data.Item{Sprite: icon.Bolt, Name: "Bolt scroll", Rarity: display.Rare}, data.ItemSlotOptions{Cooldown: 0.5})
data.ItemSlot(c, data.Item{Name: "Empty"}, data.ItemSlotOptions{})
})
}
func demoAccordion(c *ui.Context, log func(string)) {
s := dataState(c)
qs := []string{"How do I save?", "Where is the key?", "Can I pet the dog?"}
as := []string{"Visit any inn, or press START.", "Under the third torch.", "Yes. Always yes."}
ui.Box(c).Width(pixel.K(c).D(180)).Children(func() {
if data.Accordion(c, "Help", qs, s.faq, data.AccordionOptions{}, func(i int) {
text.Paragraph(c, as[i], text.ParagraphOptions{})
}).Changed {
log(fmt.Sprintf("open %v", s.faq))
}
})
}
func demoCollapsible(c *ui.Context, log func(string)) {
s := dataState(c)
if data.Collapsible(c, "Secret moves", &s.secret, data.CollapsibleOptions{}, func() {
pixel.Text(c, "↑ ↑ ↓ ↓ ← → ← → B A", pixel.TextOptions{})
}).Changed {
log(fmt.Sprintf("open %v", s.secret))
}
}
func demoCarousel(c *ui.Context, log func(string)) {
k := pixel.K(c)
s := dataState(c)
tips := []struct {
art *pixel.Sprite
line string
}{{icon.Heart, "Hearts heal you."}, {icon.Key, "Keys open doors."}, {icon.Coin, "Coins buy things."}}
if data.Carousel(c, "Tips", len(tips), &s.carousel, data.CarouselOptions{Every: 4}, func(i int) {
ui.Column(c).Width(k.D(120)).AlignItems(ui.Center).Gap(k.D(4)).Children(func() {
display.Icon(c, tips[i].art, display.IconOptions{Scale: 2})
pixel.Text(c, tips[i].line, pixel.TextOptions{})
})
}).Changed {
log(fmt.Sprintf("page %d", s.carousel+1))
}
}
func demoTimeline(c *ui.Context, log func(string)) {
ui.Box(c).Width(pixel.K(c).D(200)).Children(func() {
data.Timeline(c, []data.Event{{Date: "1985", Text: "A plumber jumps into a pipe."}, {Date: "1986", Text: "A boy in green finds a sword."},
{Date: "1987", Text: "A last fantasy begins."}, {Date: "1991", Text: "A blue hedgehog runs very fast."}}, data.TimelineOptions{})
})
}
+163
View File
@@ -0,0 +1,163 @@
package gallery
import (
"fmt"
"strings"
"time"
"github.com/egoist/mygo/ui"
"git.zacharyzhang.com/ZacharyZhang-NY/8bitMyGoUI/data"
"git.zacharyzhang.com/ZacharyZhang-NY/8bitMyGoUI/icon"
"git.zacharyzhang.com/ZacharyZhang-NY/8bitMyGoUI/input"
"git.zacharyzhang.com/ZacharyZhang-NY/8bitMyGoUI/layout"
"git.zacharyzhang.com/ZacharyZhang-NY/8bitMyGoUI/pixel"
"git.zacharyzhang.com/ZacharyZhang-NY/8bitMyGoUI/text"
)
var recordEntries = []Entry{
{164, "StatCompare", "属性对比", "data", "What a new sword does to you: ▲ in green, ▼ in red.", demoStatCompare},
{165, "Bestiary", "图鉴", "data", "Monsters you met, and ??? shadows of those you have not.", demoBestiary},
{166, "PropertyGrid", "属性表", "data", "Edit text, numbers and switches row by row.", demoPropertyGrid},
{167, "Pager", "分页器", "data", "Pick a page size; ◀ ▶ step through 312 items.", demoPager},
{168, "Feed", "动态流", "data", "What happened, newest first, with icons and times.", demoFeed},
{169, "Matrix", "矩阵", "data", "Who knows which spell. Arrows move; Space flips.", demoMatrix},
{170, "Sortable", "可排序列表", "data", "Drag a quest by its grip, or move it with Alt and the arrows.", demoSortable},
{171, "Transfer", "穿梭框", "data", "Pick a hero, then → or ← to swap benches.", demoTransfer},
{172, "InfiniteList", "无限列表", "data", "Scroll to the end; it loads twenty more.", demoInfiniteList},
{173, "FilterBar", "筛选栏", "data", "Chips turn filters on; CLEAR turns them all off.", demoFilterBar},
{174, "SearchResults", "搜索结果", "data", "Type a spell name; matches light up.", demoSearchResults},
{175, "CardGrid", "卡片网格", "data", "As many columns of cards as the width holds.", demoCardGrid},
}
func demoStatCompare(c *ui.Context, log func(string)) {
k := pixel.K(c)
ui.Column(c).Gap(k.D(4)).Children(func() {
pixel.Text(c, "Iron sword → Flame sword", pixel.TextOptions{Slot: pixel.Faint})
data.StatCompare(c, []data.Change{{Label: "ATK", Old: 12, New: 18}, {Label: "DEF", Old: 9, New: 7}, {Label: "SPD", Old: 5, New: 5}, {Label: "MAG", Old: 3, New: 6}}, data.StatCompareOptions{})
})
}
func demoBestiary(c *ui.Context, log func(string)) {
k := pixel.K(c)
ui.Row(c).Gap(k.D(6)).Children(func() {
ui.Box(c).Width(k.D(130)).Children(func() {
data.Bestiary(c, data.Monster{Sprite: slime, Name: "Slime", Seen: true, Stats: [][2]string{{"HP", "8"}, {"ATK", "2"}},
Notes: "Wobbles happily. Mostly harmless."}, data.BestiaryOptions{})
})
ui.Box(c).Width(k.D(130)).Children(func() {
data.Bestiary(c, data.Monster{Sprite: icon.Bug, Name: "Cave beetle", Stats: [][2]string{{"HP", "30"}, {"ATK", "9"}},
Notes: "Clicks in the dark."}, data.BestiaryOptions{Scale: 3})
})
})
}
func demoPropertyGrid(c *ui.Context, log func(string)) {
s := dataState(c)
props := []data.Property{{Name: "Name", Text: &s.name}, {Name: "Level", Number: &s.level, Min: 1, Max: 99}, {Name: "Hard mode", Switch: &s.hard}}
if data.PropertyGrid(c, "Hero", props, data.PropertyGridOptions{NameWidth: 56}).Changed {
log(fmt.Sprintf("%s, level %g, hard %v", s.name, s.level, s.hard))
}
}
func demoPager(c *ui.Context, log func(string)) {
s := dataState(c)
if data.Pager(c, "Items", 312, &s.page, &s.size, data.PagerOptions{}).Changed {
log(fmt.Sprintf("page %d, %d per page", s.page+1, s.size))
}
}
func demoFeed(c *ui.Context, log func(string)) {
ui.Box(c).Width(pixel.K(c).D(200)).Children(func() {
data.Feed(c, []data.Activity{{Icon: icon.Star, Text: "Aria found a star shard.", Time: "1 min ago"}, {Icon: icon.Coin, Text: "Brom sold three slime jellies.", Time: "4 min ago"},
{Icon: icon.Trophy, Text: "You cleared World 1-2.", Time: "9 min ago"}, {Icon: icon.Heart, Text: "Cyra healed the party.", Time: "12 min ago"}}, data.FeedOptions{})
})
}
func demoMatrix(c *ui.Context, log func(string)) {
s := dataState(c)
heroes, spells := []string{"Aria", "Brom", "Cyra"}, []string{"FIRE", "ICE", "BOLT"}
if data.Matrix(c, "Spells known", heroes, spells, s.rights, data.MatrixOptions{}).Changed {
log(fmt.Sprintf("%v", s.rights))
}
}
func demoSortable(c *ui.Context, log func(string)) {
s := dataState(c)
if data.Sortable(c, "Quest order", &s.quests, data.SortableOptions{}).Changed {
log(strings.Join(s.quests, " · "))
}
}
func demoTransfer(c *ui.Context, log func(string)) {
s := dataState(c)
if data.Transfer(c, "Party", &s.bench, &s.party, data.TransferOptions{Height: 70}).Changed {
log("party: " + strings.Join(s.party, ", "))
}
}
func demoInfiniteList(c *ui.Context, log func(string)) {
k := pixel.K(c)
s := dataState(c)
// A pretend server answers after one second.
if !s.loading.IsZero() && k.Since(c, s.loading, 1, 1) == 1 {
s.logs, s.loading = s.logs+20, time.Time{}
log(fmt.Sprintf("loaded %d rows", s.logs))
}
r := data.InfiniteList(c, "Battle log", s.logs, data.InfiniteListOptions{Height: 100, Done: s.logs >= 100}, func(i int) {
pixel.Text(c, fmt.Sprintf("Turn %d: the slime wobbles.", i+1), pixel.TextOptions{})
})
r.Element.Width(k.D(180))
if r.More {
s.loading = k.Now()
log("asked for more")
}
}
func demoFilterBar(c *ui.Context, log func(string)) {
s := dataState(c)
labels := []string{"FIRE", "ICE", "BOLT", "HOLY"}
if data.FilterBar(c, "Elements", labels, s.filters, data.FilterBarOptions{}).Changed {
log(fmt.Sprintf("%v", s.filters))
}
}
var spellBook = []data.Hit{{Title: "Fire", Text: "A small burst of fire."}, {Title: "Fira", Text: "A bigger fire. Warm."}, {Title: "Blizzard", Text: "Ice falls from nowhere."},
{Title: "Thunder", Text: "Bolts strike one foe."}, {Title: "Cure", Text: "Heals a little."}, {Title: "Firaga", Text: "All the fire at once."}}
func demoSearchResults(c *ui.Context, log func(string)) {
k := pixel.K(c)
s := dataState(c)
var hits []data.Hit
for _, h := range spellBook {
if strings.Contains(strings.ToLower(h.Title+" "+h.Text), strings.ToLower(s.query)) {
hits = append(hits, h)
}
}
s.hit = min(s.hit, len(hits)-1)
ui.Column(c).Gap(k.D(4)).Width(k.D(180)).Children(func() {
if input.SearchField(c, "Search spells", &s.query, input.SearchFieldOptions{Placeholder: "fire"}).Changed {
log("query " + s.query)
}
r := data.SearchResults(c, "Spells", s.query, hits, &s.hit, data.SearchResultsOptions{Height: 90})
if r.Changed {
log("chose " + hits[s.hit].Title)
}
if r.Submitted {
log("cast " + hits[s.hit].Title)
}
})
}
func demoCardGrid(c *ui.Context, log func(string)) {
cards := []struct {
art *pixel.Sprite
name, line string
}{{icon.Sword, "Sword", "Pointy end first."}, {icon.Shield, "Shield", "Blocks most things."}, {icon.Potion, "Potion", "Tastes like cherry."},
{icon.Crown, "Crown", "Heavy is the head."}, {icon.Map, "Map", "X marks the spot."}, {icon.Key, "Key", "Opens one door."}}
data.CardGrid(c, len(cards), data.CardGridOptions{MinWidth: 90}, func(i int) {
layout.Card(c, layout.CardOptions{Title: cards[i].name, Icon: cards[i].art}, func() {
text.Paragraph(c, cards[i].line, text.ParagraphOptions{})
})
})
}
+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)
var entries = slices.Concat(foundationEntries, layoutEntries, textEntries, displayEntries, inputEntries, controlEntries, feedbackEntries, navigationEntries, dataEntries, recordEntries)
func Entries() []Entry { return entries }