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, ¤t, []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) } }) }