Files
MujicaUI/examples/gallery/pages_navigation.go
T

463 lines
19 KiB
Go

package main
import (
"fmt"
"math"
"github.com/egoist/mygo/ui"
mujica "mujicaui"
"mujicaui/icons"
)
var navigationPages = []page{
{ID: "059", Name: "Tabs", ZhName: "标签页", Category: "Navigation", Demo: navigationTabsDemo, Code: navigationTabsCode,
Keys: "Tab enters the tab list at the chosen tab. Left/Right (Up/Down when vertical) choose the next enabled tab, skipping disabled ones and wrapping; Home and End choose the first and last."},
{ID: "060", Name: "SegmentedControl", ZhName: "分段控件", Category: "Navigation", Demo: navigationSegmentedDemo, Code: navigationSegmentedCode,
Keys: "One Tab stop. Arrow keys choose the previous or next segment and wrap; Home and End choose the ends."},
{ID: "061", Name: "Breadcrumb", ZhName: "面包屑", Category: "Navigation", Demo: navigationBreadcrumbDemo, Code: navigationBreadcrumbCode,
Keys: "Tab moves through the links and the fold button; Enter or Space activates. In the fold menu, Tab moves between items and Escape closes it, returning focus to the button."},
{ID: "062", Name: "Pagination", ZhName: "分页", Category: "Navigation", Demo: navigationPaginationDemo, Code: navigationPaginationCode,
Keys: "The page buttons are one Tab stop: Left/Right move, Home/End jump to the ends, Enter or Space goes to the page. The size select opens with Up/Down; the jump field goes on Enter."},
{ID: "063", Name: "Steps", ZhName: "步骤条", Category: "Navigation", Demo: navigationStepsDemo, Code: navigationStepsCode,
Keys: "Clickable steps are one Tab stop; arrow keys move between them and Enter or Space goes to the step."},
{ID: "064", Name: "Sidebar", ZhName: "侧边栏", Category: "Navigation", Demo: navigationSidebarDemo, Code: navigationSidebarCode,
Keys: "One Tab stop. Up/Down choose the previous or next item, Home/End the ends, typing the first letters of a label jumps to it. Section and group titles open and close with a click."},
{ID: "065", Name: "NavigationMenu", ZhName: "导航菜单", Category: "Navigation", Demo: navigationMenuDemo, Code: navigationMenuCode,
Keys: "Left/Right move between top links; Down or Enter opens a group with focus on its first link, Up/Down move inside, Escape closes. Back/Forward also answer Cmd+[ / Cmd+] (Alt+Left/Right off macOS)."},
{ID: "066", Name: "Menubar", ZhName: "菜单栏", Category: "Navigation", Demo: navigationMenubarDemo, Code: navigationMenubarCode,
Keys: "Tab reaches the bar; Left/Right move between menus, Down opens one. Up/Down/Home/End move through items, Right opens a submenu (or the next menu), Left closes it, Enter/Space chooses, Escape closes one level."},
{ID: "067", Name: "Toolbar", ZhName: "工具栏", Category: "Navigation", Demo: navigationToolbarDemo, Code: navigationToolbarCode,
Keys: "One Tab stop. Left/Right move between actions, Home/End to the ends, Enter or Space runs one. Actions that do not fit move to the » menu at the end."},
{ID: "068", Name: "CommandPalette", ZhName: "命令面板", Category: "Navigation", Demo: navigationPaletteDemo, Code: navigationPaletteCode,
Keys: "Cmd+K (Ctrl+K off macOS) opens it with focus in the search. Type to filter, Up/Down choose (wrapping), Enter runs and closes, Escape closes without running."},
}
// navigationCheck is a check box that logs its changes.
func navigationCheck(c *ui.Context, v *bool, label string, log func(string)) {
was := *v
ui.Checkbox(c, v, label)
if *v != was {
log(fmt.Sprintf("%s: %v", label, *v))
}
}
// navigationSlider is a labeled whole-number slider that logs its changes.
func navigationSlider(c *ui.Context, v *float64, label string, lo, hi float64, log func(string)) {
was := math.Round(*v)
ui.Row(c).Gap(8).AlignItems(ui.Center).Children(func() {
ui.Text(c, fmt.Sprintf("%s %.0f", label, math.Round(*v)))
ui.StepSlider(c, v, lo, hi, 1).Width(160)
})
if math.Round(*v) != was {
log(fmt.Sprintf("%s: %.0f", label, math.Round(*v)))
}
}
func navigationControls(c *ui.Context, fn func()) { ui.Row(c).Wrap().Gap(16).Children(fn) }
func navigationDemo(c *ui.Context, controls, body func()) {
ui.Column(c).Gap(20).AlignItems(ui.Stretch).Children(func() {
navigationControls(c, controls)
body()
})
}
const navigationTabsCode = `mujica.Tabs(c, &tab, []mujica.Tab{
{Label: "General", Panel: func() { ui.Text(c, "General settings") }},
{Label: "Fonts", Icon: icons.Must("italic"), Panel: func() { ui.Text(c, "Fonts") }},
{Label: "Archive", Disabled: true},
}, mujica.TabsOptions{Label: "Settings"})`
func navigationTabsDemo(c *ui.Context, log func(string)) {
type state struct {
tab int
vertical, archive, many bool
}
s := ui.Local(c.Root(), "059", func() state { return state{archive: true} })
navigationDemo(c, func() {
navigationCheck(c, &s.vertical, "Vertical", log)
navigationCheck(c, &s.archive, "Disable Archive", log)
navigationCheck(c, &s.many, "Many tabs (overflow)", log)
}, func() {
names := []string{"General", "Fonts", "Archive", "Advanced"}
if s.many {
names = append(names, "Seals", "Heraldry", "Ledgers", "Envoys", "Banquets", "Chapel")
}
var tabs []mujica.Tab
for i, n := range names {
tabs = append(tabs, mujica.Tab{Label: n, Disabled: n == "Archive" && s.archive, Panel: func() {
ui.Text(c, fmt.Sprintf("The %s panel (tab %d).", n, i+1))
}})
}
tabs[1].Icon = icons.Must("italic")
s.tab = min(s.tab, len(tabs)-1)
if tabs[s.tab].Disabled {
s.tab = 0
}
ui.Column(c).Width(420).Children(func() {
if mujica.Tabs(c, &s.tab, tabs, mujica.TabsOptions{Vertical: s.vertical, Label: "Settings"}).Changed() {
log("Changed: tab " + names[s.tab])
}
})
})
}
const navigationSegmentedCode = `mujica.SegmentedControl(c, &view, []mujica.Segment{
{Label: "List", Icon: icons.Must("list"), HideLabel: true},
{Label: "Grid", Icon: icons.Must("layout-grid"), HideLabel: true},
}, mujica.SegmentedControlOptions{Label: "View"})`
func navigationSegmentedDemo(c *ui.Context, log func(string)) {
type state struct {
text, icon int
equal, iconOnly, disab bool
}
s := ui.Local(c.Root(), "060", func() state { return state{} })
navigationDemo(c, func() {
navigationCheck(c, &s.equal, "Equal widths", log)
navigationCheck(c, &s.iconOnly, "Icons only", log)
navigationCheck(c, &s.disab, "Disabled", log)
}, func() {
opts := mujica.SegmentedControlOptions{Equal: s.equal, Disabled: s.disab, Label: "Range"}
ui.Row(c).Gap(24).AlignItems(ui.Center).Children(func() {
if mujica.SegmentedControl(c, &s.text, []mujica.Segment{{Label: "Day"}, {Label: "Week"}, {Label: "Fortnight"}}, opts).Changed() {
log(fmt.Sprintf("Changed: range %d", s.text))
}
opts.Label = "View"
if mujica.SegmentedControl(c, &s.icon, []mujica.Segment{
{Label: "List", Icon: icons.Must("list"), HideLabel: s.iconOnly},
{Label: "Grid", Icon: icons.Must("layout-grid"), HideLabel: s.iconOnly},
}, opts).Changed() {
log(fmt.Sprintf("Changed: view %d", s.icon))
}
})
})
}
const navigationBreadcrumbCode = `if i, ok := mujica.Breadcrumb(c, path, mujica.BreadcrumbOptions{}).Chosen(); ok {
path = path[:i+1]
}`
var navigationBreadcrumbNames = []string{"Home", "Library", "Manuscripts", "Seventeenth century", "Correspondence of the court", "Letters to the Duchess", "Draft of the winter decree"}
func navigationBreadcrumbDemo(c *ui.Context, log func(string)) {
type state struct{ depth, max float64 }
s := ui.Local(c.Root(), "061", func() state { return state{depth: 6, max: 4} })
navigationDemo(c, func() {
navigationSlider(c, &s.depth, "Depth", 1, 7, log)
navigationSlider(c, &s.max, "MaxItems", 3, 7, log)
}, func() {
var path []mujica.BreadcrumbItem
for i, n := range navigationBreadcrumbNames[:int(s.depth)] {
it := mujica.BreadcrumbItem{Label: n}
if i == 0 {
it.Icon = icons.Must("house")
}
path = append(path, it)
}
if i, ok := mujica.Breadcrumb(c, path, mujica.BreadcrumbOptions{MaxItems: int(s.max)}).Chosen(); ok {
log("Chosen: " + path[i].Label)
s.depth = float64(i + 1)
}
})
}
const navigationPaginationCode = `if mujica.Pagination(c, &page, &size, mujica.PaginationOptions{Total: 420, PageSizes: []int{10, 20, 50}, Jump: true}).Changed() {
load(page, size)
}`
func navigationPaginationDemo(c *ui.Context, log func(string)) {
type state struct {
page, size int
total float64
sizes, jump, disab bool
}
s := ui.Local(c.Root(), "062", func() state { return state{page: 1, size: 20, total: 420, sizes: true, jump: true} })
navigationDemo(c, func() {
navigationSlider(c, &s.total, "Total", 0, 1000, log)
navigationCheck(c, &s.sizes, "Page sizes", log)
navigationCheck(c, &s.jump, "Jump", log)
navigationCheck(c, &s.disab, "Disabled", log)
}, func() {
opts := mujica.PaginationOptions{Total: int(s.total), Jump: s.jump, Disabled: s.disab}
if s.sizes {
opts.PageSizes = []int{10, 20, 50}
} else {
s.size = 20
}
if mujica.Pagination(c, &s.page, &s.size, opts).Changed() {
log(fmt.Sprintf("Changed: page %d, size %d", s.page, s.size))
}
})
}
const navigationStepsCode = `mujica.Steps(c, &current, []mujica.Step{
{Title: "Account", Clickable: true},
{Title: "Profile", Description: "Name and seal"},
{Title: "Confirm"},
}, mujica.StepsOptions{Label: "Sign-up"})`
func navigationStepsDemo(c *ui.Context, log func(string)) {
type state struct {
current int
vertical, failed, clickOK bool
}
s := ui.Local(c.Root(), "063", func() state { return state{current: 1, clickOK: true} })
steps := []mujica.Step{
{Title: "Account", Description: "Name and seal"},
{Title: "Profile", Description: "Titles and house"},
{Title: "Payment", Description: "Treasury"},
{Title: "Confirm"},
}
navigationDemo(c, func() {
navigationCheck(c, &s.vertical, "Vertical", log)
navigationCheck(c, &s.failed, "Payment failed", log)
navigationCheck(c, &s.clickOK, "Done steps clickable", log)
if mujica.Button(c, "Back", mujica.ButtonOptions{Variant: mujica.Secondary, Disabled: s.current == 0}).Clicked() {
s.current--
log(fmt.Sprintf("Back: step %d", s.current+1))
}
if mujica.Button(c, "Next", mujica.ButtonOptions{Disabled: s.current == len(steps)}).Clicked() {
s.current++
log(fmt.Sprintf("Next: step %d", s.current+1))
}
}, func() {
for i := range steps {
steps[i].Clickable = s.clickOK && i < s.current
}
steps[2].Error = s.failed
if mujica.Steps(c, &s.current, steps, mujica.StepsOptions{Vertical: s.vertical, Label: "Sign-up"}).Changed() {
log("Changed: step " + steps[s.current].Title)
}
})
}
const navigationSidebarCode = `mujica.Sidebar(c, &selected, []mujica.SidebarSection{
{Title: "Mail", Items: []mujica.SidebarItem{
{ID: "inbox", Label: "Inbox", Icon: icons.Must("inbox"), Count: 3},
{Label: "Folders", Items: []mujica.SidebarItem{{ID: "work", Label: "Work", Icon: icons.Must("folder")}}},
}},
}, mujica.SidebarOptions{})`
func navigationSidebarDemo(c *ui.Context, log func(string)) {
type state struct {
selected string
compact bool
unread float64
}
s := ui.Local(c.Root(), "064", func() state { return state{selected: "inbox", unread: 12} })
navigationDemo(c, func() {
navigationCheck(c, &s.compact, "Compact", log)
navigationSlider(c, &s.unread, "Unread", 0, 1200, log)
}, func() {
sections := []mujica.SidebarSection{
{Title: "Library", Items: []mujica.SidebarItem{
{ID: "inbox", Label: "Inbox", Icon: icons.Must("inbox"), Count: int(s.unread)},
{ID: "starred", Label: "Starred", Icon: icons.Must("star")},
{Label: "Projects", Items: []mujica.SidebarItem{
{ID: "opera", Label: "Opera", Icon: icons.Must("folder")},
{ID: "ballet", Label: "Ballet", Icon: icons.Must("folder")},
}},
}},
{Title: "System", Items: []mujica.SidebarItem{
{ID: "settings", Label: "Settings", Icon: icons.Must("settings")},
{ID: "trash", Label: "Trash", Icon: icons.Must("trash-2")},
}},
}
ui.Row(c).Height(360).Border(1, mujica.Tokens(c).Border).Radius(4).Clip().Children(func() {
if mujica.Sidebar(c, &s.selected, sections, mujica.SidebarOptions{Compact: s.compact}).Changed() {
log("Changed: " + s.selected)
}
ui.Text(c, "Showing "+s.selected).Padding(24)
})
})
}
const navigationMenuCode = `mujica.NavigationMenu(c, router, []mujica.NavigationLink{
{Label: "Home", Path: "/"},
{Label: "Library", Items: []mujica.NavigationLink{
{Label: "Poems", Path: "/library/poems", Description: "Verse of the court"},
}},
}, mujica.NavigationMenuOptions{History: true})
router.View(c, func(r *ui.Route) { ui.Text(c, r.Path()) })`
func navigationMenuDemo(c *ui.Context, log func(string)) {
type state struct {
router *ui.Router
history bool
last string
}
s := ui.Local(c.Root(), "065", func() state { return state{router: ui.NewRouter("/"), history: true, last: "/"} })
navigationDemo(c, func() {
navigationCheck(c, &s.history, "Back and forward", log)
}, func() {
mujica.NavigationMenu(c, s.router, []mujica.NavigationLink{
{Label: "Home", Path: "/", Icon: icons.Must("house")},
{Label: "Library", Items: []mujica.NavigationLink{
{Label: "Poems", Path: "/library/poems", Description: "Verse of the court"},
{Label: "Letters", Path: "/library/letters", Description: "Correspondence"},
{Label: "Archive", Items: []mujica.NavigationLink{
{Label: "Older letters", Path: "/archive/letters"},
}},
}},
{Label: "About", Path: "/about"},
}, mujica.NavigationMenuOptions{History: s.history})
if p := s.router.Path(); p != s.last {
log(fmt.Sprintf("Path: %s → %s", s.last, p))
s.last = p
}
ui.Column(c).Height(120).Children(func() {
s.router.View(c, func(r *ui.Route) {
r.Title(r.Path())
ui.Text(c, "Page "+r.Path()).Padding(16)
})
})
})
}
const navigationMenubarCode = `res := mujica.Menubar(c, []mujica.MenubarMenu{
{Label: "File", Items: []mujica.MenubarItem{
{ID: "new", Label: "New", Shortcut: mujica.MenubarShortcut{Mods: ui.Cmd, Key: ui.KeyN}},
{Label: "Export", Items: []mujica.MenubarItem{{ID: "pdf", Label: "PDF"}}},
}},
{Label: "View", Items: []mujica.MenubarItem{
{ID: "wrap", Label: "Word wrap", Kind: mujica.MenubarCheck, Checked: wrap},
}},
}, mujica.MenubarOptions{})
if id, ok := res.Chosen(); ok && id == "wrap" {
wrap = !wrap
}`
func navigationMenubarDemo(c *ui.Context, log func(string)) {
type state struct {
wrap, ruler, locked bool
zoom string
}
s := ui.Local(c.Root(), "066", func() state { return state{wrap: true, zoom: "fit"} })
navigationDemo(c, func() {
navigationCheck(c, &s.locked, "Lock editing (disables Edit items)", log)
}, func() {
menus := []mujica.MenubarMenu{
{Label: "File", Items: []mujica.MenubarItem{
{ID: "new", Label: "New", Shortcut: mujica.MenubarShortcut{Mods: ui.Cmd, Key: ui.KeyN}},
{ID: "open", Label: "Open…", Shortcut: mujica.MenubarShortcut{Mods: ui.Cmd, Key: ui.KeyO}},
{Label: "Export", Items: []mujica.MenubarItem{
{ID: "pdf", Label: "PDF"},
{ID: "png", Label: "Image"},
{Label: "Archive", Items: []mujica.MenubarItem{{ID: "zip", Label: "Zip"}, {ID: "tar", Label: "Tarball"}}},
}},
{Kind: mujica.MenubarSeparator},
{ID: "quit", Label: "Quit", Shortcut: mujica.MenubarShortcut{Mods: ui.Cmd, Key: ui.KeyQ}},
}},
{Label: "Edit", Items: []mujica.MenubarItem{
{ID: "undo", Label: "Undo", Disabled: s.locked, Shortcut: mujica.MenubarShortcut{Mods: ui.Cmd, Key: ui.KeyZ}},
{ID: "redo", Label: "Redo", Disabled: s.locked, Shortcut: mujica.MenubarShortcut{Mods: ui.Cmd | ui.Shift, Key: ui.KeyZ}},
}},
{Label: "View", Items: []mujica.MenubarItem{
{ID: "wrap", Label: "Word wrap", Kind: mujica.MenubarCheck, Checked: s.wrap},
{ID: "ruler", Label: "Ruler", Kind: mujica.MenubarCheck, Checked: s.ruler},
{Kind: mujica.MenubarSeparator},
{ID: "fit", Label: "Fit to window", Kind: mujica.MenubarRadio, Checked: s.zoom == "fit"},
{ID: "actual", Label: "Actual size", Kind: mujica.MenubarRadio, Checked: s.zoom == "actual"},
}},
}
ui.Column(c).Height(260).Border(1, mujica.Tokens(c).Border).Radius(4).Clip().Children(func() {
if id, ok := mujica.Menubar(c, menus, mujica.MenubarOptions{}).Chosen(); ok {
switch id {
case "wrap":
s.wrap = !s.wrap
case "ruler":
s.ruler = !s.ruler
case "fit", "actual":
s.zoom = id
}
log("Chosen: " + id)
}
ui.Text(c, fmt.Sprintf("Word wrap %v · Ruler %v · Zoom %s", s.wrap, s.ruler, s.zoom)).Padding(16)
})
})
}
const navigationToolbarCode = `mujica.Toolbar(c, mujica.ToolbarOptions{Label: "Format"}, func() {
mujica.ToolbarGroup(c, func() {
mujica.ToolbarButton(c, "Bold", mujica.ToolbarButtonOptions{Icon: icons.Must("bold")})
mujica.ToolbarButton(c, "Italic", mujica.ToolbarButtonOptions{Icon: icons.Must("italic")})
})
mujica.ToolbarSeparator(c)
if mujica.ToolbarButton(c, "Copy", mujica.ToolbarButtonOptions{Icon: icons.Must("copy"), ShowLabel: true}).Clicked() {
copySelection()
}
})`
func navigationToolbarDemo(c *ui.Context, log func(string)) {
type state struct {
width float64
labels bool
noDeletes bool
}
s := ui.Local(c.Root(), "067", func() state { return state{width: 520, noDeletes: true} })
navigationDemo(c, func() {
navigationSlider(c, &s.width, "Width", 120, 640, log)
navigationCheck(c, &s.labels, "Show labels", log)
navigationCheck(c, &s.noDeletes, "Disable Delete", log)
}, func() {
ui.Column(c).Width(float32(s.width)).Children(func() {
mujica.Toolbar(c, mujica.ToolbarOptions{Label: "Format"}, func() {
button := func(label, icon string, disabled bool) {
if mujica.ToolbarButton(c, label, mujica.ToolbarButtonOptions{Icon: icons.Must(icon), ShowLabel: s.labels, Disabled: disabled}).Clicked() {
log("Clicked: " + label)
}
}
mujica.ToolbarGroup(c, func() {
button("Bold", "bold", false)
button("Italic", "italic", false)
button("Underline", "underline", false)
})
mujica.ToolbarSeparator(c)
button("Copy", "copy", false)
button("Image", "image", false)
button("Delete", "trash-2", s.noDeletes)
mujica.ToolbarSeparator(c)
button("Settings", "settings", false)
})
})
})
}
const navigationPaletteCode = `res := mujica.CommandPalette(c, &open, []mujica.Command{
{ID: "new", Label: "New document", Group: "File"},
{ID: "theme", Label: "Toggle dark mode", Group: "View", Keywords: []string{"night"}},
}, mujica.CommandPaletteOptions{})
if id, ok := res.Chosen(); ok {
run(id)
}`
func navigationPaletteDemo(c *ui.Context, log func(string)) {
type state struct{ open, wasOpen bool }
s := ui.Local(c.Root(), "068", func() state { return state{} })
navigationDemo(c, func() {
if mujica.Button(c, "Open command palette", mujica.ButtonOptions{Icon: icons.Must("command")}).Clicked() {
s.open = true
}
ui.Text(c, "or press Cmd+K / Ctrl+K").TextColor(mujica.Tokens(c).TextMuted)
}, func() {
res := mujica.CommandPalette(c, &s.open, []mujica.Command{
{ID: "new", Label: "New document", Group: "File", Icon: icons.Must("file")},
{ID: "open", Label: "Open recent", Group: "File", Icon: icons.Must("folder")},
{ID: "save", Label: "Save all", Group: "File", Icon: icons.Must("file-text")},
{ID: "theme", Label: "Toggle dark mode", Group: "View", Keywords: []string{"theme", "night"}, Icon: icons.Must("moon")},
{ID: "zoom-in", Label: "Zoom in", Group: "View", Icon: icons.Must("zoom-in")},
{ID: "zoom-out", Label: "Zoom out", Group: "View", Icon: icons.Must("zoom-out")},
{ID: "settings", Label: "Open settings", Group: "Application", Keywords: []string{"preferences"}, Icon: icons.Must("settings")},
}, mujica.CommandPaletteOptions{})
if s.open != s.wasOpen {
log(fmt.Sprintf("Open: %v", s.open))
s.wasOpen = s.open
}
if id, ok := res.Chosen(); ok {
log("Run: " + id)
}
})
}