Data: components 069-080

This commit is contained in:
ZacharyZhang-NY committed 2026-10-05 12:27:55 -04:00
1 parent 2a232930dd
commit 16533b8f02
71 files changed
+5400 -1

No files matched your search

+79 -1
View File
@@ -1,3 +1,81 @@
package main
var dataPages = []page{}
import (
"github.com/egoist/mygo/ui"
mujica "mujicaui"
"mujicaui/theme"
)
const dataCategory = "Data"
var dataPages = []page{
{ID: "069", Name: "Card", ZhName: "卡片", Category: dataCategory, Demo: dataCardDemo, Code: dataCardCode,
Keys: "A card takes no focus itself; Tab moves through the controls inside it."},
{ID: "070", Name: "List", ZhName: "列表", Category: dataCategory, Demo: dataListDemo, Code: dataListCode,
Keys: "Up/Down (arrows in grid mode), Home/End and PageUp/PageDown move the choice; typing jumps by label; Enter submits; Cmd/Ctrl-click and Shift-click choose several; Cmd/Ctrl+A chooses all."},
{ID: "071", Name: "DataTable", ZhName: "数据表格", Category: dataCategory, Demo: dataTableDemo, Code: dataTableCode,
Keys: "Up/Down, Home/End move the choice; Enter submits; Shift and Cmd/Ctrl extend it; click a header to sort, again to reverse; drag header edges to resize and headers to move columns."},
{ID: "072", Name: "Tree", ZhName: "树", Category: dataCategory, Demo: dataTreeDemo, Code: dataTreeCode,
Keys: "Up/Down move; Right opens or goes to the first child; Left closes or goes to the parent; Option/Shift with Right/Left opens or closes all inside; typing jumps by label."},
{ID: "073", Name: "TreeTable", ZhName: "树形表格", Category: dataCategory, Demo: dataTreeTableDemo, Code: dataTreeTableCode,
Keys: "As Tree for rows; click a header to sort siblings; drag header edges to resize and headers to move columns."},
{ID: "074", Name: "Accordion", ZhName: "手风琴", Category: dataCategory, Demo: dataAccordionDemo, Code: dataAccordionCode,
Keys: "Tab reaches a header; Up/Down, Home/End move between headers, skipping disabled ones; Enter or Space opens and closes."},
{ID: "075", Name: "Collapsible", ZhName: "折叠面板", Category: dataCategory, Demo: dataCollapsibleDemo, Code: dataCollapsibleCode,
Keys: "Enter or Space on the trigger opens and closes; closed content leaves the Tab order."},
{ID: "076", Name: "Timeline", ZhName: "时间线", Category: dataCategory, Demo: dataTimelineDemo, Code: dataTimelineCode,
Keys: "Display only: no keys."},
{ID: "077", Name: "DescriptionList", ZhName: "描述列表", Category: dataCategory, Demo: dataDescriptionDemo, Code: dataDescriptionCode,
Keys: "Tab reaches each copy button; Enter or Space copies its value."},
{ID: "078", Name: "Statistic", ZhName: "统计数值", Category: dataCategory, Demo: dataStatisticDemo, Code: dataStatisticCode,
Keys: "Display only: no keys."},
{ID: "079", Name: "ImageViewer", ZhName: "图片查看器", Category: dataCategory, Demo: dataViewerDemo, Code: dataViewerCode,
Keys: "+ and - zoom, 0 fits; Left/Right switch images; Shift+arrows pan; the wheel zooms; drag pans; Escape closes."},
{ID: "080", Name: "Carousel", ZhName: "走马灯", Category: dataCategory, Demo: dataCarouselDemo, Code: dataCarouselCode,
Keys: "With focus inside: Left/Right step, Home/End jump; arrows move among the position dots; Enter or Space activates buttons."},
}
// dataSection lays out a titled block of a demo.
func dataSection(c *ui.Context, title string, fn func()) {
ui.Column(c).Gap(12).Children(func() {
ui.Text(c, title).Font(theme.SerifFont).FontSize(theme.H3Size)
fn()
})
}
// dataStates lays labeled variants side by side.
func dataStates(c *ui.Context, fn func(state func(name string, body func()))) {
k := mujica.Tokens(c)
ui.Row(c).Gap(24).Wrap().AlignItems(ui.Start).Children(func() {
fn(func(name string, body func()) {
ui.Column(c).Gap(6).AlignItems(ui.Start).Children(func() {
ui.Text(c, name).FontSize(theme.CaptionSize).TextColor(k.TextMuted)
body()
})
})
})
}
func dataControls(c *ui.Context, fn func()) {
ui.Row(c).Gap(16).Wrap().AlignItems(ui.Center).Children(fn)
}
var dataStatuses = []string{"Ready", "Loading", "Loading more", "Failed", "Empty"}
// dataStatus maps a status choice to the status and whether rows show.
func dataStatus(s string) (mujica.DataStatus, bool) {
switch s {
case "Ready":
return mujica.DataReady, true
case "Loading":
return mujica.DataLoading, false
case "Loading more":
return mujica.DataLoadingMore, true
case "Failed":
return mujica.DataFailed, true
case "Empty":
return mujica.DataReady, false
}
panic("gallery: unknown status " + s)
}
+144
View File
@@ -0,0 +1,144 @@
package main
// Snippets are the bodies of the Example functions in example_data_test.go.
const dataCardCode = `view := func(c *ui.Context) {
mujica.Use(c, mujica.Settings{})
mujica.Card(c, mujica.CardOptions{Title: "Archive", Decorated: true, Footer: func() {
mujica.Button(c, "Open", mujica.ButtonOptions{})
}}, func() {
ui.Text(c, "Letters of the court, sealed in wax.")
}).Width(280)
}
ui.Render(view, 400, 300, 1)`
const dataListCode = `names := []string{"Matins", "Lauds", "Prime", "Terce"}
var state mujica.ListState[string]
view := func(c *ui.Context) {
mujica.Use(c, mujica.Settings{})
v := mujica.List(c, &state, len(names), mujica.ListOptions[string]{
Key: func(i int) string { return names[i] },
Label: func(i int) string { return names[i] },
}, func(i int) { ui.Text(c, names[i]) })
v.Size(240, 200)
if v.Changed() {
key, _ := state.Selected()
fmt.Println("chose", key)
}
}
ui.Render(view, 400, 300, 1)`
const dataTableCode = `type relic struct {
ID int
Name string
Age int
}
rows := []relic{{1, "Censer", 300}, {2, "Altar", 120}, {3, "Bell", 80}}
var state mujica.DataTableState[int]
opts := mujica.DataTableOptions[relic, int]{
Key: func(r relic) int { return r.ID },
Columns: []mujica.DataColumn[relic]{
{ID: "name", Title: "Name", Sortable: true,
Text: func(r relic) string { return r.Name },
Compare: func(a, b relic) int { return cmp.Compare(a.Name, b.Name) }},
{ID: "age", Title: "Age", Width: 80, Align: ui.End, Sortable: true,
Text: func(r relic) string { return strconv.Itoa(r.Age) },
Compare: func(a, b relic) int { return cmp.Compare(a.Age, b.Age) }},
},
}
view := func(c *ui.Context) {
mujica.Use(c, mujica.Settings{})
mujica.DataTable(c, &state, rows, opts).Size(360, 240)
}
ui.Render(view, 400, 300, 1)`
const dataTreeCode = `kids := map[string][]string{"Nave": {"Pulpit", "Pews"}}
var state mujica.TreeState[string]
view := func(c *ui.Context) {
mujica.Use(c, mujica.Settings{})
mujica.Tree(c, &state, mujica.TreeOptions[string]{
Roots: []string{"Nave", "Sacristy"},
Children: func(n string) []string { return kids[n] },
Label: func(n string) string { return n },
}).Size(280, 240)
}
ui.Render(view, 400, 300, 1)`
const dataTreeTableCode = `kids := map[string][]string{"Vestry": {"Stole", "Alb"}}
size := map[string]int{"Vestry": 9, "Stole": 2, "Alb": 5, "Organ": 7}
var state mujica.TreeTableState[string]
view := func(c *ui.Context) {
mujica.Use(c, mujica.Settings{})
mujica.TreeTable(c, &state, mujica.TreeTableOptions[string]{
Roots: []string{"Vestry", "Organ"},
Children: func(n string) []string { return kids[n] },
Label: func(n string) string { return n },
Columns: []mujica.TreeColumn[string]{
{ID: "name", Title: "Name", Cell: func(n string) { ui.Text(c, n) },
Compare: cmp.Compare[string]},
{ID: "size", Title: "Size", Width: 80, Align: ui.End,
Cell: func(n string) { ui.Text(c, strconv.Itoa(size[n])) }},
},
}).Size(360, 240)
}
ui.Render(view, 400, 300, 1)`
const dataAccordionCode = `open := []string{"choir"}
view := func(c *ui.Context) {
mujica.Use(c, mujica.Settings{})
mujica.Accordion(c, &open, []mujica.AccordionItem{
{ID: "choir", Title: "Choir", Content: func() { ui.Text(c, "Twelve voices.") }},
{ID: "nave", Title: "Nave", Content: func() { ui.Text(c, "Black oak pews.") }},
}, mujica.AccordionOptions{})
}
ui.Render(view, 400, 300, 1)`
const dataCollapsibleCode = `open := false
view := func(c *ui.Context) {
mujica.Use(c, mujica.Settings{})
mujica.Collapsible(c, &open, mujica.CollapsibleOptions{Title: "Details"}, func() {
ui.Text(c, "Candles are lit at the seventh bell.")
})
}
ui.Render(view, 400, 300, 1)`
const dataTimelineCode = `view := func(c *ui.Context) {
mujica.Use(c, mujica.Settings{})
mujica.Timeline(c, []mujica.TimelineItem{
{Time: "06:00", Title: "Matins sung", Status: mujica.TimelineDone},
{Time: "09:00", Title: "Relics moved", Content: "From the crypt to the altar.", Status: mujica.TimelineActive},
{Time: "18:00", Title: "Vespers"},
})
}
ui.Render(view, 400, 300, 1)`
const dataDescriptionCode = `view := func(c *ui.Context) {
mujica.Use(c, mujica.Settings{})
mujica.DescriptionList(c, []mujica.DescriptionItem{
{Label: "Archivist", Value: "Sister Agnès", Copyable: true},
{Label: "Shelf", Value: "North vault, row 7"},
{Label: "Seal", Value: "Wine-red wax", Span: -1},
}, mujica.DescriptionListOptions{})
}
ui.Render(view, 400, 300, 1)`
const dataStatisticCode = `change := 12.5
view := func(c *ui.Context) {
mujica.Use(c, mujica.Settings{})
mujica.Statistic(c, 12840, mujica.StatisticOptions{
Title: "Pilgrims", Unit: "souls", Precision: 0,
Change: &change, ChangeUnit: "%", ChangeNote: "vs last feast",
})
}
ui.Render(view, 400, 300, 1)`
const dataViewerCode = "picture := ui.MustParseSVG([]byte(`<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"240\" height=\"160\"><rect width=\"240\" height=\"160\" fill=\"#AD9164\"/><path d=\"M120 20 L200 80 L120 140 L40 80 Z\" fill=\"#7D2034\"/></svg>`))\nopen, index := true, 0\nview := func(c *ui.Context) {\n\tmujica.Use(c, mujica.Settings{})\n\tmujica.ImageViewer(c, &open, &index, []mujica.ViewerImage{{Source: picture, Alt: \"Rose window\"}})\n}\nui.Render(view, 400, 300, 1)"
const dataCarouselCode = `index := 0
view := func(c *ui.Context) {
mujica.Use(c, mujica.Settings{})
mujica.Carousel(c, &index, 3, mujica.CarouselOptions{Label: "Windows", Autoplay: true}, func(i int) {
ui.Text(c, fmt.Sprintf("Rose window %d", i+1)).Padding(24)
}).Size(360, 220)
}
ui.Render(view, 400, 300, 1)`
+303
View File
@@ -0,0 +1,303 @@
package main
import (
"cmp"
"fmt"
"strconv"
"strings"
"time"
"github.com/egoist/mygo/ui"
mujica "mujicaui"
)
var dataOffices = []string{"Matins", "Lauds", "Prime", "Terce", "Sext", "None", "Vespers", "Compline"}
func dataListDemo(c *ui.Context, log func(string)) {
type state struct {
big, grid, multiple bool
status, query string
list mujica.ListState[int]
}
s := ui.Local(c.Root(), "070", func() state { return state{status: "Ready"} })
n := 24
if s.big {
n = 100_000
}
label := func(i int) string { return fmt.Sprintf("%s %d", dataOffices[i%len(dataOffices)], i+1) }
st, show := dataStatus(s.status)
if !show {
n = 0
}
dataSection(c, "List", func() {
dataControls(c, func() {
ui.Checkbox(c, &s.big, "100,000 items")
ui.Checkbox(c, &s.grid, "Grid")
ui.Checkbox(c, &s.multiple, "Multiple")
ui.Select(c, &s.status, dataStatuses)
})
dataControls(c, func() {
ui.TextInput(c, &s.query).Width(200).Placeholder("Find by label")
if mujica.Button(c, "Find", mujica.ButtonOptions{Variant: mujica.Secondary}).Clicked() {
for i := range n {
if strings.Contains(strings.ToLower(label(i)), strings.ToLower(s.query)) {
s.list.ScrollTo(i)
log(fmt.Sprintf("found %q at %d", s.query, i))
break
}
}
}
})
v := mujica.List(c, &s.list, n, mujica.ListOptions[int]{
Key: func(i int) int { return i }, Label: label, Grid: s.grid, Multiple: s.multiple,
ItemWidth: 140, ItemHeight: 72, Status: st,
}, func(i int) {
ui.Text(c, label(i)).SingleLine()
if s.grid {
ui.Text(c, "Office").TextColor(mujica.Tokens(c).TextMuted)
}
})
v.Size(480, 320)
if v.Changed() {
k, _ := s.list.Selected()
log(fmt.Sprintf("selected %d, %d chosen", k, s.list.Selection.Len()))
}
if v.Submitted() {
k, _ := s.list.Selected()
log(fmt.Sprintf("submitted %d", k))
}
if v.Retried() {
log("retry")
}
})
}
type dataRelic struct {
ID int
Name string
Office string
Year int
Sealed bool
}
func dataRelics(n int) []dataRelic {
rows := make([]dataRelic, n)
for i := range rows {
rows[i] = dataRelic{ID: i + 1, Name: fmt.Sprintf("Relic %05d", (i*7919)%n+1), Office: dataOffices[i%len(dataOffices)],
Year: 1200 + (i*37)%600, Sealed: i%3 == 0}
}
return rows
}
func dataTableDemo(c *ui.Context, log func(string)) {
type state struct {
big, multiple bool
status string
small, large []dataRelic
table mujica.DataTableState[int]
}
s := ui.Local(c.Root(), "071", func() state { return state{status: "Ready", small: dataRelics(60)} })
rows := s.small
if s.big {
if s.large == nil {
s.large = dataRelics(100_000)
}
rows = s.large
}
st, show := dataStatus(s.status)
if !show {
rows = nil
}
k := mujica.Tokens(c)
cols := []mujica.DataColumn[dataRelic]{
{ID: "id", Title: "ID", Width: 80, Align: ui.End, Sortable: true, Text: func(r dataRelic) string { return strconv.Itoa(r.ID) },
Compare: func(a, b dataRelic) int { return cmp.Compare(a.ID, b.ID) }},
{ID: "name", Title: "Name", MinWidth: 120, Sortable: true, Text: func(r dataRelic) string { return r.Name },
Compare: func(a, b dataRelic) int { return cmp.Compare(a.Name, b.Name) }},
{ID: "office", Title: "Office", Width: 110, Sortable: true, Text: func(r dataRelic) string { return r.Office },
Compare: func(a, b dataRelic) int { return cmp.Compare(a.Office, b.Office) }},
{ID: "year", Title: "Year", Width: 80, Align: ui.End, Sortable: true, Text: func(r dataRelic) string { return strconv.Itoa(r.Year) },
Compare: func(a, b dataRelic) int { return cmp.Compare(a.Year, b.Year) }},
{ID: "seal", Title: "Seal", Width: 100, Text: func(r dataRelic) string { return map[bool]string{true: "Sealed", false: "Open"}[r.Sealed] },
Cell: func(r dataRelic) {
color := k.Success
if r.Sealed {
color = k.Warning
}
ui.Text(c, map[bool]string{true: "◆ Sealed", false: "◇ Open"}[r.Sealed]).TextColor(color).SingleLine()
}},
}
dataSection(c, "DataTable", func() {
dataControls(c, func() {
ui.Checkbox(c, &s.big, "100,000 rows")
ui.Checkbox(c, &s.multiple, "Multiple")
ui.Select(c, &s.status, dataStatuses)
ui.TextInput(c, &s.table.Filter).Width(180).Placeholder("Filter")
mujica.DataTableColumnPicker(c, &s.table, cols)
})
v := mujica.DataTable(c, &s.table, rows, mujica.DataTableOptions[dataRelic, int]{
Columns: cols, Key: func(r dataRelic) int { return r.ID }, Multiple: s.multiple, Status: st,
})
v.Size(620, 360)
ui.Text(c, fmt.Sprintf("%d of %d rows shown", s.table.Rows(), len(rows))).TextColor(k.TextMuted)
if v.SortChanged() {
log(fmt.Sprintf("sort %s descending=%v", s.table.Sort.Column, s.table.Sort.Descending))
}
if v.Changed() {
id, _ := s.table.Selected()
log(fmt.Sprintf("selected row %d, %d chosen", id, s.table.Selection.Len()))
}
if v.Submitted() {
id, _ := s.table.Selected()
log(fmt.Sprintf("opened row %d", id))
}
if v.Retried() {
log("retry")
}
})
}
// dataLazy simulates children that arrive 600 ms after they are asked for.
type dataLazy struct {
kids map[string][]string
status map[string]mujica.DataStatus
due map[string]time.Time
failed map[string]bool
}
func newDataLazy() *dataLazy {
return &dataLazy{
kids: map[string][]string{"Nave": {"Pulpit", "Pews", "Font"}, "Crypt": {}, "Archive": {},
"Pulpit": nil, "Pews": nil, "Font": nil, "Sacristy": nil},
status: map[string]mujica.DataStatus{"Crypt": mujica.DataUnloaded, "Archive": mujica.DataUnloaded},
due: map[string]time.Time{}, failed: map[string]bool{},
}
}
func (d *dataLazy) load(c *ui.Context, log func(string)) func(string) {
return func(n string) {
d.status[n] = mujica.DataLoading
d.due[n] = c.Now().Add(600 * time.Millisecond)
log("load " + n)
}
}
// tick finishes due loads; the Archive fails once to show retry.
func (d *dataLazy) tick(c *ui.Context) {
for n, at := range d.due {
if c.Now().Before(at) {
c.After(at.Sub(c.Now()))
continue
}
delete(d.due, n)
if n == "Archive" && !d.failed[n] {
d.failed[n], d.status[n] = true, mujica.DataFailed
continue
}
d.status[n] = mujica.DataReady
d.kids[n] = []string{n + " north", n + " south"}
}
}
func dataTreeDemo(c *ui.Context, log func(string)) {
type state struct {
big, multiple bool
lazy *dataLazy
tree mujica.TreeState[string]
bigTree mujica.TreeState[string]
}
s := ui.Local(c.Root(), "072", func() state { return state{lazy: newDataLazy()} })
s.lazy.tick(c)
opts := mujica.TreeOptions[string]{
Roots: []string{"Nave", "Crypt", "Archive", "Sacristy"}, Children: func(n string) []string { return s.lazy.kids[n] },
Label: func(n string) string { return n }, ItemStatus: func(n string) mujica.DataStatus { return s.lazy.status[n] },
Load: s.lazy.load(c, log), Multiple: s.multiple,
}
ts := &s.tree
if s.big {
opts = dataBigTree(s.multiple)
ts = &s.bigTree
}
dataSection(c, "Tree", func() {
dataControls(c, func() {
ui.Checkbox(c, &s.big, "100,000 nodes")
ui.Checkbox(c, &s.multiple, "Multiple")
})
v := mujica.Tree(c, ts, opts)
v.Size(420, 340)
if v.Changed() {
n, _ := ts.Selected()
log(fmt.Sprintf("selected %s, %d chosen", n, ts.Selection.Len()))
}
if v.Submitted() {
n, _ := ts.Selected()
log("submitted " + n)
}
})
}
// dataBigTree has 100 roots of 999 children: 100,000 nodes, built as they open.
func dataBigTree(multiple bool) mujica.TreeOptions[string] {
roots := make([]string, 100)
for i := range roots {
roots[i] = fmt.Sprintf("Wing %d", i+1)
}
return mujica.TreeOptions[string]{
Roots: roots, Label: func(n string) string { return n }, Multiple: multiple,
Children: func(n string) []string {
if !strings.HasPrefix(n, "Wing ") || strings.Contains(n, "·") {
return nil
}
return dataWing(n)
},
}
}
var dataWings = map[string][]string{}
func dataWing(n string) []string {
if kids, ok := dataWings[n]; ok {
return kids
}
kids := make([]string, 999)
for i := range kids {
kids[i] = fmt.Sprintf("%s · cell %d", n, i+1)
}
dataWings[n] = kids
return kids
}
func dataTreeTableDemo(c *ui.Context, log func(string)) {
type state struct {
multiple bool
lazy *dataLazy
table mujica.TreeTableState[string]
}
s := ui.Local(c.Root(), "073", func() state { return state{lazy: newDataLazy()} })
s.lazy.tick(c)
size := func(n string) int { return len(n)*7 + len(s.lazy.kids[n])*13 }
text := func(n string) { ui.Text(c, n).SingleLine() }
dataSection(c, "TreeTable", func() {
dataControls(c, func() { ui.Checkbox(c, &s.multiple, "Multiple") })
v := mujica.TreeTable(c, &s.table, mujica.TreeTableOptions[string]{
Roots: []string{"Nave", "Crypt", "Archive", "Sacristy"}, Children: func(n string) []string { return s.lazy.kids[n] },
Label: func(n string) string { return n }, ItemStatus: func(n string) mujica.DataStatus { return s.lazy.status[n] },
Load: s.lazy.load(c, log), Multiple: s.multiple,
Columns: []mujica.TreeColumn[string]{
{ID: "name", Title: "Name", MinWidth: 160, Cell: text, Compare: cmp.Compare[string]},
{ID: "size", Title: "Size", Width: 90, Align: ui.End, Cell: func(n string) { ui.Text(c, fmt.Sprintf("%d m²", size(n))) },
Compare: func(a, b string) int { return cmp.Compare(size(a), size(b)) }},
{ID: "kids", Title: "Rooms", Width: 80, Align: ui.End, Cell: func(n string) { ui.Text(c, strconv.Itoa(len(s.lazy.kids[n]))) }},
},
})
v.Size(520, 320)
if v.SortChanged() {
log(fmt.Sprintf("sort %s descending=%v", s.table.Sort.Column, s.table.Sort.Descending))
}
if v.Changed() {
n, _ := s.table.Selected()
log(fmt.Sprintf("selected %s, %d chosen", n, s.table.Selection.Len()))
}
})
}
+239
View File
@@ -0,0 +1,239 @@
package main
import (
"fmt"
"strconv"
"time"
"github.com/egoist/mygo/ui"
mujica "mujicaui"
)
func dataCardDemo(c *ui.Context, log func(string)) {
type state struct {
decorated, footer bool
title string
}
s := ui.Local(c.Root(), "069", func() state { return state{footer: true, title: "Archive"} })
dataSection(c, "Card", func() {
dataControls(c, func() {
ui.TextInput(c, &s.title).Width(180).Placeholder("Title")
ui.Checkbox(c, &s.decorated, "Decorated")
ui.Checkbox(c, &s.footer, "Footer")
})
card := func(decorated bool) {
opts := mujica.CardOptions{Title: s.title, Decorated: decorated}
if s.footer {
opts.Footer = func() {
if mujica.Button(c, "Open", mujica.ButtonOptions{Variant: mujica.Secondary}).Clicked() {
log(fmt.Sprintf("open clicked (decorated=%v)", decorated))
}
}
}
mujica.Card(c, opts, func() {
ui.Text(c, "Letters of the court, sealed in wine-red wax and kept in the north vault.")
}).Width(280)
}
dataStates(c, func(state func(string, func())) {
state("Options above", func() { card(s.decorated) })
state("Plain", func() { card(false) })
state("Decorated", func() { card(true) })
})
})
}
func dataAccordionDemo(c *ui.Context, log func(string)) {
type state struct {
multiple, keepOne bool
open []string
}
s := ui.Local(c.Root(), "074", func() state { return state{open: []string{"choir"}} })
dataSection(c, "Accordion", func() {
dataControls(c, func() {
ui.Checkbox(c, &s.multiple, "Multiple")
ui.Checkbox(c, &s.keepOne, "Keep one open")
if mujica.Button(c, "Close all", mujica.ButtonOptions{Variant: mujica.Ghost}).Clicked() {
s.open = nil
log("closed all from outside")
}
})
if mujica.Accordion(c, &s.open, []mujica.AccordionItem{
{ID: "choir", Title: "Choir", Content: func() { ui.Text(c, "Twelve voices, two at the altar.") }},
{ID: "nave", Title: "Nave", Content: func() { ui.Text(c, "Pews of black oak, polished for the feast.") }},
{ID: "crypt", Title: "Crypt (disabled)", Disabled: true, Content: func() {}},
{ID: "bells", Title: "Bells", Content: func() {
mujica.Button(c, "Ring", mujica.ButtonOptions{})
}},
}, mujica.AccordionOptions{Multiple: s.multiple, KeepOne: s.keepOne}).Width(420).Changed() {
log(fmt.Sprintf("open %v", s.open))
}
})
}
func dataCollapsibleDemo(c *ui.Context, log func(string)) {
type state struct{ open, disabled bool }
s := ui.Local(c.Root(), "075", func() state { return state{} })
dataSection(c, "Collapsible", func() {
dataControls(c, func() {
if mujica.Button(c, "Open from outside", mujica.ButtonOptions{Variant: mujica.Secondary}).Clicked() {
s.open = true
log("opened from outside")
}
ui.Checkbox(c, &s.disabled, "Disabled")
})
if mujica.Collapsible(c, &s.open, mujica.CollapsibleOptions{Title: "Rites of the evening", Disabled: s.disabled}, func() {
ui.Text(c, "Candles are lit at the seventh bell; the choir enters from the north door.")
mujica.Button(c, "Light candles", mujica.ButtonOptions{Variant: mujica.Outline})
}).Changed() {
log(fmt.Sprintf("open=%v", s.open))
}
})
}
func dataTimelineDemo(c *ui.Context, log func(string)) {
type state struct{ items []mujica.TimelineItem }
s := ui.Local(c.Root(), "076", func() state {
return state{items: []mujica.TimelineItem{
{Time: "06:00", Title: "Matins sung", Content: "The choir gathered before dawn.", Status: mujica.TimelineDone},
{Time: "09:00", Title: "Relics moved", Status: mujica.TimelineActive,
Content: "The reliquaries travel from the crypt to the high altar, carried in procession by four acolytes in black and wine."},
{Time: "12:00", Title: "Bell cracked", Status: mujica.TimelineError},
{Time: "15:00", Title: "Storm warning", Status: mujica.TimelineWarning},
{Time: "18:00", Title: "Vespers"},
}}
})
dataSection(c, "Timeline", func() {
dataControls(c, func() {
if mujica.Button(c, "Add event", mujica.ButtonOptions{Variant: mujica.Secondary}).Clicked() {
s.items = append(s.items, mujica.TimelineItem{Time: fmt.Sprintf("%02d:00", 19+len(s.items)%5), Title: "Compline"})
log(fmt.Sprintf("%d events", len(s.items)))
}
})
ui.Column(c).Width(420).Children(func() { mujica.Timeline(c, s.items) })
})
}
func dataDescriptionDemo(c *ui.Context, log func(string)) {
type state struct {
columns string
width float64
}
s := ui.Local(c.Root(), "077", func() state { return state{columns: "2", width: 560} })
dataSection(c, "DescriptionList", func() {
dataControls(c, func() {
ui.Select(c, &s.columns, []string{"1", "2", "3"})
ui.Text(c, "Width")
ui.Slider(c, &s.width, 240, 720).Width(200)
})
cols, _ := strconv.Atoi(s.columns)
ui.Column(c).Width(float32(s.width)).Children(func() {
if i, ok := mujica.DescriptionList(c, []mujica.DescriptionItem{
{Label: "Archivist", Value: "Sister Agnès", Copyable: true},
{Label: "Shelf", Value: "North vault, row 7"},
{Label: "Catalogue", Value: "MS-1347-B", Copyable: true},
{Label: "Seal", Value: "Wine-red wax, cracked at the rim", Span: -1},
}, mujica.DescriptionListOptions{Columns: cols}).Copied(); ok {
log(fmt.Sprintf("copied item %d", i))
}
})
})
}
func dataStatisticDemo(c *ui.Context, log func(string)) {
type state struct {
value, change float64
precision string
}
s := ui.Local(c.Root(), "078", func() state { return state{value: 12840, change: 12.5, precision: "0"} })
dataSection(c, "Statistic", func() {
dataControls(c, func() {
ui.Text(c, "Value")
if ui.Slider(c, &s.value, 0, 100000).Width(160).Changed() {
log(fmt.Sprintf("value %.0f", s.value))
}
ui.Text(c, "Change")
ui.Slider(c, &s.change, -50, 50).Width(160)
ui.Select(c, &s.precision, []string{"0", "1", "2"})
})
p, _ := strconv.Atoi(s.precision)
change := s.change
dataStates(c, func(state func(string, func())) {
state("With change", func() {
mujica.Statistic(c, s.value, mujica.StatisticOptions{Title: "Pilgrims", Unit: "souls", Precision: p,
Change: &change, ChangeUnit: "%", ChangeNote: "vs last feast"})
})
state("Prefix", func() {
mujica.Statistic(c, s.value/1000, mujica.StatisticOptions{Title: "Tithe", Prefix: "£", Suffix: "k", Precision: p})
})
})
})
}
var dataPictures = func() []mujica.ViewerImage {
art := func(bg, fg string, shape string) *ui.SVG {
return ui.MustParseSVG([]byte(`<svg xmlns="http://www.w3.org/2000/svg" width="480" height="320"><rect width="480" height="320" fill="` + bg + `"/>` +
`<path d="` + shape + `" fill="` + fg + `"/></svg>`))
}
return []mujica.ViewerImage{
{Source: art("#AD9164", "#7D2034", "M240 40 L380 160 L240 280 L100 160 Z"), Alt: "Rose window"},
{Source: art("#211C1D", "#BDA172", "M80 280 L80 120 L240 40 L400 120 L400 280 Z"), Alt: "Chapel arch"},
{Source: art("#F2EBDD", "#651729", "M60 60 H420 V100 H60 Z M60 140 H420 V180 H60 Z M60 220 H420 V260 H60 Z"), Alt: "Choir stalls"},
}
}()
func dataViewerDemo(c *ui.Context, log func(string)) {
type state struct {
open bool
index int
}
s := ui.Local(c.Root(), "079", func() state { return state{} })
dataSection(c, "ImageViewer", func() {
k := mujica.Tokens(c)
ui.Text(c, "Choose a picture to open the viewer.").TextColor(k.TextMuted)
ui.Row(c).Gap(12).Children(func() {
for i, p := range dataPictures {
thumb := ui.ButtonBase(c).Label("Open "+p.Alt).Radius(4).Border(1, k.Border).Clip()
thumb.Children(func() { ui.Image(c, p.Source).Size(144, 96) })
if thumb.Clicked() {
s.open, s.index = true, i
log("open " + p.Alt)
}
}
})
wasOpen := s.open
if mujica.ImageViewer(c, &s.open, &s.index, dataPictures).Changed() {
log(fmt.Sprintf("image %d", s.index+1))
}
if wasOpen && !s.open {
log("closed")
}
})
}
func dataCarouselDemo(c *ui.Context, log func(string)) {
type state struct {
index int
autoplay, loop bool
interval string
}
s := ui.Local(c.Root(), "080", func() state { return state{interval: "3s"} })
dataSection(c, "Carousel", func() {
dataControls(c, func() {
ui.Checkbox(c, &s.autoplay, "Autoplay")
ui.Checkbox(c, &s.loop, "Loop")
ui.Select(c, &s.interval, []string{"2s", "3s", "5s"})
})
d, err := time.ParseDuration(s.interval)
if err != nil {
panic(err)
}
v := mujica.Carousel(c, &s.index, len(dataPictures), mujica.CarouselOptions{Label: "Windows", Autoplay: s.autoplay, Loop: s.loop, Interval: d}, func(i int) {
ui.Image(c, dataPictures[i].Source).Grow(1).Label(dataPictures[i].Alt)
})
v.Size(520, 300)
if v.Changed() {
log(fmt.Sprintf("slide %d", s.index+1))
}
})
}