package main import ( "fmt" "time" "github.com/egoist/mygo/ui" mujica "mujicaui" ) const overlayCategory = "Overlays" var overlayPages = []page{ {ID: "081", Name: "Dialog", ZhName: "对话框", Category: overlayCategory, Demo: dialogDemo, Code: dialogCode, Keys: "Tab / Shift+Tab 在对话框内循环;Escape 关闭(可关闭此规则);关闭后焦点回到触发按钮。"}, {ID: "082", Name: "AlertDialog", ZhName: "警示对话框", Category: overlayCategory, Demo: alertDialogDemo, Code: alertDialogCode, Keys: "打开时焦点在取消;Enter 只触发聚焦的按钮,输入框中的 Enter 不确认;Escape 等同取消;点击遮罩无效。"}, {ID: "083", Name: "Drawer", ZhName: "抽屉", Category: overlayCategory, Demo: drawerDemo, Code: drawerCode, Keys: "模态:焦点限制在抽屉内并在关闭后恢复;Escape 关闭。非模态:窗口仍可操作,Escape 关闭。"}, {ID: "084", Name: "Popover", ZhName: "弹出框", Category: overlayCategory, Demo: popoverDemo, Code: popoverCode, Keys: "Tab 从锚点进入弹层;Escape 或外部点击关闭并把焦点还给锚点。"}, {ID: "085", Name: "Tooltip", ZhName: "文字提示", Category: overlayCategory, Demo: tooltipDemo, Code: tooltipCode, Keys: "键盘聚焦或悬停超过延迟后显示;Escape 隐藏;移开或失焦隐藏。"}, {ID: "086", Name: "HoverCard", ZhName: "悬停卡片", Category: overlayCategory, Demo: hoverCardDemo, Code: hoverCardCode, Keys: "键盘聚焦锚点后显示,Tab 进入卡片;Escape 关闭。"}, {ID: "087", Name: "DropdownMenu", ZhName: "下拉菜单", Category: overlayCategory, Demo: dropdownMenuDemo, Code: dropdownMenuCode, Keys: "Enter / Space / ↓ 打开;↑↓ Home End 移动;→ 打开子菜单,← 关闭;Enter 选择;Escape 关闭最上层;Tab 在任一层关闭整个菜单。"}, {ID: "088", Name: "ContextMenu", ZhName: "右键菜单", Category: overlayCategory, Demo: contextMenuDemo, Code: contextMenuCode, Keys: "右键在指针处打开;焦点在区域内时按 Menu 键或 Shift+F10 在区域下方打开;其余按键同下拉菜单。"}, {ID: "089", Name: "Popconfirm", ZhName: "气泡确认", Category: overlayCategory, Demo: popconfirmDemo, Code: popconfirmCode, Keys: "打开后焦点在确认(Danger 时在取消);Enter 触发聚焦的按钮;Escape 或外部点击取消。"}, {ID: "090", Name: "Overlay", ZhName: "覆盖层", Category: overlayCategory, Demo: overlayDemo, Code: overlayCode, Keys: "Escape 关闭;模态时焦点限制在覆盖层内,点击遮罩关闭。"}, } // overlayControls lays out a demo's parameter row. func overlayControls(c *ui.Context, fn func()) { ui.Row(c).Gap(16).Wrap().AlignItems(ui.Center).Padding(0, 0, 12, 0).Children(fn) } func overlayCheck(c *ui.Context, log func(string), v *bool, label string) { if ui.Checkbox(c, v, label).Changed() { log(fmt.Sprintf("%s = %v", label, *v)) } } const dialogCode = `open := true name := "Ophelia" view := func(c *ui.Context) { mujica.Use(c, mujica.Settings{}) if mujica.Button(c, "Rename…", mujica.ButtonOptions{}).Clicked() { open = true } mujica.Dialog(c, &open, mujica.DialogOptions{ Title: "Rename the chamber", Description: "The new name shows in every hall.", Decorated: true, Actions: func() { if mujica.Button(c, "Cancel", mujica.ButtonOptions{Variant: mujica.Secondary}).Clicked() { open = false } if mujica.Button(c, "Rename", mujica.ButtonOptions{}).Clicked() { open = false } }, }, func() { ui.TextInput(c, &name) }) } ui.Render(view, 400, 300, 1)` type dialogDemoState struct { open, decorated, keepBackdrop, keepEscape, hideClose bool name string } func dialogDemo(c *ui.Context, log func(string)) { s := ui.Local(c.Root(), "081", func() dialogDemoState { return dialogDemoState{decorated: true, name: "Ophelia"} }) overlayControls(c, func() { overlayCheck(c, log, &s.decorated, "Decorated") overlayCheck(c, log, &s.keepBackdrop, "KeepOnBackdrop") overlayCheck(c, log, &s.keepEscape, "KeepOnEscape") overlayCheck(c, log, &s.hideClose, "HideClose") }) if mujica.Button(c, "Rename…", mujica.ButtonOptions{}).Clicked() { s.open = true log("open") } was := s.open mujica.Dialog(c, &s.open, mujica.DialogOptions{ Title: "Rename the chamber", Description: "The new name shows in every hall.", Decorated: s.decorated, KeepOnBackdrop: s.keepBackdrop, KeepOnEscape: s.keepEscape, HideClose: s.hideClose, Actions: func() { if mujica.Button(c, "Cancel", mujica.ButtonOptions{Variant: mujica.Secondary}).Clicked() { s.open = false } if mujica.Button(c, "Rename", mujica.ButtonOptions{}).Clicked() { log("renamed to " + s.name) s.open = false } }, }, func() { ui.TextInput(c, &s.name) }) if was && !s.open { log("closed") } } const alertDialogCode = `open := true view := func(c *ui.Context) { mujica.Use(c, mujica.Settings{}) if mujica.Button(c, "Delete archive", mujica.ButtonOptions{Variant: mujica.Danger}).Clicked() { open = true } r := mujica.AlertDialog(c, &open, mujica.AlertDialogOptions{ Title: "Delete the archive?", Description: "Twelve letters will be destroyed.", ConfirmText: "archive", }) if r.Confirmed { fmt.Println("deleted") } } ui.Render(view, 400, 300, 1)` type alertDialogDemoState struct{ open, typed bool } func alertDialogDemo(c *ui.Context, log func(string)) { s := ui.Local(c.Root(), "082", func() alertDialogDemoState { return alertDialogDemoState{} }) overlayControls(c, func() { overlayCheck(c, log, &s.typed, "ConfirmText") }) if mujica.Button(c, "Delete archive", mujica.ButtonOptions{Variant: mujica.Danger}).Clicked() { s.open = true log("open") } opts := mujica.AlertDialogOptions{Title: "Delete the archive?", Description: "Twelve letters will be destroyed. This cannot be undone."} if s.typed { opts.ConfirmText = "archive" } r := mujica.AlertDialog(c, &s.open, opts) if r.Confirmed { log("confirmed") } if r.Canceled { log("canceled") } } const drawerCode = `open := true view := func(c *ui.Context) { mujica.Use(c, mujica.Settings{}) if mujica.Button(c, "Filters", mujica.ButtonOptions{}).Clicked() { open = true } mujica.Drawer(c, &open, mujica.DrawerOptions{Title: "Filters", Side: mujica.DrawerRight, Size: 320}, func() { ui.Text(c, "Guests are placed by rank.") }) } ui.Render(view, 400, 300, 1)` type drawerDemoState struct { open, nonModal bool side string size float64 } func drawerDemo(c *ui.Context, log func(string)) { s := ui.Local(c.Root(), "083", func() drawerDemoState { return drawerDemoState{side: "Right", size: 320} }) sides := map[string]mujica.DrawerSide{"Right": mujica.DrawerRight, "Left": mujica.DrawerLeft, "Top": mujica.DrawerTop, "Bottom": mujica.DrawerBottom} overlayControls(c, func() { if ui.Select(c, &s.side, []string{"Right", "Left", "Top", "Bottom"}).Changed() { log("side = " + s.side) } overlayCheck(c, log, &s.nonModal, "NonModal") overlaySlider(c, log, &s.size, "Size", "DIP", 120, 900, 20) }) if mujica.Button(c, "Open drawer", mujica.ButtonOptions{}).Clicked() { s.open = true log("open") } was := s.open mujica.Drawer(c, &s.open, mujica.DrawerOptions{Title: "Seating chart", Side: sides[s.side], Size: float32(s.size), NonModal: s.nonModal, Actions: func() { if mujica.Button(c, "Apply", mujica.ButtonOptions{}).Clicked() { log("applied") s.open = false } }}, func() { ui.Text(c, "Guests are placed by rank, then by arrival.").TextColor(mujica.Tokens(c).TextMuted) }) if was && !s.open { log("closed") } } const popoverCode = `open := true view := func(c *ui.Context) { mujica.Use(c, mujica.Settings{}) b := mujica.Button(c, "Guests", mujica.ButtonOptions{Variant: mujica.Secondary}) if b.Clicked() { open = !open } mujica.Popover(c, b, &open, mujica.PopoverOptions{Title: "Guest list"}, func() { ui.Text(c, "Twelve guests confirmed.") }) } ui.Render(view, 400, 300, 1)` type popoverDemoState struct { open, titled bool rsvp bool } func popoverDemo(c *ui.Context, log func(string)) { s := ui.Local(c.Root(), "084", func() popoverDemoState { return popoverDemoState{titled: true} }) overlayControls(c, func() { overlayCheck(c, log, &s.titled, "Title") }) b := mujica.Button(c, "Guests", mujica.ButtonOptions{Variant: mujica.Secondary}) if b.Clicked() { s.open = !s.open log(fmt.Sprintf("open = %v", s.open)) } opts := mujica.PopoverOptions{Label: "Guest list"} if s.titled { opts.Title = "Guest list" } was := s.open mujica.Popover(c, b, &s.open, opts, func() { ui.Text(c, "Twelve guests confirmed; three await a reply.") if ui.Checkbox(c, &s.rsvp, "Remind the others").Changed() { log(fmt.Sprintf("remind = %v", s.rsvp)) } }) if was && !s.open { log("closed") } } const tooltipCode = `view := func(c *ui.Context) { mujica.Use(c, mujica.Settings{}) b := mujica.Button(c, "Seal", mujica.ButtonOptions{}) mujica.Tooltip(c, b, "Seal the letter with wax", mujica.TooltipOptions{}) } ui.Render(view, 400, 300, 1)` func tooltipDemo(c *ui.Context, log func(string)) { delay := ui.Local(c.Root(), "085", func() float64 { return 500 }) overlayControls(c, func() { overlaySlider(c, log, delay, "Delay", "ms", 100, 1500, 100) }) opts := mujica.TooltipOptions{Delay: time.Duration(*delay) * time.Millisecond} ui.Row(c).Gap(12).Children(func() { b := mujica.Button(c, "Seal", mujica.ButtonOptions{}) if b.Clicked() { log("seal clicked") } mujica.Tooltip(c, b, "Seal the letter with wax", opts) d := mujica.Button(c, "Burn", mujica.ButtonOptions{Variant: mujica.Danger}) if d.Clicked() { log("burn clicked") } mujica.Tooltip(c, d, "Burn every copy of the letter; nothing is kept", opts) }) } const hoverCardCode = `view := func(c *ui.Context) { mujica.Use(c, mujica.Settings{}) b := mujica.Button(c, "Lady Sakiko", mujica.ButtonOptions{Variant: mujica.Ghost}) mujica.HoverCard(c, b, mujica.HoverCardOptions{}, func() { ui.Text(c, "Keyboardist of the court.") }) } ui.Render(view, 400, 300, 1)` type hoverCardDemoState struct { shown bool openDelay, closeDelay, width float64 } func hoverCardDemo(c *ui.Context, log func(string)) { s := ui.Local(c.Root(), "086", func() hoverCardDemoState { return hoverCardDemoState{openDelay: 500, closeDelay: 300, width: 280} }) overlayControls(c, func() { overlaySlider(c, log, &s.openDelay, "OpenDelay", "ms", 100, 1500, 100) overlaySlider(c, log, &s.closeDelay, "CloseDelay", "ms", 100, 1500, 100) overlaySlider(c, log, &s.width, "Width", "DIP", 200, 420, 20) }) b := mujica.Button(c, "Lady Sakiko", mujica.ButtonOptions{Variant: mujica.Ghost}) card := mujica.HoverCard(c, b, mujica.HoverCardOptions{ OpenDelay: time.Duration(s.openDelay) * time.Millisecond, CloseDelay: time.Duration(s.closeDelay) * time.Millisecond, Width: float32(s.width), }, func() { ui.Text(c, "Lady Sakiko").FontWeight(600) ui.Text(c, "Keyboardist of the court; writes the masquerade's score.").TextColor(mujica.Tokens(c).TextMuted) if mujica.Button(c, "Follow", mujica.ButtonOptions{Variant: mujica.Outline}).Clicked() { log("follow") } }) if (card != nil) != s.shown { s.shown = card != nil log(fmt.Sprintf("card shown = %v", s.shown)) } } // overlaySlider is a labeled step slider logging its value. func overlaySlider(c *ui.Context, log func(string), v *float64, name, unit string, lo, hi, step float64) { ui.Text(c, fmt.Sprintf("%s %.0f %s", name, *v, unit)) if ui.StepSlider(c, v, lo, hi, step).Width(140).Changed() { log(fmt.Sprintf("%s = %.0f %s", name, *v, unit)) } } const dropdownMenuCode = `pinned := false view := func(c *ui.Context) { mujica.Use(c, mujica.Settings{}) mujica.DropdownMenu(c, "Actions", mujica.DropdownMenuOptions{}, func(m *mujica.PopupMenu) { m.Group("Letter", func() { if m.Item("Rename", mujica.PopupMenuItemOptions{}) { fmt.Println("rename") } m.Check("Pinned", &pinned, mujica.PopupMenuItemOptions{KeepOpen: true}) }) m.Submenu("Move to", mujica.PopupMenuItemOptions{}, func(m *mujica.PopupMenu) { m.Item("Archive", mujica.PopupMenuItemOptions{}) }) m.Separator() m.Item("Burn", mujica.PopupMenuItemOptions{Disabled: true}) }) } ui.Render(view, 400, 300, 1)` type menuDemoState struct{ pinned, sealed, locked, disabled bool } // overlayMenu fills the shared demo menu, logging every choice. func overlayMenu(s *menuDemoState, log func(string)) func(m *mujica.PopupMenu) { return func(m *mujica.PopupMenu) { m.Group("Letter", func() { if m.Item("Rename", mujica.PopupMenuItemOptions{}) { log("rename") } if m.Check("Pinned", &s.pinned, mujica.PopupMenuItemOptions{KeepOpen: true}) { log(fmt.Sprintf("pinned = %v", s.pinned)) } if m.Check("Sealed", &s.sealed, mujica.PopupMenuItemOptions{}) { log(fmt.Sprintf("sealed = %v", s.sealed)) } }) m.Group("Move", func() { m.Submenu("Move to", mujica.PopupMenuItemOptions{}, func(m *mujica.PopupMenu) { for _, f := range []string{"Archive", "Drafts", "Vault"} { if m.Item(f, mujica.PopupMenuItemOptions{}) { log("move to " + f) } } }) }) m.Separator() if m.Item("Burn", mujica.PopupMenuItemOptions{Disabled: s.locked}) { log("burn") } } } func dropdownMenuDemo(c *ui.Context, log func(string)) { s := ui.Local(c.Root(), "087", func() menuDemoState { return menuDemoState{locked: true} }) overlayControls(c, func() { overlayCheck(c, log, &s.locked, "Burn disabled") overlayCheck(c, log, &s.disabled, "Disabled") }) mujica.DropdownMenu(c, "Actions", mujica.DropdownMenuOptions{Disabled: s.disabled}, overlayMenu(s, log)) } const contextMenuCode = `view := func(c *ui.Context) { mujica.Use(c, mujica.Settings{}) row := ui.Row(c).Padding(12).Children(func() { ui.Text(c, "Letter to the Countess") }) mujica.ContextMenu(c, row, mujica.ContextMenuOptions{}, func(m *mujica.PopupMenu) { if m.Item("Reply", mujica.PopupMenuItemOptions{}) { fmt.Println("reply") } m.Item("Delete", mujica.PopupMenuItemOptions{}) }) } ui.Render(view, 400, 300, 1)` func contextMenuDemo(c *ui.Context, log func(string)) { s := ui.Local(c.Root(), "088", func() menuDemoState { return menuDemoState{} }) overlayControls(c, func() { overlayCheck(c, log, &s.locked, "Burn disabled") overlayCheck(c, log, &s.disabled, "Disabled") }) k := mujica.Tokens(c) row := ui.Column(c).Height(160).FillWidth().Padding(16).Gap(8).Background(k.Surface).Border(1, k.Border).Radius(4) row.Children(func() { ui.Text(c, "Right-click anywhere here, or focus the button and press Shift+F10.").TextColor(k.TextMuted) if mujica.Button(c, "Letter to the Countess", mujica.ButtonOptions{Variant: mujica.Ghost}).Clicked() { log("letter clicked") } }) mujica.ContextMenu(c, row, mujica.ContextMenuOptions{Disabled: s.disabled}, overlayMenu(s, log)) } const popconfirmCode = `open := false view := func(c *ui.Context) { mujica.Use(c, mujica.Settings{}) b := mujica.Button(c, "Archive", mujica.ButtonOptions{Variant: mujica.Secondary}) if b.Clicked() { open = true } if mujica.Popconfirm(c, b, &open, mujica.PopconfirmOptions{Title: "Archive this letter?"}).Confirmed { fmt.Println("archived") } } ui.Render(view, 400, 300, 1)` type popconfirmDemoState struct{ open, danger bool } func popconfirmDemo(c *ui.Context, log func(string)) { s := ui.Local(c.Root(), "089", func() popconfirmDemoState { return popconfirmDemoState{} }) overlayControls(c, func() { overlayCheck(c, log, &s.danger, "Danger") }) b := mujica.Button(c, "Archive", mujica.ButtonOptions{Variant: mujica.Secondary}) if b.Clicked() { s.open = true log("open") } r := mujica.Popconfirm(c, b, &s.open, mujica.PopconfirmOptions{Title: "Archive this letter?", Description: "It leaves the inbox.", Danger: s.danger}) if r.Confirmed { log("confirmed") } if r.Canceled { log("canceled") } } const overlayCode = `open := true view := func(c *ui.Context) { mujica.Use(c, mujica.Settings{}) mujica.Overlay(c, &open, mujica.OverlayOptions{Place: mujica.OverlayBottomRight, Label: "Now playing"}, func() { ui.Text(c, "Symbol I: △") }) } ui.Render(view, 400, 300, 1)` type overlayDemoState struct { open, modal, dim bool place string } func overlayDemo(c *ui.Context, log func(string)) { s := ui.Local(c.Root(), "090", func() overlayDemoState { return overlayDemoState{place: "BottomRight"} }) names := []string{"Center", "Top", "Bottom", "Left", "Right", "TopLeft", "TopRight", "BottomLeft", "BottomRight"} place := mujica.OverlayCenter for i, n := range names { if n == s.place { place = mujica.OverlayPlace(i) } } overlayControls(c, func() { if ui.Select(c, &s.place, names).Changed() { log("place = " + s.place) } overlayCheck(c, log, &s.modal, "Modal") if s.modal { overlayCheck(c, log, &s.dim, "Dim") } }) if mujica.Button(c, "Show overlay", mujica.ButtonOptions{}).Clicked() { s.open = true log("open") } was := s.open mujica.Overlay(c, &s.open, mujica.OverlayOptions{Place: place, Modal: s.modal, Dim: s.modal && s.dim, Label: "Now playing"}, func() { ui.Text(c, "Now playing").FontWeight(600) ui.Text(c, "Symbol I: △").TextColor(mujica.Tokens(c).TextMuted) if mujica.Button(c, "Close", mujica.ButtonOptions{Variant: mujica.Secondary}).Clicked() { s.open = false } }) if was && !s.open { log("closed") } }