Split into category packages; module github.com/ZacharyZhang-NY/MujicaUI

This commit is contained in:
ZacharyZhang-NY committed 2026-10-05 16:18:48 -04:00
1 parent 5a7e921c7e
commit 22b53e9e9e
1822 files changed
+24568 -21314

No files matched your search

+26 -22
View File
@@ -7,9 +7,13 @@ import (
"github.com/egoist/mygo/ui"
mujica "mujicaui"
"mujicaui/icons"
"mujicaui/theme"
"github.com/ZacharyZhang-NY/MujicaUI/core"
"github.com/ZacharyZhang-NY/MujicaUI/feedback"
"github.com/ZacharyZhang-NY/MujicaUI/icons"
"github.com/ZacharyZhang-NY/MujicaUI/input"
"github.com/ZacharyZhang-NY/MujicaUI/layout"
"github.com/ZacharyZhang-NY/MujicaUI/navigation"
"github.com/ZacharyZhang-NY/MujicaUI/theme"
)
// event is one line of the gallery's event log.
@@ -65,16 +69,16 @@ func (g *gallery) logFor(id string) func(string) {
}
}
func (g *gallery) settings() mujica.Settings {
s := mujica.Settings{Mode: mujica.Mode(g.mode), Density: densities[g.density], Locale: mujica.EnUS}
func (g *gallery) settings() core.Settings {
s := core.Settings{Mode: core.Mode(g.mode), Density: densities[g.density], Locale: core.EnUS}
if g.zh {
s.Locale = mujica.ZhCN
s.Locale = core.ZhCN
}
return s
}
func (g *gallery) view(c *ui.Context) {
mujica.Use(c, g.settings())
core.Use(c, g.settings())
w, _ := c.Size()
if narrow := w < 900; narrow != g.autoFold {
g.autoFold, g.folded = narrow, narrow
@@ -84,7 +88,7 @@ func (g *gallery) view(c *ui.Context) {
scroll = &ui.ScrollState{}
g.scrolls[g.selected] = scroll
}
mujica.AppShell(c, &g.folded, mujica.AppShellOptions{
layout.AppShell(c, &g.folded, layout.AppShellOptions{
TitleBar: func() { g.titleBar(c) },
Sidebar: func() { g.sidebar(c) },
Content: func() { g.content(c) },
@@ -92,33 +96,33 @@ func (g *gallery) view(c *ui.Context) {
ContentScroll: scroll,
})
g.commandPalette(c)
mujica.Toaster(c)
feedback.Toaster(c)
}
func (g *gallery) titleBar(c *ui.Context) {
mujica.TitleBar(c, "MujicaUI", mujica.TitleBarOptions{Trailing: func() {
layout.TitleBar(c, "MujicaUI", layout.TitleBarOptions{Trailing: func() {
ui.Row(c).Gap(8).AlignItems(ui.Center).Children(func() {
mode := g.mode
if mujica.SegmentedControl(c, &g.mode, []mujica.Segment{
if navigation.SegmentedControl(c, &g.mode, []navigation.Segment{
{Label: g.t("System", "跟随系统"), Icon: icons.Must("settings"), HideLabel: true},
{Label: g.t("Light", "浅色"), Icon: icons.Must("sun"), HideLabel: true},
{Label: g.t("Dark", "深色"), Icon: icons.Must("moon"), HideLabel: true},
}, mujica.SegmentedControlOptions{Label: g.t("Theme", "主题")}).Changed() && mode != g.mode {
g.logFor("shell")("theme " + mujica.Mode(g.mode).String())
}, navigation.SegmentedControlOptions{Label: g.t("Theme", "主题")}).Changed() && mode != g.mode {
g.logFor("shell")("theme " + core.Mode(g.mode).String())
g.overture.switches++
}
density := g.density
if mujica.SegmentedControl(c, &g.density, []mujica.Segment{
if navigation.SegmentedControl(c, &g.density, []navigation.Segment{
{Label: g.t("Compact", "紧凑")}, {Label: g.t("Default", "默认")}, {Label: g.t("Comfortable", "宽松")},
}, mujica.SegmentedControlOptions{Label: g.t("Density", "密度")}).Changed() && density != g.density {
}, navigation.SegmentedControlOptions{Label: g.t("Density", "密度")}).Changed() && density != g.density {
g.logFor("shell")("density " + densities[g.density].String())
g.overture.switches++
}
if mujica.Button(c, g.t("中文", "English"), mujica.ButtonOptions{Variant: mujica.Ghost}).Clicked() {
if input.Button(c, g.t("中文", "English"), input.ButtonOptions{Variant: input.Ghost}).Clicked() {
g.zh = !g.zh
g.logFor("shell")(fmt.Sprintf("locale %s", g.settings().Locale))
}
if mujica.IconButton(c, icons.Must("command"), g.t("Command palette", "命令面板"), mujica.IconButtonOptions{Variant: mujica.Ghost}).Clicked() {
if input.IconButton(c, icons.Must("command"), g.t("Command palette", "命令面板"), input.IconButtonOptions{Variant: input.Ghost}).Clicked() {
g.palette = true
}
})
@@ -126,16 +130,16 @@ func (g *gallery) titleBar(c *ui.Context) {
}
func (g *gallery) statusBar(c *ui.Context) {
left := []mujica.StatusItem{
left := []layout.StatusItem{
{ID: "count", Text: fmt.Sprintf(g.t("%d components", "%d 个组件"), len(g.pages)), Icon: icons.Must("layout-grid")},
{ID: "look", Text: fmt.Sprintf("%s · %s", mujica.Mode(g.mode), densities[g.density]), Secondary: true},
{ID: "look", Text: fmt.Sprintf("%s · %s", core.Mode(g.mode), densities[g.density]), Secondary: true},
}
var right []mujica.StatusItem
var right []layout.StatusItem
if n := len(g.events); n > 0 {
e := g.events[n-1]
right = append(right, mujica.StatusItem{ID: "last", Text: e.page + " · " + e.text, Icon: icons.Must("bell"), Secondary: true})
right = append(right, layout.StatusItem{ID: "last", Text: e.page + " · " + e.text, Icon: icons.Must("bell"), Secondary: true})
}
mujica.StatusBar(c, mujica.StatusBarOptions{Left: left, Right: right})
layout.StatusBar(c, layout.StatusBarOptions{Left: left, Right: right})
}
func (g *gallery) content(c *ui.Context) {
+3 -1
View File
@@ -1,6 +1,8 @@
package main
import "github.com/egoist/mygo/ui"
import (
"github.com/egoist/mygo/ui"
)
// page is one component's gallery entry.
type page struct {
+51 -45
View File
@@ -11,9 +11,15 @@ import (
"github.com/egoist/mygo/ui"
mujica "mujicaui"
"mujicaui/icons"
"mujicaui/theme"
"github.com/ZacharyZhang-NY/MujicaUI/core"
"github.com/ZacharyZhang-NY/MujicaUI/data"
"github.com/ZacharyZhang-NY/MujicaUI/feedback"
"github.com/ZacharyZhang-NY/MujicaUI/icons"
"github.com/ZacharyZhang-NY/MujicaUI/input"
"github.com/ZacharyZhang-NY/MujicaUI/layout"
"github.com/ZacharyZhang-NY/MujicaUI/navigation"
"github.com/ZacharyZhang-NY/MujicaUI/overlay"
"github.com/ZacharyZhang-NY/MujicaUI/theme"
)
const filesID = "c-files"
@@ -30,8 +36,8 @@ type filesDemo struct {
next int
version int
query string
table mujica.DataTableState[int]
dropped []mujica.DroppedFile
table data.DataTableState[int]
dropped []input.DroppedFile
picked []string
renaming bool
renameID int
@@ -68,14 +74,14 @@ func fileSize(n int64) string {
}
}
func (g *gallery) fileColumns(c *ui.Context) []mujica.DataColumn[fileEntry] {
return []mujica.DataColumn[fileEntry]{
func (g *gallery) fileColumns(c *ui.Context) []data.DataColumn[fileEntry] {
return []data.DataColumn[fileEntry]{
{ID: "name", Title: g.t("Name", "名称"), MinWidth: 200, Sortable: true,
Text: func(f fileEntry) string { return f.Name },
Compare: func(a, b fileEntry) int { return cmp.Compare(a.Name, b.Name) },
Cell: func(f fileEntry) {
ui.Row(c).Gap(8).AlignItems(ui.Center).MinWidth(0).Children(func() {
ui.Icon(c, icons.Must("file")).Size(16, 16).TextColor(mujica.Tokens(c).TextMuted)
ui.Icon(c, icons.Must("file")).Size(16, 16).TextColor(core.Tokens(c).TextMuted)
ui.Text(c, f.Name).SingleLine()
})
}},
@@ -121,39 +127,39 @@ func (s *filesDemo) add(path string, size int64, at time.Time) bool {
func (g *gallery) filesView(c *ui.Context, log func(string)) {
s := &g.cFiles
k := mujica.Tokens(c)
k := core.Tokens(c)
cols := g.fileColumns(c)
targets := s.targets()
ui.Row(c).FillWidth().Gap(12).AlignItems(ui.Center).Children(func() {
mujica.Toolbar(c, mujica.ToolbarOptions{Label: g.t("File actions", "文件操作")}, func() {
g.fileActions(c, log, targets, func(m *mujica.PopupMenu) { g.fileMenu(m, log, targets) })
navigation.Toolbar(c, navigation.ToolbarOptions{Label: g.t("File actions", "文件操作")}, func() {
g.fileActions(c, log, targets, func(m *overlay.PopupMenu) { g.fileMenu(m, log, targets) })
}).Grow(1)
before := s.query
mujica.SearchInput(c, &s.query, mujica.SearchInputOptions{Placeholder: g.t("Filter files", "筛选文件")}).Element.Width(240)
input.SearchInput(c, &s.query, input.SearchInputOptions{Placeholder: g.t("Filter files", "筛选文件")}).Element.Width(240)
if s.query != before {
log(fmt.Sprintf("filter %q", s.query))
}
})
s.table.Filter = s.query
shown := len(mujica.DataTableRows(s.entries, cols, s.table.Sort, s.query))
shown := len(data.DataTableRows(s.entries, cols, s.table.Sort, s.query))
switch {
case len(s.entries) == 0:
if mujica.EmptyState(c, mujica.EmptyStateOptions{Kind: mujica.EmptyNoData, Title: g.t("No files", "没有文件"),
if feedback.EmptyState(c, feedback.EmptyStateOptions{Kind: feedback.EmptyNoData, Title: g.t("No files", "没有文件"),
Description: g.t("Drop files below or choose some to add them.", "将文件拖到下方,或选取文件以添加。"),
Action: g.t("Restore samples", "恢复示例")}).Action {
g.restoreFiles(log)
}
case shown == 0:
if mujica.EmptyState(c, mujica.EmptyStateOptions{Kind: mujica.EmptyNoResults, Query: s.query, Action: g.t("Clear filter", "清除筛选")}).Action {
if feedback.EmptyState(c, feedback.EmptyStateOptions{Kind: feedback.EmptyNoResults, Query: s.query, Action: g.t("Clear filter", "清除筛选")}).Action {
s.query = ""
log("filter cleared")
}
default:
v := mujica.DataTable(c, &s.table, s.entries, mujica.DataTableOptions[fileEntry, int]{
v := data.DataTable(c, &s.table, s.entries, data.DataTableOptions[fileEntry, int]{
Columns: cols, Key: func(f fileEntry) int { return f.ID }, Multiple: true, Version: s.version,
})
v.Element.FillWidth().Height(430)
mujica.ContextMenu(c, v.Element, mujica.ContextMenuOptions{}, func(m *mujica.PopupMenu) { g.fileMenu(m, log, targets) })
overlay.ContextMenu(c, v.Element, overlay.ContextMenuOptions{}, func(m *overlay.PopupMenu) { g.fileMenu(m, log, targets) })
if v.Changed() {
log(fmt.Sprintf("selection %d checked", s.table.Selection.Len()))
}
@@ -168,12 +174,12 @@ func (g *gallery) filesView(c *ui.Context, log func(string)) {
for _, f := range s.entries {
size += f.Size
}
mujica.StatusBar(c, mujica.StatusBarOptions{
Left: []mujica.StatusItem{
layout.StatusBar(c, layout.StatusBarOptions{
Left: []layout.StatusItem{
{ID: "count", Icon: icons.Must("folder"), Text: fmt.Sprintf(g.t("%d files · %s", "%d 个文件 · %s"), len(s.entries), fileSize(size))},
{ID: "shown", Text: fmt.Sprintf(g.t("%d shown", "显示 %d 个"), shown), Secondary: true},
},
Right: []mujica.StatusItem{{ID: "selected", Text: fmt.Sprintf(g.t("%d selected", "已选 %d 个"), s.table.Selection.Len())}},
Right: []layout.StatusItem{{ID: "selected", Text: fmt.Sprintf(g.t("%d selected", "已选 %d 个"), s.table.Selection.Len())}},
}).Element.Radius(theme.CardRadius).Border(theme.BorderWidth, k.Border)
g.fileAdders(c, log)
g.renameDialog(c, log)
@@ -181,39 +187,39 @@ func (g *gallery) filesView(c *ui.Context, log func(string)) {
}
// fileActions fills the toolbar; menu fills the More dropdown, shared with the context menu.
func (g *gallery) fileActions(c *ui.Context, log func(string), targets []int, menu func(m *mujica.PopupMenu)) {
mujica.ToolbarGroup(c, func() {
if mujica.ToolbarButton(c, g.t("Rename", "重命名"), mujica.ToolbarButtonOptions{Icon: icons.Must("file-text"), ShowLabel: true, Disabled: len(targets) != 1}).Clicked() {
func (g *gallery) fileActions(c *ui.Context, log func(string), targets []int, menu func(m *overlay.PopupMenu)) {
navigation.ToolbarGroup(c, func() {
if navigation.ToolbarButton(c, g.t("Rename", "重命名"), navigation.ToolbarButtonOptions{Icon: icons.Must("file-text"), ShowLabel: true, Disabled: len(targets) != 1}).Clicked() {
g.startRename(log, targets[0])
}
if mujica.ToolbarButton(c, g.t("Delete", "删除"), mujica.ToolbarButtonOptions{Icon: icons.Must("trash-2"), ShowLabel: true, Disabled: len(targets) == 0}).Clicked() {
if navigation.ToolbarButton(c, g.t("Delete", "删除"), navigation.ToolbarButtonOptions{Icon: icons.Must("trash-2"), ShowLabel: true, Disabled: len(targets) == 0}).Clicked() {
g.startDelete(log, targets)
}
})
mujica.ToolbarSeparator(c)
mujica.DropdownMenu(c, g.t("Actions", "操作"), mujica.DropdownMenuOptions{Icon: icons.Must("ellipsis")}, menu)
navigation.ToolbarSeparator(c)
overlay.DropdownMenu(c, g.t("Actions", "操作"), overlay.DropdownMenuOptions{Icon: icons.Must("ellipsis")}, menu)
}
// fileMenu holds every file action; the dropdown and the context menu share it.
func (g *gallery) fileMenu(m *mujica.PopupMenu, log func(string), targets []int) {
func (g *gallery) fileMenu(m *overlay.PopupMenu, log func(string), targets []int) {
s := &g.cFiles
if m.Item(g.t("Rename…", "重命名…"), mujica.PopupMenuItemOptions{Disabled: len(targets) != 1}) {
if m.Item(g.t("Rename…", "重命名…"), overlay.PopupMenuItemOptions{Disabled: len(targets) != 1}) {
g.startRename(log, targets[0])
}
if m.Item(g.t("Delete…", "删除…"), mujica.PopupMenuItemOptions{Disabled: len(targets) == 0}) {
if m.Item(g.t("Delete…", "删除…"), overlay.PopupMenuItemOptions{Disabled: len(targets) == 0}) {
g.startDelete(log, targets)
}
if m.Item(g.t("Select all", "全选"), mujica.PopupMenuItemOptions{Disabled: len(s.entries) == 0}) {
if m.Item(g.t("Select all", "全选"), overlay.PopupMenuItemOptions{Disabled: len(s.entries) == 0}) {
for _, f := range s.entries {
s.table.Selection.Add(f.ID)
}
log(fmt.Sprintf("selected all %d", len(s.entries)))
}
if m.Item(g.t("Clear selection", "清除选择"), mujica.PopupMenuItemOptions{Disabled: s.table.Selection.Len() == 0}) {
if m.Item(g.t("Clear selection", "清除选择"), overlay.PopupMenuItemOptions{Disabled: s.table.Selection.Len() == 0}) {
s.table.Selection.Clear()
log("selection cleared")
}
if m.Item(g.t("Restore samples", "恢复示例"), mujica.PopupMenuItemOptions{}) {
if m.Item(g.t("Restore samples", "恢复示例"), overlay.PopupMenuItemOptions{}) {
g.restoreFiles(log)
}
}
@@ -229,16 +235,16 @@ func (g *gallery) restoreFiles(log func(string)) {
// fileAdders are the drop zone and the picker; both only list files, nothing is uploaded.
func (g *gallery) fileAdders(c *ui.Context, log func(string)) {
s := &g.cFiles
mujica.Card(c, mujica.CardOptions{Title: g.t("Add files", "添加文件")}, func() {
ui.Text(c, g.t("Files are checked and listed, never read or uploaded.", "文件只做校验并列出,不会读取或上传。")).TextColor(mujica.Tokens(c).TextMuted)
if mujica.FileDropZone(c, &s.dropped, mujica.FileDropZoneOptions{MaxFiles: 8, MaxSize: 50 << 20,
data.Card(c, data.CardOptions{Title: g.t("Add files", "添加文件")}, func() {
ui.Text(c, g.t("Files are checked and listed, never read or uploaded.", "文件只做校验并列出,不会读取或上传。")).TextColor(core.Tokens(c).TextMuted)
if input.FileDropZone(c, &s.dropped, input.FileDropZoneOptions{MaxFiles: 8, MaxSize: 50 << 20,
Hint: g.t("Drop files here to list them", "将文件拖到此处以列出")}).Changed() {
for _, f := range s.dropped {
g.addFile(c, log, f.Path, f.Size, time.Now())
}
s.dropped = nil
}
p := mujica.FilePicker(c, &s.picked, mujica.FilePickerOptions{Multiple: true, Label: g.t("Choose files…", "选取文件…")})
p := input.FilePicker(c, &s.picked, input.FilePickerOptions{Multiple: true, Label: g.t("Choose files…", "选取文件…")})
if p.Canceled() {
log("picker canceled")
}
@@ -246,7 +252,7 @@ func (g *gallery) fileAdders(c *ui.Context, log func(string)) {
for _, path := range s.picked {
info, err := os.Stat(path)
if err != nil {
mujica.ShowToast(c, mujica.ToastMessage{Severity: mujica.SeverityError, Message: err.Error()})
feedback.ShowToast(c, feedback.ToastMessage{Severity: core.SeverityError, Message: err.Error()})
log("stat failed: " + err.Error())
continue
}
@@ -263,7 +269,7 @@ func (g *gallery) addFile(c *ui.Context, log func(string), path string, size int
return
}
log("skipped duplicate " + filepath.Base(path))
mujica.ShowToast(c, mujica.ToastMessage{ID: "dup-" + path, Severity: mujica.SeverityWarning,
feedback.ShowToast(c, feedback.ToastMessage{ID: "dup-" + path, Severity: core.SeverityWarning,
Message: fmt.Sprintf(g.t("%s is already listed", "%s 已在列表中"), filepath.Base(path))})
}
@@ -304,17 +310,17 @@ func (g *gallery) renameDialog(c *ui.Context, log func(string)) {
s.version++
s.renaming = false
}
mujica.Dialog(c, &s.renaming, mujica.DialogOptions{Title: g.t("Rename file", "重命名文件"), Actions: func() {
if mujica.Button(c, g.t("Cancel", "取消"), mujica.ButtonOptions{Variant: mujica.Secondary}).Clicked() {
overlay.Dialog(c, &s.renaming, overlay.DialogOptions{Title: g.t("Rename file", "重命名文件"), Actions: func() {
if input.Button(c, g.t("Cancel", "取消"), input.ButtonOptions{Variant: input.Secondary}).Clicked() {
s.renaming = false
log("rename canceled")
}
if mujica.Button(c, g.t("Rename", "重命名"), mujica.ButtonOptions{Disabled: bad != ""}).Clicked() {
if input.Button(c, g.t("Rename", "重命名"), input.ButtonOptions{Disabled: bad != ""}).Clicked() {
commit()
}
}}, func() {
mujica.FormField(c, g.t("New name", "新名称"), mujica.FormFieldOptions{Error: bad}, func() *ui.Element {
r := mujica.TextInput(c, &s.newName, mujica.TextInputOptions{Error: bad})
input.FormField(c, g.t("New name", "新名称"), input.FormFieldOptions{Error: bad}, func() *ui.Element {
r := input.TextInput(c, &s.newName, input.TextInputOptions{Error: bad})
if r.Submitted() {
commit()
}
@@ -338,7 +344,7 @@ func (g *gallery) deleteDialog(c *ui.Context, log func(string)) {
if len(s.doomed) == 1 {
desc = fmt.Sprintf(g.t("“%s” will be removed from the list.", "将从列表中移除“%s”。"), s.entry(s.doomed[0]).Name)
}
r := mujica.AlertDialog(c, &s.deleting, mujica.AlertDialogOptions{Title: g.t("Delete files?", "删除文件?"), Description: desc,
r := overlay.AlertDialog(c, &s.deleting, overlay.AlertDialogOptions{Title: g.t("Delete files?", "删除文件?"), Description: desc,
ConfirmLabel: g.t("Delete permanently", "永久删除")})
switch {
case r.Confirmed:
@@ -348,7 +354,7 @@ func (g *gallery) deleteDialog(c *ui.Context, log func(string)) {
}
s.version++
log(fmt.Sprintf("deleted %d", len(s.doomed)))
mujica.ShowToast(c, mujica.ToastMessage{ID: "files-deleted", Severity: mujica.SeveritySuccess,
feedback.ShowToast(c, feedback.ToastMessage{ID: "files-deleted", Severity: core.SeveritySuccess,
Message: fmt.Sprintf(g.t("Deleted %d files", "已删除 %d 个文件"), len(s.doomed))})
s.doomed = nil
case r.Canceled:
+24 -20
View File
@@ -7,7 +7,11 @@ import (
"github.com/egoist/mygo/ui"
mujica "mujicaui"
"github.com/ZacharyZhang-NY/MujicaUI/core"
"github.com/ZacharyZhang-NY/MujicaUI/data"
"github.com/ZacharyZhang-NY/MujicaUI/datetime"
"github.com/ZacharyZhang-NY/MujicaUI/feedback"
"github.com/ZacharyZhang-NY/MujicaUI/input"
)
const formID = "c-form"
@@ -28,10 +32,10 @@ const (
)
type formSubmitDemo struct {
st mujica.FormState
st input.FormState
name string
password string
debut mujica.Date
debut core.Date
tags []string
terms bool
failNext bool
@@ -65,7 +69,7 @@ func (g *gallery) formView(c *ui.Context, log func(string)) {
drained = true
}
}
mujica.Card(c, mujica.CardOptions{Title: g.t("Audition entry", "试演报名")}, func() {
data.Card(c, data.CardOptions{Title: g.t("Audition entry", "试演报名")}, func() {
switch s.phase {
case formSucceeded:
g.formSuccess(c, log)
@@ -79,11 +83,11 @@ func (g *gallery) formView(c *ui.Context, log func(string)) {
func (g *gallery) formEdit(c *ui.Context, log func(string)) {
s := &g.cForm
if mujica.Switch(c, &s.failNext, g.t("The server rejects the next submit", "服务器拒绝下一次提交"), mujica.SwitchOptions{Disabled: s.st.Submitting()}).Changed() {
if input.Switch(c, &s.failNext, g.t("The server rejects the next submit", "服务器拒绝下一次提交"), input.SwitchOptions{Disabled: s.st.Submitting()}).Changed() {
log(fmt.Sprintf("fail next submit %v", s.failNext))
}
r := mujica.LoadingOverlay(c, s.st.Submitting(), mujica.LoadingOverlayOptions{Label: g.t("Submitting…", "正在提交…")}, func() {
res := mujica.Form(c, &s.st, mujica.FormOptions{Horizontal: true, SubmitLabel: g.t("Submit entry", "提交报名")}, func(f *mujica.FormScope) {
r := feedback.LoadingOverlay(c, s.st.Submitting(), feedback.LoadingOverlayOptions{Label: g.t("Submitting…", "正在提交…")}, func() {
res := input.Form(c, &s.st, input.FormOptions{Horizontal: true, SubmitLabel: g.t("Submit entry", "提交报名")}, func(f *input.FormScope) {
g.formFields(c, f, log)
})
res.Element.FillWidth()
@@ -108,9 +112,9 @@ func (g *gallery) formEdit(c *ui.Context, log func(string)) {
r.Element.FillWidth()
}
func (g *gallery) formFields(c *ui.Context, f *mujica.FormScope, log func(string)) {
func (g *gallery) formFields(c *ui.Context, f *input.FormScope, log func(string)) {
s := &g.cForm
mujica.FormItem(c, f, "name", &s.name, mujica.FormItemOptions[string]{Label: g.t("Stage name", "艺名"), Required: true,
input.FormItem(c, f, "name", &s.name, input.FormItemOptions[string]{Label: g.t("Stage name", "艺名"), Required: true,
Description: g.t("Checked against the archive; “Oblivionis” is taken.", "将与档案核对;“Oblivionis”已被占用。"),
Validate: func(v string) string {
if len([]rune(strings.TrimSpace(v))) < 2 {
@@ -132,44 +136,44 @@ func (g *gallery) formFields(c *ui.Context, f *mujica.FormScope, log func(string
c.Invalidate()
}()
}}, func() *ui.Element {
return mujica.TextInput(c, &s.name, mujica.TextInputOptions{}).Input
return input.TextInput(c, &s.name, input.TextInputOptions{}).Input
})
mujica.FormItem(c, f, "password", &s.password, mujica.FormItemOptions[string]{Label: g.t("Password", "密码"), Required: true,
input.FormItem(c, f, "password", &s.password, input.FormItemOptions[string]{Label: g.t("Password", "密码"), Required: true,
Validate: func(v string) string {
if len([]rune(v)) < 8 {
return g.t("Use at least 8 characters.", "至少 8 个字符。")
}
return ""
}}, func() *ui.Element {
r := mujica.PasswordInput(c, &s.password, mujica.PasswordInputOptions{})
r := input.PasswordInput(c, &s.password, input.PasswordInputOptions{})
if r.Submitted() {
s.st.Submit()
}
return r.Input
})
mujica.FormItem(c, f, "debut", &s.debut, mujica.FormItemOptions[mujica.Date]{Label: g.t("Debut date", "首演日期"), Required: true}, func() *ui.Element {
r := mujica.DatePicker(c, &s.debut, mujica.DatePickerOptions{Clearable: true})
input.FormItem(c, f, "debut", &s.debut, input.FormItemOptions[core.Date]{Label: g.t("Debut date", "首演日期"), Required: true}, func() *ui.Element {
r := datetime.DatePicker(c, &s.debut, datetime.DatePickerOptions{Clearable: true})
if r.Changed() {
log("debut " + s.debut.Format("2006-01-02"))
}
return r.Input
})
mujica.FormItem(c, f, "roles", &s.tags, mujica.FormItemOptions[[]string]{Label: g.t("Roles", "担当"), Required: true,
input.FormItem(c, f, "roles", &s.tags, input.FormItemOptions[[]string]{Label: g.t("Roles", "担当"), Required: true,
Description: g.t("Up to 4; press Enter after each.", "最多 4 个;每个后按 Enter。")}, func() *ui.Element {
r := mujica.TagsInput(c, &s.tags, mujica.TagsInputOptions{Max: 4, Invalid: s.st.Error("roles") != "", Placeholder: g.t("keyboard, bass…", "键盘、贝斯…")})
r := input.TagsInput(c, &s.tags, input.TagsInputOptions{Max: 4, Invalid: s.st.Error("roles") != "", Placeholder: g.t("keyboard, bass…", "键盘、贝斯…")})
if r.Changed() {
log(fmt.Sprintf("roles %q", s.tags))
}
return r.Input
})
mujica.FormItem(c, f, "terms", &s.terms, mujica.FormItemOptions[bool]{Label: g.t("Terms", "条款"),
input.FormItem(c, f, "terms", &s.terms, input.FormItemOptions[bool]{Label: g.t("Terms", "条款"),
Validate: func(v bool) string {
if !v {
return g.t("Accept the terms to continue.", "请先同意条款。")
}
return ""
}}, func() *ui.Element {
e := mujica.Checkbox(c, &s.terms, g.t("I accept the covenant of the masquerade", "我接受假面舞会的盟约"), mujica.CheckboxOptions{})
e := input.Checkbox(c, &s.terms, g.t("I accept the covenant of the masquerade", "我接受假面舞会的盟约"), input.CheckboxOptions{})
if e.Changed() {
log(fmt.Sprintf("terms %v", s.terms))
}
@@ -179,7 +183,7 @@ func (g *gallery) formFields(c *ui.Context, f *mujica.FormScope, log func(string
func (g *gallery) formSuccess(c *ui.Context, log func(string)) {
s := &g.cForm
r := mujica.Result(c, mujica.ResultOptions{Kind: mujica.ResultSuccess, Title: g.t("Entry received", "报名已收到"),
r := feedback.Result(c, feedback.ResultOptions{Kind: feedback.ResultSuccess, Title: g.t("Entry received", "报名已收到"),
Description: fmt.Sprintf(g.t("%s, your debut on %s is on the ledger.", "%s,你在 %s 的首演已登记。"), s.name, s.debut.Format("2006-01-02")),
Actions: []string{g.t("New entry", "新的报名")}})
if r.Action == 0 {
@@ -190,7 +194,7 @@ func (g *gallery) formSuccess(c *ui.Context, log func(string)) {
func (g *gallery) formFailure(c *ui.Context, log func(string)) {
s := &g.cForm
r := mujica.Result(c, mujica.ResultOptions{Kind: mujica.ResultFailure, Title: g.t("Entry not sent", "报名未送达"), Description: s.failure,
r := feedback.Result(c, feedback.ResultOptions{Kind: feedback.ResultFailure, Title: g.t("Entry not sent", "报名未送达"), Description: s.failure,
Actions: []string{g.t("Retry", "重试"), g.t("Back to the form", "返回表单")}})
switch r.Action {
case 0:
+43 -39
View File
@@ -6,8 +6,12 @@ import (
"github.com/egoist/mygo/ui"
mujica "mujicaui"
"mujicaui/icons"
"github.com/ZacharyZhang-NY/MujicaUI/core"
"github.com/ZacharyZhang-NY/MujicaUI/data"
"github.com/ZacharyZhang-NY/MujicaUI/feedback"
"github.com/ZacharyZhang-NY/MujicaUI/icons"
"github.com/ZacharyZhang-NY/MujicaUI/input"
"github.com/ZacharyZhang-NY/MujicaUI/navigation"
)
const settingsID = "c-settings"
@@ -45,13 +49,13 @@ func newSettingsDemo() settingsDemo {
func (g *gallery) settingsView(c *ui.Context, log func(string)) {
s := &g.cSettings
mujica.Card(c, mujica.CardOptions{Title: g.t("Preferences", "偏好设置"), Footer: func() { g.settingsFooter(c, log) }}, func() {
tabs := []mujica.Tab{
data.Card(c, data.CardOptions{Title: g.t("Preferences", "偏好设置"), Footer: func() { g.settingsFooter(c, log) }}, func() {
tabs := []navigation.Tab{
{Label: g.t("General", "通用"), Icon: icons.Must("settings"), Panel: func() { g.settingsGeneral(c, log) }},
{Label: g.t("Appearance", "外观"), Icon: icons.Must("palette"), Panel: func() { g.settingsAppearance(c, log) }},
{Label: g.t("Privacy", "隐私"), Icon: icons.Must("lock"), Panel: func() { g.settingsPrivacy(c, log) }},
}
if mujica.Tabs(c, &s.tab, tabs, mujica.TabsOptions{Label: g.t("Preference groups", "设置分组")}).Changed() {
if navigation.Tabs(c, &s.tab, tabs, navigation.TabsOptions{Label: g.t("Preference groups", "设置分组")}).Changed() {
log("tab " + tabs[s.tab].Label)
}
})
@@ -74,36 +78,36 @@ func (g *gallery) settingsGeneral(c *ui.Context, log func(string)) {
d := &g.cSettings.draft
settingsPanel(c, func() {
bad := g.workspaceError(d.workspace)
mujica.FormField(c, g.t("Workspace name", "工作区名称"), mujica.FormFieldOptions{Required: true, Error: bad,
input.FormField(c, g.t("Workspace name", "工作区名称"), input.FormFieldOptions{Required: true, Error: bad,
Description: g.t("Shown in the title bar.", "显示在标题栏中。")}, func() *ui.Element {
r := mujica.TextInput(c, &d.workspace, mujica.TextInputOptions{Error: bad})
r := input.TextInput(c, &d.workspace, input.TextInputOptions{Error: bad})
if r.Changed() {
log(fmt.Sprintf("workspace %q", d.workspace))
}
return r.Input
})
mujica.FormField(c, g.t("Language", "语言"), mujica.FormFieldOptions{}, func() *ui.Element {
r := mujica.Select(c, &d.language, []mujica.SelectOption[string]{
input.FormField(c, g.t("Language", "语言"), input.FormFieldOptions{}, func() *ui.Element {
r := input.Select(c, &d.language, []input.SelectOption[string]{
{Value: "en", Label: "English"}, {Value: "zh", Label: "简体中文"}, {Value: "ja", Label: "日本語"},
}, mujica.SelectOptions{})
}, input.SelectOptions{})
if r.Changed() {
log("language " + d.language)
}
return r.Element
})
mujica.FormField(c, g.t("On startup", "启动时"), mujica.FormFieldOptions{}, func() *ui.Element {
r := mujica.RadioGroup(c, &d.startPage, []mujica.RadioItem[string]{
input.FormField(c, g.t("On startup", "启动时"), input.FormFieldOptions{}, func() *ui.Element {
r := input.RadioGroup(c, &d.startPage, []input.RadioItem[string]{
{Value: "blank", Label: g.t("Open a blank page", "打开空白页")},
{Value: "last", Label: g.t("Reopen the last session", "恢复上次会话")},
{Value: "home", Label: g.t("Open the home page", "打开主页")},
}, mujica.RadioGroupOptions{})
}, input.RadioGroupOptions{})
if r.Changed() {
log("startup " + d.startPage)
}
return r.Element
})
mujica.FormField(c, g.t("Login item", "登录项"), mujica.FormFieldOptions{}, func() *ui.Element {
e := mujica.Switch(c, &d.launch, g.t("Launch at login", "登录时启动"), mujica.SwitchOptions{})
input.FormField(c, g.t("Login item", "登录项"), input.FormFieldOptions{}, func() *ui.Element {
e := input.Switch(c, &d.launch, g.t("Launch at login", "登录时启动"), input.SwitchOptions{})
if e.Changed() {
log(fmt.Sprintf("launch at login %v", d.launch))
}
@@ -115,33 +119,33 @@ func (g *gallery) settingsGeneral(c *ui.Context, log func(string)) {
func (g *gallery) settingsAppearance(c *ui.Context, log func(string)) {
d := &g.cSettings.draft
settingsPanel(c, func() {
mujica.FormField(c, g.t("Theme", "主题"), mujica.FormFieldOptions{}, func() *ui.Element {
r := mujica.RadioGroup(c, &d.theme, []mujica.RadioItem[string]{
input.FormField(c, g.t("Theme", "主题"), input.FormFieldOptions{}, func() *ui.Element {
r := input.RadioGroup(c, &d.theme, []input.RadioItem[string]{
{Value: "system", Label: g.t("System", "跟随系统")}, {Value: "light", Label: g.t("Light", "浅色")}, {Value: "dark", Label: g.t("Dark", "深色")},
}, mujica.RadioGroupOptions{Horizontal: true})
}, input.RadioGroupOptions{Horizontal: true})
if r.Changed() {
log("theme " + d.theme)
}
return r.Element
})
mujica.FormField(c, g.t("Text size", "文字大小"), mujica.FormFieldOptions{}, func() *ui.Element {
r := mujica.Slider(c, &d.fontSize, mujica.SliderOptions{Min: 12, Max: 20, Step: 1, ShowValue: true,
Marks: []mujica.SliderMark{{Value: 12, Label: "12"}, {Value: 16, Label: "16"}, {Value: 20, Label: "20"}},
input.FormField(c, g.t("Text size", "文字大小"), input.FormFieldOptions{}, func() *ui.Element {
r := input.Slider(c, &d.fontSize, input.SliderOptions{Min: 12, Max: 20, Step: 1, ShowValue: true,
Marks: []input.SliderMark{{Value: 12, Label: "12"}, {Value: 16, Label: "16"}, {Value: 20, Label: "20"}},
Format: func(v float64) string { return fmt.Sprintf("%.0f pt", v) }})
if r.Changed() {
log(fmt.Sprintf("text size %.0f", d.fontSize))
}
return r.Element
})
mujica.FormField(c, g.t("Sidebar width", "侧栏宽度"), mujica.FormFieldOptions{Description: g.t("In DIPs, from 180 to 360.", "单位 DIP,180 至 360。")}, func() *ui.Element {
r := mujica.NumberInput(c, &d.sidebar, mujica.NumberInputOptions{Min: 180, Max: 360, Step: 10})
input.FormField(c, g.t("Sidebar width", "侧栏宽度"), input.FormFieldOptions{Description: g.t("In DIPs, from 180 to 360.", "单位 DIP,180 至 360。")}, func() *ui.Element {
r := input.NumberInput(c, &d.sidebar, input.NumberInputOptions{Min: 180, Max: 360, Step: 10})
if r.Changed() {
log(fmt.Sprintf("sidebar width %.0f", d.sidebar))
}
return r.Input
})
mujica.FormField(c, g.t("Motion", "动效"), mujica.FormFieldOptions{}, func() *ui.Element {
e := mujica.Switch(c, &d.reduce, g.t("Reduce motion", "减少动效"), mujica.SwitchOptions{})
input.FormField(c, g.t("Motion", "动效"), input.FormFieldOptions{}, func() *ui.Element {
e := input.Switch(c, &d.reduce, g.t("Reduce motion", "减少动效"), input.SwitchOptions{})
if e.Changed() {
log(fmt.Sprintf("reduce motion %v", d.reduce))
}
@@ -153,32 +157,32 @@ func (g *gallery) settingsAppearance(c *ui.Context, log func(string)) {
func (g *gallery) settingsPrivacy(c *ui.Context, log func(string)) {
d := &g.cSettings.draft
settingsPanel(c, func() {
mujica.FormField(c, g.t("Usage data", "使用数据"), mujica.FormFieldOptions{Description: g.t("Nothing leaves this demo.", "本示例不会发送任何数据。")}, func() *ui.Element {
e := mujica.Switch(c, &d.telemetry, g.t("Share anonymous usage data", "共享匿名使用数据"), mujica.SwitchOptions{})
input.FormField(c, g.t("Usage data", "使用数据"), input.FormFieldOptions{Description: g.t("Nothing leaves this demo.", "本示例不会发送任何数据。")}, func() *ui.Element {
e := input.Switch(c, &d.telemetry, g.t("Share anonymous usage data", "共享匿名使用数据"), input.SwitchOptions{})
if e.Changed() {
log(fmt.Sprintf("usage data %v", d.telemetry))
}
return e
})
mujica.FormField(c, g.t("Crash reports", "崩溃报告"), mujica.FormFieldOptions{}, func() *ui.Element {
e := mujica.Switch(c, &d.crashes, g.t("Send crash reports", "发送崩溃报告"), mujica.SwitchOptions{})
input.FormField(c, g.t("Crash reports", "崩溃报告"), input.FormFieldOptions{}, func() *ui.Element {
e := input.Switch(c, &d.crashes, g.t("Send crash reports", "发送崩溃报告"), input.SwitchOptions{})
if e.Changed() {
log(fmt.Sprintf("crash reports %v", d.crashes))
}
return e
})
mujica.FormField(c, g.t("Keep history", "保留历史"), mujica.FormFieldOptions{Description: g.t("Days, from 0 to 365; 0 keeps none.", "天数,0 至 365;0 表示不保留。")}, func() *ui.Element {
r := mujica.NumberInput(c, &d.history, mujica.NumberInputOptions{Min: 0, Max: 365, Step: 1})
input.FormField(c, g.t("Keep history", "保留历史"), input.FormFieldOptions{Description: g.t("Days, from 0 to 365; 0 keeps none.", "天数,0 至 365;0 表示不保留。")}, func() *ui.Element {
r := input.NumberInput(c, &d.history, input.NumberInputOptions{Min: 0, Max: 365, Step: 1})
if r.Changed() {
log(fmt.Sprintf("history %.0f days", d.history))
}
return r.Input
})
mujica.FormField(c, g.t("Lock after", "自动锁定"), mujica.FormFieldOptions{}, func() *ui.Element {
r := mujica.Select(c, &d.lock, []mujica.SelectOption[string]{
input.FormField(c, g.t("Lock after", "自动锁定"), input.FormFieldOptions{}, func() *ui.Element {
r := input.Select(c, &d.lock, []input.SelectOption[string]{
{Value: "never", Label: g.t("Never", "从不")}, {Value: "5", Label: g.t("5 minutes", "5 分钟")},
{Value: "15", Label: g.t("15 minutes", "15 分钟")}, {Value: "60", Label: g.t("1 hour", "1 小时")},
}, mujica.SelectOptions{})
}, input.SelectOptions{})
if r.Changed() {
log("lock after " + d.lock)
}
@@ -194,19 +198,19 @@ func (g *gallery) settingsFooter(c *ui.Context, log func(string)) {
invalid := g.workspaceError(s.draft.workspace) != ""
ui.Row(c).FillWidth().AlignItems(ui.Center).Gap(12).Children(func() {
if dirty {
mujica.StatusIndicator(c, mujica.StatusWarning, mujica.StatusIndicatorOptions{Label: g.t("Unsaved changes", "有未保存的更改")})
feedback.StatusIndicator(c, feedback.StatusWarning, feedback.StatusIndicatorOptions{Label: g.t("Unsaved changes", "有未保存的更改")})
} else {
mujica.StatusIndicator(c, mujica.StatusSuccess, mujica.StatusIndicatorOptions{Label: g.t("All changes saved", "更改均已保存")})
feedback.StatusIndicator(c, feedback.StatusSuccess, feedback.StatusIndicatorOptions{Label: g.t("All changes saved", "更改均已保存")})
}
ui.Box(c).Grow(1)
if mujica.Button(c, g.t("Reset", "重置"), mujica.ButtonOptions{Variant: mujica.Secondary, Disabled: s.draft == settingsInitial}).Clicked() {
if input.Button(c, g.t("Reset", "重置"), input.ButtonOptions{Variant: input.Secondary, Disabled: s.draft == settingsInitial}).Clicked() {
s.draft = settingsInitial
log("reset to initial values")
}
if mujica.Button(c, g.t("Save", "保存"), mujica.ButtonOptions{Disabled: !dirty || invalid}).Clicked() {
if input.Button(c, g.t("Save", "保存"), input.ButtonOptions{Disabled: !dirty || invalid}).Clicked() {
s.saved = s.draft
log("saved")
mujica.ShowToast(c, mujica.ToastMessage{ID: "settings-saved", Severity: mujica.SeveritySuccess, Message: g.t("Settings saved", "设置已保存")})
feedback.ShowToast(c, feedback.ToastMessage{ID: "settings-saved", Severity: core.SeveritySuccess, Message: g.t("Settings saved", "设置已保存")})
}
})
}
+16 -15
View File
@@ -5,8 +5,9 @@ import (
"github.com/egoist/mygo/ui"
mujica "mujicaui"
"mujicaui/icons"
"github.com/ZacharyZhang-NY/MujicaUI/icons"
"github.com/ZacharyZhang-NY/MujicaUI/input"
"github.com/ZacharyZhang-NY/MujicaUI/navigation"
)
const overtureID = "overture"
@@ -83,19 +84,19 @@ func (g *gallery) categoryName(cat string) string {
func (g *gallery) pageName(p page) string { return g.t(p.Name, p.ZhName) }
// sections groups the pages that match the query by category, in PRD order.
func (g *gallery) sections() []mujica.SidebarSection {
out := []mujica.SidebarSection{{Title: g.t("Overture", "序章"), Items: []mujica.SidebarItem{
func (g *gallery) sections() []navigation.SidebarSection {
out := []navigation.SidebarSection{{Title: g.t("Overture", "序章"), Items: []navigation.SidebarItem{
{ID: overtureID, Label: g.t("Overture", "序章"), Icon: icons.Must("house")},
}}}
var comps []mujica.SidebarItem
var comps []navigation.SidebarItem
q := strings.ToLower(strings.TrimSpace(g.query))
for _, cp := range compositions {
if q == "" || strings.Contains(strings.ToLower(cp.ID+" "+cp.Name+" "+cp.ZhName), q) {
comps = append(comps, mujica.SidebarItem{ID: cp.ID, Label: g.t(cp.Name, cp.ZhName), Icon: icons.Must(cp.Icon)})
comps = append(comps, navigation.SidebarItem{ID: cp.ID, Label: g.t(cp.Name, cp.ZhName), Icon: icons.Must(cp.Icon)})
}
}
if len(comps) > 0 {
out = append(out, mujica.SidebarSection{Title: g.t("Compositions", "组合示例"), Items: comps})
out = append(out, navigation.SidebarSection{Title: g.t("Compositions", "组合示例"), Items: comps})
}
index := map[string]int{}
for _, p := range g.pages {
@@ -106,9 +107,9 @@ func (g *gallery) sections() []mujica.SidebarSection {
if !ok {
i = len(out)
index[p.Category] = i
out = append(out, mujica.SidebarSection{Title: g.categoryName(p.Category)})
out = append(out, navigation.SidebarSection{Title: g.categoryName(p.Category)})
}
out[i].Items = append(out[i].Items, mujica.SidebarItem{ID: p.ID, Label: p.ID + " " + g.pageName(p)})
out[i].Items = append(out[i].Items, navigation.SidebarItem{ID: p.ID, Label: p.ID + " " + g.pageName(p)})
}
return out
}
@@ -116,7 +117,7 @@ func (g *gallery) sections() []mujica.SidebarSection {
func (g *gallery) sidebar(c *ui.Context) {
ui.Column(c).FillWidth().Gap(8).Padding(8, 8, 0, 8).Children(func() {
before := g.query
mujica.SearchInput(c, &g.query, mujica.SearchInputOptions{Placeholder: g.t("Search · 搜索", "搜索 · Search")})
input.SearchInput(c, &g.query, input.SearchInputOptions{Placeholder: g.t("Search · 搜索", "搜索 · Search")})
if g.query != before {
g.logFor("shell")("search " + g.query)
}
@@ -125,27 +126,27 @@ func (g *gallery) sidebar(c *ui.Context) {
if len(sections) == 1 && g.query != "" {
ui.Text(c, g.t("No component matches.", "没有匹配的组件。")).Padding(8, 16)
}
if mujica.Sidebar(c, &g.selected, sections, mujica.SidebarOptions{}).Changed() {
if navigation.Sidebar(c, &g.selected, sections, navigation.SidebarOptions{}).Changed() {
g.logFor("shell")("open " + g.selected)
}
}
func (g *gallery) commandPalette(c *ui.Context) {
cmds := []mujica.Command{
cmds := []navigation.Command{
{ID: overtureID, Label: g.t("Overture", "序章"), Group: g.t("Go to", "前往")},
{ID: "theme", Label: g.t("Cycle theme", "切换主题"), Group: g.t("Window", "窗口"), Keywords: []string{"dark", "light", "深色", "浅色"}},
{ID: "density", Label: g.t("Cycle density", "切换密度"), Group: g.t("Window", "窗口"), Keywords: []string{"compact", "紧凑"}},
{ID: "locale", Label: g.t("Switch to Chinese", "切换到英文"), Group: g.t("Window", "窗口"), Keywords: []string{"language", "语言"}},
}
for _, cp := range compositions {
cmds = append(cmds, mujica.Command{ID: "page:" + cp.ID, Label: g.t(cp.Name, cp.ZhName), Group: g.t("Compositions", "组合示例"),
cmds = append(cmds, navigation.Command{ID: "page:" + cp.ID, Label: g.t(cp.Name, cp.ZhName), Group: g.t("Compositions", "组合示例"),
Keywords: []string{cp.Name, cp.ZhName, "composition", "组合"}})
}
for _, p := range g.pages {
cmds = append(cmds, mujica.Command{ID: "page:" + p.ID, Label: p.ID + " " + g.pageName(p), Group: g.categoryName(p.Category),
cmds = append(cmds, navigation.Command{ID: "page:" + p.ID, Label: p.ID + " " + g.pageName(p), Group: g.categoryName(p.Category),
Keywords: []string{p.Name, p.ZhName, p.Category, categoryZh[p.Category]}})
}
id, ok := mujica.CommandPalette(c, &g.palette, cmds, mujica.CommandPaletteOptions{}).Chosen()
id, ok := navigation.CommandPalette(c, &g.palette, cmds, navigation.CommandPaletteOptions{}).Chosen()
if !ok {
return
}
+25 -21
View File
@@ -5,8 +5,12 @@ import (
"github.com/egoist/mygo/ui"
mujica "mujicaui"
"mujicaui/icons"
"github.com/ZacharyZhang-NY/MujicaUI/core"
"github.com/ZacharyZhang-NY/MujicaUI/data"
"github.com/ZacharyZhang-NY/MujicaUI/display"
"github.com/ZacharyZhang-NY/MujicaUI/icons"
"github.com/ZacharyZhang-NY/MujicaUI/input"
"github.com/ZacharyZhang-NY/MujicaUI/layout"
)
// overtureState is the continuity proof: values that must survive theme changes.
@@ -18,11 +22,11 @@ type overtureState struct {
}
func (g *gallery) overturePage(c *ui.Context) {
k := mujica.Tokens(c)
k := core.Tokens(c)
w, _ := c.Size()
mujica.Container(c, mujica.ContainerOptions{MaxWidth: 1080, Center: true}, func() {
layout.Container(c, layout.ContainerOptions{MaxWidth: 1080, Center: true}, func() {
ui.Column(c).FillWidth().Gap(32).Children(func() {
mujica.Heading(c, "MujicaUI", mujica.HeadingOptions{
display.Heading(c, "MujicaUI", display.HeadingOptions{
Subtitle: g.t("One hundred court-gothic components for MyGo native UI.", "为 MyGo 原生界面打造的一百件宫廷哥特组件。"),
Divider: true,
})
@@ -30,10 +34,10 @@ func (g *gallery) overturePage(c *ui.Context) {
"Every pixel here is drawn by Go. No HTML, no WebView. Ivory and charcoal, a wine accent, old gold on the edges.",
"这里每一个像素都由 Go 绘制。没有 HTML,没有 WebView。象牙白与炭黑,酒红为主色,旧金只落在边缘。",
)).TextColor(k.TextMuted).FillWidth()
mujica.Grid(c, mujica.GridOptions{Columns: []ui.Track{ui.Fr(1), ui.Fr(1), ui.Fr(1)}}, func() {
mujica.Statistic(c, float64(len(g.pages)), mujica.StatisticOptions{Title: g.t("Components", "组件")})
mujica.Statistic(c, 2, mujica.StatisticOptions{Title: g.t("Themes", "主题")})
mujica.Statistic(c, 3, mujica.StatisticOptions{Title: g.t("Densities", "密度")})
layout.Grid(c, layout.GridOptions{Columns: []ui.Track{ui.Fr(1), ui.Fr(1), ui.Fr(1)}}, func() {
data.Statistic(c, float64(len(g.pages)), data.StatisticOptions{Title: g.t("Components", "组件")})
data.Statistic(c, 2, data.StatisticOptions{Title: g.t("Themes", "主题")})
data.Statistic(c, 3, data.StatisticOptions{Title: g.t("Densities", "密度")})
})
g.continuity(c)
g.categoryGrid(c, w)
@@ -44,29 +48,29 @@ func (g *gallery) overturePage(c *ui.Context) {
// continuity holds live state while the user flips theme and density.
func (g *gallery) continuity(c *ui.Context) {
s := &g.overture
k := mujica.Tokens(c)
mujica.Card(c, mujica.CardOptions{Title: g.t("Proof of continuity", "连续性证明"), Decorated: true}, func() {
k := core.Tokens(c)
data.Card(c, data.CardOptions{Title: g.t("Proof of continuity", "连续性证明"), Decorated: true}, func() {
ui.Column(c).FillWidth().Gap(16).Children(func() {
ui.Text(c, g.t(
"Type a line, move the slider, then switch theme or density above, or press ⌘K. Nothing resets: not the text, not the caret, not the focus.",
"写下一行字,拖动滑块,再切换上方的主题或密度,或按 ⌘K。什么都不会重置:文字、光标、焦点都留在原处。",
)).FillWidth()
if mujica.TextInput(c, &s.note, mujica.TextInputOptions{Placeholder: g.t("A line for the court ledger", "写给宫廷账簿的一行"), Label: g.t("Ledger line", "账簿")}).Changed() {
if input.TextInput(c, &s.note, input.TextInputOptions{Placeholder: g.t("A line for the court ledger", "写给宫廷账簿的一行"), Label: g.t("Ledger line", "账簿")}).Changed() {
g.logFor(overtureID)("ledger line edited")
}
if mujica.Slider(c, &s.tempo, mujica.SliderOptions{Max: 100, Step: 1, ShowValue: true, Label: g.t("Tempo", "速度")}).Changed() {
if input.Slider(c, &s.tempo, input.SliderOptions{Max: 100, Step: 1, ShowValue: true, Label: g.t("Tempo", "速度")}).Changed() {
g.logFor(overtureID)(fmt.Sprintf("tempo %.0f", s.tempo))
}
if mujica.Checkbox(c, &s.sealed, g.t("Seal with wax", "以火漆封缄"), mujica.CheckboxOptions{}).Changed() {
if input.Checkbox(c, &s.sealed, g.t("Seal with wax", "以火漆封缄"), input.CheckboxOptions{}).Changed() {
g.logFor(overtureID)(fmt.Sprintf("sealed %v", s.sealed))
}
ui.Row(c).Gap(12).AlignItems(ui.Center).Children(func() {
if mujica.Button(c, g.t("Cycle theme", "切换主题"), mujica.ButtonOptions{Icon: icons.Must("moon")}).Clicked() {
if input.Button(c, g.t("Cycle theme", "切换主题"), input.ButtonOptions{Icon: icons.Must("moon")}).Clicked() {
g.mode = (g.mode + 1) % 3
s.switches++
g.logFor(overtureID)("theme " + mujica.Mode(g.mode).String())
g.logFor(overtureID)("theme " + core.Mode(g.mode).String())
}
if mujica.Button(c, g.t("Cycle density", "切换密度"), mujica.ButtonOptions{Variant: mujica.Secondary}).Clicked() {
if input.Button(c, g.t("Cycle density", "切换密度"), input.ButtonOptions{Variant: input.Secondary}).Clicked() {
g.density = (g.density + 1) % len(densities)
s.switches++
g.logFor(overtureID)("density " + densities[g.density].String())
@@ -98,17 +102,17 @@ func (g *gallery) categoryGrid(c *ui.Context, width float32) {
}
count[p.Category]++
}
mujica.Grid(c, mujica.GridOptions{Columns: tracks}, func() {
layout.Grid(c, layout.GridOptions{Columns: tracks}, func() {
for _, cat := range cats {
ui.Box(c).Key(cat).Children(func() {
mujica.Card(c, mujica.CardOptions{Title: g.categoryName(cat), Footer: func() {
if mujica.Button(c, g.t("Enter", "进入"), mujica.ButtonOptions{Variant: mujica.Outline}).Clicked() {
data.Card(c, data.CardOptions{Title: g.categoryName(cat), Footer: func() {
if input.Button(c, g.t("Enter", "进入"), input.ButtonOptions{Variant: input.Outline}).Clicked() {
g.selected = first[cat].ID
g.logFor(overtureID)("enter " + cat)
}
}}, func() {
ui.Text(c, fmt.Sprintf(g.t("%d components, from %s", "%d 个组件,始于 %s"), count[cat], g.pageName(first[cat]))).
TextColor(mujica.Tokens(c).TextMuted)
TextColor(core.Tokens(c).TextMuted)
})
})
}
+22 -17
View File
@@ -5,32 +5,37 @@ import (
"github.com/egoist/mygo/ui"
mujica "mujicaui"
"mujicaui/icons"
"mujicaui/theme"
"github.com/ZacharyZhang-NY/MujicaUI/core"
"github.com/ZacharyZhang-NY/MujicaUI/data"
"github.com/ZacharyZhang-NY/MujicaUI/display"
"github.com/ZacharyZhang-NY/MujicaUI/feedback"
"github.com/ZacharyZhang-NY/MujicaUI/icons"
"github.com/ZacharyZhang-NY/MujicaUI/input"
"github.com/ZacharyZhang-NY/MujicaUI/layout"
"github.com/ZacharyZhang-NY/MujicaUI/theme"
)
// componentPage shows one component: demo, code, keys and its events.
func (g *gallery) componentPage(c *ui.Context, p page) {
k := mujica.Tokens(c)
mujica.Container(c, mujica.ContainerOptions{MaxWidth: 1080, Center: true}, func() {
k := core.Tokens(c)
layout.Container(c, layout.ContainerOptions{MaxWidth: 1080, Center: true}, func() {
ui.Column(c).FillWidth().Gap(24).Children(func() {
mujica.Heading(c, g.pageName(p), mujica.HeadingOptions{
display.Heading(c, g.pageName(p), display.HeadingOptions{
Subtitle: fmt.Sprintf("%s · %s · %s", p.ID, g.t(p.ZhName, p.Name), g.categoryName(p.Category)),
Divider: true,
})
ui.Column(c).FillWidth().Gap(16).Children(func() { p.Demo(c, g.logFor(p.ID)) })
mujica.Grid(c, mujica.GridOptions{Columns: []ui.Track{ui.Fr(3), ui.Fr(2)}, Gap: mujica.SpaceL}, func() {
mujica.Card(c, mujica.CardOptions{Title: g.t("Code", "代码"), Footer: func() {
if mujica.Button(c, g.t("Copy", "复制"), mujica.ButtonOptions{Variant: mujica.Secondary, Icon: icons.Must("copy")}).Clicked() {
layout.Grid(c, layout.GridOptions{Columns: []ui.Track{ui.Fr(3), ui.Fr(2)}, Gap: layout.SpaceL}, func() {
data.Card(c, data.CardOptions{Title: g.t("Code", "代码"), Footer: func() {
if input.Button(c, g.t("Copy", "复制"), input.ButtonOptions{Variant: input.Secondary, Icon: icons.Must("copy")}).Clicked() {
c.WriteClipboard(p.Code)
g.logFor(p.ID)("code copied")
mujica.ShowToast(c, mujica.ToastMessage{ID: "copied", Severity: mujica.SeveritySuccess, Message: g.t("Code copied", "代码已复制")})
feedback.ShowToast(c, feedback.ToastMessage{ID: "copied", Severity: core.SeveritySuccess, Message: g.t("Code copied", "代码已复制")})
}
}}, func() {
ui.Text(c, p.Code).Font(theme.MonoFont).FontSize(mujica.FontSize(c, theme.CaptionSize)).Selectable().FillWidth()
ui.Text(c, p.Code).Font(theme.MonoFont).FontSize(core.FontSize(c, theme.CaptionSize)).Selectable().FillWidth()
})
mujica.Card(c, mujica.CardOptions{Title: g.t("Keyboard", "键盘")}, func() {
data.Card(c, data.CardOptions{Title: g.t("Keyboard", "键盘")}, func() {
ui.Text(c, p.Keys).TextColor(k.TextMuted).FillWidth()
})
})
@@ -41,8 +46,8 @@ func (g *gallery) componentPage(c *ui.Context, p page) {
// eventCard lists the newest events of one page.
func (g *gallery) eventCard(c *ui.Context, id string) {
k := mujica.Tokens(c)
mujica.Card(c, mujica.CardOptions{Title: g.t("Events", "事件")}, func() {
k := core.Tokens(c)
data.Card(c, data.CardOptions{Title: g.t("Events", "事件")}, func() {
shown := 0
for i := len(g.events) - 1; i >= 0 && shown < 8; i-- {
e := g.events[i]
@@ -51,7 +56,7 @@ func (g *gallery) eventCard(c *ui.Context, id string) {
}
shown++
ui.Row(c).Gap(12).Children(func() {
ui.Text(c, e.at.Format("15:04:05.000")).Font(theme.MonoFont).FontSize(mujica.FontSize(c, theme.CaptionSize)).TextColor(k.TextMuted)
ui.Text(c, e.at.Format("15:04:05.000")).Font(theme.MonoFont).FontSize(core.FontSize(c, theme.CaptionSize)).TextColor(k.TextMuted)
ui.Text(c, e.text).Grow(1)
})
}
@@ -64,9 +69,9 @@ func (g *gallery) eventCard(c *ui.Context, id string) {
// compositePage shows one composition demo above its events.
func (g *gallery) compositePage(c *ui.Context, id string, body func(c *ui.Context, log func(string))) {
cp := compositionOf(id)
mujica.Container(c, mujica.ContainerOptions{MaxWidth: 1080, Center: true}, func() {
layout.Container(c, layout.ContainerOptions{MaxWidth: 1080, Center: true}, func() {
ui.Column(c).FillWidth().Gap(24).Children(func() {
mujica.Heading(c, g.t(cp.Name, cp.ZhName), mujica.HeadingOptions{Subtitle: g.t(cp.About, cp.ZhAbout), Divider: true})
display.Heading(c, g.t(cp.Name, cp.ZhName), display.HeadingOptions{Subtitle: g.t(cp.About, cp.ZhAbout), Divider: true})
ui.Column(c).FillWidth().Gap(16).Children(func() { body(c, g.logFor(id)) })
g.eventCard(c, id)
})
+15 -13
View File
@@ -5,7 +5,9 @@ import (
"github.com/egoist/mygo/ui"
mujica "mujicaui"
"github.com/ZacharyZhang-NY/MujicaUI/account"
"github.com/ZacharyZhang-NY/MujicaUI/core"
"github.com/ZacharyZhang-NY/MujicaUI/input"
)
const accountCategory = "Settings & Account"
@@ -58,9 +60,9 @@ type accountSettingsState struct {
sound, digest, report bool
}
var accountSettingPages = []mujica.SettingsPage{{ID: "general", Label: "General"}, {ID: "privacy", Label: "Privacy"}}
var accountSettingPages = []account.SettingsPage{{ID: "general", Label: "General"}, {ID: "privacy", Label: "Privacy"}}
var accountSettingEntries = []mujica.SettingEntry{
var accountSettingEntries = []account.SettingEntry{
{ID: "sound", Page: "general", Label: "Sound", Description: "Chime on new messages.", Keywords: []string{"audio", "notification"}},
{ID: "digest", Page: "general", Label: "Weekly digest", Description: "A summary every Monday.", Keywords: []string{"email"}},
{ID: "report", Page: "privacy", Label: "Usage reports", Description: "Send anonymous usage."},
@@ -69,15 +71,15 @@ var accountSettingEntries = []mujica.SettingEntry{
// accountSettingsPage builds the demo pages shared by 461 and 462.
func accountSettingsPage(c *ui.Context, d *accountSettingsState, log func(string)) {
ui.Box(c).Width(900).Height(320).Children(func() {
if mujica.SettingsLayout(c, &d.page, accountSettingPages, mujica.SettingsLayoutOptions{}, func(id string) {
if account.SettingsLayout(c, &d.page, accountSettingPages, account.SettingsLayoutOptions{}, func(id string) {
switch id {
case "general":
mujica.SettingsSection(c, "Notifications", func() {
account.SettingsSection(c, "Notifications", func() {
accountSwitchRow(c, "sound", "Sound", "Chime on new messages.", &d.sound, log)
accountSwitchRow(c, "digest", "Weekly digest", "A summary every Monday.", &d.digest, log)
})
case "privacy":
mujica.SettingsSection(c, "Data", func() {
account.SettingsSection(c, "Data", func() {
accountSwitchRow(c, "report", "Usage reports", "Send anonymous usage.", &d.report, log)
})
}
@@ -89,8 +91,8 @@ func accountSettingsPage(c *ui.Context, d *accountSettingsState, log func(string
// accountSwitchRow is a settings row with a switch.
func accountSwitchRow(c *ui.Context, id, label, desc string, v *bool, log func(string)) {
mujica.SettingsRow(c, label, mujica.SettingsRowOptions{ID: id, Description: desc}, func() *ui.Element {
sw := mujica.Switch(c, v, "", mujica.SwitchOptions{})
account.SettingsRow(c, label, account.SettingsRowOptions{ID: id, Description: desc}, func() *ui.Element {
sw := input.Switch(c, v, "", input.SwitchOptions{})
if sw.Changed() {
log(fmt.Sprintf("%s %v", label, *v))
}
@@ -106,7 +108,7 @@ func accountSettingsDemo(c *ui.Context, log func(string)) {
func accountSearchDemo(c *ui.Context, log func(string)) {
d := ui.Local(c.Root(), "462", func() accountSettingsState { return accountSettingsState{sound: true} })
ui.Box(c).Width(320).Children(func() {
if e, ok := mujica.SettingsSearch(c, &d.query, accountSettingEntries, mujica.SettingsSearchOptions{}).Chosen(); ok {
if e, ok := account.SettingsSearch(c, &d.query, accountSettingEntries, account.SettingsSearchOptions{}).Chosen(); ok {
d.page = e.Page
log("jump to " + e.ID)
}
@@ -115,14 +117,14 @@ func accountSearchDemo(c *ui.Context, log func(string)) {
}
type accountThemeState struct {
mode mujica.Mode
mode core.Mode
disabled bool
}
func accountThemeDemo(c *ui.Context, log func(string)) {
d := ui.Local(c.Root(), "463", func() accountThemeState { return accountThemeState{} })
accountToggles(c, log, []string{"Disabled"}, &d.disabled)
if mujica.ThemeSelector(c, &d.mode, mujica.ThemeSelectorOptions{Disabled: d.disabled}).Changed() {
if account.ThemeSelector(c, &d.mode, account.ThemeSelectorOptions{Disabled: d.disabled}).Changed() {
log("theme " + d.mode.String())
}
}
@@ -135,10 +137,10 @@ type accountAccentState struct {
func accountAccentDemo(c *ui.Context, log func(string)) {
d := ui.Local(c.Root(), "464", func() accountAccentState { return accountAccentState{color: ui.Hex("#7D2034")} })
accountToggles(c, log, []string{"Disabled"}, &d.disabled)
if mujica.AccentColorPicker(c, &d.color, mujica.AccentColorPickerOptions{Disabled: d.disabled}).Changed() {
if account.AccentColorPicker(c, &d.color, account.AccentColorPickerOptions{Disabled: d.disabled}).Changed() {
log(fmt.Sprintf("accent %v", d.color))
}
mujica.Button(c, "Preview button", mujica.ButtonOptions{}).Background(d.color)
input.Button(c, "Preview button", input.ButtonOptions{}).Background(d.color)
}
func accountToggles(c *ui.Context, log func(string), labels []string, values ...*bool) {
+23 -23
View File
@@ -6,27 +6,27 @@ import (
"github.com/egoist/mygo/ui"
mujica "mujicaui"
"github.com/ZacharyZhang-NY/MujicaUI/account"
)
type accountRecorderState struct {
palette, zen mujica.Shortcut
palette, zen account.Shortcut
disabled bool
}
var accountTaken = []mujica.ShortcutBinding{{Action: "Save", Shortcut: mujica.Shortcut{Mods: ui.Cmd, Key: ui.KeyS}}}
var accountTaken = []account.ShortcutBinding{{Action: "Save", Shortcut: account.Shortcut{Mods: ui.Cmd, Key: ui.KeyS}}}
func accountRecorderDemo(c *ui.Context, log func(string)) {
d := ui.Local(c.Root(), "465", func() accountRecorderState {
return accountRecorderState{palette: mujica.Shortcut{Mods: ui.Cmd | ui.Shift, Key: ui.KeyP}}
return accountRecorderState{palette: account.Shortcut{Mods: ui.Cmd | ui.Shift, Key: ui.KeyP}}
})
accountToggles(c, log, []string{"Disabled"}, &d.disabled)
ui.Text(c, "Cmd/Ctrl+S is taken by Save.")
for _, r := range []struct {
name string
v *mujica.Shortcut
v *account.Shortcut
}{{"Command palette", &d.palette}, {"Zen mode", &d.zen}} {
if mujica.ShortcutRecorder(c, r.v, mujica.ShortcutRecorderOptions{Label: r.name, Taken: accountTaken, Disabled: d.disabled}).Changed() {
if account.ShortcutRecorder(c, r.v, account.ShortcutRecorderOptions{Label: r.name, Taken: accountTaken, Disabled: d.disabled}).Changed() {
log(fmt.Sprintf("%s = %s", r.name, r.v.String()))
}
}
@@ -36,27 +36,27 @@ type accountShortcutsState struct{ query string }
func accountShortcutsDemo(c *ui.Context, log func(string)) {
d := ui.Local(c.Root(), "466", func() accountShortcutsState { return accountShortcutsState{} })
groups := []mujica.ShortcutGroup{
{Title: "General", Bindings: []mujica.ShortcutBinding{
{Action: "Command palette", Shortcut: mujica.Shortcut{Mods: ui.Cmd | ui.Shift, Key: ui.KeyP}},
{Action: "Settings", Shortcut: mujica.Shortcut{Mods: ui.Cmd, Key: ui.KeyComma}},
{Action: "Save", Shortcut: mujica.Shortcut{Mods: ui.Cmd, Key: ui.KeyS}},
groups := []account.ShortcutGroup{
{Title: "General", Bindings: []account.ShortcutBinding{
{Action: "Command palette", Shortcut: account.Shortcut{Mods: ui.Cmd | ui.Shift, Key: ui.KeyP}},
{Action: "Settings", Shortcut: account.Shortcut{Mods: ui.Cmd, Key: ui.KeyComma}},
{Action: "Save", Shortcut: account.Shortcut{Mods: ui.Cmd, Key: ui.KeyS}},
}},
{Title: "Editing", Bindings: []mujica.ShortcutBinding{
{Action: "Duplicate line", Shortcut: mujica.Shortcut{Mods: ui.Alt | ui.Shift, Key: ui.KeyDown}},
{Action: "Toggle comment", Shortcut: mujica.Shortcut{Mods: ui.Cmd, Key: ui.KeySlash}},
{Title: "Editing", Bindings: []account.ShortcutBinding{
{Action: "Duplicate line", Shortcut: account.Shortcut{Mods: ui.Alt | ui.Shift, Key: ui.KeyDown}},
{Action: "Toggle comment", Shortcut: account.Shortcut{Mods: ui.Cmd, Key: ui.KeySlash}},
{Action: "Zen mode"},
}},
}
before := d.query
ui.Box(c).Width(480).Children(func() { mujica.KeyboardShortcutsList(c, &d.query, groups, mujica.KeyboardShortcutsListOptions{}) })
ui.Box(c).Width(480).Children(func() { account.KeyboardShortcutsList(c, &d.query, groups, account.KeyboardShortcutsListOptions{}) })
if d.query != before {
log(fmt.Sprintf("search %q", d.query))
}
}
type accountLoginState struct {
form mujica.LoginFormState
form account.LoginFormState
disabled, reject bool
attempts int
}
@@ -71,7 +71,7 @@ func accountLoginDemo(c *ui.Context, log func(string)) {
default:
}
accountToggles(c, log, []string{"Disabled", "Server rejects"}, &d.disabled, &d.reject)
mujica.LoginForm(c, &d.form, mujica.LoginFormOptions{Disabled: d.disabled, OnSubmit: func(account, password string, remember bool) {
account.LoginForm(c, &d.form, account.LoginFormOptions{Disabled: d.disabled, OnSubmit: func(account, password string, remember bool) {
// Credentials stay in the caller's sign-in request.
d.attempts++
log(fmt.Sprintf("submit %q, remember %v", account, remember))
@@ -98,8 +98,8 @@ type accountOAuthState struct {
func accountOAuthDemo(c *ui.Context, log func(string)) {
d := ui.Local(c.Root(), "468", func() accountOAuthState { return accountOAuthState{} })
accountToggles(c, log, []string{"Disabled"}, &d.disabled)
providers := []mujica.OAuthProvider{{ID: "git", Name: "Git host"}, {ID: "sso", Name: "Company SSO"}, {ID: "mail", Name: "Email link"}}
if id, ok := mujica.OAuthButtons(c, providers, mujica.OAuthButtonsOptions{Pending: d.pending, Disabled: d.disabled}).Chosen(); ok {
providers := []account.OAuthProvider{{ID: "git", Name: "Git host"}, {ID: "sso", Name: "Company SSO"}, {ID: "mail", Name: "Email link"}}
if id, ok := account.OAuthButtons(c, providers, account.OAuthButtonsOptions{Pending: d.pending, Disabled: d.disabled}).Chosen(); ok {
d.pending = id
log("continue with " + id)
}
@@ -119,7 +119,7 @@ type accountTwoFactorState struct {
func accountTwoFactorDemo(c *ui.Context, log func(string)) {
d := ui.Local(c.Root(), "469", func() accountTwoFactorState { return accountTwoFactorState{} })
accountToggles(c, log, []string{"Disabled"}, &d.disabled)
r := mujica.TwoFactorInput(c, &d.code, mujica.TwoFactorInputOptions{ResendAt: d.resendAt, Error: d.err, Disabled: d.disabled})
r := account.TwoFactorInput(c, &d.code, account.TwoFactorInputOptions{ResendAt: d.resendAt, Error: d.err, Disabled: d.disabled})
if r.Completed() {
// The code is checked here and never logged.
d.err = ""
@@ -135,17 +135,17 @@ func accountTwoFactorDemo(c *ui.Context, log func(string)) {
}
type accountProfileState struct {
saved, edit mujica.Profile
saved, edit account.Profile
saving, disabled bool
}
func accountProfileDemo(c *ui.Context, log func(string)) {
d := ui.Local(c.Root(), "470", func() accountProfileState {
p := mujica.Profile{Name: "Sakiko Togawa", Bio: "Keyboard, Ave Mujica."}
p := account.Profile{Name: "Sakiko Togawa", Bio: "Keyboard, Ave Mujica."}
return accountProfileState{saved: p, edit: p}
})
accountToggles(c, log, []string{"Saving", "Disabled"}, &d.saving, &d.disabled)
r := mujica.ProfileEditor(c, &d.edit, mujica.ProfileEditorOptions{Saved: d.saved, Saving: d.saving, Disabled: d.disabled})
r := account.ProfileEditor(c, &d.edit, account.ProfileEditorOptions{Saved: d.saved, Saving: d.saving, Disabled: d.disabled})
if r.Save() {
d.saved = d.edit
log("saved")
+64 -64
View File
@@ -4,71 +4,71 @@ package main
var accountCode = map[string]string{
"SettingsLayout": `page := ""
sound := true
pages := []mujica.SettingsPage{{ID: "general", Label: "General"}, {ID: "privacy", Label: "Privacy"}}
pages := []account.SettingsPage{{ID: "general", Label: "General"}, {ID: "privacy", Label: "Privacy"}}
view := func(c *ui.Context) {
mujica.Use(c, mujica.Settings{})
mujica.SettingsLayout(c, &page, pages, mujica.SettingsLayoutOptions{}, func(id string) {
mujica.SettingsSection(c, "Notifications", func() {
mujica.SettingsRow(c, "Sound", mujica.SettingsRowOptions{ID: "sound"}, func() *ui.Element {
return mujica.Switch(c, &sound, "", mujica.SwitchOptions{})
core.Use(c, core.Settings{})
account.SettingsLayout(c, &page, pages, account.SettingsLayoutOptions{}, func(id string) {
account.SettingsSection(c, "Notifications", func() {
account.SettingsRow(c, "Sound", account.SettingsRowOptions{ID: "sound"}, func() *ui.Element {
return input.Switch(c, &sound, "", input.SwitchOptions{})
})
})
})
}
ui.Render(view, 400, 300, 1)`,
"SettingsSearch": `query, page := "", "general"
entries := []mujica.SettingEntry{{ID: "sound", Page: "general", Label: "Sound", Keywords: []string{"audio"}}}
entries := []account.SettingEntry{{ID: "sound", Page: "general", Label: "Sound", Keywords: []string{"audio"}}}
view := func(c *ui.Context) {
mujica.Use(c, mujica.Settings{})
if e, ok := mujica.SettingsSearch(c, &query, entries, mujica.SettingsSearchOptions{}).Chosen(); ok {
core.Use(c, core.Settings{})
if e, ok := account.SettingsSearch(c, &query, entries, account.SettingsSearchOptions{}).Chosen(); ok {
page = e.Page
}
fmt.Println("page", page)
}
ui.Render(view, 400, 300, 1)`,
"ThemeSelector": `mode := mujica.System
"ThemeSelector": `mode := core.System
view := func(c *ui.Context) {
mujica.Use(c, mujica.Settings{Mode: mode})
mujica.ThemeSelector(c, &mode, mujica.ThemeSelectorOptions{})
core.Use(c, core.Settings{Mode: mode})
account.ThemeSelector(c, &mode, account.ThemeSelectorOptions{})
}
ui.Render(view, 400, 300, 1)`,
"AccentColorPicker": `accent := ui.Hex("#7D2034")
view := func(c *ui.Context) {
mujica.Use(c, mujica.Settings{})
mujica.AccentColorPicker(c, &accent, mujica.AccentColorPickerOptions{})
core.Use(c, core.Settings{})
account.AccentColorPicker(c, &accent, account.AccentColorPickerOptions{})
}
ui.Render(view, 400, 300, 1)`,
"ShortcutRecorder": `palette := mujica.Shortcut{Mods: ui.Cmd | ui.Shift, Key: ui.KeyP}
"ShortcutRecorder": `palette := account.Shortcut{Mods: ui.Cmd | ui.Shift, Key: ui.KeyP}
view := func(c *ui.Context) {
mujica.Use(c, mujica.Settings{})
mujica.ShortcutRecorder(c, &palette, mujica.ShortcutRecorderOptions{
core.Use(c, core.Settings{})
account.ShortcutRecorder(c, &palette, account.ShortcutRecorderOptions{
Label: "Command palette",
Taken: []mujica.ShortcutBinding{{Action: "Save", Shortcut: mujica.Shortcut{Mods: ui.Cmd, Key: ui.KeyS}}},
Taken: []account.ShortcutBinding{{Action: "Save", Shortcut: account.Shortcut{Mods: ui.Cmd, Key: ui.KeyS}}},
})
}
ui.Render(view, 400, 300, 1)`,
"KeyboardShortcutsList": `query := ""
groups := []mujica.ShortcutGroup{{Title: "General", Bindings: []mujica.ShortcutBinding{
{Action: "Command palette", Shortcut: mujica.Shortcut{Mods: ui.Cmd | ui.Shift, Key: ui.KeyP}},
groups := []account.ShortcutGroup{{Title: "General", Bindings: []account.ShortcutBinding{
{Action: "Command palette", Shortcut: account.Shortcut{Mods: ui.Cmd | ui.Shift, Key: ui.KeyP}},
}}}
view := func(c *ui.Context) {
mujica.Use(c, mujica.Settings{})
mujica.KeyboardShortcutsList(c, &query, groups, mujica.KeyboardShortcutsListOptions{})
core.Use(c, core.Settings{})
account.KeyboardShortcutsList(c, &query, groups, account.KeyboardShortcutsListOptions{})
}
ui.Render(view, 400, 300, 1)`,
"LoginForm": `var state mujica.LoginFormState
"LoginForm": `var state account.LoginFormState
view := func(c *ui.Context) {
mujica.Use(c, mujica.Settings{})
mujica.LoginForm(c, &state, mujica.LoginFormOptions{OnSubmit: func(account, password string, remember bool) {
core.Use(c, core.Settings{})
account.LoginForm(c, &state, account.LoginFormOptions{OnSubmit: func(account, password string, remember bool) {
// Submit credentials through the caller-provided auth service.
state.EndSubmit("")
}})
}
ui.Render(view, 400, 300, 1)`,
"OAuthButtons": `providers := []mujica.OAuthProvider{{ID: "sso", Name: "Company SSO"}}
"OAuthButtons": `providers := []account.OAuthProvider{{ID: "sso", Name: "Company SSO"}}
view := func(c *ui.Context) {
mujica.Use(c, mujica.Settings{})
if id, ok := mujica.OAuthButtons(c, providers, mujica.OAuthButtonsOptions{}).Chosen(); ok {
core.Use(c, core.Settings{})
if id, ok := account.OAuthButtons(c, providers, account.OAuthButtonsOptions{}).Chosen(); ok {
fmt.Println("start sign-in with", id)
}
}
@@ -76,97 +76,97 @@ ui.Render(view, 400, 300, 1)`,
"TwoFactorInput": `code := ""
resendAt := time.Now().Add(30 * time.Second)
view := func(c *ui.Context) {
mujica.Use(c, mujica.Settings{})
r := mujica.TwoFactorInput(c, &code, mujica.TwoFactorInputOptions{ResendAt: resendAt})
core.Use(c, core.Settings{})
r := account.TwoFactorInput(c, &code, account.TwoFactorInputOptions{ResendAt: resendAt})
if r.Completed() {
fmt.Println("verify the code")
}
}
ui.Render(view, 400, 300, 1)`,
"ProfileEditor": `saved := mujica.Profile{Name: "Sakiko", Bio: "Keyboard."}
"ProfileEditor": `saved := account.Profile{Name: "Sakiko", Bio: "Keyboard."}
profile := saved
view := func(c *ui.Context) {
mujica.Use(c, mujica.Settings{})
if mujica.ProfileEditor(c, &profile, mujica.ProfileEditorOptions{Saved: saved}).Save() {
core.Use(c, core.Settings{})
if account.ProfileEditor(c, &profile, account.ProfileEditorOptions{Saved: saved}).Save() {
saved = profile
}
}
ui.Render(view, 400, 300, 1)`,
"AccountSwitcher": `current := "a"
accounts := []mujica.Account{{ID: "a", Name: "Sakiko Togawa", Email: "sakiko@court"}, {ID: "b", Name: "Oblivionis"}}
accounts := []account.Account{{ID: "a", Name: "Sakiko Togawa", Email: "sakiko@court"}, {ID: "b", Name: "Oblivionis"}}
view := func(c *ui.Context) {
mujica.Use(c, mujica.Settings{})
if mujica.AccountSwitcher(c, &current, accounts, mujica.AccountSwitcherOptions{}).Add() {
core.Use(c, core.Settings{})
if account.AccountSwitcher(c, &current, accounts, account.AccountSwitcherOptions{}).Add() {
fmt.Println("sign in another account")
}
}
ui.Render(view, 400, 300, 1)`,
"WorkspaceSwitcher": `current := "ave"
spaces := []mujica.Workspace{{ID: "ave", Name: "Ave Mujica"}, {ID: "mygo", Name: "MyGO!!!!!"}}
spaces := []account.Workspace{{ID: "ave", Name: "Ave Mujica"}, {ID: "mygo", Name: "MyGO!!!!!"}}
view := func(c *ui.Context) {
mujica.Use(c, mujica.Settings{})
mujica.WorkspaceSwitcher(c, &current, spaces, mujica.WorkspaceSwitcherOptions{})
core.Use(c, core.Settings{})
account.WorkspaceSwitcher(c, &current, spaces, account.WorkspaceSwitcherOptions{})
}
ui.Render(view, 400, 300, 1)`,
"UserMenu": `status := mujica.UserStatusOnline
"UserMenu": `status := account.UserStatusOnline
view := func(c *ui.Context) {
mujica.Use(c, mujica.Settings{})
if mujica.UserMenu(c, &status, mujica.UserMenuOptions{Name: "Sakiko Togawa"}).SignOut() {
core.Use(c, core.Settings{})
if account.UserMenu(c, &status, account.UserMenuOptions{Name: "Sakiko Togawa"}).SignOut() {
fmt.Println("sign out")
}
}
ui.Render(view, 400, 300, 1)`,
"SessionList": `sessions := []mujica.Session{{ID: "s1", Device: "MacBook Pro", Current: true}, {ID: "s2", Device: "iPhone", Location: "Osaka"}}
"SessionList": `sessions := []account.Session{{ID: "s1", Device: "MacBook Pro", Current: true}, {ID: "s2", Device: "iPhone", Location: "Osaka"}}
view := func(c *ui.Context) {
mujica.Use(c, mujica.Settings{})
if id, ok := mujica.SessionList(c, sessions, mujica.SessionListOptions{}).Revoked(); ok {
core.Use(c, core.Settings{})
if id, ok := account.SessionList(c, sessions, account.SessionListOptions{}).Revoked(); ok {
fmt.Println("sign out", id)
}
}
ui.Render(view, 400, 300, 1)`,
"ApiKeyManager": `var state mujica.ApiKeyManagerState
keys := []mujica.APIKey{{ID: "k1", Name: "Deploy bot", Prefix: "mk_live_9f2c"}}
"ApiKeyManager": `var state account.ApiKeyManagerState
keys := []account.APIKey{{ID: "k1", Name: "Deploy bot", Prefix: "mk_live_9f2c"}}
view := func(c *ui.Context) {
mujica.Use(c, mujica.Settings{})
if name, ok := mujica.ApiKeyManager(c, &state, keys, mujica.ApiKeyManagerOptions{}).Create(); ok {
core.Use(c, core.Settings{})
if name, ok := account.ApiKeyManager(c, &state, keys, account.ApiKeyManagerOptions{}).Create(); ok {
fmt.Println("create", name)
state.Reveal("secret from your key service")
}
}
ui.Render(view, 400, 300, 1)`,
"UsageQuota": `items := []mujica.QuotaItem{{Label: "API calls", Used: 4200, Limit: 10000}, {Label: "Storage", Used: 9, Limit: 10, Unit: "GB"}}
"UsageQuota": `items := []account.QuotaItem{{Label: "API calls", Used: 4200, Limit: 10000}, {Label: "Storage", Used: 9, Limit: 10, Unit: "GB"}}
view := func(c *ui.Context) {
mujica.Use(c, mujica.Settings{})
mujica.UsageQuota(c, items, mujica.UsageQuotaOptions{ResetAt: time.Now().Add(72 * time.Hour)})
core.Use(c, core.Settings{})
account.UsageQuota(c, items, account.UsageQuotaOptions{ResetAt: time.Now().Add(72 * time.Hour)})
}
ui.Render(view, 400, 300, 1)`,
"OnboardingWizard": `step := 0
steps := []mujica.WizardStep{{Title: "Welcome"}, {Title: "Pick a theme"}, {Title: "Done"}}
steps := []account.WizardStep{{Title: "Welcome"}, {Title: "Pick a theme"}, {Title: "Done"}}
view := func(c *ui.Context) {
mujica.Use(c, mujica.Settings{})
if mujica.OnboardingWizard(c, &step, steps, mujica.OnboardingWizardOptions{}).Finished() {
core.Use(c, core.Settings{})
if account.OnboardingWizard(c, &step, steps, account.OnboardingWizardOptions{}).Finished() {
fmt.Println("onboarded")
}
}
ui.Render(view, 400, 300, 1)`,
"Coachmark": `open, step := true, 0
view := func(c *ui.Context) {
mujica.Use(c, mujica.Settings{})
compose := mujica.Button(c, "Compose", mujica.ButtonOptions{})
mujica.Coachmark(c, &open, &step, []mujica.CoachmarkStep{{Anchor: compose, Title: "Write here", Body: "Start a new letter."}})
core.Use(c, core.Settings{})
compose := input.Button(c, "Compose", input.ButtonOptions{})
account.Coachmark(c, &open, &step, []account.CoachmarkStep{{Anchor: compose, Title: "Write here", Body: "Start a new letter."}})
}
ui.Render(view, 400, 300, 1)`,
"WhatsNewDialog": `open, hide := true, false
releases := []mujica.Release{{Version: "1.1", Changes: []mujica.ReleaseChange{{Kind: mujica.ChangeNew, Text: "500 components."}}}}
releases := []account.Release{{Version: "1.1", Changes: []account.ReleaseChange{{Kind: account.ChangeNew, Text: "500 components."}}}}
view := func(c *ui.Context) {
mujica.Use(c, mujica.Settings{})
mujica.WhatsNewDialog(c, &open, &hide, releases, mujica.WhatsNewDialogOptions{})
core.Use(c, core.Settings{})
account.WhatsNewDialog(c, &open, &hide, releases, account.WhatsNewDialogOptions{})
}
ui.Render(view, 400, 300, 1)`,
"FeedbackWidget": `var state mujica.FeedbackWidgetState
"FeedbackWidget": `var state account.FeedbackWidgetState
view := func(c *ui.Context) {
mujica.Use(c, mujica.Settings{})
if f, ok := mujica.FeedbackWidget(c, &state, mujica.FeedbackWidgetOptions{}).Submitted(); ok {
core.Use(c, core.Settings{})
if f, ok := account.FeedbackWidget(c, &state, account.FeedbackWidgetOptions{}).Submitted(); ok {
fmt.Println("send", f.Kind)
state.EndSubmit("")
}
+36 -34
View File
@@ -6,33 +6,35 @@ import (
"github.com/egoist/mygo/ui"
mujica "mujicaui"
"github.com/ZacharyZhang-NY/MujicaUI/account"
"github.com/ZacharyZhang-NY/MujicaUI/core"
"github.com/ZacharyZhang-NY/MujicaUI/input"
)
type accountSwitchState struct {
account, space string
accounts []mujica.Account
accounts []account.Account
disabled bool
}
func accountSwitcherDemo(c *ui.Context, log func(string)) {
d := ui.Local(c.Root(), "471", func() accountSwitchState {
return accountSwitchState{account: "a", accounts: []mujica.Account{
return accountSwitchState{account: "a", accounts: []account.Account{
{ID: "a", Name: "Sakiko Togawa", Email: "sakiko@court"}, {ID: "b", Name: "Oblivionis", Email: "ob@mask"}}}
})
accountToggles(c, log, []string{"Disabled"}, &d.disabled)
r := mujica.AccountSwitcher(c, &d.account, d.accounts, mujica.AccountSwitcherOptions{Disabled: d.disabled})
r := account.AccountSwitcher(c, &d.account, d.accounts, account.AccountSwitcherOptions{Disabled: d.disabled})
if r.Changed() {
log("switched to " + d.account)
}
if r.Add() {
n := len(d.accounts) + 1
d.accounts = append(d.accounts, mujica.Account{ID: fmt.Sprint(n), Name: fmt.Sprintf("Guest %d", n)})
d.accounts = append(d.accounts, account.Account{ID: fmt.Sprint(n), Name: fmt.Sprintf("Guest %d", n)})
log("add account")
}
}
var accountSpaces = []mujica.Workspace{
var accountSpaces = []account.Workspace{
{ID: "ave", Name: "Ave Mujica", Detail: "5 members"}, {ID: "mygo", Name: "MyGO!!!!!", Detail: "5 members"},
{ID: "crychic", Name: "CRYCHIC", Detail: "Archived"}, {ID: "sumimi", Name: "sumimi", Detail: "2 members"},
}
@@ -40,20 +42,20 @@ var accountSpaces = []mujica.Workspace{
func accountWorkspaceDemo(c *ui.Context, log func(string)) {
d := ui.Local(c.Root(), "472", func() accountSwitchState { return accountSwitchState{space: "ave"} })
accountToggles(c, log, []string{"Disabled"}, &d.disabled)
if mujica.WorkspaceSwitcher(c, &d.space, accountSpaces, mujica.WorkspaceSwitcherOptions{Disabled: d.disabled}).Changed() {
if account.WorkspaceSwitcher(c, &d.space, accountSpaces, account.WorkspaceSwitcherOptions{Disabled: d.disabled}).Changed() {
log("workspace " + d.space)
}
}
type accountUserState struct {
status mujica.UserStatus
status account.UserStatus
disabled bool
}
func accountUserMenuDemo(c *ui.Context, log func(string)) {
d := ui.Local(c.Root(), "473", func() accountUserState { return accountUserState{} })
accountToggles(c, log, []string{"Disabled"}, &d.disabled)
r := mujica.UserMenu(c, &d.status, mujica.UserMenuOptions{Name: "Sakiko Togawa", Email: "sakiko@court", Disabled: d.disabled})
r := account.UserMenu(c, &d.status, account.UserMenuOptions{Name: "Sakiko Togawa", Email: "sakiko@court", Disabled: d.disabled})
if r.StatusChanged() {
log(fmt.Sprintf("status %d", d.status))
}
@@ -66,20 +68,20 @@ func accountUserMenuDemo(c *ui.Context, log func(string)) {
}
type accountSessionsState struct {
sessions []mujica.Session
sessions []account.Session
disabled bool
}
func accountSessionsDemo(c *ui.Context, log func(string)) {
d := ui.Local(c.Root(), "474", func() accountSessionsState {
return accountSessionsState{sessions: []mujica.Session{
return accountSessionsState{sessions: []account.Session{
{ID: "s1", Device: "MacBook Pro · Safari", Location: "Tokyo", LastActive: time.Now(), Current: true},
{ID: "s2", Device: "iPhone · App", Location: "Osaka", LastActive: time.Now().Add(-26 * time.Hour)},
{ID: "s3", Device: "Windows · Edge", Location: "Kyoto"},
}}
})
accountToggles(c, log, []string{"Disabled"}, &d.disabled)
r := mujica.SessionList(c, d.sessions, mujica.SessionListOptions{Disabled: d.disabled})
r := account.SessionList(c, d.sessions, account.SessionListOptions{Disabled: d.disabled})
if id, ok := r.Revoked(); ok {
for i, s := range d.sessions {
if s.ID == id {
@@ -96,17 +98,17 @@ func accountSessionsDemo(c *ui.Context, log func(string)) {
}
type accountKeysState struct {
state mujica.ApiKeyManagerState
keys []mujica.APIKey
state account.ApiKeyManagerState
keys []account.APIKey
disabled, reject bool
}
func accountKeysDemo(c *ui.Context, log func(string)) {
d := ui.Local(c.Root(), "475", func() accountKeysState {
return accountKeysState{keys: []mujica.APIKey{{ID: "k1", Name: "Deploy bot", Prefix: "mk_live_9f2c", Created: time.Now().AddDate(0, -1, 0)}}}
return accountKeysState{keys: []account.APIKey{{ID: "k1", Name: "Deploy bot", Prefix: "mk_live_9f2c", Created: time.Now().AddDate(0, -1, 0)}}}
})
accountToggles(c, log, []string{"Disabled", "Create fails"}, &d.disabled, &d.reject)
r := mujica.ApiKeyManager(c, &d.state, d.keys, mujica.ApiKeyManagerOptions{Disabled: d.disabled})
r := account.ApiKeyManager(c, &d.state, d.keys, account.ApiKeyManagerOptions{Disabled: d.disabled})
if name, ok := r.Create(); ok {
if d.reject {
d.state.Fail("Key limit reached.")
@@ -116,7 +118,7 @@ func accountKeysDemo(c *ui.Context, log func(string)) {
// The caller supplies the generated key.
n := len(d.keys) + 1
prefix := fmt.Sprintf("mk_live_%04x", n*7919)
d.keys = append(d.keys, mujica.APIKey{ID: fmt.Sprint("k", n), Name: name, Prefix: prefix, Created: time.Now()})
d.keys = append(d.keys, account.APIKey{ID: fmt.Sprint("k", n), Name: name, Prefix: prefix, Created: time.Now()})
d.state.Reveal(fmt.Sprintf("%s_%x", prefix, time.Now().UnixNano()))
log("created key " + name)
}
@@ -135,18 +137,18 @@ type accountQuotaState struct{ calls float64 }
func accountQuotaDemo(c *ui.Context, log func(string)) {
d := ui.Local(c.Root(), "476", func() accountQuotaState { return accountQuotaState{calls: 4200} })
if mujica.Slider(c, &d.calls, mujica.SliderOptions{Min: 0, Max: 12000, Step: 100, Label: "API calls used", ShowValue: true}).Changed() {
if input.Slider(c, &d.calls, input.SliderOptions{Min: 0, Max: 12000, Step: 100, Label: "API calls used", ShowValue: true}).Changed() {
log(fmt.Sprintf("calls %.0f", d.calls))
}
ui.Box(c).Width(440).Children(func() {
mujica.UsageQuota(c, []mujica.QuotaItem{{Label: "API calls", Used: d.calls, Limit: 10000}, {Label: "Storage", Used: 9, Limit: 10, Unit: "GB"}},
mujica.UsageQuotaOptions{ResetAt: time.Now().Add(52 * time.Hour)})
account.UsageQuota(c, []account.QuotaItem{{Label: "API calls", Used: d.calls, Limit: 10000}, {Label: "Storage", Used: 9, Limit: 10, Unit: "GB"}},
account.UsageQuotaOptions{ResetAt: time.Now().Add(52 * time.Hour)})
})
}
type accountWizardState struct {
step int
theme mujica.Mode
theme core.Mode
name string
noSkip bool
}
@@ -154,21 +156,21 @@ type accountWizardState struct {
func accountWizardDemo(c *ui.Context, log func(string)) {
d := ui.Local(c.Root(), "477", func() accountWizardState { return accountWizardState{} })
accountToggles(c, log, []string{"Hide Skip"}, &d.noSkip)
steps := []mujica.WizardStep{
steps := []account.WizardStep{
{Title: "Welcome", Description: "Three short steps to set up your court."},
{Title: "Your name", Ready: func() bool { return d.name != "" }, Content: func() {
if mujica.TextInput(c, &d.name, mujica.TextInputOptions{Label: "Name", Placeholder: "Name"}).Changed() {
if input.TextInput(c, &d.name, input.TextInputOptions{Label: "Name", Placeholder: "Name"}).Changed() {
log("name changed")
}
}},
{Title: "Pick a theme", Content: func() {
if mujica.ThemeSelector(c, &d.theme, mujica.ThemeSelectorOptions{}).Changed() {
if account.ThemeSelector(c, &d.theme, account.ThemeSelectorOptions{}).Changed() {
log("theme " + d.theme.String())
}
}},
}
from := d.step
r := mujica.OnboardingWizard(c, &d.step, steps, mujica.OnboardingWizardOptions{NoSkip: d.noSkip})
r := account.OnboardingWizard(c, &d.step, steps, account.OnboardingWizardOptions{NoSkip: d.noSkip})
if d.step != from {
log(fmt.Sprintf("step %d", d.step+1))
}
@@ -192,8 +194,8 @@ func accountCoachDemo(c *ui.Context, log func(string)) {
}
var compose, archive *ui.Element
ui.Row(c).Gap(48).Padding(8).Children(func() {
compose = mujica.Button(c, "Compose", mujica.ButtonOptions{Disabled: d.disabled})
archive = mujica.Button(c, "Archive", mujica.ButtonOptions{Variant: mujica.Secondary})
compose = input.Button(c, "Compose", input.ButtonOptions{Disabled: d.disabled})
archive = input.Button(c, "Archive", input.ButtonOptions{Variant: input.Secondary})
})
if compose.Clicked() {
log("compose")
@@ -202,7 +204,7 @@ func accountCoachDemo(c *ui.Context, log func(string)) {
log("archive")
}
from := d.step
r := mujica.Coachmark(c, &d.open, &d.step, []mujica.CoachmarkStep{
r := account.Coachmark(c, &d.open, &d.step, []account.CoachmarkStep{
{Anchor: compose, Title: "Write here", Body: "Start a new letter."},
{Anchor: archive, Title: "Keep it tidy", Body: "Archive threads you have finished."},
})
@@ -225,12 +227,12 @@ func accountWhatsNewDemo(c *ui.Context, log func(string)) {
d.open = true
log("open")
}
releases := []mujica.Release{
{Version: "1.1", Date: "2026-10-05", Changes: []mujica.ReleaseChange{{Kind: mujica.ChangeNew, Text: "400 new components."}, {Kind: mujica.ChangeFixed, Text: "Snapshots no longer drift."}}},
{Version: "1.0", Date: "2026-09-01", Changes: []mujica.ReleaseChange{{Kind: mujica.ChangeImproved, Text: "Court-gothic theme."}}},
releases := []account.Release{
{Version: "1.1", Date: "2026-10-05", Changes: []account.ReleaseChange{{Kind: account.ChangeNew, Text: "400 new components."}, {Kind: account.ChangeFixed, Text: "Snapshots no longer drift."}}},
{Version: "1.0", Date: "2026-09-01", Changes: []account.ReleaseChange{{Kind: account.ChangeImproved, Text: "Court-gothic theme."}}},
}
was := d.open
if mujica.WhatsNewDialog(c, &d.open, &d.hide, releases, mujica.WhatsNewDialogOptions{}).Changed() {
if account.WhatsNewDialog(c, &d.open, &d.hide, releases, account.WhatsNewDialogOptions{}).Changed() {
log(fmt.Sprintf("don't show again %v", d.hide))
}
if was && !d.open {
@@ -239,7 +241,7 @@ func accountWhatsNewDemo(c *ui.Context, log func(string)) {
}
type accountFeedbackState struct {
state mujica.FeedbackWidgetState
state account.FeedbackWidgetState
disabled bool
}
@@ -254,7 +256,7 @@ func accountFeedbackDemo(c *ui.Context, log func(string)) {
}
accountToggles(c, log, []string{"Disabled"}, &d.disabled)
before := d.state.Draft
r := mujica.FeedbackWidget(c, &d.state, mujica.FeedbackWidgetOptions{Disabled: d.disabled, Attachments: func() {
r := account.FeedbackWidget(c, &d.state, account.FeedbackWidgetOptions{Disabled: d.disabled, Attachments: func() {
if tradingAction(c, "Attach screenshot") {
log("attach screenshot")
}
+45 -44
View File
@@ -6,7 +6,8 @@ import (
"github.com/egoist/mygo/ui"
mujica "mujicaui"
"github.com/ZacharyZhang-NY/MujicaUI/agent"
"github.com/ZacharyZhang-NY/MujicaUI/input"
)
const agentCategory = "Agent"
@@ -58,13 +59,13 @@ var agentPages = []page{
var agentStates = []string{"Pending", "Planning", "Running", "Waiting", "Done", "Failed", "Skipped"}
// agentStatePick is a select over agentStates, logging changes.
func agentStatePick(c *ui.Context, log func(string), v *string) mujica.AgentState {
func agentStatePick(c *ui.Context, log func(string), v *string) agent.AgentState {
if ui.Select(c, v, agentStates).Changed() {
log("state = " + *v)
}
for i, s := range agentStates {
if s == *v {
return mujica.AgentState(i)
return agent.AgentState(i)
}
}
panic("gallery: unknown state " + *v)
@@ -78,41 +79,41 @@ type toolCallDemoState struct {
func toolCallCardDemo(c *ui.Context, log func(string)) {
s := ui.Local(c.Root(), "261", func() toolCallDemoState { return toolCallDemoState{state: "Done"} })
var st mujica.AgentState
var st agent.AgentState
overlayControls(c, func() {
st = agentStatePick(c, log, &s.state)
overlayCheck(c, log, &s.failed, "Error")
overlayCheck(c, log, &s.still, "Still")
})
call := mujica.ToolCall{ID: "1", Name: "read_file", Args: `{"path":"court/ledger.md","limit":40}`,
call := agent.ToolCall{ID: "1", Name: "read_file", Args: `{"path":"court/ledger.md","limit":40}`,
Result: `{"lines":40,"truncated":false}`, State: st, Duration: 420 * time.Millisecond}
if s.failed {
call.Error = "permission denied: court/ledger.md"
}
if mujica.ToolCallCard(c, call, mujica.ToolCallCardOptions{Still: s.still}).Changed() {
if agent.ToolCallCard(c, call, agent.ToolCallCardOptions{Still: s.still}).Changed() {
log("folded or unfolded a section")
}
}
func toolCallGroupDemo(c *ui.Context, log func(string)) {
open := ui.Local(c.Root(), "262", func() bool { return true })
calls := []mujica.ToolCall{
{ID: "a", Name: "list_dir", Args: `{"path":"court"}`, Result: `["ledger.md","menu.md"]`, State: mujica.AgentDone, Duration: 300 * time.Millisecond},
{ID: "b", Name: "read_file", Args: `{"path":"court/wine.md"}`, Error: "not found", State: mujica.AgentFailed, Duration: 1200 * time.Millisecond},
{ID: "c", Name: "grep", Args: `{"pattern":"Sakiko"}`, Result: `{"matches":3}`, State: mujica.AgentDone, Duration: 500 * time.Millisecond},
calls := []agent.ToolCall{
{ID: "a", Name: "list_dir", Args: `{"path":"court"}`, Result: `["ledger.md","menu.md"]`, State: agent.AgentDone, Duration: 300 * time.Millisecond},
{ID: "b", Name: "read_file", Args: `{"path":"court/wine.md"}`, Error: "not found", State: agent.AgentFailed, Duration: 1200 * time.Millisecond},
{ID: "c", Name: "grep", Args: `{"pattern":"Sakiko"}`, Result: `{"matches":3}`, State: agent.AgentDone, Duration: 500 * time.Millisecond},
}
if mujica.ToolCallGroup(c, open, calls, mujica.ToolCallGroupOptions{}).Changed() {
if agent.ToolCallGroup(c, open, calls, agent.ToolCallGroupOptions{}).Changed() {
log(fmt.Sprintf("group open = %v", *open))
}
}
func toolApprovalDemo(c *ui.Context, log func(string)) {
open := ui.Local(c.Root(), "263", func() bool { return false })
if mujica.Button(c, "Run delete_files", mujica.ButtonOptions{}).Clicked() {
if input.Button(c, "Run delete_files", input.ButtonOptions{}).Clicked() {
*open = true
log("asked for approval")
}
r := mujica.ToolApprovalDialog(c, open, mujica.ToolApprovalOptions{Tool: "delete_files",
r := agent.ToolApprovalDialog(c, open, agent.ToolApprovalOptions{Tool: "delete_files",
Args: `{"glob":"build/*.tmp","dry_run":false}`, Reason: "The agent wants to clean temporary files from the build folder."})
if r.Allowed {
log("allowed")
@@ -130,35 +131,35 @@ type permissionDemoState struct {
func permissionDemo(c *ui.Context, log func(string)) {
s := ui.Local(c.Root(), "264", func() permissionDemoState { return permissionDemoState{grant: "none"} })
ui.Row(c).Gap(12).AlignItems(ui.Center).Children(func() {
if mujica.Button(c, "Ask to write", mujica.ButtonOptions{}).Clicked() {
if input.Button(c, "Ask to write", input.ButtonOptions{}).Clicked() {
s.open = true
log("asked for permission")
}
ui.Text(c, "Grant: "+s.grant)
})
r := mujica.PermissionPrompt(c, &s.open, mujica.PermissionPromptOptions{Title: "Write to the ledger?",
r := agent.PermissionPrompt(c, &s.open, agent.PermissionPromptOptions{Title: "Write to the ledger?",
Description: "The agent wants to record tonight's guests.", Scope: "Write access to ~/court/ledger and its subfolders"})
if r.Choice != mujica.PermissionNone {
if r.Choice != agent.PermissionNone {
s.grant = [...]string{"none", "once", "always", "denied"}[r.Choice]
log("choice = " + s.grant)
}
}
type stepDemoState struct {
steps []mujica.AgentStep
steps []agent.AgentStep
disabled bool
}
func agentStepListDemo(c *ui.Context, log func(string)) {
s := ui.Local(c.Root(), "265", func() stepDemoState {
return stepDemoState{steps: []mujica.AgentStep{
{ID: "read", Title: "Read the guest list", State: mujica.AgentDone, Duration: 800 * time.Millisecond, Detail: "42 guests, 3 arrive late."},
{ID: "seat", Title: "Assign the seats", State: mujica.AgentFailed, Duration: 2 * time.Second, Detail: "Table 4 is over capacity."},
{ID: "send", Title: "Send the invitations", State: mujica.AgentPending},
return stepDemoState{steps: []agent.AgentStep{
{ID: "read", Title: "Read the guest list", State: agent.AgentDone, Duration: 800 * time.Millisecond, Detail: "42 guests, 3 arrive late."},
{ID: "seat", Title: "Assign the seats", State: agent.AgentFailed, Duration: 2 * time.Second, Detail: "Table 4 is over capacity."},
{ID: "send", Title: "Send the invitations", State: agent.AgentPending},
}}
})
overlayControls(c, func() { overlayCheck(c, log, &s.disabled, "Disabled") })
r := mujica.AgentStepList(c, s.steps, mujica.AgentStepListOptions{Disabled: s.disabled})
r := agent.AgentStepList(c, s.steps, agent.AgentStepListOptions{Disabled: s.disabled})
if r.Toggled != "" {
log("toggled " + r.Toggled)
}
@@ -166,38 +167,38 @@ func agentStepListDemo(c *ui.Context, log func(string)) {
log("retry " + r.Retry)
for i := range s.steps {
if s.steps[i].ID == r.Retry {
s.steps[i].State, s.steps[i].Detail = mujica.AgentRunning, "Retrying with a larger table."
s.steps[i].State, s.steps[i].Detail = agent.AgentRunning, "Retrying with a larger table."
}
}
}
}
type planDemoState struct {
items []mujica.PlanItem
items []agent.PlanItem
disabled bool
}
func agentPlanDemo(c *ui.Context, log func(string)) {
s := ui.Local(c.Root(), "266", func() planDemoState {
return planDemoState{items: []mujica.PlanItem{
{ID: "a", Text: "Read the guest list", State: mujica.AgentDone},
{ID: "b", Text: "Assign the seats", State: mujica.AgentRunning},
return planDemoState{items: []agent.PlanItem{
{ID: "a", Text: "Read the guest list", State: agent.AgentDone},
{ID: "b", Text: "Assign the seats", State: agent.AgentRunning},
{ID: "c", Text: "Order the flowers"},
}}
})
overlayControls(c, func() {
overlayCheck(c, log, &s.disabled, "Disabled")
if mujica.Button(c, "Agent finishes a step", mujica.ButtonOptions{Variant: mujica.Secondary}).Clicked() {
if input.Button(c, "Agent finishes a step", input.ButtonOptions{Variant: input.Secondary}).Clicked() {
for i := range s.items {
if s.items[i].State != mujica.AgentDone && s.items[i].State != mujica.AgentSkipped {
s.items[i].State = mujica.AgentDone
if s.items[i].State != agent.AgentDone && s.items[i].State != agent.AgentSkipped {
s.items[i].State = agent.AgentDone
log("agent checked " + s.items[i].Text)
break
}
}
}
})
if mujica.AgentPlan(c, &s.items, mujica.AgentPlanOptions{Disabled: s.disabled}).Changed() {
if agent.AgentPlan(c, &s.items, agent.AgentPlanOptions{Disabled: s.disabled}).Changed() {
log(fmt.Sprintf("plan changed (%d items)", len(s.items)))
}
}
@@ -209,15 +210,15 @@ type statusDemoState struct {
func agentStatusDemo(c *ui.Context, log func(string)) {
s := ui.Local(c.Root(), "267", func() statusDemoState { return statusDemoState{state: "Running"} })
var st mujica.AgentState
var st agent.AgentState
overlayControls(c, func() {
st = agentStatePick(c, log, &s.state)
overlayCheck(c, log, &s.still, "Still")
})
ui.Row(c).Gap(8).Wrap().Children(func() {
mujica.AgentStatus(c, st, mujica.AgentStatusOptions{Still: s.still})
for _, v := range []mujica.AgentState{mujica.AgentPlanning, mujica.AgentRunning, mujica.AgentWaiting, mujica.AgentDone, mujica.AgentFailed} {
mujica.AgentStatus(c, v, mujica.AgentStatusOptions{Still: s.still})
agent.AgentStatus(c, st, agent.AgentStatusOptions{Still: s.still})
for _, v := range []agent.AgentState{agent.AgentPlanning, agent.AgentRunning, agent.AgentWaiting, agent.AgentDone, agent.AgentFailed} {
agent.AgentStatus(c, v, agent.AgentStatusOptions{Still: s.still})
}
})
}
@@ -239,25 +240,25 @@ func agentProgressDemo(c *ui.Context, log func(string)) {
if s.value > 0.5 {
action = "Writing the seating chart"
}
mujica.AgentProgress(c, s.value, mujica.AgentProgressOptions{Label: "Preparing the ball", Action: action, Indeterminate: s.indeterminate})
agent.AgentProgress(c, s.value, agent.AgentProgressOptions{Label: "Preparing the ball", Action: action, Indeterminate: s.indeterminate})
}
func subAgentTreeDemo(c *ui.Context, log func(string)) {
disabled := ui.Local(c.Root(), "269", func() bool { return false })
overlayControls(c, func() { overlayCheck(c, log, disabled, "Disabled") })
agents := []mujica.SubAgent{{ID: "lead", Name: "Steward", Task: "Prepare the ball", State: mujica.AgentRunning, Children: []mujica.SubAgent{
{ID: "music", Name: "Musician", Task: "Hire the quartet", State: mujica.AgentDone},
{ID: "kitchen", Name: "Cook", Task: "Plan the menu", State: mujica.AgentWaiting, Children: []mujica.SubAgent{
{ID: "wine", Name: "Sommelier", Task: "Choose the wine", State: mujica.AgentFailed},
agents := []agent.SubAgent{{ID: "lead", Name: "Steward", Task: "Prepare the ball", State: agent.AgentRunning, Children: []agent.SubAgent{
{ID: "music", Name: "Musician", Task: "Hire the quartet", State: agent.AgentDone},
{ID: "kitchen", Name: "Cook", Task: "Plan the menu", State: agent.AgentWaiting, Children: []agent.SubAgent{
{ID: "wine", Name: "Sommelier", Task: "Choose the wine", State: agent.AgentFailed},
}},
}}}
if mujica.SubAgentTree(c, agents, mujica.SubAgentTreeOptions{Disabled: *disabled}).Changed() {
if agent.SubAgentTree(c, agents, agent.SubAgentTreeOptions{Disabled: *disabled}).Changed() {
log("expanded or collapsed an agent")
}
}
type fileChangeDemoState struct {
decision mujica.FileDecision
decision agent.FileDecision
disabled bool
preview float64
}
@@ -268,7 +269,7 @@ func fileChangeCardDemo(c *ui.Context, log func(string)) {
overlayCheck(c, log, &s.disabled, "Disabled")
overlaySlider(c, log, &s.preview, "Preview", "lines", 2, 12, 1)
})
r := mujica.FileChangeCard(c, &s.decision, agentDemoChange, mujica.FileChangeCardOptions{Preview: int(s.preview), Disabled: s.disabled})
r := agent.FileChangeCard(c, &s.decision, agentDemoChange, agent.FileChangeCardOptions{Preview: int(s.preview), Disabled: s.disabled})
if r.Decided {
log(fmt.Sprintf("decision = %d", s.decision))
}
@@ -277,6 +278,6 @@ func fileChangeCardDemo(c *ui.Context, log func(string)) {
}
}
var agentDemoChange = mujica.FileChange{Path: "court/ledger.md",
var agentDemoChange = agent.FileChange{Path: "court/ledger.md",
Old: "# Ledger\nGuests: 40\nWine: claret\nMusic: none\nHall: east wing\n",
New: "# Ledger\nGuests: 42\nWine: claret\nMusic: quartet\nFlowers: lilies\nHall: east wing\n"}
+69 -69
View File
@@ -2,29 +2,29 @@ package main
// Code snippets of the Agent pages, the same as their compiled Examples.
const toolCallCardCode = `call := mujica.ToolCall{ID: "1", Name: "read_file", Args: "{\"path\":\"ledger.md\"}",
Result: "{\"lines\":40}", State: mujica.AgentDone, Duration: 420 * time.Millisecond}
const toolCallCardCode = `call := agent.ToolCall{ID: "1", Name: "read_file", Args: "{\"path\":\"ledger.md\"}",
Result: "{\"lines\":40}", State: agent.AgentDone, Duration: 420 * time.Millisecond}
view := func(c *ui.Context) {
mujica.Use(c, mujica.Settings{})
mujica.ToolCallCard(c, call, mujica.ToolCallCardOptions{OpenArgs: true})
core.Use(c, core.Settings{})
agent.ToolCallCard(c, call, agent.ToolCallCardOptions{OpenArgs: true})
}
ui.Render(view, 400, 300, 1)`
const toolCallGroupCode = `open := false
calls := []mujica.ToolCall{
{ID: "a", Name: "list_dir", State: mujica.AgentDone, Duration: 300 * time.Millisecond},
{ID: "b", Name: "read_file", State: mujica.AgentFailed, Error: "not found"},
calls := []agent.ToolCall{
{ID: "a", Name: "list_dir", State: agent.AgentDone, Duration: 300 * time.Millisecond},
{ID: "b", Name: "read_file", State: agent.AgentFailed, Error: "not found"},
}
view := func(c *ui.Context) {
mujica.Use(c, mujica.Settings{})
mujica.ToolCallGroup(c, &open, calls, mujica.ToolCallGroupOptions{})
core.Use(c, core.Settings{})
agent.ToolCallGroup(c, &open, calls, agent.ToolCallGroupOptions{})
}
ui.Render(view, 400, 300, 1)`
const toolApprovalDialogCode = `open := true
view := func(c *ui.Context) {
mujica.Use(c, mujica.Settings{})
r := mujica.ToolApprovalDialog(c, &open, mujica.ToolApprovalOptions{
core.Use(c, core.Settings{})
r := agent.ToolApprovalDialog(c, &open, agent.ToolApprovalOptions{
Tool: "delete_files", Args: "{\"glob\":\"*.tmp\"}", Reason: "Clean the build folder.",
})
if r.Allowed {
@@ -35,149 +35,149 @@ ui.Render(view, 400, 300, 1)`
const permissionPromptCode = `open := true
view := func(c *ui.Context) {
mujica.Use(c, mujica.Settings{})
r := mujica.PermissionPrompt(c, &open, mujica.PermissionPromptOptions{
core.Use(c, core.Settings{})
r := agent.PermissionPrompt(c, &open, agent.PermissionPromptOptions{
Title: "Write to the ledger?", Scope: "Write access to ~/court/ledger",
})
if r.Choice == mujica.PermissionAllowAlways {
if r.Choice == agent.PermissionAllowAlways {
fmt.Println("remember the grant")
}
}
ui.Render(view, 400, 300, 1)`
const agentStepListCode = `steps := []mujica.AgentStep{
{ID: "read", Title: "Read the guest list", State: mujica.AgentDone, Duration: time.Second},
{ID: "seat", Title: "Assign the seats", State: mujica.AgentFailed, Detail: "Table 4 is full."},
{ID: "send", Title: "Send invitations", State: mujica.AgentPending},
const agentStepListCode = `steps := []agent.AgentStep{
{ID: "read", Title: "Read the guest list", State: agent.AgentDone, Duration: time.Second},
{ID: "seat", Title: "Assign the seats", State: agent.AgentFailed, Detail: "Table 4 is full."},
{ID: "send", Title: "Send invitations", State: agent.AgentPending},
}
view := func(c *ui.Context) {
mujica.Use(c, mujica.Settings{})
if r := mujica.AgentStepList(c, steps, mujica.AgentStepListOptions{}); r.Retry != "" {
core.Use(c, core.Settings{})
if r := agent.AgentStepList(c, steps, agent.AgentStepListOptions{}); r.Retry != "" {
fmt.Println("retry", r.Retry)
}
}
ui.Render(view, 400, 300, 1)`
const agentPlanCode = `items := []mujica.PlanItem{
{ID: "a", Text: "Read the guest list", State: mujica.AgentDone},
{ID: "b", Text: "Assign the seats", State: mujica.AgentRunning},
const agentPlanCode = `items := []agent.PlanItem{
{ID: "a", Text: "Read the guest list", State: agent.AgentDone},
{ID: "b", Text: "Assign the seats", State: agent.AgentRunning},
{ID: "c", Text: "Order the flowers"},
}
view := func(c *ui.Context) {
mujica.Use(c, mujica.Settings{})
mujica.AgentPlan(c, &items, mujica.AgentPlanOptions{})
core.Use(c, core.Settings{})
agent.AgentPlan(c, &items, agent.AgentPlanOptions{})
}
ui.Render(view, 400, 300, 1)`
const agentStatusCode = `view := func(c *ui.Context) {
mujica.Use(c, mujica.Settings{})
mujica.AgentStatus(c, mujica.AgentWaiting, mujica.AgentStatusOptions{})
core.Use(c, core.Settings{})
agent.AgentStatus(c, agent.AgentWaiting, agent.AgentStatusOptions{})
}
ui.Render(view, 400, 300, 1)`
const agentProgressCode = `view := func(c *ui.Context) {
mujica.Use(c, mujica.Settings{})
mujica.AgentProgress(c, 0.4, mujica.AgentProgressOptions{Action: "Reading ledger.md"})
core.Use(c, core.Settings{})
agent.AgentProgress(c, 0.4, agent.AgentProgressOptions{Action: "Reading ledger.md"})
}
ui.Render(view, 400, 300, 1)`
const subAgentTreeCode = `agents := []mujica.SubAgent{{ID: "lead", Name: "Steward", State: mujica.AgentRunning, Children: []mujica.SubAgent{
{ID: "cook", Name: "Cook", Task: "Plan the menu", State: mujica.AgentWaiting},
const subAgentTreeCode = `agents := []agent.SubAgent{{ID: "lead", Name: "Steward", State: agent.AgentRunning, Children: []agent.SubAgent{
{ID: "cook", Name: "Cook", Task: "Plan the menu", State: agent.AgentWaiting},
}}}
view := func(c *ui.Context) {
mujica.Use(c, mujica.Settings{})
mujica.SubAgentTree(c, agents, mujica.SubAgentTreeOptions{})
core.Use(c, core.Settings{})
agent.SubAgentTree(c, agents, agent.SubAgentTreeOptions{})
}
ui.Render(view, 400, 300, 1)`
const fileChangeCardCode = `decision := mujica.FilePending
change := mujica.FileChange{Path: "ledger.md", Old: "Guests: 40\n", New: "Guests: 42\n"}
const fileChangeCardCode = `decision := agent.FilePending
change := agent.FileChange{Path: "ledger.md", Old: "Guests: 40\n", New: "Guests: 42\n"}
view := func(c *ui.Context) {
mujica.Use(c, mujica.Settings{})
if mujica.FileChangeCard(c, &decision, change, mujica.FileChangeCardOptions{}).OpenDiff {
core.Use(c, core.Settings{})
if agent.FileChangeCard(c, &decision, change, agent.FileChangeCardOptions{}).OpenDiff {
fmt.Println("open the full diff")
}
}
ui.Render(view, 400, 300, 1)`
const multiFileDiffReviewCode = `files := []mujica.FileChange{
const multiFileDiffReviewCode = `files := []agent.FileChange{
{Path: "ledger.md", Old: "Guests: 40\n", New: "Guests: 42\n"},
{Path: "menu.md", Old: "soup\n", New: "soup\ntart\n"},
}
decisions := make([]mujica.FileDecision, len(files))
decisions := make([]agent.FileDecision, len(files))
view := func(c *ui.Context) {
mujica.Use(c, mujica.Settings{})
mujica.MultiFileDiffReview(c, &decisions, files, mujica.MultiFileDiffReviewOptions{})
core.Use(c, core.Settings{})
agent.MultiFileDiffReview(c, &decisions, files, agent.MultiFileDiffReviewOptions{})
}
ui.Render(view, 400, 300, 1)`
const commandExecutionCardCode = `run := mujica.CommandRun{Command: "go test ./...", Dir: "~/court", Running: true, Output: "\x1b[32mok\x1b[0m court\n"}
const commandExecutionCardCode = `run := agent.CommandRun{Command: "go test ./...", Dir: "~/court", Running: true, Output: "\x1b[32mok\x1b[0m court\n"}
view := func(c *ui.Context) {
mujica.Use(c, mujica.Settings{})
if mujica.CommandExecutionCard(c, run, mujica.CommandExecutionCardOptions{}).Changed() {
core.Use(c, core.Settings{})
if agent.CommandExecutionCard(c, run, agent.CommandExecutionCardOptions{}).Changed() {
fmt.Println("abort the process")
}
}
ui.Render(view, 400, 300, 1)`
const screenshotStreamCode = `var st mujica.ScreenshotStreamState
var frames []mujica.ScreenshotFrame // appended as the agent captures them
const screenshotStreamCode = `var st agent.ScreenshotStreamState
var frames []agent.ScreenshotFrame // appended as the agent captures them
view := func(c *ui.Context) {
mujica.Use(c, mujica.Settings{})
mujica.ScreenshotStream(c, &st, frames, mujica.ScreenshotStreamOptions{})
core.Use(c, core.Settings{})
agent.ScreenshotStream(c, &st, frames, agent.ScreenshotStreamOptions{})
}
ui.Render(view, 400, 300, 1)`
const artifactPanelCode = `art := mujica.Artifact{Title: "seating.go", Language: "go", Content: "package court\n"}
const artifactPanelCode = `art := agent.Artifact{Title: "seating.go", Language: "go", Content: "package court\n"}
view := func(c *ui.Context) {
mujica.Use(c, mujica.Settings{})
if mujica.ArtifactPanel(c, art, mujica.ArtifactPanelOptions{}).Exported {
core.Use(c, core.Settings{})
if agent.ArtifactPanel(c, art, agent.ArtifactPanelOptions{}).Exported {
fmt.Println("save the file")
}
}
ui.Render(view, 400, 300, 1)`
const artifactVersionSwitcherCode = `current := 1
versions := []mujica.ArtifactVersion{{Label: "v1", Note: "First draft"}, {Label: "v2", Note: "Added music"}}
versions := []agent.ArtifactVersion{{Label: "v1", Note: "First draft"}, {Label: "v2", Note: "Added music"}}
view := func(c *ui.Context) {
mujica.Use(c, mujica.Settings{})
if mujica.ArtifactVersionSwitcher(c, &current, versions, mujica.ArtifactVersionSwitcherOptions{}).Compare {
core.Use(c, core.Settings{})
if agent.ArtifactVersionSwitcher(c, &current, versions, agent.ArtifactVersionSwitcherOptions{}).Compare {
fmt.Println("diff v1 and v2")
}
}
ui.Render(view, 400, 300, 1)`
const sandboxStatusCode = `info := mujica.SandboxInfo{Name: "sandbox-7", State: mujica.SandboxRunning,
Resources: []mujica.SandboxResource{{Name: "CPU", Used: 1.5, Limit: 2, Unit: "cores"}}}
const sandboxStatusCode = `info := agent.SandboxInfo{Name: "sandbox-7", State: agent.SandboxRunning,
Resources: []agent.SandboxResource{{Name: "CPU", Used: 1.5, Limit: 2, Unit: "cores"}}}
view := func(c *ui.Context) {
mujica.Use(c, mujica.Settings{})
mujica.SandboxStatus(c, info, mujica.SandboxStatusOptions{})
core.Use(c, core.Settings{})
agent.SandboxStatus(c, info, agent.SandboxStatusOptions{})
}
ui.Render(view, 400, 300, 1)`
const mcpServerListCode = `servers := []mujica.MCPServer{{ID: "fs", Name: "filesystem", Transport: "stdio", State: mujica.MCPConnected, Tools: 12}}
const mcpServerListCode = `servers := []agent.MCPServer{{ID: "fs", Name: "filesystem", Transport: "stdio", State: agent.MCPConnected, Tools: 12}}
view := func(c *ui.Context) {
mujica.Use(c, mujica.Settings{})
if r := mujica.MCPServerList(c, servers, mujica.MCPServerListOptions{}); r.Action == mujica.MCPStop {
core.Use(c, core.Settings{})
if r := agent.MCPServerList(c, servers, agent.MCPServerListOptions{}); r.Action == agent.MCPStop {
fmt.Println("stop", r.Server)
}
}
ui.Render(view, 400, 300, 1)`
const memoryPanelCode = `entries := []mujica.MemoryEntry{{ID: "1", Text: "Lady Sakiko prefers the east wing.", Source: "Chat"}}
const memoryPanelCode = `entries := []agent.MemoryEntry{{ID: "1", Text: "Lady Sakiko prefers the east wing.", Source: "Chat"}}
view := func(c *ui.Context) {
mujica.Use(c, mujica.Settings{})
if r := mujica.MemoryPanel(c, &entries, mujica.MemoryPanelOptions{}); r.Deleted != "" {
core.Use(c, core.Settings{})
if r := agent.MemoryPanel(c, &entries, agent.MemoryPanelOptions{}); r.Deleted != "" {
fmt.Println("forget", r.Deleted)
}
}
ui.Render(view, 400, 300, 1)`
const checkpointListCode = `current := "c2"
points := []mujica.Checkpoint{{ID: "c2", Label: "Menu drafted"}, {ID: "c1", Label: "Start"}}
points := []agent.Checkpoint{{ID: "c2", Label: "Menu drafted"}, {ID: "c1", Label: "Start"}}
view := func(c *ui.Context) {
mujica.Use(c, mujica.Settings{})
if mujica.CheckpointList(c, &current, points, mujica.CheckpointListOptions{}).Changed() {
core.Use(c, core.Settings{})
if agent.CheckpointList(c, &current, points, agent.CheckpointListOptions{}).Changed() {
fmt.Println("restore", current)
}
}
@@ -185,8 +185,8 @@ ui.Render(view, 400, 300, 1)`
const humanInputRequestCode = `answer := ""
view := func(c *ui.Context) {
mujica.Use(c, mujica.Settings{})
r := mujica.HumanInputRequest(c, &answer, mujica.HumanInputRequestOptions{
core.Use(c, core.Settings{})
r := agent.HumanInputRequest(c, &answer, agent.HumanInputRequestOptions{
Question: "Which hall?", Options: []string{"East wing", "Great hall"}, AllowFree: true,
})
if r.Submitted {
+49 -47
View File
@@ -7,15 +7,17 @@ import (
"github.com/egoist/mygo/ui"
mujica "mujicaui"
"github.com/ZacharyZhang-NY/MujicaUI/agent"
"github.com/ZacharyZhang-NY/MujicaUI/display"
"github.com/ZacharyZhang-NY/MujicaUI/input"
)
type multiFileDemoState struct {
decisions []mujica.FileDecision
decisions []agent.FileDecision
disabled bool
}
var agentDemoFiles = []mujica.FileChange{
var agentDemoFiles = []agent.FileChange{
agentDemoChange,
{Path: "court/menu.md", Old: "soup\nroast\n", New: "soup\nroast\ntart\n"},
{Path: "court/old-plan.md", Old: "Hold the ball in the cellar.\n"},
@@ -23,44 +25,44 @@ var agentDemoFiles = []mujica.FileChange{
func multiFileDiffDemo(c *ui.Context, log func(string)) {
s := ui.Local(c.Root(), "271", func() multiFileDemoState {
return multiFileDemoState{decisions: make([]mujica.FileDecision, len(agentDemoFiles))}
return multiFileDemoState{decisions: make([]agent.FileDecision, len(agentDemoFiles))}
})
overlayControls(c, func() {
overlayCheck(c, log, &s.disabled, "Disabled")
if mujica.Button(c, "Reset", mujica.ButtonOptions{Variant: mujica.Secondary}).Clicked() {
if input.Button(c, "Reset", input.ButtonOptions{Variant: input.Secondary}).Clicked() {
clear(s.decisions)
log("reset")
}
})
if mujica.MultiFileDiffReview(c, &s.decisions, agentDemoFiles, mujica.MultiFileDiffReviewOptions{Disabled: s.disabled}).Changed() {
if agent.MultiFileDiffReview(c, &s.decisions, agentDemoFiles, agent.MultiFileDiffReviewOptions{Disabled: s.disabled}).Changed() {
log(fmt.Sprintf("decisions = %v", s.decisions))
}
}
type commandDemoState struct {
run mujica.CommandRun
run agent.CommandRun
disabled bool
lines int
}
func commandExecutionDemo(c *ui.Context, log func(string)) {
s := ui.Local(c.Root(), "272", func() commandDemoState {
return commandDemoState{run: mujica.CommandRun{Command: "go test ./court/...", Dir: "~/projects/court", Running: true}}
return commandDemoState{run: agent.CommandRun{Command: "go test ./court/...", Dir: "~/projects/court", Running: true}}
})
overlayControls(c, func() {
overlayCheck(c, log, &s.disabled, "Disabled")
if mujica.Button(c, "Print a line", mujica.ButtonOptions{Variant: mujica.Secondary}).Clicked() {
if input.Button(c, "Print a line", input.ButtonOptions{Variant: input.Secondary}).Clicked() {
s.lines++
color := []string{"32mok ", "33mwarn", "31mFAIL"}[s.lines%3]
s.run.Output += fmt.Sprintf("\x1b[%s\x1b[0m court/case%d 0.%02ds\n", color, s.lines, s.lines)
log("printed a line")
}
if mujica.Button(c, "Restart", mujica.ButtonOptions{Variant: mujica.Secondary}).Clicked() {
if input.Button(c, "Restart", input.ButtonOptions{Variant: input.Secondary}).Clicked() {
s.run.Running, s.run.Output, s.lines = true, "", 0
log("restarted")
}
})
if mujica.CommandExecutionCard(c, s.run, mujica.CommandExecutionCardOptions{Disabled: s.disabled}).Changed() {
if agent.CommandExecutionCard(c, s.run, agent.CommandExecutionCardOptions{Disabled: s.disabled}).Changed() {
s.run.Running, s.run.ExitCode, s.run.Duration = false, 130, time.Duration(s.lines)*time.Second
s.run.Output += "\x1b[31maborted by user\x1b[0m\n"
log("abort requested")
@@ -68,13 +70,13 @@ func commandExecutionDemo(c *ui.Context, log func(string)) {
}
type screenshotDemoState struct {
st mujica.ScreenshotStreamState
frames []mujica.ScreenshotFrame
st agent.ScreenshotStreamState
frames []agent.ScreenshotFrame
disabled bool
}
// agentDemoFrame paints a frame with a moving block.
func agentDemoFrame(i int) mujica.ScreenshotFrame {
func agentDemoFrame(i int) agent.ScreenshotFrame {
img := image.NewRGBA(image.Rect(0, 0, 320, 180))
for y := range 180 {
for x := range 320 {
@@ -86,21 +88,21 @@ func agentDemoFrame(i int) mujica.ScreenshotFrame {
img.Pix[o], img.Pix[o+1], img.Pix[o+2], img.Pix[o+3] = v, v-10, v-20, 255
}
}
return mujica.ScreenshotFrame{Image: ui.NewBitmap(img), Caption: fmt.Sprintf("Clicked button %d", i+1), At: time.Duration(i) * 2 * time.Second}
return agent.ScreenshotFrame{Image: ui.NewBitmap(img), Caption: fmt.Sprintf("Clicked button %d", i+1), At: time.Duration(i) * 2 * time.Second}
}
func screenshotStreamDemo(c *ui.Context, log func(string)) {
s := ui.Local(c.Root(), "273", func() screenshotDemoState {
return screenshotDemoState{frames: []mujica.ScreenshotFrame{agentDemoFrame(0), agentDemoFrame(1)}}
return screenshotDemoState{frames: []agent.ScreenshotFrame{agentDemoFrame(0), agentDemoFrame(1)}}
})
overlayControls(c, func() {
overlayCheck(c, log, &s.disabled, "Disabled")
if mujica.Button(c, "Capture a frame", mujica.ButtonOptions{Variant: mujica.Secondary}).Clicked() {
if input.Button(c, "Capture a frame", input.ButtonOptions{Variant: input.Secondary}).Clicked() {
s.frames = append(s.frames, agentDemoFrame(len(s.frames)))
log(fmt.Sprintf("frame %d arrived", len(s.frames)))
}
})
if mujica.ScreenshotStream(c, &s.st, s.frames, mujica.ScreenshotStreamOptions{Height: 220, Disabled: s.disabled}).Changed() {
if agent.ScreenshotStream(c, &s.st, s.frames, agent.ScreenshotStreamOptions{Height: 220, Disabled: s.disabled}).Changed() {
log(fmt.Sprintf("frame %d, paused = %v", s.st.Index+1, s.st.Paused))
}
}
@@ -118,17 +120,17 @@ func artifactPanelDemo(c *ui.Context, log func(string)) {
}
overlayCheck(c, log, &s.disabled, "Disabled")
})
art := mujica.Artifact{Title: "seating.go", Language: "go",
art := agent.Artifact{Title: "seating.go", Language: "go",
Content: "package court\n\n// Seats maps guests to tables.\nfunc Seats() map[string]int {\n\treturn map[string]int{\"Sakiko\": 1, \"Uika\": 2}\n}\n"}
opts := mujica.ArtifactPanelOptions{Height: 200, Disabled: s.disabled}
opts := agent.ArtifactPanelOptions{Height: 200, Disabled: s.disabled}
if s.kind == "Document" {
art = mujica.Artifact{Title: "Invitation", Kind: mujica.ArtifactDocument, Content: "# You are invited\n\nThe masquerade begins at nine.\n"}
art = agent.Artifact{Title: "Invitation", Kind: agent.ArtifactDocument, Content: "# You are invited\n\nThe masquerade begins at nine.\n"}
opts.Preview = func() {
mujica.Heading(c, "You are invited", mujica.HeadingOptions{})
display.Heading(c, "You are invited", display.HeadingOptions{})
ui.Text(c, "The masquerade begins at nine.")
}
}
r := mujica.ArtifactPanel(c, art, opts)
r := agent.ArtifactPanel(c, art, opts)
if r.Copied {
log("copied to the clipboard")
}
@@ -145,8 +147,8 @@ type versionDemoState struct {
func artifactVersionDemo(c *ui.Context, log func(string)) {
s := ui.Local(c.Root(), "275", func() versionDemoState { return versionDemoState{current: 2} })
overlayControls(c, func() { overlayCheck(c, log, &s.disabled, "Disabled") })
versions := []mujica.ArtifactVersion{{Label: "v1", Note: "First draft"}, {Label: "v2", Note: "Added the quartet"}, {Label: "v3", Note: "Moved the wine table"}}
r := mujica.ArtifactVersionSwitcher(c, &s.current, versions, mujica.ArtifactVersionSwitcherOptions{Disabled: s.disabled})
versions := []agent.ArtifactVersion{{Label: "v1", Note: "First draft"}, {Label: "v2", Note: "Added the quartet"}, {Label: "v3", Note: "Moved the wine table"}}
r := agent.ArtifactVersionSwitcher(c, &s.current, versions, agent.ArtifactVersionSwitcherOptions{Disabled: s.disabled})
if r.Changed {
log("version = " + versions[s.current].Label)
}
@@ -166,43 +168,43 @@ func sandboxStatusDemo(c *ui.Context, log func(string)) {
return sandboxDemoState{state: "Running", cpu: 1.2, expires: time.Now().Add(10 * time.Minute)}
})
names := []string{"Starting", "Running", "Stopped", "Failed"}
state := mujica.SandboxStarting
state := agent.SandboxStarting
overlayControls(c, func() {
if ui.Select(c, &s.state, names).Changed() {
log("state = " + s.state)
}
overlaySlider(c, log, &s.cpu, "CPU", "cores", 0, 2, 0.1)
if mujica.Button(c, "Extend 5 min", mujica.ButtonOptions{Variant: mujica.Secondary}).Clicked() {
if input.Button(c, "Extend 5 min", input.ButtonOptions{Variant: input.Secondary}).Clicked() {
s.expires = s.expires.Add(5 * time.Minute)
log("extended")
}
})
for i, n := range names {
if n == s.state {
state = mujica.SandboxState(i)
state = agent.SandboxState(i)
}
}
mujica.SandboxStatus(c, mujica.SandboxInfo{Name: "sandbox-7", State: state, Expires: s.expires, Resources: []mujica.SandboxResource{
agent.SandboxStatus(c, agent.SandboxInfo{Name: "sandbox-7", State: state, Expires: s.expires, Resources: []agent.SandboxResource{
{Name: "CPU", Used: s.cpu, Limit: 2, Unit: "cores"}, {Name: "Memory", Used: 3.1, Limit: 4, Unit: "GB"},
}}, mujica.SandboxStatusOptions{})
}}, agent.SandboxStatusOptions{})
}
type mcpDemoState struct {
servers []mujica.MCPServer
servers []agent.MCPServer
disabled bool
}
func mcpServerListDemo(c *ui.Context, log func(string)) {
s := ui.Local(c.Root(), "277", func() mcpDemoState {
return mcpDemoState{servers: []mujica.MCPServer{
{ID: "fs", Name: "filesystem", Transport: "stdio", State: mujica.MCPConnected, Tools: 12},
{ID: "gh", Name: "github", Transport: "http", State: mujica.MCPError, Error: "401 Unauthorized"},
{ID: "db", Name: "postgres", Transport: "stdio", State: mujica.MCPStopped},
return mcpDemoState{servers: []agent.MCPServer{
{ID: "fs", Name: "filesystem", Transport: "stdio", State: agent.MCPConnected, Tools: 12},
{ID: "gh", Name: "github", Transport: "http", State: agent.MCPError, Error: "401 Unauthorized"},
{ID: "db", Name: "postgres", Transport: "stdio", State: agent.MCPStopped},
}}
})
overlayControls(c, func() { overlayCheck(c, log, &s.disabled, "Disabled") })
r := mujica.MCPServerList(c, s.servers, mujica.MCPServerListOptions{Disabled: s.disabled})
if r.Action == mujica.MCPNone {
r := agent.MCPServerList(c, s.servers, agent.MCPServerListOptions{Disabled: s.disabled})
if r.Action == agent.MCPNone {
return
}
log(fmt.Sprintf("%s: %s", r.Server, [...]string{"", "start", "stop", "reconnect"}[r.Action]))
@@ -211,29 +213,29 @@ func mcpServerListDemo(c *ui.Context, log func(string)) {
continue
}
switch r.Action {
case mujica.MCPStop:
s.servers[i].State = mujica.MCPStopped
case agent.MCPStop:
s.servers[i].State = agent.MCPStopped
default:
s.servers[i].State, s.servers[i].Tools, s.servers[i].Error = mujica.MCPConnected, 5, ""
s.servers[i].State, s.servers[i].Tools, s.servers[i].Error = agent.MCPConnected, 5, ""
}
}
}
type memoryDemoState struct {
entries []mujica.MemoryEntry
entries []agent.MemoryEntry
disabled bool
}
func memoryPanelDemo(c *ui.Context, log func(string)) {
s := ui.Local(c.Root(), "278", func() memoryDemoState {
return memoryDemoState{entries: []mujica.MemoryEntry{
return memoryDemoState{entries: []agent.MemoryEntry{
{ID: "1", Text: "Lady Sakiko prefers the east wing.", Source: "Chat, 3 Oct"},
{ID: "2", Text: "The quartet needs two days' notice.", Source: "Plan, 1 Oct"},
{ID: "3", Text: "Claret is served after nine.", Source: "Ledger"},
}}
})
overlayControls(c, func() { overlayCheck(c, log, &s.disabled, "Disabled") })
r := mujica.MemoryPanel(c, &s.entries, mujica.MemoryPanelOptions{Disabled: s.disabled})
r := agent.MemoryPanel(c, &s.entries, agent.MemoryPanelOptions{Disabled: s.disabled})
if r.Edited != "" {
log("edited " + r.Edited)
}
@@ -250,9 +252,9 @@ type checkpointDemoState struct {
func checkpointListDemo(c *ui.Context, log func(string)) {
s := ui.Local(c.Root(), "279", func() checkpointDemoState { return checkpointDemoState{current: "c3"} })
overlayControls(c, func() { overlayCheck(c, log, &s.disabled, "Disabled") })
points := []mujica.Checkpoint{{ID: "c3", Label: "Seats assigned", Note: "12 files changed"},
points := []agent.Checkpoint{{ID: "c3", Label: "Seats assigned", Note: "12 files changed"},
{ID: "c2", Label: "Menu drafted", Note: "3 files changed"}, {ID: "c1", Label: "Start"}}
if mujica.CheckpointList(c, &s.current, points, mujica.CheckpointListOptions{Disabled: s.disabled}).Changed() {
if agent.CheckpointList(c, &s.current, points, agent.CheckpointListOptions{Disabled: s.disabled}).Changed() {
log("rolled back to " + s.current)
}
}
@@ -269,12 +271,12 @@ func humanInputDemo(c *ui.Context, log func(string)) {
overlayControls(c, func() {
overlayCheck(c, log, &s.free, "AllowFree")
overlayCheck(c, log, &s.disabled, "Disabled")
if mujica.Button(c, "Start a 30 s timer", mujica.ButtonOptions{Variant: mujica.Secondary}).Clicked() {
if input.Button(c, "Start a 30 s timer", input.ButtonOptions{Variant: input.Secondary}).Clicked() {
s.deadline = time.Now().Add(30 * time.Second)
log("timer started")
}
})
r := mujica.HumanInputRequest(c, &s.answer, mujica.HumanInputRequestOptions{Question: "Which hall should host the masquerade?",
r := agent.HumanInputRequest(c, &s.answer, agent.HumanInputRequestOptions{Question: "Which hall should host the masquerade?",
Options: []string{"East wing", "Great hall"}, AllowFree: s.free, Deadline: s.deadline, Disabled: s.disabled})
if r.Submitted {
log("answer sent: " + s.answer)
+78 -78
View File
@@ -2,85 +2,85 @@ package main
// Code snippets mirror example_calendar_test.go.
const (
calCalendarMonthViewCode = `func exampleEvents(loc *time.Location) []mujica.CalendarEvent {
calCalendarMonthViewCode = `func exampleEvents(loc *time.Location) []datetime.CalendarEvent {
start := time.Date(2026, time.October, 14, 9, 0, 0, 0, loc)
return []mujica.CalendarEvent{
return []datetime.CalendarEvent{
{ID: "rehearsal", Title: "Rehearsal", Start: start, End: start.Add(2 * time.Hour), Location: "Hall"},
{ID: "fitting", Title: "Fitting", Start: start.Add(time.Hour), End: start.Add(90 * time.Minute), Color: mujica.EventInfo},
{ID: "fitting", Title: "Fitting", Start: start.Add(time.Hour), End: start.Add(90 * time.Minute), Color: datetime.EventInfo},
}
}
loc := time.Local
events := exampleEvents(loc)
month := mujica.YearMonth{Year: 2026, Month: time.October}
month := datetime.YearMonth{Year: 2026, Month: time.October}
view := func(c *ui.Context) {
mujica.Use(c, mujica.Settings{})
r := mujica.CalendarMonthView(c, &month, events, mujica.CalendarMonthViewOptions{Location: loc})
core.Use(c, core.Settings{})
r := datetime.CalendarMonthView(c, &month, events, datetime.CalendarMonthViewOptions{Location: loc})
if ch, ok := r.Changed(); ok {
fmt.Println("move", ch.ID, "to", ch.Start)
}
}
ui.Render(view, 400, 300, 1)`
calCalendarWeekViewCode = `func exampleEvents(loc *time.Location) []mujica.CalendarEvent {
calCalendarWeekViewCode = `func exampleEvents(loc *time.Location) []datetime.CalendarEvent {
start := time.Date(2026, time.October, 14, 9, 0, 0, 0, loc)
return []mujica.CalendarEvent{
return []datetime.CalendarEvent{
{ID: "rehearsal", Title: "Rehearsal", Start: start, End: start.Add(2 * time.Hour), Location: "Hall"},
{ID: "fitting", Title: "Fitting", Start: start.Add(time.Hour), End: start.Add(90 * time.Minute), Color: mujica.EventInfo},
{ID: "fitting", Title: "Fitting", Start: start.Add(time.Hour), End: start.Add(90 * time.Minute), Color: datetime.EventInfo},
}
}
loc := time.Local
events := exampleEvents(loc)
week := mujica.Date{Year: 2026, Month: time.October, Day: 14}
week := core.Date{Year: 2026, Month: time.October, Day: 14}
view := func(c *ui.Context) {
mujica.Use(c, mujica.Settings{})
r := mujica.CalendarWeekView(c, &week, events, mujica.CalendarWeekViewOptions{Location: loc, SlotMinutes: 15})
if ch, ok := r.Changed(); ok && ch.Kind == mujica.CalendarCreate {
core.Use(c, core.Settings{})
r := datetime.CalendarWeekView(c, &week, events, datetime.CalendarWeekViewOptions{Location: loc, SlotMinutes: 15})
if ch, ok := r.Changed(); ok && ch.Kind == datetime.CalendarCreate {
fmt.Println("new event", ch.Start, ch.End)
}
}
ui.Render(view, 400, 300, 1)`
calCalendarDayViewCode = `func exampleEvents(loc *time.Location) []mujica.CalendarEvent {
calCalendarDayViewCode = `func exampleEvents(loc *time.Location) []datetime.CalendarEvent {
start := time.Date(2026, time.October, 14, 9, 0, 0, 0, loc)
return []mujica.CalendarEvent{
return []datetime.CalendarEvent{
{ID: "rehearsal", Title: "Rehearsal", Start: start, End: start.Add(2 * time.Hour), Location: "Hall"},
{ID: "fitting", Title: "Fitting", Start: start.Add(time.Hour), End: start.Add(90 * time.Minute), Color: mujica.EventInfo},
{ID: "fitting", Title: "Fitting", Start: start.Add(time.Hour), End: start.Add(90 * time.Minute), Color: datetime.EventInfo},
}
}
loc := time.Local
events := exampleEvents(loc)
day := mujica.Date{Year: 2026, Month: time.October, Day: 14}
day := core.Date{Year: 2026, Month: time.October, Day: 14}
view := func(c *ui.Context) {
mujica.Use(c, mujica.Settings{})
if id, ok := mujica.CalendarDayView(c, &day, events, mujica.CalendarDayViewOptions{Location: loc}).Opened(); ok {
core.Use(c, core.Settings{})
if id, ok := datetime.CalendarDayView(c, &day, events, datetime.CalendarDayViewOptions{Location: loc}).Opened(); ok {
fmt.Println("open", id)
}
}
ui.Render(view, 400, 300, 1)`
calAgendaViewCode = `func exampleEvents(loc *time.Location) []mujica.CalendarEvent {
calAgendaViewCode = `func exampleEvents(loc *time.Location) []datetime.CalendarEvent {
start := time.Date(2026, time.October, 14, 9, 0, 0, 0, loc)
return []mujica.CalendarEvent{
return []datetime.CalendarEvent{
{ID: "rehearsal", Title: "Rehearsal", Start: start, End: start.Add(2 * time.Hour), Location: "Hall"},
{ID: "fitting", Title: "Fitting", Start: start.Add(time.Hour), End: start.Add(90 * time.Minute), Color: mujica.EventInfo},
{ID: "fitting", Title: "Fitting", Start: start.Add(time.Hour), End: start.Add(90 * time.Minute), Color: datetime.EventInfo},
}
}
loc := time.Local
events := exampleEvents(loc)
view := func(c *ui.Context) {
mujica.Use(c, mujica.Settings{})
opts := mujica.AgendaViewOptions{Location: loc, From: mujica.Date{Year: 2026, Month: time.October, Day: 12}, Days: 14}
if id, ok := mujica.AgendaView(c, events, opts).Opened(); ok {
core.Use(c, core.Settings{})
opts := datetime.AgendaViewOptions{Location: loc, From: core.Date{Year: 2026, Month: time.October, Day: 12}, Days: 14}
if id, ok := datetime.AgendaView(c, events, opts).Opened(); ok {
fmt.Println("open", id)
}
}
ui.Render(view, 400, 300, 1)`
calYearViewCode = `func exampleEvents(loc *time.Location) []mujica.CalendarEvent {
calYearViewCode = `func exampleEvents(loc *time.Location) []datetime.CalendarEvent {
start := time.Date(2026, time.October, 14, 9, 0, 0, 0, loc)
return []mujica.CalendarEvent{
return []datetime.CalendarEvent{
{ID: "rehearsal", Title: "Rehearsal", Start: start, End: start.Add(2 * time.Hour), Location: "Hall"},
{ID: "fitting", Title: "Fitting", Start: start.Add(time.Hour), End: start.Add(90 * time.Minute), Color: mujica.EventInfo},
{ID: "fitting", Title: "Fitting", Start: start.Add(time.Hour), End: start.Add(90 * time.Minute), Color: datetime.EventInfo},
}
}
@@ -88,52 +88,52 @@ loc := time.Local
events := exampleEvents(loc)
year := 2026
view := func(c *ui.Context) {
mujica.Use(c, mujica.Settings{})
if day, ok := mujica.YearView(c, &year, events, mujica.YearViewOptions{Location: loc}).DayChosen(); ok {
core.Use(c, core.Settings{})
if day, ok := datetime.YearView(c, &year, events, datetime.YearViewOptions{Location: loc}).DayChosen(); ok {
fmt.Println("go to", day)
}
}
ui.Render(view, 400, 300, 1)`
calEventChipCode = `func exampleEvents(loc *time.Location) []mujica.CalendarEvent {
calEventChipCode = `func exampleEvents(loc *time.Location) []datetime.CalendarEvent {
start := time.Date(2026, time.October, 14, 9, 0, 0, 0, loc)
return []mujica.CalendarEvent{
return []datetime.CalendarEvent{
{ID: "rehearsal", Title: "Rehearsal", Start: start, End: start.Add(2 * time.Hour), Location: "Hall"},
{ID: "fitting", Title: "Fitting", Start: start.Add(time.Hour), End: start.Add(90 * time.Minute), Color: mujica.EventInfo},
{ID: "fitting", Title: "Fitting", Start: start.Add(time.Hour), End: start.Add(90 * time.Minute), Color: datetime.EventInfo},
}
}
loc := time.Local
ev := exampleEvents(loc)[0]
view := func(c *ui.Context) {
mujica.Use(c, mujica.Settings{})
if mujica.EventChip(c, ev, mujica.EventChipOptions{Location: loc}).Clicked() {
core.Use(c, core.Settings{})
if datetime.EventChip(c, ev, datetime.EventChipOptions{Location: loc}).Clicked() {
fmt.Println("open", ev.ID)
}
}
ui.Render(view, 400, 300, 1)`
calEventEditorCode = `func exampleEvents(loc *time.Location) []mujica.CalendarEvent {
calEventEditorCode = `func exampleEvents(loc *time.Location) []datetime.CalendarEvent {
start := time.Date(2026, time.October, 14, 9, 0, 0, 0, loc)
return []mujica.CalendarEvent{
return []datetime.CalendarEvent{
{ID: "rehearsal", Title: "Rehearsal", Start: start, End: start.Add(2 * time.Hour), Location: "Hall"},
{ID: "fitting", Title: "Fitting", Start: start.Add(time.Hour), End: start.Add(90 * time.Minute), Color: mujica.EventInfo},
{ID: "fitting", Title: "Fitting", Start: start.Add(time.Hour), End: start.Add(90 * time.Minute), Color: datetime.EventInfo},
}
}
loc := time.Local
ev := exampleEvents(loc)[0]
view := func(c *ui.Context) {
mujica.Use(c, mujica.Settings{})
r := mujica.EventEditor(c, &ev, mujica.EventEditorOptions{Location: loc})
core.Use(c, core.Settings{})
r := datetime.EventEditor(c, &ev, datetime.EventEditorOptions{Location: loc})
if r.Saved() {
fmt.Println("saved", ev.Title, ev.Start)
}
}
ui.Render(view, 400, 300, 1)`
calEventPopoverCode = `func exampleEvents(loc *time.Location) []mujica.CalendarEvent {
calEventPopoverCode = `func exampleEvents(loc *time.Location) []datetime.CalendarEvent {
start := time.Date(2026, time.October, 14, 9, 0, 0, 0, loc)
return []mujica.CalendarEvent{
return []datetime.CalendarEvent{
{ID: "rehearsal", Title: "Rehearsal", Start: start, End: start.Add(2 * time.Hour), Location: "Hall"},
{ID: "fitting", Title: "Fitting", Start: start.Add(time.Hour), End: start.Add(90 * time.Minute), Color: mujica.EventInfo},
{ID: "fitting", Title: "Fitting", Start: start.Add(time.Hour), End: start.Add(90 * time.Minute), Color: datetime.EventInfo},
}
}
@@ -141,88 +141,88 @@ loc := time.Local
ev := exampleEvents(loc)[0]
open := false
view := func(c *ui.Context) {
mujica.Use(c, mujica.Settings{})
chip := mujica.EventChip(c, ev, mujica.EventChipOptions{Location: loc})
core.Use(c, core.Settings{})
chip := datetime.EventChip(c, ev, datetime.EventChipOptions{Location: loc})
if chip.Clicked() {
open = true
}
r := mujica.EventPopover(c, chip, &open, ev, mujica.EventPopoverOptions{Location: loc})
r := datetime.EventPopover(c, chip, &open, ev, datetime.EventPopoverOptions{Location: loc})
if r.Delete() {
fmt.Println("delete", ev.ID)
}
}
ui.Render(view, 400, 300, 1)`
calCurrentTimeIndicatorCode = `view := func(c *ui.Context) {
mujica.Use(c, mujica.Settings{})
core.Use(c, core.Settings{})
ui.Box(c).Size(200, 240).Children(func() {
mujica.CurrentTimeIndicator(c, mujica.CurrentTimeIndicatorOptions{Location: time.Local})
datetime.CurrentTimeIndicator(c, datetime.CurrentTimeIndicatorOptions{Location: time.Local})
})
}
ui.Render(view, 400, 300, 1)`
calRecurrenceEditorCode = `rule := mujica.Recurrence{Unit: mujica.RepeatWeekly, Interval: 1, Weekdays: []time.Weekday{time.Monday}}
calRecurrenceEditorCode = `rule := datetime.Recurrence{Unit: datetime.RepeatWeekly, Interval: 1, Weekdays: []time.Weekday{time.Monday}}
view := func(c *ui.Context) {
mujica.Use(c, mujica.Settings{})
r := mujica.RecurrenceEditor(c, &rule, mujica.RecurrenceEditorOptions{Start: mujica.Date{Year: 2026, Month: time.October, Day: 5}})
core.Use(c, core.Settings{})
r := datetime.RecurrenceEditor(c, &rule, datetime.RecurrenceEditorOptions{Start: core.Date{Year: 2026, Month: time.October, Day: 5}})
if r.Changed() && r.Problem() == "" {
fmt.Println(mujica.RecurrenceSummary(c, rule))
fmt.Println(datetime.RecurrenceSummary(c, rule))
}
}
ui.Render(view, 400, 300, 1)`
calAvailabilityPickerCode = `days := []mujica.Date{{Year: 2026, Month: time.October, Day: 12}, {Year: 2026, Month: time.October, Day: 13}}
calAvailabilityPickerCode = `days := []core.Date{{Year: 2026, Month: time.October, Day: 12}, {Year: 2026, Month: time.October, Day: 13}}
mine := [][]bool{make([]bool, 18), make([]bool, 18)}
view := func(c *ui.Context) {
mujica.Use(c, mujica.Settings{})
if mujica.AvailabilityPicker(c, &mine, mujica.AvailabilityPickerOptions{Days: days}).Changed() {
core.Use(c, core.Settings{})
if datetime.AvailabilityPicker(c, &mine, datetime.AvailabilityPickerOptions{Days: days}).Changed() {
fmt.Println("availability changed")
}
}
ui.Render(view, 400, 300, 1)`
calTimezoneSelectCode = `zone := "Asia/Shanghai"
view := func(c *ui.Context) {
mujica.Use(c, mujica.Settings{})
if mujica.TimezoneSelect(c, &zone, mujica.TimezoneSelectOptions{}).Changed() {
core.Use(c, core.Settings{})
if datetime.TimezoneSelect(c, &zone, datetime.TimezoneSelectOptions{}).Changed() {
fmt.Println("zone", zone)
}
}
ui.Render(view, 400, 300, 1)`
calReminderPickerCode = `reminders := []time.Duration{15 * time.Minute}
view := func(c *ui.Context) {
mujica.Use(c, mujica.Settings{})
if mujica.ReminderPicker(c, &reminders, mujica.ReminderPickerOptions{Max: 3}).Changed() {
core.Use(c, core.Settings{})
if datetime.ReminderPicker(c, &reminders, datetime.ReminderPickerOptions{Max: 3}).Changed() {
fmt.Println("reminders", reminders)
}
}
ui.Render(view, 400, 300, 1)`
calAttendeeListCode = `people := []mujica.Attendee{{Name: "Sakiko", Status: mujica.AttendeeAccepted, Organizer: true}, {Name: "Mutsumi"}}
calAttendeeListCode = `people := []datetime.Attendee{{Name: "Sakiko", Status: datetime.AttendeeAccepted, Organizer: true}, {Name: "Mutsumi"}}
view := func(c *ui.Context) {
mujica.Use(c, mujica.Settings{})
if i, ok := mujica.AttendeeList(c, people, mujica.AttendeeListOptions{Removable: true}).Removed(); ok {
core.Use(c, core.Settings{})
if i, ok := datetime.AttendeeList(c, people, datetime.AttendeeListOptions{Removable: true}).Removed(); ok {
people = append(people[:i], people[i+1:]...)
}
}
ui.Render(view, 400, 300, 1)`
calWeekPickerCode = `var week mujica.Date
calWeekPickerCode = `var week core.Date
view := func(c *ui.Context) {
mujica.Use(c, mujica.Settings{})
if mujica.WeekPicker(c, &week, mujica.WeekPickerOptions{WeekStart: mujica.WeekStartMonday, Clearable: true}).Changed() {
core.Use(c, core.Settings{})
if datetime.WeekPicker(c, &week, datetime.WeekPickerOptions{WeekStart: datetime.WeekStartMonday, Clearable: true}).Changed() {
fmt.Println("week of", week)
}
}
ui.Render(view, 400, 300, 1)`
calDurationPickerCode = `length := 90 * time.Minute
view := func(c *ui.Context) {
mujica.Use(c, mujica.Settings{})
opts := mujica.DurationPickerOptions{Min: 15 * time.Minute, Max: 8 * time.Hour}
if mujica.DurationPicker(c, &length, opts).Changed() {
core.Use(c, core.Settings{})
opts := datetime.DurationPickerOptions{Min: 15 * time.Minute, Max: 8 * time.Hour}
if datetime.DurationPicker(c, &length, opts).Changed() {
fmt.Println("length", length)
}
}
ui.Render(view, 400, 300, 1)`
calCronEditorCode = `expr := "0 9 * * MON-FRI"
view := func(c *ui.Context) {
mujica.Use(c, mujica.Settings{})
if mujica.CronEditor(c, &expr, mujica.CronEditorOptions{Location: time.Local}).Changed() {
if _, err := mujica.ParseCron(expr); err == nil {
core.Use(c, core.Settings{})
if datetime.CronEditor(c, &expr, datetime.CronEditorOptions{Location: time.Local}).Changed() {
if _, err := datetime.ParseCron(expr); err == nil {
fmt.Println("schedule", expr)
}
}
@@ -230,22 +230,22 @@ view := func(c *ui.Context) {
ui.Render(view, 400, 300, 1)`
calRelativeTimeCode = `posted := time.Date(2026, time.October, 5, 9, 0, 0, 0, time.Local)
view := func(c *ui.Context) {
mujica.Use(c, mujica.Settings{})
mujica.RelativeTime(c, posted, mujica.RelativeTimeOptions{Location: time.Local})
core.Use(c, core.Settings{})
datetime.RelativeTime(c, posted, datetime.RelativeTimeOptions{Location: time.Local})
}
ui.Render(view, 400, 300, 1)`
calCountdownCode = `deadline := time.Date(2026, time.December, 31, 23, 59, 59, 0, time.Local)
view := func(c *ui.Context) {
mujica.Use(c, mujica.Settings{})
if mujica.Countdown(c, deadline, mujica.CountdownOptions{}).Finished() {
core.Use(c, core.Settings{})
if datetime.Countdown(c, deadline, datetime.CountdownOptions{}).Finished() {
fmt.Println("happy new year")
}
}
ui.Render(view, 400, 300, 1)`
calStopwatchCode = `var sw mujica.StopwatchState
calStopwatchCode = `var sw datetime.StopwatchState
view := func(c *ui.Context) {
mujica.Use(c, mujica.Settings{})
if mujica.Stopwatch(c, &sw, mujica.StopwatchOptions{}).Changed() {
core.Use(c, core.Settings{})
if datetime.Stopwatch(c, &sw, datetime.StopwatchOptions{}).Changed() {
fmt.Println("laps", len(sw.Laps))
}
}
+43 -41
View File
@@ -7,7 +7,9 @@ import (
"github.com/egoist/mygo/ui"
mujica "mujicaui"
"github.com/ZacharyZhang-NY/MujicaUI/core"
"github.com/ZacharyZhang-NY/MujicaUI/datetime"
"github.com/ZacharyZhang-NY/MujicaUI/input"
)
var calColors = []string{"Accent", "Info", "Success", "Warning", "Danger", "Neutral"}
@@ -20,19 +22,19 @@ func calChipDemo(c *ui.Context, log func(string)) {
s := ui.Local(c.Root(), "426", func() state { return state{color: "Accent"} })
dtSection(c, "EventChip", func() {
dtControls(c, func() {
mujica.Select(c, &s.color, calOptions(calColors), mujica.SelectOptions{Label: "Color"})
mujica.Checkbox(c, &s.inline, "Inline", mujica.CheckboxOptions{})
mujica.Checkbox(c, &s.selected, "Selected", mujica.CheckboxOptions{})
mujica.Checkbox(c, &s.disabled, "Disabled", mujica.CheckboxOptions{})
input.Select(c, &s.color, calOptions(calColors), input.SelectOptions{Label: "Color"})
input.Checkbox(c, &s.inline, "Inline", input.CheckboxOptions{})
input.Checkbox(c, &s.selected, "Selected", input.CheckboxOptions{})
input.Checkbox(c, &s.disabled, "Disabled", input.CheckboxOptions{})
})
ev := calSample()[0]
for i, n := range calColors {
if n == s.color {
ev.Color = mujica.EventColor(i)
ev.Color = datetime.EventColor(i)
}
}
ui.Box(c).Width(240).Children(func() {
if mujica.EventChip(c, ev, mujica.EventChipOptions{Location: time.Local, Inline: s.inline, Selected: s.selected, Disabled: s.disabled}).Clicked() {
if datetime.EventChip(c, ev, datetime.EventChipOptions{Location: time.Local, Inline: s.inline, Selected: s.selected, Disabled: s.disabled}).Clicked() {
log("clicked " + ev.Title)
}
})
@@ -41,13 +43,13 @@ func calChipDemo(c *ui.Context, log func(string)) {
func calEditorDemo(c *ui.Context, log func(string)) {
type state struct {
ev mujica.CalendarEvent
ev datetime.CalendarEvent
disabled bool
}
s := ui.Local(c.Root(), "427", func() state { return state{ev: calSample()[0]} })
dtSection(c, "EventEditor", func() {
dtControls(c, func() { mujica.Checkbox(c, &s.disabled, "Disabled", mujica.CheckboxOptions{}) })
r := mujica.EventEditor(c, &s.ev, mujica.EventEditorOptions{Location: time.Local, Disabled: s.disabled})
dtControls(c, func() { input.Checkbox(c, &s.disabled, "Disabled", input.CheckboxOptions{}) })
r := datetime.EventEditor(c, &s.ev, datetime.EventEditorOptions{Location: time.Local, Disabled: s.disabled})
if r.Saved() {
log(fmt.Sprintf("saved %q %s, %d guests", s.ev.Title, s.ev.Start.Format("Jan 2 15:04"), len(s.ev.Attendees)))
}
@@ -64,19 +66,19 @@ func calPopoverDemo(c *ui.Context, log func(string)) {
s := ui.Local(c.Root(), "428", func() state { return state{} })
dtSection(c, "EventPopover", func() {
dtControls(c, func() {
mujica.Checkbox(c, &s.readOnly, "Read-only", mujica.CheckboxOptions{})
mujica.Checkbox(c, &s.disabled, "Disabled", mujica.CheckboxOptions{})
input.Checkbox(c, &s.readOnly, "Read-only", input.CheckboxOptions{})
input.Checkbox(c, &s.disabled, "Disabled", input.CheckboxOptions{})
})
ev := calSample()[0]
ev.Attendees = []mujica.Attendee{{Name: "Sakiko", Status: mujica.AttendeeAccepted, Organizer: true}, {Name: "Mutsumi"}}
ev.Attendees = []datetime.Attendee{{Name: "Sakiko", Status: datetime.AttendeeAccepted, Organizer: true}, {Name: "Mutsumi"}}
ev.Reminders = []time.Duration{15 * time.Minute}
ui.Box(c).Width(240).Children(func() {
chip := mujica.EventChip(c, ev, mujica.EventChipOptions{Location: time.Local, Disabled: s.disabled})
chip := datetime.EventChip(c, ev, datetime.EventChipOptions{Location: time.Local, Disabled: s.disabled})
if chip.Clicked() {
s.open = true
log("opened popover")
}
r := mujica.EventPopover(c, chip, &s.open, ev, mujica.EventPopoverOptions{Location: time.Local, ReadOnly: s.readOnly, Disabled: s.disabled})
r := datetime.EventPopover(c, chip, &s.open, ev, datetime.EventPopoverOptions{Location: time.Local, ReadOnly: s.readOnly, Disabled: s.disabled})
if r.Edit() {
log("edit " + ev.ID)
}
@@ -89,24 +91,24 @@ func calPopoverDemo(c *ui.Context, log func(string)) {
func calNowDemo(c *ui.Context, log func(string)) {
dtSection(c, "CurrentTimeIndicator", func() {
k := mujica.Tokens(c)
k := core.Tokens(c)
ui.Box(c).Size(320, 240).Border(1, k.Border).Children(func() {
mujica.CurrentTimeIndicator(c, mujica.CurrentTimeIndicatorOptions{Location: time.Local})
datetime.CurrentTimeIndicator(c, datetime.CurrentTimeIndicatorOptions{Location: time.Local})
})
})
}
func calRecurrenceDemo(c *ui.Context, log func(string)) {
type state struct {
rule mujica.Recurrence
rule datetime.Recurrence
disabled bool
}
s := ui.Local(c.Root(), "430", func() state { return state{} })
dtSection(c, "RecurrenceEditor", func() {
dtControls(c, func() { mujica.Checkbox(c, &s.disabled, "Disabled", mujica.CheckboxOptions{}) })
r := mujica.RecurrenceEditor(c, &s.rule, mujica.RecurrenceEditorOptions{Start: mujica.DateOf(time.Now()), Disabled: s.disabled})
dtControls(c, func() { input.Checkbox(c, &s.disabled, "Disabled", input.CheckboxOptions{}) })
r := datetime.RecurrenceEditor(c, &s.rule, datetime.RecurrenceEditorOptions{Start: core.DateOf(time.Now()), Disabled: s.disabled})
if r.Changed() {
log(mujica.RecurrenceSummary(c, s.rule) + strings.TrimSpace(" "+r.Problem()))
log(datetime.RecurrenceSummary(c, s.rule) + strings.TrimSpace(" "+r.Problem()))
}
})
}
@@ -114,11 +116,11 @@ func calRecurrenceDemo(c *ui.Context, log func(string)) {
func calAvailabilityDemo(c *ui.Context, log func(string)) {
type state struct {
mine, theirs [][]bool
days []mujica.Date
days []core.Date
others, frozen bool
}
s := ui.Local(c.Root(), "431", func() state {
today := mujica.DateOf(time.Now())
today := core.DateOf(time.Now())
grid := func(seed int) [][]bool {
g := make([][]bool, 5)
for i := range g {
@@ -129,19 +131,19 @@ func calAvailabilityDemo(c *ui.Context, log func(string)) {
}
return g
}
days := []mujica.Date{today, today.AddDays(1), today.AddDays(2), today.AddDays(3), today.AddDays(4)}
days := []core.Date{today, today.AddDays(1), today.AddDays(2), today.AddDays(3), today.AddDays(4)}
return state{mine: grid(2), theirs: grid(1), days: days, others: true}
})
dtSection(c, "AvailabilityPicker", func() {
dtControls(c, func() {
mujica.Checkbox(c, &s.others, "Show Mutsumi", mujica.CheckboxOptions{})
mujica.Checkbox(c, &s.frozen, "Disabled", mujica.CheckboxOptions{})
input.Checkbox(c, &s.others, "Show Mutsumi", input.CheckboxOptions{})
input.Checkbox(c, &s.frozen, "Disabled", input.CheckboxOptions{})
})
var others []mujica.AvailabilityPerson
var others []datetime.AvailabilityPerson
if s.others {
others = []mujica.AvailabilityPerson{{Name: "Mutsumi", Free: s.theirs}}
others = []datetime.AvailabilityPerson{{Name: "Mutsumi", Free: s.theirs}}
}
if mujica.AvailabilityPicker(c, &s.mine, mujica.AvailabilityPickerOptions{Days: s.days, Others: others, Disabled: s.frozen}).Changed() {
if datetime.AvailabilityPicker(c, &s.mine, datetime.AvailabilityPickerOptions{Days: s.days, Others: others, Disabled: s.frozen}).Changed() {
n := 0
for _, d := range s.mine {
for _, v := range d {
@@ -162,8 +164,8 @@ func calTimezoneDemo(c *ui.Context, log func(string)) {
}
s := ui.Local(c.Root(), "432", func() state { return state{zone: "Asia/Shanghai"} })
dtSection(c, "TimezoneSelect", func() {
dtControls(c, func() { mujica.Checkbox(c, &s.disabled, "Disabled", mujica.CheckboxOptions{}) })
if mujica.TimezoneSelect(c, &s.zone, mujica.TimezoneSelectOptions{Disabled: s.disabled}).Changed() {
dtControls(c, func() { input.Checkbox(c, &s.disabled, "Disabled", input.CheckboxOptions{}) })
if datetime.TimezoneSelect(c, &s.zone, datetime.TimezoneSelectOptions{Disabled: s.disabled}).Changed() {
log("zone " + s.zone)
}
})
@@ -178,13 +180,13 @@ func calReminderDemo(c *ui.Context, log func(string)) {
s := ui.Local(c.Root(), "433", func() state { return state{v: []time.Duration{15 * time.Minute}, max: "0"} })
dtSection(c, "ReminderPicker", func() {
dtControls(c, func() {
mujica.Select(c, &s.max, calOptions([]string{"0", "2", "3"}), mujica.SelectOptions{Label: "Max (0 = any)"})
mujica.Checkbox(c, &s.disabled, "Disabled", mujica.CheckboxOptions{})
input.Select(c, &s.max, calOptions([]string{"0", "2", "3"}), input.SelectOptions{Label: "Max (0 = any)"})
input.Checkbox(c, &s.disabled, "Disabled", input.CheckboxOptions{})
})
if limit := calAtoi(s.max); limit > 0 && len(s.v) > limit {
s.v = s.v[:limit]
}
if mujica.ReminderPicker(c, &s.v, mujica.ReminderPickerOptions{Max: calAtoi(s.max), Disabled: s.disabled}).Changed() {
if datetime.ReminderPicker(c, &s.v, datetime.ReminderPickerOptions{Max: calAtoi(s.max), Disabled: s.disabled}).Changed() {
log(fmt.Sprint("reminders ", s.v))
}
})
@@ -192,24 +194,24 @@ func calReminderDemo(c *ui.Context, log func(string)) {
func calAttendeeDemo(c *ui.Context, log func(string)) {
type state struct {
list []mujica.Attendee
list []datetime.Attendee
removable bool
}
people := func() []mujica.Attendee {
return []mujica.Attendee{{Name: "Sakiko Togawa", Status: mujica.AttendeeAccepted, Organizer: true},
{Name: "Mutsumi Wakaba", Status: mujica.AttendeeTentative}, {Name: "Uika Misumi", Status: mujica.AttendeeDeclined},
people := func() []datetime.Attendee {
return []datetime.Attendee{{Name: "Sakiko Togawa", Status: datetime.AttendeeAccepted, Organizer: true},
{Name: "Mutsumi Wakaba", Status: datetime.AttendeeTentative}, {Name: "Uika Misumi", Status: datetime.AttendeeDeclined},
{Name: "Nyamu Yuutenji", Email: "nyamu@example.com"}}
}
s := ui.Local(c.Root(), "434", func() state { return state{list: people(), removable: true} })
dtSection(c, "AttendeeList", func() {
dtControls(c, func() {
mujica.Checkbox(c, &s.removable, "Removable", mujica.CheckboxOptions{})
if mujica.Button(c, "Reset", mujica.ButtonOptions{Variant: mujica.Secondary}).Clicked() {
input.Checkbox(c, &s.removable, "Removable", input.CheckboxOptions{})
if input.Button(c, "Reset", input.ButtonOptions{Variant: input.Secondary}).Clicked() {
s.list = people()
log("reset")
}
})
if i, ok := mujica.AttendeeList(c, s.list, mujica.AttendeeListOptions{Removable: s.removable}).Removed(); ok {
if i, ok := datetime.AttendeeList(c, s.list, datetime.AttendeeListOptions{Removable: s.removable}).Removed(); ok {
log("removed " + s.list[i].Name)
s.list = append(s.list[:i:i], s.list[i+1:]...)
}
+27 -25
View File
@@ -5,26 +5,28 @@ import (
"github.com/egoist/mygo/ui"
mujica "mujicaui"
"github.com/ZacharyZhang-NY/MujicaUI/core"
"github.com/ZacharyZhang-NY/MujicaUI/datetime"
"github.com/ZacharyZhang-NY/MujicaUI/input"
)
func calWeekPickerDemo(c *ui.Context, log func(string)) {
type state struct {
week mujica.Date
week core.Date
weekStart string
disabled bool
}
s := ui.Local(c.Root(), "435", func() state { return state{weekStart: "Locale"} })
dtSection(c, "WeekPicker", func() {
dtControls(c, func() {
if mujica.Select(c, &s.weekStart, calOptions(calWeekStarts), mujica.SelectOptions{Label: "Week start"}).Changed() {
s.week = mujica.Date{}
if input.Select(c, &s.weekStart, calOptions(calWeekStarts), input.SelectOptions{Label: "Week start"}).Changed() {
s.week = core.Date{}
log("week start " + s.weekStart + ", value cleared")
}
mujica.Checkbox(c, &s.disabled, "Disabled", mujica.CheckboxOptions{})
input.Checkbox(c, &s.disabled, "Disabled", input.CheckboxOptions{})
})
opts := mujica.WeekPickerOptions{WeekStart: calWeekStart(s.weekStart), Clearable: true, Disabled: s.disabled}
if mujica.WeekPicker(c, &s.week, opts).Changed() {
opts := datetime.WeekPickerOptions{WeekStart: calWeekStart(s.weekStart), Clearable: true, Disabled: s.disabled}
if datetime.WeekPicker(c, &s.week, opts).Changed() {
log("week of " + s.week.String())
}
})
@@ -38,11 +40,11 @@ func calDurationDemo(c *ui.Context, log func(string)) {
s := ui.Local(c.Root(), "436", func() state { return state{v: 90 * time.Minute} })
dtSection(c, "DurationPicker", func() {
dtControls(c, func() {
mujica.Checkbox(c, &s.seconds, "Seconds", mujica.CheckboxOptions{})
mujica.Checkbox(c, &s.disabled, "Disabled", mujica.CheckboxOptions{})
input.Checkbox(c, &s.seconds, "Seconds", input.CheckboxOptions{})
input.Checkbox(c, &s.disabled, "Disabled", input.CheckboxOptions{})
})
opts := mujica.DurationPickerOptions{Seconds: s.seconds, Min: 15 * time.Minute, Max: 8 * time.Hour, Disabled: s.disabled}
if mujica.DurationPicker(c, &s.v, opts).Changed() {
opts := datetime.DurationPickerOptions{Seconds: s.seconds, Min: 15 * time.Minute, Max: 8 * time.Hour, Disabled: s.disabled}
if datetime.DurationPicker(c, &s.v, opts).Changed() {
log("duration " + s.v.String())
}
})
@@ -55,9 +57,9 @@ func calCronDemo(c *ui.Context, log func(string)) {
}
s := ui.Local(c.Root(), "437", func() state { return state{expr: "*/15 9-17 * * MON-FRI"} })
dtSection(c, "CronEditor", func() {
dtControls(c, func() { mujica.Checkbox(c, &s.disabled, "Disabled", mujica.CheckboxOptions{}) })
if mujica.CronEditor(c, &s.expr, mujica.CronEditorOptions{Location: time.Local, Disabled: s.disabled}).Changed() {
if _, err := mujica.ParseCron(s.expr); err != nil {
dtControls(c, func() { input.Checkbox(c, &s.disabled, "Disabled", input.CheckboxOptions{}) })
if datetime.CronEditor(c, &s.expr, datetime.CronEditorOptions{Location: time.Local, Disabled: s.disabled}).Changed() {
if _, err := datetime.ParseCron(s.expr); err != nil {
log("invalid: " + s.expr)
} else {
log("schedule " + s.expr)
@@ -75,13 +77,13 @@ func calRelativeDemo(c *ui.Context, log func(string)) {
label string
d time.Duration
}{{"Now", 0}, {"5 minutes ago", -5 * time.Minute}, {"In 3 hours", 3 * time.Hour}, {"40 days ago", -40 * 24 * time.Hour}} {
if mujica.Button(c, b.label, mujica.ButtonOptions{Variant: mujica.Secondary}).Clicked() {
if input.Button(c, b.label, input.ButtonOptions{Variant: input.Secondary}).Clicked() {
s.at = time.Now().Add(b.d)
log("time set: " + b.label)
}
}
})
mujica.RelativeTime(c, s.at, mujica.RelativeTimeOptions{Location: time.Local})
datetime.RelativeTime(c, s.at, datetime.RelativeTimeOptions{Location: time.Local})
})
}
@@ -93,17 +95,17 @@ func calCountdownDemo(c *ui.Context, log func(string)) {
s := ui.Local(c.Root(), "439", func() state { return state{deadline: time.Now().Add(10 * time.Second)} })
dtSection(c, "Countdown", func() {
dtControls(c, func() {
if mujica.Button(c, "Restart 10 s", mujica.ButtonOptions{Variant: mujica.Secondary}).Clicked() {
if input.Button(c, "Restart 10 s", input.ButtonOptions{Variant: input.Secondary}).Clicked() {
s.deadline = time.Now().Add(10 * time.Second)
log("restarted")
}
mujica.Checkbox(c, &s.words, "Words", mujica.CheckboxOptions{})
input.Checkbox(c, &s.words, "Words", input.CheckboxOptions{})
})
format := mujica.CountdownClock
format := datetime.CountdownClock
if s.words {
format = mujica.CountdownWords
format = datetime.CountdownWords
}
if mujica.Countdown(c, s.deadline, mujica.CountdownOptions{Format: format}).Finished() {
if datetime.Countdown(c, s.deadline, datetime.CountdownOptions{Format: format}).Finished() {
log("finished")
}
})
@@ -111,18 +113,18 @@ func calCountdownDemo(c *ui.Context, log func(string)) {
func calStopwatchDemo(c *ui.Context, log func(string)) {
type state struct {
sw mujica.StopwatchState
sw datetime.StopwatchState
disabled bool
}
s := ui.Local(c.Root(), "440", func() state { return state{} })
dtSection(c, "Stopwatch", func() {
dtControls(c, func() { mujica.Checkbox(c, &s.disabled, "Disabled", mujica.CheckboxOptions{}) })
if mujica.Stopwatch(c, &s.sw, mujica.StopwatchOptions{Disabled: s.disabled}).Changed() {
dtControls(c, func() { input.Checkbox(c, &s.disabled, "Disabled", input.CheckboxOptions{}) })
if datetime.Stopwatch(c, &s.sw, datetime.StopwatchOptions{Disabled: s.disabled}).Changed() {
state := "paused"
if s.sw.Running() {
state = "running"
}
log(state + ", " + mujica.StopwatchText(s.sw.Elapsed(time.Now())))
log(state + ", " + datetime.StopwatchText(s.sw.Elapsed(time.Now())))
}
})
}
+46 -44
View File
@@ -7,39 +7,41 @@ import (
"github.com/egoist/mygo/ui"
mujica "mujicaui"
"github.com/ZacharyZhang-NY/MujicaUI/core"
"github.com/ZacharyZhang-NY/MujicaUI/datetime"
"github.com/ZacharyZhang-NY/MujicaUI/input"
)
// calSample is demo events around today in the local zone.
func calSample() []mujica.CalendarEvent {
func calSample() []datetime.CalendarEvent {
now := time.Now()
day := time.Date(now.Year(), now.Month(), now.Day(), 0, 0, 0, 0, time.Local)
at := func(d, h, m, mins int) (time.Time, time.Time) {
s := day.AddDate(0, 0, d).Add(time.Duration(h)*time.Hour + time.Duration(m)*time.Minute)
return s, s.Add(time.Duration(mins) * time.Minute)
}
mk := func(id, title string, color mujica.EventColor, d, h, m, mins int) mujica.CalendarEvent {
mk := func(id, title string, color datetime.EventColor, d, h, m, mins int) datetime.CalendarEvent {
s, e := at(d, h, m, mins)
return mujica.CalendarEvent{ID: id, Title: title, Color: color, Start: s, End: e, Location: "Rehearsal hall"}
return datetime.CalendarEvent{ID: id, Title: title, Color: color, Start: s, End: e, Location: "Rehearsal hall"}
}
evs := []mujica.CalendarEvent{
mk("1", "Rehearsal", mujica.EventAccent, 0, 9, 0, 120), mk("2", "Costume fitting", mujica.EventInfo, 0, 10, 0, 60),
mk("3", "Lunch", mujica.EventSuccess, 0, 12, 30, 60), mk("4", "Lyrics review", mujica.EventWarning, 1, 14, 0, 90),
mk("5", "Press", mujica.EventDanger, 2, 16, 0, 45), mk("6", "Sound check", mujica.EventNeutral, 3, 18, 0, 60),
mk("7", "Encore notes", mujica.EventInfo, 0, 15, 0, 30), mk("8", "Stage walk", mujica.EventAccent, 0, 17, 0, 30),
evs := []datetime.CalendarEvent{
mk("1", "Rehearsal", datetime.EventAccent, 0, 9, 0, 120), mk("2", "Costume fitting", datetime.EventInfo, 0, 10, 0, 60),
mk("3", "Lunch", datetime.EventSuccess, 0, 12, 30, 60), mk("4", "Lyrics review", datetime.EventWarning, 1, 14, 0, 90),
mk("5", "Press", datetime.EventDanger, 2, 16, 0, 45), mk("6", "Sound check", datetime.EventNeutral, 3, 18, 0, 60),
mk("7", "Encore notes", datetime.EventInfo, 0, 15, 0, 30), mk("8", "Stage walk", datetime.EventAccent, 0, 17, 0, 30),
}
holiday := mk("9", "Masquerade", mujica.EventAccent, 4, 0, 0, 24*60)
holiday := mk("9", "Masquerade", datetime.EventAccent, 4, 0, 0, 24*60)
holiday.AllDay, holiday.Start = true, day.AddDate(0, 0, 4)
holiday.End = holiday.Start.AddDate(0, 0, 1)
return append(evs, holiday)
}
// calApply applies and logs a view change.
func calApply(evs *[]mujica.CalendarEvent, ch mujica.CalendarChange, log func(string)) {
func calApply(evs *[]datetime.CalendarEvent, ch datetime.CalendarChange, log func(string)) {
switch ch.Kind {
case mujica.CalendarCreate:
case datetime.CalendarCreate:
id := fmt.Sprint(len(*evs) + 100)
*evs = append(*evs, mujica.CalendarEvent{ID: id, Title: "New event", Start: ch.Start, End: ch.End})
*evs = append(*evs, datetime.CalendarEvent{ID: id, Title: "New event", Start: ch.Start, End: ch.End})
log("created " + ch.Start.Format("Jan 2 15:04") + " – " + ch.End.Format("15:04"))
return
}
@@ -49,14 +51,14 @@ func calApply(evs *[]mujica.CalendarEvent, ch mujica.CalendarChange, log func(st
}
}
verb := "moved"
if ch.Kind == mujica.CalendarResize {
if ch.Kind == datetime.CalendarResize {
verb = "resized"
}
log(fmt.Sprintf("%s %s to %s – %s", verb, ch.ID, ch.Start.Format("Jan 2 15:04"), ch.End.Format("15:04")))
}
// calViewResult logs what a view reported and applies its change.
func calViewResult(r mujica.CalendarViewResult, evs *[]mujica.CalendarEvent, version *int, log func(string)) {
func calViewResult(r datetime.CalendarViewResult, evs *[]datetime.CalendarEvent, version *int, log func(string)) {
if id, ok := r.Opened(); ok {
log("opened " + id)
}
@@ -71,10 +73,10 @@ func calViewResult(r mujica.CalendarViewResult, evs *[]mujica.CalendarEvent, ver
var calWeekStarts = []string{"Locale", "Sunday", "Monday", "Saturday"}
func calWeekStart(s string) mujica.WeekStart {
func calWeekStart(s string) datetime.WeekStart {
for i, n := range calWeekStarts {
if n == s {
return mujica.WeekStart(i)
return datetime.WeekStart(i)
}
}
panic("gallery: unknown week start " + s)
@@ -82,24 +84,24 @@ func calWeekStart(s string) mujica.WeekStart {
func calMonthDemo(c *ui.Context, log func(string)) {
type state struct {
month mujica.YearMonth
events []mujica.CalendarEvent
month datetime.YearMonth
events []datetime.CalendarEvent
version, maxRows int
weekStart, maxStr string
disabled bool
}
s := ui.Local(c.Root(), "421", func() state {
now := time.Now()
return state{month: mujica.YearMonth{Year: now.Year(), Month: now.Month()}, events: calSample(), weekStart: "Locale", maxStr: "3"}
return state{month: datetime.YearMonth{Year: now.Year(), Month: now.Month()}, events: calSample(), weekStart: "Locale", maxStr: "3"}
})
dtSection(c, "CalendarMonthView", func() {
dtControls(c, func() {
mujica.Select(c, &s.weekStart, calOptions(calWeekStarts), mujica.SelectOptions{Label: "Week start"})
mujica.Select(c, &s.maxStr, calOptions([]string{"1", "2", "3", "4"}), mujica.SelectOptions{Label: "Rows per day"})
mujica.Checkbox(c, &s.disabled, "Disabled", mujica.CheckboxOptions{})
input.Select(c, &s.weekStart, calOptions(calWeekStarts), input.SelectOptions{Label: "Week start"})
input.Select(c, &s.maxStr, calOptions([]string{"1", "2", "3", "4"}), input.SelectOptions{Label: "Rows per day"})
input.Checkbox(c, &s.disabled, "Disabled", input.CheckboxOptions{})
})
s.maxRows = calAtoi(s.maxStr)
r := mujica.CalendarMonthView(c, &s.month, s.events, mujica.CalendarMonthViewOptions{Location: time.Local, Version: s.version,
r := datetime.CalendarMonthView(c, &s.month, s.events, datetime.CalendarMonthViewOptions{Location: time.Local, Version: s.version,
WeekStart: calWeekStart(s.weekStart), MaxPerDay: s.maxRows, Disabled: s.disabled})
calViewResult(r, &s.events, &s.version, log)
})
@@ -107,24 +109,24 @@ func calMonthDemo(c *ui.Context, log func(string)) {
func calWeekDemo(c *ui.Context, log func(string)) {
type state struct {
week mujica.Date
events []mujica.CalendarEvent
week core.Date
events []datetime.CalendarEvent
version int
slot string
hour12 bool
disabled bool
}
s := ui.Local(c.Root(), "422", func() state {
return state{week: mujica.DateOf(time.Now()), events: calSample(), slot: "30"}
return state{week: core.DateOf(time.Now()), events: calSample(), slot: "30"}
})
dtSection(c, "CalendarWeekView", func() {
dtControls(c, func() {
mujica.Select(c, &s.slot, calOptions([]string{"15", "30", "60"}), mujica.SelectOptions{Label: "Slot minutes"})
mujica.Checkbox(c, &s.hour12, "12-hour", mujica.CheckboxOptions{})
mujica.Checkbox(c, &s.disabled, "Disabled", mujica.CheckboxOptions{})
input.Select(c, &s.slot, calOptions([]string{"15", "30", "60"}), input.SelectOptions{Label: "Slot minutes"})
input.Checkbox(c, &s.hour12, "12-hour", input.CheckboxOptions{})
input.Checkbox(c, &s.disabled, "Disabled", input.CheckboxOptions{})
})
slot := calAtoi(s.slot)
r := mujica.CalendarWeekView(c, &s.week, s.events, mujica.CalendarWeekViewOptions{Location: time.Local, Version: s.version,
r := datetime.CalendarWeekView(c, &s.week, s.events, datetime.CalendarWeekViewOptions{Location: time.Local, Version: s.version,
SlotMinutes: slot, Hour12: s.hour12, Disabled: s.disabled})
calViewResult(r, &s.events, &s.version, log)
})
@@ -132,32 +134,32 @@ func calWeekDemo(c *ui.Context, log func(string)) {
func calDayDemo(c *ui.Context, log func(string)) {
type state struct {
day mujica.Date
events []mujica.CalendarEvent
day core.Date
events []datetime.CalendarEvent
version int
disabled bool
}
s := ui.Local(c.Root(), "423", func() state { return state{day: mujica.DateOf(time.Now()), events: calSample()} })
s := ui.Local(c.Root(), "423", func() state { return state{day: core.DateOf(time.Now()), events: calSample()} })
dtSection(c, "CalendarDayView", func() {
dtControls(c, func() { mujica.Checkbox(c, &s.disabled, "Disabled", mujica.CheckboxOptions{}) })
r := mujica.CalendarDayView(c, &s.day, s.events, mujica.CalendarDayViewOptions{Location: time.Local, Version: s.version, Disabled: s.disabled})
dtControls(c, func() { input.Checkbox(c, &s.disabled, "Disabled", input.CheckboxOptions{}) })
r := datetime.CalendarDayView(c, &s.day, s.events, datetime.CalendarDayViewOptions{Location: time.Local, Version: s.version, Disabled: s.disabled})
calViewResult(r, &s.events, &s.version, log)
})
}
func calAgendaDemo(c *ui.Context, log func(string)) {
type state struct {
events []mujica.CalendarEvent
events []datetime.CalendarEvent
days string
}
s := ui.Local(c.Root(), "424", func() state { return state{events: calSample(), days: "7"} })
dtSection(c, "AgendaView", func() {
dtControls(c, func() {
mujica.Select(c, &s.days, calOptions([]string{"1", "7", "30"}), mujica.SelectOptions{Label: "Days"})
input.Select(c, &s.days, calOptions([]string{"1", "7", "30"}), input.SelectOptions{Label: "Days"})
})
days := calAtoi(s.days)
ui.Column(c).Height(420).Children(func() {
if id, ok := mujica.AgendaView(c, s.events, mujica.AgendaViewOptions{Location: time.Local, Days: days}).Opened(); ok {
if id, ok := datetime.AgendaView(c, s.events, datetime.AgendaViewOptions{Location: time.Local, Days: days}).Opened(); ok {
log("opened " + id)
}
})
@@ -167,11 +169,11 @@ func calAgendaDemo(c *ui.Context, log func(string)) {
func calYearDemo(c *ui.Context, log func(string)) {
type state struct {
year int
events []mujica.CalendarEvent
events []datetime.CalendarEvent
}
s := ui.Local(c.Root(), "425", func() state { return state{year: time.Now().Year(), events: calSample()} })
dtSection(c, "YearView", func() {
if d, ok := mujica.YearView(c, &s.year, s.events, mujica.YearViewOptions{Location: time.Local}).DayChosen(); ok {
if d, ok := datetime.YearView(c, &s.year, s.events, datetime.YearViewOptions{Location: time.Local}).DayChosen(); ok {
log("day " + d.String())
}
})
@@ -185,10 +187,10 @@ func calAtoi(s string) int {
return n
}
func calOptions(values []string) []mujica.SelectOption[string] {
out := make([]mujica.SelectOption[string], len(values))
func calOptions(values []string) []input.SelectOption[string] {
out := make([]input.SelectOption[string], len(values))
for i, v := range values {
out[i] = mujica.SelectOption[string]{Value: v, Label: v}
out[i] = input.SelectOption[string]{Value: v, Label: v}
}
return out
}
+10 -8
View File
@@ -5,8 +5,10 @@ import (
"github.com/egoist/mygo/ui"
mujica "mujicaui"
"mujicaui/theme"
"github.com/ZacharyZhang-NY/MujicaUI/chart"
"github.com/ZacharyZhang-NY/MujicaUI/core"
"github.com/ZacharyZhang-NY/MujicaUI/data"
"github.com/ZacharyZhang-NY/MujicaUI/theme"
)
const c1Category = "Charts I"
@@ -39,14 +41,14 @@ var charts1Pages = []page{
var c1Statuses = []string{"Ready", "Loading", "Failed", "Empty"}
// c1Frame shows the status picker and returns the frame.
func c1Frame(c *ui.Context, label string, status *string) (mujica.ChartFrame, bool) {
func c1Frame(c *ui.Context, label string, status *string) (chart.ChartFrame, bool) {
ui.Select(c, status, c1Statuses).Width(160).AlignSelf(ui.Start)
f := mujica.ChartFrame{Label: label}
f := chart.ChartFrame{Label: label}
switch *status {
case "Loading":
f.Status = mujica.DataLoading
f.Status = data.DataLoading
case "Failed":
f.Status, f.Error = mujica.DataFailed, "The archive did not answer"
f.Status, f.Error = data.DataFailed, "The archive did not answer"
}
return f, *status != "Empty"
}
@@ -54,14 +56,14 @@ func c1Frame(c *ui.Context, label string, status *string) (mujica.ChartFrame, bo
// c1Section lays out a title, controls, then the chart.
func c1Section(c *ui.Context, title string, controls, body func()) {
ui.Column(c).Gap(12).Children(func() {
ui.Text(c, title).Font(theme.SerifFont).FontSize(mujica.FontSize(c, theme.H3Size))
ui.Text(c, title).Font(theme.SerifFont).FontSize(core.FontSize(c, theme.H3Size))
ui.Row(c).Gap(16).Wrap().AlignItems(ui.Center).Children(controls)
ui.Column(c).Width(560).Children(body)
})
}
// c1Log records a chart's events in the gallery log.
func c1Log(log func(string), name string, r mujica.ChartResult) {
func c1Log(log func(string), name string, r chart.ChartResult) {
if r.Changed() {
log(fmt.Sprintf("%s: point %d", name, r.Point))
}
+64 -64
View File
@@ -2,173 +2,173 @@ package main
// Snippets are the bodies of the Examples in example_charts1_test.go.
const c1LineCode = `series := []mujica.ChartSeries{
const c1LineCode = `series := []chart.ChartSeries{
{Name: "Pews", Values: []float64{3, 5, math.NaN(), 4, 8}},
{Name: "Candles", Values: []float64{5, 2, 6, 7, 1}},
}
view := func(c *ui.Context) {
mujica.Use(c, mujica.Settings{})
mujica.LineChart(c, series, mujica.LineChartOptions{Frame: mujica.ChartFrame{Label: "Chapel"}})
core.Use(c, core.Settings{})
chart.LineChart(c, series, chart.LineChartOptions{Frame: chart.ChartFrame{Label: "Chapel"}})
}
ui.Render(view, 400, 300, 1)`
const c1AreaCode = `series := []mujica.ChartSeries{
const c1AreaCode = `series := []chart.ChartSeries{
{Name: "Matins", Values: []float64{3, 4, 6, 5}},
{Name: "Vespers", Values: []float64{2, 3, 2, 4}},
}
view := func(c *ui.Context) {
mujica.Use(c, mujica.Settings{})
mujica.AreaChart(c, series, mujica.AreaChartOptions{Frame: mujica.ChartFrame{Label: "Attendance"}, Stacked: true})
core.Use(c, core.Settings{})
chart.AreaChart(c, series, chart.AreaChartOptions{Frame: chart.ChartFrame{Label: "Attendance"}, Stacked: true})
}
ui.Render(view, 400, 300, 1)`
const c1BarCode = `categories := []string{"Nave", "Choir", "Crypt"}
series := []mujica.ChartSeries{{Name: "Gold", Values: []float64{4, -2, 3}}, {Name: "Silver", Values: []float64{2, 5, 1}}}
series := []chart.ChartSeries{{Name: "Gold", Values: []float64{4, -2, 3}}, {Name: "Silver", Values: []float64{2, 5, 1}}}
view := func(c *ui.Context) {
mujica.Use(c, mujica.Settings{})
mujica.BarChart(c, categories, series, mujica.BarChartOptions{Frame: mujica.ChartFrame{Label: "Tithes"}, Labels: true})
core.Use(c, core.Settings{})
chart.BarChart(c, categories, series, chart.BarChartOptions{Frame: chart.ChartFrame{Label: "Tithes"}, Labels: true})
}
ui.Render(view, 400, 300, 1)`
const c1PieCode = `slices := []mujica.ChartSlice{{Label: "Wax", Value: 50}, {Label: "Oil", Value: 30}, {Label: "Salt", Value: 4}, {Label: "Ash", Value: 3}}
const c1PieCode = `slices := []chart.ChartSlice{{Label: "Wax", Value: 50}, {Label: "Oil", Value: 30}, {Label: "Salt", Value: 4}, {Label: "Ash", Value: 3}}
view := func(c *ui.Context) {
mujica.Use(c, mujica.Settings{})
mujica.PieChart(c, slices, mujica.PieChartOptions{Frame: mujica.ChartFrame{Label: "Stores"}, Donut: true, MinAngle: 20})
core.Use(c, core.Settings{})
chart.PieChart(c, slices, chart.PieChartOptions{Frame: chart.ChartFrame{Label: "Stores"}, Donut: true, MinAngle: 20})
}
ui.Render(view, 400, 300, 1)`
const c1ScatterCode = `var selected []mujica.ScatterKey
groups := []mujica.ScatterGroup{{Name: "Monks", Points: []mujica.ScatterPoint{{X: 1, Y: 2, Size: 4}, {X: 3, Y: 5, Size: 9}}}}
const c1ScatterCode = `var selected []chart.ScatterKey
groups := []chart.ScatterGroup{{Name: "Monks", Points: []chart.ScatterPoint{{X: 1, Y: 2, Size: 4}, {X: 3, Y: 5, Size: 9}}}}
view := func(c *ui.Context) {
mujica.Use(c, mujica.Settings{})
mujica.ScatterChart(c, &selected, groups, mujica.ScatterChartOptions{Frame: mujica.ChartFrame{Label: "Choir"}, Bubbles: true})
core.Use(c, core.Settings{})
chart.ScatterChart(c, &selected, groups, chart.ScatterChartOptions{Frame: chart.ChartFrame{Label: "Choir"}, Bubbles: true})
}
ui.Render(view, 400, 300, 1)`
const c1RadarCode = `axes := []string{"Voice", "Latin", "Chant", "Organ"}
series := []mujica.ChartSeries{{Name: "Agnès", Values: []float64{8, 6, 9, 4}}}
series := []chart.ChartSeries{{Name: "Agnès", Values: []float64{8, 6, 9, 4}}}
view := func(c *ui.Context) {
mujica.Use(c, mujica.Settings{})
mujica.RadarChart(c, axes, series, mujica.RadarChartOptions{Frame: mujica.ChartFrame{Label: "Skills"}})
core.Use(c, core.Settings{})
chart.RadarChart(c, axes, series, chart.RadarChartOptions{Frame: chart.ChartFrame{Label: "Skills"}})
}
ui.Render(view, 400, 300, 1)`
const c1HeatCode = `rows, cols := []string{"Mon", "Tue"}, []string{"Matins", "Lauds", "Vespers"}
values := [][]float64{{1, 4, 9}, {3, math.NaN(), 7}}
view := func(c *ui.Context) {
mujica.Use(c, mujica.Settings{})
mujica.HeatmapChart(c, rows, cols, values, mujica.HeatmapChartOptions{Frame: mujica.ChartFrame{Label: "Hours"}})
core.Use(c, core.Settings{})
chart.HeatmapChart(c, rows, cols, values, chart.HeatmapChartOptions{Frame: chart.ChartFrame{Label: "Hours"}})
}
ui.Render(view, 400, 300, 1)`
const c1CalendarCode = `year := 2026
values := map[mujica.Date]float64{{Year: 2026, Month: time.March, Day: 4}: 5}
values := map[core.Date]float64{{Year: 2026, Month: time.March, Day: 4}: 5}
view := func(c *ui.Context) {
mujica.Use(c, mujica.Settings{})
mujica.CalendarHeatmap(c, &year, values, mujica.CalendarHeatmapOptions{Frame: mujica.ChartFrame{Label: "Prayers"}})
core.Use(c, core.Settings{})
chart.CalendarHeatmap(c, &year, values, chart.CalendarHeatmapOptions{Frame: chart.ChartFrame{Label: "Prayers"}})
}
ui.Render(view, 400, 300, 1)`
const c1HistogramCode = `values := []float64{1, 2, 2, 3, 4, 5, 9, 10}
view := func(c *ui.Context) {
mujica.Use(c, mujica.Settings{})
mujica.Histogram(c, values, mujica.HistogramOptions{Frame: mujica.ChartFrame{Label: "Ages"}, BinWidth: 5})
core.Use(c, core.Settings{})
chart.Histogram(c, values, chart.HistogramOptions{Frame: chart.ChartFrame{Label: "Ages"}, BinWidth: 5})
}
ui.Render(view, 400, 300, 1)`
const c1BoxCode = `groups := []mujica.BoxGroup{{Name: "Choir", Values: []float64{1, 2, 3, 4, 5, 40}}, {Name: "Nave", Values: []float64{4, 5, 6, 7}}}
const c1BoxCode = `groups := []chart.BoxGroup{{Name: "Choir", Values: []float64{1, 2, 3, 4, 5, 40}}, {Name: "Nave", Values: []float64{4, 5, 6, 7}}}
view := func(c *ui.Context) {
mujica.Use(c, mujica.Settings{})
mujica.BoxPlot(c, groups, mujica.BoxPlotOptions{Frame: mujica.ChartFrame{Label: "Hymns"}})
core.Use(c, core.Settings{})
chart.BoxPlot(c, groups, chart.BoxPlotOptions{Frame: chart.ChartFrame{Label: "Hymns"}})
}
ui.Render(view, 400, 300, 1)`
const c1WaterfallCode = `steps := []mujica.WaterfallStep{
const c1WaterfallCode = `steps := []chart.WaterfallStep{
{Label: "Tithes", Value: 12},
{Label: "Repairs", Value: -5},
{Label: "Year", Kind: mujica.WaterfallTotal},
{Label: "Year", Kind: chart.WaterfallTotal},
}
view := func(c *ui.Context) {
mujica.Use(c, mujica.Settings{})
mujica.WaterfallChart(c, steps, mujica.WaterfallChartOptions{Frame: mujica.ChartFrame{Label: "Ledger"}})
core.Use(c, core.Settings{})
chart.WaterfallChart(c, steps, chart.WaterfallChartOptions{Frame: chart.ChartFrame{Label: "Ledger"}})
}
ui.Render(view, 400, 300, 1)`
const c1FunnelCode = `stages := []mujica.ChartSlice{{Label: "Visitors", Value: 1000}, {Label: "Novices", Value: 400}, {Label: "Vows", Value: 100}}
const c1FunnelCode = `stages := []chart.ChartSlice{{Label: "Visitors", Value: 1000}, {Label: "Novices", Value: 400}, {Label: "Vows", Value: 100}}
view := func(c *ui.Context) {
mujica.Use(c, mujica.Settings{})
mujica.FunnelChart(c, stages, mujica.FunnelChartOptions{Frame: mujica.ChartFrame{Label: "Order"}})
core.Use(c, core.Settings{})
chart.FunnelChart(c, stages, chart.FunnelChartOptions{Frame: chart.ChartFrame{Label: "Order"}})
}
ui.Render(view, 400, 300, 1)`
const c1BulletCode = `target := 250.0
view := func(c *ui.Context) {
mujica.Use(c, mujica.Settings{})
mujica.BulletChart(c, 270, mujica.BulletChartOptions{Frame: mujica.ChartFrame{Label: "Alms"}, Target: &target, Bands: []float64{150, 225, 300}})
core.Use(c, core.Settings{})
chart.BulletChart(c, 270, chart.BulletChartOptions{Frame: chart.ChartFrame{Label: "Alms"}, Target: &target, Bands: []float64{150, 225, 300}})
}
ui.Render(view, 400, 300, 1)`
const c1SparkCode = `values := []float64{3, 5, 4, 7, 6, 9}
view := func(c *ui.Context) {
mujica.Use(c, mujica.Settings{})
mujica.Sparkline(c, values, mujica.SparklineOptions{Frame: mujica.ChartFrame{Label: "Bells"}, Kind: mujica.SparklineArea})
core.Use(c, core.Settings{})
chart.Sparkline(c, values, chart.SparklineOptions{Frame: chart.ChartFrame{Label: "Bells"}, Kind: chart.SparklineArea})
}
ui.Render(view, 400, 300, 1)`
const c1GaugeCode = `segments := []mujica.GaugeSegment{{To: 60, Label: "Calm", Tone: mujica.GaugeGood}, {To: 100, Label: "Crowded", Tone: mujica.GaugeDanger}}
const c1GaugeCode = `segments := []chart.GaugeSegment{{To: 60, Label: "Calm", Tone: chart.GaugeGood}, {To: 100, Label: "Crowded", Tone: chart.GaugeDanger}}
view := func(c *ui.Context) {
mujica.Use(c, mujica.Settings{})
mujica.Gauge(c, 72, mujica.GaugeOptions{Frame: mujica.ChartFrame{Label: "Nave"}, Max: 100, Unit: "%", Segments: segments})
core.Use(c, core.Settings{})
chart.Gauge(c, 72, chart.GaugeOptions{Frame: chart.ChartFrame{Label: "Nave"}, Max: 100, Unit: "%", Segments: segments})
}
ui.Render(view, 400, 300, 1)`
const c1TreemapCode = `var path []string
root := mujica.ChartNode{Name: "Abbey", Children: []mujica.ChartNode{
{Name: "Library", Children: []mujica.ChartNode{{Name: "Psalters", Value: 30}, {Name: "Maps", Value: 10}}},
root := chart.ChartNode{Name: "Abbey", Children: []chart.ChartNode{
{Name: "Library", Children: []chart.ChartNode{{Name: "Psalters", Value: 30}, {Name: "Maps", Value: 10}}},
{Name: "Kitchen", Value: 25},
}}
view := func(c *ui.Context) {
mujica.Use(c, mujica.Settings{})
mujica.Treemap(c, &path, root, mujica.TreemapOptions{Frame: mujica.ChartFrame{Label: "Rooms"}})
core.Use(c, core.Settings{})
chart.Treemap(c, &path, root, chart.TreemapOptions{Frame: chart.ChartFrame{Label: "Rooms"}})
}
ui.Render(view, 400, 300, 1)`
const c1SunburstCode = `var path []string
root := mujica.ChartNode{Name: "Abbey", Children: []mujica.ChartNode{
{Name: "Library", Children: []mujica.ChartNode{{Name: "Psalters", Value: 30}, {Name: "Maps", Value: 10}}},
root := chart.ChartNode{Name: "Abbey", Children: []chart.ChartNode{
{Name: "Library", Children: []chart.ChartNode{{Name: "Psalters", Value: 30}, {Name: "Maps", Value: 10}}},
{Name: "Kitchen", Value: 25},
}}
view := func(c *ui.Context) {
mujica.Use(c, mujica.Settings{})
mujica.Sunburst(c, &path, root, mujica.SunburstOptions{Frame: mujica.ChartFrame{Label: "Rooms"}})
core.Use(c, core.Settings{})
chart.Sunburst(c, &path, root, chart.SunburstOptions{Frame: chart.ChartFrame{Label: "Rooms"}})
}
ui.Render(view, 400, 300, 1)`
const c1SankeyCode = `nodes := []mujica.SankeyNode{{ID: "tithe", Label: "Tithes"}, {ID: "fund", Label: "Fund"}, {ID: "roof", Label: "Roof"}}
links := []mujica.SankeyLink{{From: "tithe", To: "fund", Value: 60}, {From: "fund", To: "roof", Value: 50}}
const c1SankeyCode = `nodes := []chart.SankeyNode{{ID: "tithe", Label: "Tithes"}, {ID: "fund", Label: "Fund"}, {ID: "roof", Label: "Roof"}}
links := []chart.SankeyLink{{From: "tithe", To: "fund", Value: 60}, {From: "fund", To: "roof", Value: 50}}
view := func(c *ui.Context) {
mujica.Use(c, mujica.Settings{})
mujica.SankeyChart(c, nodes, links, mujica.SankeyChartOptions{Frame: mujica.ChartFrame{Label: "Money"}})
core.Use(c, core.Settings{})
chart.SankeyChart(c, nodes, links, chart.SankeyChartOptions{Frame: chart.ChartFrame{Label: "Money"}})
}
ui.Render(view, 400, 300, 1)`
const c1NetworkCode = `var state mujica.NetworkGraphState
nodes := []mujica.GraphNode{{ID: "a", Label: "Abbot"}, {ID: "b", Label: "Prior"}, {ID: "c", Label: "Novice"}}
edges := []mujica.GraphEdge{{From: "a", To: "b"}, {From: "b", To: "c"}}
const c1NetworkCode = `var state chart.NetworkGraphState
nodes := []chart.GraphNode{{ID: "a", Label: "Abbot"}, {ID: "b", Label: "Prior"}, {ID: "c", Label: "Novice"}}
edges := []chart.GraphEdge{{From: "a", To: "b"}, {From: "b", To: "c"}}
view := func(c *ui.Context) {
mujica.Use(c, mujica.Settings{})
mujica.NetworkGraph(c, &state, nodes, edges, mujica.NetworkGraphOptions{Frame: mujica.ChartFrame{Label: "House"}, Force: true})
core.Use(c, core.Settings{})
chart.NetworkGraph(c, &state, nodes, edges, chart.NetworkGraphOptions{Frame: chart.ChartFrame{Label: "House"}, Force: true})
}
ui.Render(view, 400, 300, 1)`
const c1RealtimeCode = `buf := mujica.NewRealtimeBuffer(600, "Bell")
const c1RealtimeCode = `buf := chart.NewRealtimeBuffer(600, "Bell")
start := time.Date(2026, 10, 5, 9, 0, 0, 0, time.UTC)
for i := range 60 {
buf.Append(start.Add(time.Duration(i)*time.Second), float64(i%7))
}
var state mujica.RealtimeChartState
var state chart.RealtimeChartState
view := func(c *ui.Context) {
mujica.Use(c, mujica.Settings{})
mujica.RealtimeChart(c, &state, buf, mujica.RealtimeChartOptions{Frame: mujica.ChartFrame{Label: "Sound"}, Window: 30 * time.Second})
core.Use(c, core.Settings{})
chart.RealtimeChart(c, &state, buf, chart.RealtimeChartOptions{Frame: chart.ChartFrame{Label: "Sound"}, Window: 30 * time.Second})
}
ui.Render(view, 400, 300, 1)`
+22 -21
View File
@@ -8,14 +8,15 @@ import (
"github.com/egoist/mygo/ui"
mujica "mujicaui"
"github.com/ZacharyZhang-NY/MujicaUI/chart"
"github.com/ZacharyZhang-NY/MujicaUI/input"
)
var c1Abbey = mujica.ChartNode{Name: "Abbey", Children: []mujica.ChartNode{
{Name: "Library", Children: []mujica.ChartNode{{Name: "Psalters", Value: 30}, {Name: "Maps", Value: 10}, {Name: "Letters", Value: 8}}},
var c1Abbey = chart.ChartNode{Name: "Abbey", Children: []chart.ChartNode{
{Name: "Library", Children: []chart.ChartNode{{Name: "Psalters", Value: 30}, {Name: "Maps", Value: 10}, {Name: "Letters", Value: 8}}},
{Name: "Kitchen", Value: 25},
{Name: "Garden", Children: []mujica.ChartNode{{Name: "Herbs", Value: 12}, {Name: "Vines", Value: 8}}},
{Name: "Chapel", Children: []mujica.ChartNode{{Name: "Nave", Value: 18}, {Name: "Crypt", Value: 6}}},
{Name: "Garden", Children: []chart.ChartNode{{Name: "Herbs", Value: 12}, {Name: "Vines", Value: 8}}},
{Name: "Chapel", Children: []chart.ChartNode{{Name: "Nave", Value: 18}, {Name: "Crypt", Value: 6}}},
}}
func c1TreemapDemo(c *ui.Context, log func(string)) {
@@ -28,10 +29,10 @@ func c1TreemapDemo(c *ui.Context, log func(string)) {
frame, show := c1Frame(c, "Rooms", &s.status)
root := c1Abbey
if !show {
root = mujica.ChartNode{Name: "Abbey"}
root = chart.ChartNode{Name: "Abbey"}
s.path = nil
}
r := mujica.Treemap(c, &s.path, root, mujica.TreemapOptions{Frame: frame})
r := chart.Treemap(c, &s.path, root, chart.TreemapOptions{Frame: frame})
c1Log(log, "Treemap", r)
if r.ValueChanged() {
log("Treemap: path /" + strings.Join(s.path, "/"))
@@ -54,10 +55,10 @@ func c1SunburstDemo(c *ui.Context, log func(string)) {
frame.Height = 300
root := c1Abbey
if !show {
root = mujica.ChartNode{Name: "Abbey"}
root = chart.ChartNode{Name: "Abbey"}
s.path = nil
}
r := mujica.Sunburst(c, &s.path, root, mujica.SunburstOptions{Frame: frame, Depth: int(s.depth)})
r := chart.Sunburst(c, &s.path, root, chart.SunburstOptions{Frame: frame, Depth: int(s.depth)})
c1Log(log, "Sunburst", r)
if r.ValueChanged() {
log("Sunburst: path /" + strings.Join(s.path, "/"))
@@ -70,12 +71,12 @@ func c1SankeyDemo(c *ui.Context, log func(string)) {
s := ui.Local(c.Root(), "118", func() state { return state{status: "Ready"} })
c1Section(c, "SankeyChart", func() {}, func() {
frame, show := c1Frame(c, "Money", &s.status)
nodes := []mujica.SankeyNode{{ID: "tithe", Label: "Tithes"}, {ID: "alms", Label: "Alms"}, {ID: "fund", Label: "Fund"}, {ID: "roof", Label: "Roof"}, {ID: "bread", Label: "Bread"}, {ID: "bells", Label: "Bells"}}
links := []mujica.SankeyLink{{From: "tithe", To: "fund", Value: 60}, {From: "alms", To: "fund", Value: 30}, {From: "alms", To: "bread", Value: 10}, {From: "fund", To: "roof", Value: 50}, {From: "fund", To: "bells", Value: 40}}
nodes := []chart.SankeyNode{{ID: "tithe", Label: "Tithes"}, {ID: "alms", Label: "Alms"}, {ID: "fund", Label: "Fund"}, {ID: "roof", Label: "Roof"}, {ID: "bread", Label: "Bread"}, {ID: "bells", Label: "Bells"}}
links := []chart.SankeyLink{{From: "tithe", To: "fund", Value: 60}, {From: "alms", To: "fund", Value: 30}, {From: "alms", To: "bread", Value: 10}, {From: "fund", To: "roof", Value: 50}, {From: "fund", To: "bells", Value: 40}}
if !show {
links = nil
}
c1Log(log, "SankeyChart", mujica.SankeyChart(c, nodes, links, mujica.SankeyChartOptions{Frame: frame}))
c1Log(log, "SankeyChart", chart.SankeyChart(c, nodes, links, chart.SankeyChartOptions{Frame: frame}))
})
}
@@ -83,23 +84,23 @@ func c1NetworkDemo(c *ui.Context, log func(string)) {
type state struct {
status string
force bool
graph mujica.NetworkGraphState
graph chart.NetworkGraphState
}
s := ui.Local(c.Root(), "119", func() state { return state{status: "Ready", force: true} })
c1Section(c, "NetworkGraph", func() {
ui.Checkbox(c, &s.force, "Force layout")
if mujica.Button(c, "Reset view", mujica.ButtonOptions{Variant: mujica.Secondary}).Clicked() {
if input.Button(c, "Reset view", input.ButtonOptions{Variant: input.Secondary}).Clicked() {
s.graph.ResetView()
log("NetworkGraph: reset view")
}
}, func() {
frame, show := c1Frame(c, "House", &s.status)
nodes := []mujica.GraphNode{{ID: "a", Label: "Abbot", X: 0, Y: 0}, {ID: "b", Label: "Prior", X: 10, Y: 0}, {ID: "c", Label: "Cellarer", X: 10, Y: 10}, {ID: "d", Label: "Novice", X: 0, Y: 10}, {ID: "e", Label: "Cantor", X: 5, Y: 5}}
edges := []mujica.GraphEdge{{From: "a", To: "b"}, {From: "a", To: "c"}, {From: "c", To: "d"}, {From: "b", To: "e"}, {From: "e", To: "d"}}
nodes := []chart.GraphNode{{ID: "a", Label: "Abbot", X: 0, Y: 0}, {ID: "b", Label: "Prior", X: 10, Y: 0}, {ID: "c", Label: "Cellarer", X: 10, Y: 10}, {ID: "d", Label: "Novice", X: 0, Y: 10}, {ID: "e", Label: "Cantor", X: 5, Y: 5}}
edges := []chart.GraphEdge{{From: "a", To: "b"}, {From: "a", To: "c"}, {From: "c", To: "d"}, {From: "b", To: "e"}, {From: "e", To: "d"}}
if !show {
nodes, edges = nil, nil
}
c1Log(log, "NetworkGraph", mujica.NetworkGraph(c, &s.graph, nodes, edges, mujica.NetworkGraphOptions{Frame: frame, Force: s.force}))
c1Log(log, "NetworkGraph", chart.NetworkGraph(c, &s.graph, nodes, edges, chart.NetworkGraphOptions{Frame: frame, Force: s.force}))
})
}
@@ -107,13 +108,13 @@ func c1RealtimeDemo(c *ui.Context, log func(string)) {
type state struct {
status string
feed bool
buf *mujica.RealtimeBuffer
buf *chart.RealtimeBuffer
next time.Time
tick int
view mujica.RealtimeChartState
view chart.RealtimeChartState
}
s := ui.Local(c.Root(), "120", func() state {
return state{status: "Ready", feed: true, buf: mujica.NewRealtimeBuffer(600, "Bell", "Organ")}
return state{status: "Ready", feed: true, buf: chart.NewRealtimeBuffer(600, "Bell", "Organ")}
})
// The demo appends a sample every 500 ms while feeding.
if s.feed && !c.Now().Before(s.next) {
@@ -128,7 +129,7 @@ func c1RealtimeDemo(c *ui.Context, log func(string)) {
}
c1Section(c, "RealtimeChart", func() { ui.Checkbox(c, &s.feed, "Feed data") }, func() {
frame, _ := c1Frame(c, "Sound", &s.status)
r := mujica.RealtimeChart(c, &s.view, s.buf, mujica.RealtimeChartOptions{Frame: frame, Window: 20 * time.Second})
r := chart.RealtimeChart(c, &s.view, s.buf, chart.RealtimeChartOptions{Frame: frame, Window: 20 * time.Second})
c1Log(log, "RealtimeChart", r)
if r.ValueChanged() {
log(fmt.Sprintf("RealtimeChart: paused=%v", s.view.Paused))
+19 -19
View File
@@ -7,15 +7,15 @@ import (
"github.com/egoist/mygo/ui"
mujica "mujicaui"
"github.com/ZacharyZhang-NY/MujicaUI/chart"
)
func c1Series(gap bool) []mujica.ChartSeries {
func c1Series(gap bool) []chart.ChartSeries {
a := []float64{3, 5, 4, 6, 8, 7, 9, 11}
if gap {
a = []float64{3, 5, math.NaN(), 6, 8, 7, 9, 11}
}
return []mujica.ChartSeries{
return []chart.ChartSeries{
{Name: "Matins", Values: a},
{Name: "Vespers", Values: []float64{2, 3, 5, 4, 6, 9, 8, 7}},
{Name: "Compline", Values: []float64{1, 2, 2, 3, 2, 4, 5, 4}},
@@ -35,9 +35,9 @@ func c1LineDemo(c *ui.Context, log func(string)) {
ui.Checkbox(c, &s.big, "100,000 points")
}, func() {
frame, show := c1Frame(c, "Attendance", &s.status)
series, opts := c1Series(s.gap), mujica.LineChartOptions{Frame: frame}
series, opts := c1Series(s.gap), chart.LineChartOptions{Frame: frame}
if s.big {
series = []mujica.ChartSeries{{Name: "Wave", Values: c1Wave()}}
series = []chart.ChartSeries{{Name: "Wave", Values: c1Wave()}}
}
if s.time {
t0 := time.Date(2026, 10, 5, 6, 0, 0, 0, time.UTC)
@@ -50,7 +50,7 @@ func c1LineDemo(c *ui.Context, log func(string)) {
series = nil
opts.Times = nil
}
c1Log(log, "LineChart", mujica.LineChart(c, series, opts))
c1Log(log, "LineChart", chart.LineChart(c, series, opts))
})
}
@@ -84,7 +84,7 @@ func c1AreaDemo(c *ui.Context, log func(string)) {
if !show {
series = nil
}
c1Log(log, "AreaChart", mujica.AreaChart(c, series, mujica.AreaChartOptions{Frame: frame, Stacked: s.stacked, Baseline: s.base}))
c1Log(log, "AreaChart", chart.AreaChart(c, series, chart.AreaChartOptions{Frame: frame, Stacked: s.stacked, Baseline: s.base}))
})
}
@@ -101,11 +101,11 @@ func c1BarDemo(c *ui.Context, log func(string)) {
}, func() {
frame, show := c1Frame(c, "Tithes", &s.status)
cats := []string{"Nave", "Choir", "Crypt", "Bells"}
series := []mujica.ChartSeries{{Name: "Gold", Values: []float64{4, -2, 3, 5}}, {Name: "Silver", Values: []float64{2, 5, 1, -1}}}
series := []chart.ChartSeries{{Name: "Gold", Values: []float64{4, -2, 3, 5}}, {Name: "Silver", Values: []float64{2, 5, 1, -1}}}
if !show {
series = nil
}
c1Log(log, "BarChart", mujica.BarChart(c, cats, series, mujica.BarChartOptions{Frame: frame, Horizontal: s.horizontal, Stacked: s.stacked, Labels: s.labels}))
c1Log(log, "BarChart", chart.BarChart(c, cats, series, chart.BarChartOptions{Frame: frame, Horizontal: s.horizontal, Stacked: s.stacked, Labels: s.labels}))
})
}
@@ -122,11 +122,11 @@ func c1PieDemo(c *ui.Context, log func(string)) {
ui.StepSlider(c, &s.min, 0, 40, 5).Width(140)
}, func() {
frame, show := c1Frame(c, "Stores", &s.status)
slices := []mujica.ChartSlice{{Label: "Wax", Value: 50}, {Label: "Oil", Value: 30}, {Label: "Myrrh", Value: 12}, {Label: "Salt", Value: 4}, {Label: "Ash", Value: 4}}
slices := []chart.ChartSlice{{Label: "Wax", Value: 50}, {Label: "Oil", Value: 30}, {Label: "Myrrh", Value: 12}, {Label: "Salt", Value: 4}, {Label: "Ash", Value: 4}}
if !show {
slices = nil
}
c1Log(log, "PieChart", mujica.PieChart(c, slices, mujica.PieChartOptions{Frame: frame, Donut: s.donut, MinAngle: s.min}))
c1Log(log, "PieChart", chart.PieChart(c, slices, chart.PieChartOptions{Frame: frame, Donut: s.donut, MinAngle: s.min}))
})
}
@@ -134,7 +134,7 @@ func c1ScatterDemo(c *ui.Context, log func(string)) {
type state struct {
status string
bubbles bool
selected []mujica.ScatterKey
selected []chart.ScatterKey
}
s := ui.Local(c.Root(), "105", func() state { return state{status: "Ready", bubbles: true} })
c1Section(c, "ScatterChart", func() {
@@ -142,14 +142,14 @@ func c1ScatterDemo(c *ui.Context, log func(string)) {
ui.Text(c, fmt.Sprintf("%d selected", len(s.selected)))
}, func() {
frame, show := c1Frame(c, "Choir", &s.status)
groups := []mujica.ScatterGroup{
{Name: "Monks", Points: []mujica.ScatterPoint{{X: 1, Y: 2, Size: 4}, {X: 3, Y: 5, Size: 9}, {X: 6, Y: 4, Size: 1}, {X: 7, Y: 8, Size: 6}}},
{Name: "Nuns", Points: []mujica.ScatterPoint{{X: 2, Y: 7, Size: 16}, {X: 8, Y: 9, Size: 2}, {X: 5, Y: 2, Size: 8}}},
groups := []chart.ScatterGroup{
{Name: "Monks", Points: []chart.ScatterPoint{{X: 1, Y: 2, Size: 4}, {X: 3, Y: 5, Size: 9}, {X: 6, Y: 4, Size: 1}, {X: 7, Y: 8, Size: 6}}},
{Name: "Nuns", Points: []chart.ScatterPoint{{X: 2, Y: 7, Size: 16}, {X: 8, Y: 9, Size: 2}, {X: 5, Y: 2, Size: 8}}},
}
if !show {
groups = nil
}
r := mujica.ScatterChart(c, &s.selected, groups, mujica.ScatterChartOptions{Frame: frame, Bubbles: s.bubbles})
r := chart.ScatterChart(c, &s.selected, groups, chart.ScatterChartOptions{Frame: frame, Bubbles: s.bubbles})
c1Log(log, "ScatterChart", r)
if r.ValueChanged() {
log(fmt.Sprintf("ScatterChart: %d selected", len(s.selected)))
@@ -162,11 +162,11 @@ func c1RadarDemo(c *ui.Context, log func(string)) {
s := ui.Local(c.Root(), "106", func() state { return state{status: "Ready"} })
c1Section(c, "RadarChart", func() {}, func() {
frame, show := c1Frame(c, "Skills", &s.status)
series := []mujica.ChartSeries{{Name: "Agnès", Values: []float64{8, 6, 9, 4, 7}}, {Name: "Clare", Values: []float64{5, 9, 6, 8, 3}}}
series := []chart.ChartSeries{{Name: "Agnès", Values: []float64{8, 6, 9, 4, 7}}, {Name: "Clare", Values: []float64{5, 9, 6, 8, 3}}}
if !show {
series = nil
}
c1Log(log, "RadarChart", mujica.RadarChart(c, []string{"Voice", "Latin", "Chant", "Organ", "Bells"}, series, mujica.RadarChartOptions{Frame: frame}))
c1Log(log, "RadarChart", chart.RadarChart(c, []string{"Voice", "Latin", "Chant", "Organ", "Bells"}, series, chart.RadarChartOptions{Frame: frame}))
})
}
@@ -192,6 +192,6 @@ func c1HeatDemo(c *ui.Context, log func(string)) {
if !show {
rows, values = nil, nil
}
c1Log(log, "HeatmapChart", mujica.HeatmapChart(c, rows, cols, values, mujica.HeatmapChartOptions{Frame: frame}))
c1Log(log, "HeatmapChart", chart.HeatmapChart(c, rows, cols, values, chart.HeatmapChartOptions{Frame: frame}))
})
}
+21 -19
View File
@@ -8,7 +8,9 @@ import (
"github.com/egoist/mygo/ui"
mujica "mujicaui"
"github.com/ZacharyZhang-NY/MujicaUI/chart"
"github.com/ZacharyZhang-NY/MujicaUI/core"
"github.com/ZacharyZhang-NY/MujicaUI/datetime"
)
func c1CalendarDemo(c *ui.Context, log func(string)) {
@@ -20,17 +22,17 @@ func c1CalendarDemo(c *ui.Context, log func(string)) {
c1Section(c, "CalendarHeatmap", func() { ui.Select(c, &s.start, []string{"Locale", "Sunday", "Monday", "Saturday"}) }, func() {
frame, show := c1Frame(c, "Prayers", &s.status)
frame.Height = 160
values := map[mujica.Date]float64{}
values := map[core.Date]float64{}
if show {
d := mujica.Date{Year: s.year, Month: time.January, Day: 1}
d := core.Date{Year: s.year, Month: time.January, Day: 1}
for i := range 366 {
if (i*37)%5 != 0 {
values[d.AddDays(i)] = float64((i * 13) % 9)
}
}
}
start := map[string]mujica.WeekStart{"Locale": mujica.WeekStartLocale, "Sunday": mujica.WeekStartSunday, "Monday": mujica.WeekStartMonday, "Saturday": mujica.WeekStartSaturday}[s.start]
r := mujica.CalendarHeatmap(c, &s.year, values, mujica.CalendarHeatmapOptions{Frame: frame, WeekStart: start})
start := map[string]datetime.WeekStart{"Locale": datetime.WeekStartLocale, "Sunday": datetime.WeekStartSunday, "Monday": datetime.WeekStartMonday, "Saturday": datetime.WeekStartSaturday}[s.start]
r := chart.CalendarHeatmap(c, &s.year, values, chart.CalendarHeatmapOptions{Frame: frame, WeekStart: start})
c1Log(log, "CalendarHeatmap", r)
if r.ValueChanged() {
log("CalendarHeatmap: year " + strconv.Itoa(s.year))
@@ -62,7 +64,7 @@ func c1HistogramDemo(c *ui.Context, log func(string)) {
if !show {
values = nil
}
c1Log(log, "Histogram", mujica.Histogram(c, values, mujica.HistogramOptions{Frame: frame, Bins: int(s.bins), Cumulative: s.cumulative}))
c1Log(log, "Histogram", chart.Histogram(c, values, chart.HistogramOptions{Frame: frame, Bins: int(s.bins), Cumulative: s.cumulative}))
})
}
@@ -71,7 +73,7 @@ func c1BoxDemo(c *ui.Context, log func(string)) {
s := ui.Local(c.Root(), "110", func() state { return state{status: "Ready"} })
c1Section(c, "BoxPlot", func() {}, func() {
frame, show := c1Frame(c, "Hymns", &s.status)
groups := []mujica.BoxGroup{
groups := []chart.BoxGroup{
{Name: "Choir", Values: []float64{1, 2, 3, 4, 5, 6, 7, 8, 9, 24}},
{Name: "Nave", Values: []float64{4, 5, 5, 6, 7, 8}},
{Name: "Crypt", Values: []float64{12, 14, 15, 18, 22, 25}},
@@ -79,7 +81,7 @@ func c1BoxDemo(c *ui.Context, log func(string)) {
if !show {
groups = nil
}
c1Log(log, "BoxPlot", mujica.BoxPlot(c, groups, mujica.BoxPlotOptions{Frame: frame}))
c1Log(log, "BoxPlot", chart.BoxPlot(c, groups, chart.BoxPlotOptions{Frame: frame}))
})
}
@@ -88,14 +90,14 @@ func c1WaterfallDemo(c *ui.Context, log func(string)) {
s := ui.Local(c.Root(), "111", func() state { return state{status: "Ready"} })
c1Section(c, "WaterfallChart", func() {}, func() {
frame, show := c1Frame(c, "Ledger", &s.status)
steps := []mujica.WaterfallStep{
{Label: "Tithes", Value: 12}, {Label: "Repairs", Value: -5}, {Label: "Q1", Kind: mujica.WaterfallSubtotal},
{Label: "Alms", Value: 4}, {Label: "Bread", Value: -2}, {Label: "Year", Kind: mujica.WaterfallTotal},
steps := []chart.WaterfallStep{
{Label: "Tithes", Value: 12}, {Label: "Repairs", Value: -5}, {Label: "Q1", Kind: chart.WaterfallSubtotal},
{Label: "Alms", Value: 4}, {Label: "Bread", Value: -2}, {Label: "Year", Kind: chart.WaterfallTotal},
}
if !show {
steps = nil
}
c1Log(log, "WaterfallChart", mujica.WaterfallChart(c, steps, mujica.WaterfallChartOptions{Frame: frame}))
c1Log(log, "WaterfallChart", chart.WaterfallChart(c, steps, chart.WaterfallChartOptions{Frame: frame}))
})
}
@@ -104,11 +106,11 @@ func c1FunnelDemo(c *ui.Context, log func(string)) {
s := ui.Local(c.Root(), "112", func() state { return state{status: "Ready"} })
c1Section(c, "FunnelChart", func() {}, func() {
frame, show := c1Frame(c, "Order", &s.status)
stages := []mujica.ChartSlice{{Label: "Visitors", Value: 1000}, {Label: "Novices", Value: 400}, {Label: "Vows", Value: 100}, {Label: "Priors", Value: 12}}
stages := []chart.ChartSlice{{Label: "Visitors", Value: 1000}, {Label: "Novices", Value: 400}, {Label: "Vows", Value: 100}, {Label: "Priors", Value: 12}}
if !show {
stages = nil
}
c1Log(log, "FunnelChart", mujica.FunnelChart(c, stages, mujica.FunnelChartOptions{Frame: frame}))
c1Log(log, "FunnelChart", chart.FunnelChart(c, stages, chart.FunnelChartOptions{Frame: frame}))
})
}
@@ -129,7 +131,7 @@ func c1BulletDemo(c *ui.Context, log func(string)) {
frame.Height = 200
}
target := s.target
c1Log(log, "BulletChart", mujica.BulletChart(c, s.value, mujica.BulletChartOptions{Frame: frame, Target: &target, Bands: []float64{150, 225, 300}, Vertical: s.vertical}))
c1Log(log, "BulletChart", chart.BulletChart(c, s.value, chart.BulletChartOptions{Frame: frame, Target: &target, Bands: []float64{150, 225, 300}, Vertical: s.vertical}))
})
}
@@ -143,10 +145,10 @@ func c1SparkDemo(c *ui.Context, log func(string)) {
values = nil
}
ui.Row(c).Gap(24).Wrap().Children(func() {
for _, k := range []mujica.SparklineKind{mujica.SparklineLine, mujica.SparklineBar, mujica.SparklineArea} {
for _, k := range []chart.SparklineKind{chart.SparklineLine, chart.SparklineBar, chart.SparklineArea} {
f := frame
f.Label = fmt.Sprintf("Bells %d", k+1)
c1Log(log, f.Label, mujica.Sparkline(c, values, mujica.SparklineOptions{Frame: f, Kind: k}))
c1Log(log, f.Label, chart.Sparkline(c, values, chart.SparklineOptions{Frame: f, Kind: k}))
}
})
})
@@ -165,7 +167,7 @@ func c1GaugeDemo(c *ui.Context, log func(string)) {
ui.Checkbox(c, &s.full, "Full circle")
}, func() {
frame, _ := c1Frame(c, "Nave", &s.status)
segments := []mujica.GaugeSegment{{To: 60, Label: "Calm", Tone: mujica.GaugeGood}, {To: 85, Label: "Busy", Tone: mujica.GaugeWarning}, {To: 100, Label: "Crowded", Tone: mujica.GaugeDanger}}
c1Log(log, "Gauge", mujica.Gauge(c, s.value, mujica.GaugeOptions{Frame: frame, Max: 100, Unit: "%", Segments: segments, Full: s.full}))
segments := []chart.GaugeSegment{{To: 60, Label: "Calm", Tone: chart.GaugeGood}, {To: 85, Label: "Busy", Tone: chart.GaugeWarning}, {To: 100, Label: "Crowded", Tone: chart.GaugeDanger}}
c1Log(log, "Gauge", chart.Gauge(c, s.value, chart.GaugeOptions{Frame: frame, Max: 100, Unit: "%", Segments: segments, Full: s.full}))
})
}
+2 -2
View File
@@ -7,7 +7,7 @@ import (
"github.com/egoist/mygo/ui"
mujica "mujicaui"
"github.com/ZacharyZhang-NY/MujicaUI/core"
)
// c1Demo runs page id's demo with an event log.
@@ -19,7 +19,7 @@ func c1Demo(t *testing.T, id string) (*ui.Tester, *[]string) {
}
events := &[]string{}
tt := ui.NewTester(func(c *ui.Context) {
mujica.Use(c, mujica.Settings{})
core.Use(c, core.Settings{})
ui.Scroll(c).Fill().Padding(24).Children(func() {
charts1Pages[i].Demo(c, func(s string) { *events = append(*events, s) })
})
+12 -10
View File
@@ -5,7 +5,9 @@ import (
"github.com/egoist/mygo/ui"
mujica "mujicaui"
"github.com/ZacharyZhang-NY/MujicaUI/chart"
"github.com/ZacharyZhang-NY/MujicaUI/data"
"github.com/ZacharyZhang-NY/MujicaUI/input"
)
const c2Category = "Charts II"
@@ -29,7 +31,7 @@ var charts2Pages = []page{
}
func c2Check(c *ui.Context, value *bool, label string, log func(string)) {
if mujica.Checkbox(c, value, label, mujica.CheckboxOptions{}).Changed() {
if input.Checkbox(c, value, label, input.CheckboxOptions{}).Changed() {
log(fmt.Sprintf("%s = %v", label, *value))
}
}
@@ -37,7 +39,7 @@ func c2Check(c *ui.Context, value *bool, label string, log func(string)) {
func c2Number(c *ui.Context, value *float64, label string, lo, hi, step float64, log func(string)) {
ui.Row(c).Gap(8).AlignItems(ui.Center).Children(func() {
ui.Text(c, label)
r := mujica.NumberInput(c, value, mujica.NumberInputOptions{Label: label, Min: lo, Max: hi, Step: step})
r := input.NumberInput(c, value, input.NumberInputOptions{Label: label, Min: lo, Max: hi, Step: step})
r.Element.Width(150)
if r.Changed() {
log(fmt.Sprintf("%s = %g", label, *value))
@@ -46,25 +48,25 @@ func c2Number(c *ui.Context, value *float64, label string, lo, hi, step float64,
}
func c2Select(c *ui.Context, value *string, label string, choices []string, log func(string)) {
items := make([]mujica.SelectOption[string], len(choices))
items := make([]input.SelectOption[string], len(choices))
for i, s := range choices {
items[i] = mujica.SelectOption[string]{Value: s, Label: s}
items[i] = input.SelectOption[string]{Value: s, Label: s}
}
r := mujica.Select(c, value, items, mujica.SelectOptions{Label: label})
r := input.Select(c, value, items, input.SelectOptions{Label: label})
r.Element.Width(160)
if r.Changed() {
log(label + " = " + *value)
}
}
func c2Frame(c *ui.Context, label string, status *string, log func(string)) (mujica.ChartFrame, bool) {
func c2Frame(c *ui.Context, label string, status *string, log func(string)) (chart.ChartFrame, bool) {
c2Select(c, status, "Status", c1Statuses, log)
f := mujica.ChartFrame{Label: label}
f := chart.ChartFrame{Label: label}
if *status == "Loading" {
f.Status = mujica.DataLoading
f.Status = data.DataLoading
}
if *status == "Failed" {
f.Status, f.Error = mujica.DataFailed, "The archive did not answer"
f.Status, f.Error = data.DataFailed, "The archive did not answer"
}
return f, *status != "Empty"
}
+44 -44
View File
@@ -2,25 +2,25 @@ package main
const c2ChartLegendCode = `hidden := map[string]bool{}
view := func(c *ui.Context) {
mujica.Use(c, mujica.Settings{})
k := mujica.Tokens(c)
items := []mujica.ChartLegendItem{{Name: "Pews", Color: k.Info}, {Name: "Candles", Color: k.Success, Shape: 1}}
mujica.ChartLegend(c, &hidden, items, mujica.ChartLegendOptions{MaxItems: 1})
core.Use(c, core.Settings{})
k := core.Tokens(c)
items := []chart.ChartLegendItem{{Name: "Pews", Color: k.Info}, {Name: "Candles", Color: k.Success, Shape: 1}}
chart.ChartLegend(c, &hidden, items, chart.ChartLegendOptions{MaxItems: 1})
}
ui.Render(view, 400, 200, 1)`
const c2ChartTooltipCode = `view := func(c *ui.Context) {
mujica.Use(c, mujica.Settings{})
k := mujica.Tokens(c)
mujica.ChartTooltip(c, 40, 40, []mujica.ChartTooltipLine{{Text: "Lauds"}, {Text: "Pews 5", Color: k.Info}, {Text: "Candles 2", Color: k.Success, Shape: 1}})
core.Use(c, core.Settings{})
k := core.Tokens(c)
chart.ChartTooltip(c, 40, 40, []chart.ChartTooltipLine{{Text: "Lauds"}, {Text: "Pews 5", Color: k.Info}, {Text: "Candles 2", Color: k.Success, Shape: 1}})
}
ui.Render(view, 400, 200, 1)`
const c2ChartCrosshairCode = `view := func(c *ui.Context) {
mujica.Use(c, mujica.Settings{})
x := mujica.ChartAxis(c, mujica.ChartAxisOptions{Max: 10})
y := mujica.ChartAxis(c, mujica.ChartAxisOptions{Side: mujica.SideLeft, Max: 100})
hair := mujica.ChartCrosshair(c, mujica.ChartCrosshairOptions{X: &x, Y: &y})
core.Use(c, core.Settings{})
x := chart.ChartAxis(c, chart.ChartAxisOptions{Max: 10})
y := chart.ChartAxis(c, chart.ChartAxisOptions{Side: chart.SideLeft, Max: 100})
hair := chart.ChartCrosshair(c, chart.ChartCrosshairOptions{X: &x, Y: &y})
ui.Box(c).Fill().Draw(func(p *ui.Painter, r ui.Rect) {
plot := ui.Rect{X: r.X + 60, Y: r.Y + 20, W: r.W - 80, H: r.H - 60}
x.Draw(p, plot)
@@ -31,8 +31,8 @@ const c2ChartCrosshairCode = `view := func(c *ui.Context) {
ui.Render(view, 400, 300, 1)`
const c2ChartAxisCode = `view := func(c *ui.Context) {
mujica.Use(c, mujica.Settings{})
axis := mujica.ChartAxis(c, mujica.ChartAxisOptions{Side: mujica.SideLeft, Kind: mujica.AxisLog, Min: 1, Max: 1000})
core.Use(c, core.Settings{})
axis := chart.ChartAxis(c, chart.ChartAxisOptions{Side: chart.SideLeft, Kind: chart.AxisLog, Min: 1, Max: 1000})
ui.Box(c).Fill().Draw(func(p *ui.Painter, r ui.Rect) {
axis.Draw(p, ui.Rect{X: r.X + 60, Y: r.Y + 20, W: r.W - 80, H: r.H - 60})
})
@@ -40,10 +40,10 @@ const c2ChartAxisCode = `view := func(c *ui.Context) {
ui.Render(view, 400, 300, 1)`
const c2ChartGridCode = `view := func(c *ui.Context) {
mujica.Use(c, mujica.Settings{})
x := mujica.ChartAxis(c, mujica.ChartAxisOptions{Max: 10})
y := mujica.ChartAxis(c, mujica.ChartAxisOptions{Side: mujica.SideLeft, Max: 100})
grid := mujica.ChartGrid(c, mujica.ChartGridOptions{X: &x, Y: &y, Minor: 2})
core.Use(c, core.Settings{})
x := chart.ChartAxis(c, chart.ChartAxisOptions{Max: 10})
y := chart.ChartAxis(c, chart.ChartAxisOptions{Side: chart.SideLeft, Max: 100})
grid := chart.ChartGrid(c, chart.ChartGridOptions{X: &x, Y: &y, Minor: 2})
ui.Box(c).Fill().Draw(func(p *ui.Painter, r ui.Rect) {
plot := ui.Rect{X: r.X + 60, Y: r.Y + 20, W: r.W - 80, H: r.H - 60}
grid.Draw(p, plot)
@@ -55,17 +55,17 @@ ui.Render(view, 400, 300, 1)`
const c2ChartBrushCode = `value := [2]float64{20, 60}
view := func(c *ui.Context) {
mujica.Use(c, mujica.Settings{})
mujica.ChartBrush(c, &value, mujica.ChartBrushOptions{Label: "Months", Max: 100, Step: 1, Values: []float64{2, 4, 3, 8, 5}})
core.Use(c, core.Settings{})
chart.ChartBrush(c, &value, chart.ChartBrushOptions{Label: "Months", Max: 100, Step: 1, Values: []float64{2, 4, 3, 8, 5}})
}
ui.Render(view, 400, 160, 1)`
const c2ChartAnnotationCode = `notes := []mujica.ChartNote{{Kind: mujica.NoteHLine, Value: 50, Text: "Goal", Draggable: true}, {Kind: mujica.NoteBand, Value: 6, End: 8, Text: "Lent"}}
const c2ChartAnnotationCode = `notes := []chart.ChartNote{{Kind: chart.NoteHLine, Value: 50, Text: "Goal", Draggable: true}, {Kind: chart.NoteBand, Value: 6, End: 8, Text: "Lent"}}
view := func(c *ui.Context) {
mujica.Use(c, mujica.Settings{})
x := mujica.ChartAxis(c, mujica.ChartAxisOptions{Max: 10})
y := mujica.ChartAxis(c, mujica.ChartAxisOptions{Side: mujica.SideLeft, Max: 100})
annotation := mujica.ChartAnnotation(c, notes, mujica.ChartAnnotationOptions{X: &x, Y: &y})
core.Use(c, core.Settings{})
x := chart.ChartAxis(c, chart.ChartAxisOptions{Max: 10})
y := chart.ChartAxis(c, chart.ChartAxisOptions{Side: chart.SideLeft, Max: 100})
annotation := chart.ChartAnnotation(c, notes, chart.ChartAnnotationOptions{X: &x, Y: &y})
box := ui.Box(c).Fill().Focusable().Label("Annotations")
b := box.Bounds()
annotation.Drag(box, ui.Rect{X: 60, Y: 20, W: b.W - 80, H: b.H - 60})
@@ -79,10 +79,10 @@ view := func(c *ui.Context) {
ui.Render(view, 400, 300, 1)`
const c2ChartExportCode = `view := func(c *ui.Context) {
mujica.Use(c, mujica.Settings{})
data, err := mujica.ChartExport(c, func(c *ui.Context) {
mujica.SparkBar(c, []float64{3, -2, 5}, mujica.SparkBarOptions{Frame: mujica.ChartFrame{Label: "Alms"}})
}, mujica.ChartExportOptions{Width: 300, Height: 100, Scale: 2})
core.Use(c, core.Settings{})
data, err := chart.ChartExport(c, func(c *ui.Context) {
chart.SparkBar(c, []float64{3, -2, 5}, chart.SparkBarOptions{Frame: chart.ChartFrame{Label: "Alms"}})
}, chart.ChartExportOptions{Width: 300, Height: 100, Scale: 2})
if err != nil {
panic(err)
}
@@ -96,27 +96,27 @@ ui.Render(view, 400, 160, 1)`
const c2SparkBarCode = `values := []float64{3, -2, 4, 1, -3}
view := func(c *ui.Context) {
mujica.Use(c, mujica.Settings{})
mujica.SparkBar(c, values, mujica.SparkBarOptions{Frame: mujica.ChartFrame{Label: "Alms"}, BarWidth: 10})
core.Use(c, core.Settings{})
chart.SparkBar(c, values, chart.SparkBarOptions{Frame: chart.ChartFrame{Label: "Alms"}, BarWidth: 10})
}
ui.Render(view, 400, 100, 1)`
const c2TrendIndicatorCode = `view := func(c *ui.Context) {
mujica.Use(c, mujica.Settings{})
mujica.TrendIndicator(c, 12.5, mujica.TrendIndicatorOptions{Precision: 1, Unit: "%", Note: "vs Lent"})
core.Use(c, core.Settings{})
chart.TrendIndicator(c, 12.5, chart.TrendIndicatorOptions{Precision: 1, Unit: "%", Note: "vs Lent"})
}
ui.Render(view, 400, 100, 1)`
const c2ChartEmptyStateCode = `view := func(c *ui.Context) {
mujica.Use(c, mujica.Settings{})
mujica.ChartEmptyState(c, mujica.DataFailed, mujica.ChartEmptyStateOptions{Height: 180, Error: "The archive did not answer"})
core.Use(c, core.Settings{})
chart.ChartEmptyState(c, data.DataFailed, chart.ChartEmptyStateOptions{Height: 180, Error: "The archive did not answer"})
}
ui.Render(view, 400, 240, 1)`
const c2ViolinPlotCode = `groups := []mujica.BoxGroup{{Name: "Choir", Values: []float64{1, 2, 3, 4, 7, 9}}, {Name: "Nave", Values: []float64{2, 4, 5, 6, 8}}}
const c2ViolinPlotCode = `groups := []chart.BoxGroup{{Name: "Choir", Values: []float64{1, 2, 3, 4, 7, 9}}, {Name: "Nave", Values: []float64{2, 4, 5, 6, 8}}}
view := func(c *ui.Context) {
mujica.Use(c, mujica.Settings{})
mujica.ViolinPlot(c, groups, mujica.ViolinPlotOptions{Frame: mujica.ChartFrame{Label: "Hymns"}, Bandwidth: 1})
core.Use(c, core.Settings{})
chart.ViolinPlot(c, groups, chart.ViolinPlotOptions{Frame: chart.ChartFrame{Label: "Hymns"}, Bandwidth: 1})
}
ui.Render(view, 400, 300, 1)`
@@ -124,22 +124,22 @@ const c2ParallelCoordinatesCode = `filters := map[string][2]float64{}
axes := []string{"Age", "Hymns", "Vigils"}
rows := [][]float64{{20, 3, 9}, {35, 8, 2}, {50, 5, 5}}
view := func(c *ui.Context) {
mujica.Use(c, mujica.Settings{})
mujica.ParallelCoordinates(c, &filters, axes, rows, mujica.ParallelCoordinatesOptions{Frame: mujica.ChartFrame{Label: "Monks"}, Names: []string{"Agnes", "Clare", "Hild"}})
core.Use(c, core.Settings{})
chart.ParallelCoordinates(c, &filters, axes, rows, chart.ParallelCoordinatesOptions{Frame: chart.ChartFrame{Label: "Monks"}, Names: []string{"Agnes", "Clare", "Hild"}})
}
ui.Render(view, 400, 300, 1)`
const c2ChordDiagramCode = `names := []string{"Nave", "Choir", "Crypt"}
matrix := [][]float64{{0, 5, 2}, {4, 0, 1}, {1, 2, 0}}
view := func(c *ui.Context) {
mujica.Use(c, mujica.Settings{})
mujica.ChordDiagram(c, names, matrix, mujica.ChordDiagramOptions{Frame: mujica.ChartFrame{Label: "Paths"}})
core.Use(c, core.Settings{})
chart.ChordDiagram(c, names, matrix, chart.ChordDiagramOptions{Frame: chart.ChartFrame{Label: "Paths"}})
}
ui.Render(view, 400, 300, 1)`
const c2DensityPlotCode = `groups := []mujica.BoxGroup{{Name: "Matins", Values: []float64{1, 2, 2, 3, 7}}, {Name: "Vespers", Values: []float64{3, 4, 5, 6, 6}}}
const c2DensityPlotCode = `groups := []chart.BoxGroup{{Name: "Matins", Values: []float64{1, 2, 2, 3, 7}}, {Name: "Vespers", Values: []float64{3, 4, 5, 6, 6}}}
view := func(c *ui.Context) {
mujica.Use(c, mujica.Settings{})
mujica.DensityPlot(c, groups, mujica.DensityPlotOptions{Frame: mujica.ChartFrame{Label: "Ages"}, Bandwidth: 1, Points: 128})
core.Use(c, core.Settings{})
chart.DensityPlot(c, groups, chart.DensityPlotOptions{Frame: chart.ChartFrame{Label: "Ages"}, Bandwidth: 1, Points: 128})
}
ui.Render(view, 400, 300, 1)`
@@ -5,7 +5,8 @@ import (
"github.com/egoist/mygo/ui"
mujica "mujicaui"
"github.com/ZacharyZhang-NY/MujicaUI/chart"
"github.com/ZacharyZhang-NY/MujicaUI/input"
)
func c2ViolinDemo(c *ui.Context, log func(string)) { c2DistributionDemo(c, log, "129", "ViolinPlot") }
@@ -16,10 +17,10 @@ func c2DistributionDemo(c *ui.Context, log func(string), id, name string) {
status string
bandwidth float64
disabled bool
groups []mujica.BoxGroup
groups []chart.BoxGroup
}
s := ui.Local(c.Root(), id, func() state {
return state{status: "Ready", groups: []mujica.BoxGroup{
return state{status: "Ready", groups: []chart.BoxGroup{
{Name: "Matins", Values: []float64{1, 2, 2, 3, 4, 5, 8, 9}},
{Name: "Vespers", Values: []float64{3, 4, 5, 6, 6, 7, 8, 11}},
}}
@@ -35,9 +36,9 @@ func c2DistributionDemo(c *ui.Context, log func(string), id, name string) {
}
ui.Column(c).Disabled(s.disabled).Children(func() {
if id == "129" {
c1Log(log, name, mujica.ViolinPlot(c, groups, mujica.ViolinPlotOptions{Frame: f, Bandwidth: s.bandwidth}))
c1Log(log, name, chart.ViolinPlot(c, groups, chart.ViolinPlotOptions{Frame: f, Bandwidth: s.bandwidth}))
} else {
c1Log(log, name, mujica.DensityPlot(c, groups, mujica.DensityPlotOptions{Frame: f, Bandwidth: s.bandwidth}))
c1Log(log, name, chart.DensityPlot(c, groups, chart.DensityPlotOptions{Frame: f, Bandwidth: s.bandwidth}))
}
})
})
@@ -55,7 +56,7 @@ func c2ParallelDemo(c *ui.Context, log func(string)) {
})
c1Section(c, "ParallelCoordinates", func() {
c2Check(c, &s.disabled, "Disabled", log)
if mujica.Button(c, "Clear filters", mujica.ButtonOptions{Disabled: s.disabled}).Clicked() {
if input.Button(c, "Clear filters", input.ButtonOptions{Disabled: s.disabled}).Clicked() {
s.filters = map[string][2]float64{}
log("ParallelCoordinates: clear filters")
}
@@ -68,7 +69,7 @@ func c2ParallelDemo(c *ui.Context, log func(string)) {
names = nil
}
ui.Column(c).Disabled(s.disabled).Children(func() {
c1Log(log, "ParallelCoordinates", mujica.ParallelCoordinates(c, &s.filters, []string{"Age", "Hymns", "Vigils"}, rows, mujica.ParallelCoordinatesOptions{Frame: f, Names: names}))
c1Log(log, "ParallelCoordinates", chart.ParallelCoordinates(c, &s.filters, []string{"Age", "Hymns", "Vigils"}, rows, chart.ParallelCoordinatesOptions{Frame: f, Names: names}))
})
ui.Text(c, fmt.Sprintf("Filters: %v", s.filters))
})
@@ -99,7 +100,7 @@ func c2ChordDemo(c *ui.Context, log func(string)) {
matrix = nil
}
ui.Column(c).Disabled(s.disabled).Children(func() {
c1Log(log, "ChordDiagram", mujica.ChordDiagram(c, names, matrix, mujica.ChordDiagramOptions{Frame: f}))
c1Log(log, "ChordDiagram", chart.ChordDiagram(c, names, matrix, chart.ChordDiagramOptions{Frame: f}))
})
})
}
+24 -23
View File
@@ -6,15 +6,16 @@ import (
"github.com/egoist/mygo/ui"
mujica "mujicaui"
"mujicaui/internal"
"github.com/ZacharyZhang-NY/MujicaUI/chart"
"github.com/ZacharyZhang-NY/MujicaUI/core"
"github.com/ZacharyZhang-NY/MujicaUI/internal"
)
type c2PartsState struct {
status, kind string
disabled, crosshair, draggable, fixed bool
minor float64
notes []mujica.ChartNote
notes []chart.ChartNote
}
func c2TooltipDemo(c *ui.Context, log func(string)) { c2PartsDemo(c, log, "122", "ChartTooltip") }
@@ -25,11 +26,11 @@ func c2AnnotationDemo(c *ui.Context, log func(string)) { c2PartsDemo(c, log, "12
func c2PartsDemo(c *ui.Context, log func(string), id, name string) {
s := ui.Local(c.Root(), id, func() c2PartsState {
return c2PartsState{status: "Ready", kind: "Linear", crosshair: true, minor: 2, draggable: true, notes: []mujica.ChartNote{
{Kind: mujica.NoteHLine, Value: 50, Text: "Goal"},
{Kind: mujica.NoteVLine, Value: 3, Text: "Advent"},
{Kind: mujica.NoteBand, Value: 6, End: 8, Text: "Lent"},
{Kind: mujica.NoteText, Value: 1, End: 90, Text: "Feast"},
return c2PartsState{status: "Ready", kind: "Linear", crosshair: true, minor: 2, draggable: true, notes: []chart.ChartNote{
{Kind: chart.NoteHLine, Value: 50, Text: "Goal"},
{Kind: chart.NoteVLine, Value: 3, Text: "Advent"},
{Kind: chart.NoteBand, Value: 6, End: 8, Text: "Lent"},
{Kind: chart.NoteText, Value: 1, End: 90, Text: "Feast"},
}}
})
c1Section(c, name, func() {
@@ -56,23 +57,23 @@ func c2PartsDemo(c *ui.Context, log func(string), id, name string) {
})
}
func c2PartsChart(c *ui.Context, frame mujica.ChartFrame, show bool, s *c2PartsState, id string, log func(string)) mujica.ChartResult {
xo := mujica.ChartAxisOptions{Max: 10}
yo := mujica.ChartAxisOptions{Side: mujica.SideLeft, Max: 100}
func c2PartsChart(c *ui.Context, frame chart.ChartFrame, show bool, s *c2PartsState, id string, log func(string)) chart.ChartResult {
xo := chart.ChartAxisOptions{Max: 10}
yo := chart.ChartAxisOptions{Side: chart.SideLeft, Max: 100}
pts := [][2]float64{{2, 30}, {5, 80}, {8, 55}}
if id == "124" {
switch s.kind {
case "Log":
yo.Kind, yo.Min = mujica.AxisLog, 1
yo.Kind, yo.Min = chart.AxisLog, 1
case "Time":
xo.Kind = mujica.AxisTime
xo.Kind = chart.AxisTime
xo.From = time.Date(2026, 10, 5, 9, 0, 0, 0, time.UTC)
xo.To = xo.From.Add(10 * time.Hour)
for i := range pts {
pts[i][0] = float64(xo.From.Unix()) + pts[i][0]*3600
}
case "Category":
xo.Kind, xo.Categories = mujica.AxisCategory, []string{"Nave", "Choir", "Crypt"}
xo.Kind, xo.Categories = chart.AxisCategory, []string{"Nave", "Choir", "Crypt"}
for i := range pts {
pts[i][0] = float64(i)
}
@@ -81,17 +82,17 @@ func c2PartsChart(c *ui.Context, frame mujica.ChartFrame, show bool, s *c2PartsS
yo.Ticks = []float64{10, 50, 90}
}
}
x, y := mujica.ChartAxis(c, xo), mujica.ChartAxis(c, yo)
grid := mujica.ChartGrid(c, mujica.ChartGridOptions{Y: &y, Minor: int(s.minor)})
hair := mujica.ChartCrosshair(c, mujica.ChartCrosshairOptions{X: &x, Y: &y})
var notes []mujica.ChartNote
x, y := chart.ChartAxis(c, xo), chart.ChartAxis(c, yo)
grid := chart.ChartGrid(c, chart.ChartGridOptions{Y: &y, Minor: int(s.minor)})
hair := chart.ChartCrosshair(c, chart.ChartCrosshairOptions{X: &x, Y: &y})
var notes []chart.ChartNote
if id == "127" {
notes = s.notes
for i := range notes {
notes[i].Draggable = s.draggable
}
}
annotation := mujica.ChartAnnotation(c, notes, mujica.ChartAnnotationOptions{X: &x, Y: &y})
annotation := chart.ChartAnnotation(c, notes, chart.ChartAnnotationOptions{X: &x, Y: &y})
plotOf := func(r ui.Rect) ui.Rect {
return ui.Rect{X: r.X + 64, Y: r.Y + 16, W: max(1, r.W-100), H: max(1, r.H-56)}
}
@@ -103,16 +104,16 @@ func c2PartsChart(c *ui.Context, frame mujica.ChartFrame, show bool, s *c2PartsS
if !show {
marks = 0
}
colors := internal.Palette(mujica.Tokens(c))
return mujica.Chart(c, frame, mujica.ChartSpec{
colors := internal.Palette(core.Tokens(c))
return chart.Chart(c, frame, chart.ChartSpec{
Marks: marks,
Pick: func(r ui.Rect, px, py float32) int {
return hair.Snap(len(pts), func(i int) (float32, float32) { return at(r, i) }, px, py)
},
Anchor: at,
Readout: func(i int) string { return fmt.Sprintf("x %s · y %s", x.Label(pts[i][0]), y.Label(pts[i][1])) },
Tooltip: func(i int) []mujica.ChartTooltipLine {
return []mujica.ChartTooltipLine{{Text: x.Label(pts[i][0])}, {Text: "Pews " + y.Label(pts[i][1]), Color: colors[0]}, {Text: "Candles " + y.Label(pts[i][1]/2), Color: colors[1], Shape: 1}}
Tooltip: func(i int) []chart.ChartTooltipLine {
return []chart.ChartTooltipLine{{Text: x.Label(pts[i][0])}, {Text: "Pews " + y.Label(pts[i][1]), Color: colors[0]}, {Text: "Candles " + y.Label(pts[i][1]/2), Color: colors[1], Shape: 1}}
},
Input: func(el *ui.Element, r ui.Rect) {
if annotation.Drag(el, plotOf(r)) {
+25 -23
View File
@@ -5,8 +5,10 @@ import (
"github.com/egoist/mygo/ui"
mujica "mujicaui"
"mujicaui/internal"
"github.com/ZacharyZhang-NY/MujicaUI/chart"
"github.com/ZacharyZhang-NY/MujicaUI/core"
"github.com/ZacharyZhang-NY/MujicaUI/input"
"github.com/ZacharyZhang-NY/MujicaUI/internal"
)
func c2LegendDemo(c *ui.Context, log func(string)) {
@@ -21,14 +23,14 @@ func c2LegendDemo(c *ui.Context, log func(string)) {
c2Check(c, &s.disabled, "Disabled", log)
c2Number(c, &s.max, "Max items", 0, 6, 1, log)
}, func() {
pal := internal.Palette(mujica.Tokens(c))
pal := internal.Palette(core.Tokens(c))
names := []string{"Matins", "Lauds", "Prime", "Terce", "Sext", "None"}
items := make([]mujica.ChartLegendItem, len(names))
items := make([]chart.ChartLegendItem, len(names))
for i, n := range names {
items[i] = mujica.ChartLegendItem{Name: n, Color: pal[i], Shape: i % 5}
items[i] = chart.ChartLegendItem{Name: n, Color: pal[i], Shape: i % 5}
}
ui.Column(c).Disabled(s.disabled).Children(func() {
r := mujica.ChartLegend(c, &s.hidden, items, mujica.ChartLegendOptions{Vertical: s.vertical, MaxItems: int(s.max)})
r := chart.ChartLegend(c, &s.hidden, items, chart.ChartLegendOptions{Vertical: s.vertical, MaxItems: int(s.max)})
if expanded, ok := r.Expanded(); ok {
log(fmt.Sprintf("ChartLegend: expanded %v", expanded))
}
@@ -52,12 +54,12 @@ func c2BrushDemo(c *ui.Context, log func(string)) {
c2Number(c, &s.step, "Step", 1, 10, 1, log)
}, func() {
vals := []float64{1, 4, 2, 7, 3, 8, 4, 9}
r := mujica.ChartBrush(c, &s.value, mujica.ChartBrushOptions{Label: "Months", Max: 100, Step: s.step, Values: vals, Disabled: s.disabled})
r := chart.ChartBrush(c, &s.value, chart.ChartBrushOptions{Label: "Months", Max: 100, Step: s.step, Values: vals, Disabled: s.disabled})
if r.Changed() {
log(fmt.Sprintf("ChartBrush: range %g–%g", s.value[0], s.value[1]))
}
x := mujica.ChartAxis(c, mujica.ChartAxisOptions{Min: s.value[0], Max: s.value[1]})
y := mujica.ChartAxis(c, mujica.ChartAxisOptions{Side: mujica.SideLeft, Max: 10})
x := chart.ChartAxis(c, chart.ChartAxisOptions{Min: s.value[0], Max: s.value[1]})
y := chart.ChartAxis(c, chart.ChartAxisOptions{Side: chart.SideLeft, Max: 10})
xs := []float64{0, 15, 30, 45, 60, 75, 90, 100}
var visible []int
for i, v := range xs {
@@ -70,7 +72,7 @@ func c2BrushDemo(c *ui.Context, log func(string)) {
plot := plotOf(r)
return x.Map(plot, xs[visible[i]]), y.Map(plot, vals[visible[i]])
}
c1Log(log, "Zoom preview", mujica.Chart(c, mujica.ChartFrame{Label: "Zoom preview", Height: 140}, mujica.ChartSpec{
c1Log(log, "Zoom preview", chart.Chart(c, chart.ChartFrame{Label: "Zoom preview", Height: 140}, chart.ChartSpec{
Marks: len(visible), Anchor: at,
Pick: func(r ui.Rect, px, py float32) int {
return internal.Nearest(len(visible), func(i int) (float32, float32) { return at(r, i) }, px, py, 24, false)
@@ -82,7 +84,7 @@ func c2BrushDemo(c *ui.Context, log func(string)) {
y.Draw(p, plot)
for i := range visible {
px, py := at(r, i)
internal.Marker(p, 0, px, py, 8, mujica.Tokens(c).AccentText)
internal.Marker(p, 0, px, py, 8, core.Tokens(c).AccentText)
}
},
}))
@@ -104,10 +106,10 @@ func c2ExportDemo(c *ui.Context, log func(string)) {
c2Number(c, &s.scale, "Scale", 1, 3, 0.5, log)
c2Check(c, &s.disabled, "Disabled", log)
}, func() {
if mujica.Button(c, "Export PNG", mujica.ButtonOptions{Disabled: s.disabled}).Clicked() {
data, err := mujica.ChartExport(c, func(c *ui.Context) {
mujica.BarChart(c, []string{"Nave", "Choir", "Crypt"}, []mujica.ChartSeries{{Name: "Pews", Values: []float64{3, 5, 2}}}, mujica.BarChartOptions{Frame: mujica.ChartFrame{Label: "Exported chart", Height: float32(s.height) - 80}})
}, mujica.ChartExportOptions{Width: int(s.width), Height: int(s.height), Scale: float32(s.scale)})
if input.Button(c, "Export PNG", input.ButtonOptions{Disabled: s.disabled}).Clicked() {
data, err := chart.ChartExport(c, func(c *ui.Context) {
chart.BarChart(c, []string{"Nave", "Choir", "Crypt"}, []chart.ChartSeries{{Name: "Pews", Values: []float64{3, 5, 2}}}, chart.BarChartOptions{Frame: chart.ChartFrame{Label: "Exported chart", Height: float32(s.height) - 80}})
}, chart.ChartExportOptions{Width: int(s.width), Height: int(s.height), Scale: float32(s.scale)})
if err == nil {
s.bitmap, err = ui.DecodeBitmap(data)
}
@@ -121,7 +123,7 @@ func c2ExportDemo(c *ui.Context, log func(string)) {
}
}
if s.error != "" {
ui.Text(c, s.error).TextColor(mujica.Tokens(c).Danger).Role(ui.RoleStatus)
ui.Text(c, s.error).TextColor(core.Tokens(c).Danger).Role(ui.RoleStatus)
}
if s.bitmap != nil {
ui.Image(c, s.bitmap).Size(float32(s.width), float32(s.height))
@@ -147,7 +149,7 @@ func c2SparkBarDemo(c *ui.Context, log func(string)) {
vals = nil
}
ui.Column(c).Disabled(s.disabled).Children(func() {
c1Log(log, "SparkBar", mujica.SparkBar(c, vals, mujica.SparkBarOptions{Frame: f, BarWidth: float32(s.width)}))
c1Log(log, "SparkBar", chart.SparkBar(c, vals, chart.SparkBarOptions{Frame: f, BarWidth: float32(s.width)}))
})
})
}
@@ -163,11 +165,11 @@ func c2TrendDemo(c *ui.Context, log func(string)) {
c2Number(c, &s.precision, "Precision", 0, 3, 1, log)
c2Check(c, &s.invert, "Invert", log)
}, func() {
mujica.TrendIndicator(c, s.change, mujica.TrendIndicatorOptions{Precision: int(s.precision), Unit: "%", Note: "vs Lent", Invert: s.invert})
chart.TrendIndicator(c, s.change, chart.TrendIndicatorOptions{Precision: int(s.precision), Unit: "%", Note: "vs Lent", Invert: s.invert})
ui.Row(c).Gap(24).Children(func() {
mujica.TrendIndicator(c, 3, mujica.TrendIndicatorOptions{})
mujica.TrendIndicator(c, -3, mujica.TrendIndicatorOptions{})
mujica.TrendIndicator(c, 0, mujica.TrendIndicatorOptions{})
chart.TrendIndicator(c, 3, chart.TrendIndicatorOptions{})
chart.TrendIndicator(c, -3, chart.TrendIndicatorOptions{})
chart.TrendIndicator(c, 0, chart.TrendIndicatorOptions{})
})
})
}
@@ -184,8 +186,8 @@ func c2EmptyDemo(c *ui.Context, log func(string)) {
c2Check(c, &s.disabled, "Disabled", log)
}, func() {
f, _ := c2Frame(c, "ChartEmptyState", &s.status, log)
ui.Column(c).Disabled(s.disabled).Border(1, mujica.Tokens(c).Border).Children(func() {
if mujica.ChartEmptyState(c, f.Status, mujica.ChartEmptyStateOptions{Height: float32(s.height), Error: f.Error}).Changed() {
ui.Column(c).Disabled(s.disabled).Border(1, core.Tokens(c).Border).Children(func() {
if chart.ChartEmptyState(c, f.Status, chart.ChartEmptyStateOptions{Height: float32(s.height), Error: f.Error}).Changed() {
log("ChartEmptyState: retry")
s.status = "Loading"
}
+2 -2
View File
@@ -7,7 +7,7 @@ import (
"github.com/egoist/mygo/ui"
mujica "mujicaui"
"github.com/ZacharyZhang-NY/MujicaUI/core"
)
func c2Demo(t *testing.T, id string) (*ui.Tester, *[]string) {
@@ -18,7 +18,7 @@ func c2Demo(t *testing.T, id string) (*ui.Tester, *[]string) {
}
events := &[]string{}
tt := ui.NewTester(func(c *ui.Context) {
mujica.Use(c, mujica.Settings{})
core.Use(c, core.Settings{})
ui.Scroll(c).Fill().Padding(24).Children(func() { charts2Pages[i].Demo(c, func(s string) { *events = append(*events, s) }) })
}, 900, 760)
tt.SetPreferences(ui.Preferences{ReduceMotion: true, TextScale: 1})
+20 -20
View File
@@ -5,62 +5,62 @@ const chatinputCategory = "Chat Input"
var chatinputPages = []page{
{ID: "221", Name: "PromptComposer", ZhName: "提示编辑器", Category: chatinputCategory, Demo: chatinputDemoPromptComposer,
Keys: "Enter 发送,Shift+Enter 换行;CmdEnterSends 下使用 Command/Ctrl+Enter;组合保护期间 Enter 留给输入法。",
Code: "var prompt string\nview := func(c *ui.Context) {\n\tmujica.Use(c, mujica.Settings{})\n\tr := mujica.PromptComposer(c, &prompt, mujica.PromptComposerOptions{Placeholder: \"Ask the herald\"})\n\tif r.Submitted() {\n\t\tfmt.Println(\"send\", prompt)\n\t\tprompt = \"\"\n\t}\n}\nui.Render(view, 400, 300, 1)"},
Code: "var prompt string\nview := func(c *ui.Context) {\n\tcore.Use(c, core.Settings{})\n\tr := chat.PromptComposer(c, &prompt, chat.PromptComposerOptions{Placeholder: \"Ask the herald\"})\n\tif r.Submitted() {\n\t\tfmt.Println(\"send\", prompt)\n\t\tprompt = \"\"\n\t}\n}\nui.Render(view, 400, 300, 1)"},
{ID: "222", Name: "AttachmentChip", ZhName: "附件标签", Category: chatinputCategory, Demo: chatinputDemoAttachmentChip,
Keys: "Tab 到移除与重试;Enter、Space 激活。",
Code: "view := func(c *ui.Context) {\n\tmujica.Use(c, mujica.Settings{})\n\tr := mujica.AttachmentChip(c, mujica.Attachment{Name: \"guests.csv\", Size: 4096, Uploading: true, Progress: 0.4})\n\tif r.Removed() {\n\t\tfmt.Println(\"cancel the upload\")\n\t}\n}\nui.Render(view, 400, 300, 1)"},
Code: "view := func(c *ui.Context) {\n\tcore.Use(c, core.Settings{})\n\tr := chat.AttachmentChip(c, chat.Attachment{Name: \"guests.csv\", Size: 4096, Uploading: true, Progress: 0.4})\n\tif r.Removed() {\n\t\tfmt.Println(\"cancel the upload\")\n\t}\n}\nui.Render(view, 400, 300, 1)"},
{ID: "223", Name: "PasteImagePreview", ZhName: "图片预览", Category: chatinputCategory, Demo: chatinputDemoPasteImagePreview,
Keys: "Tab 到移除按钮;Enter、Space 移除。",
Code: "pic := ui.MustParseSVG([]byte(`<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"4\" height=\"3\"/>`))\nimages := []mujica.PastedImage{{ID: \"p1\", Source: pic, Name: \"screenshot\"}}\nview := func(c *ui.Context) {\n\tmujica.Use(c, mujica.Settings{})\n\tif id, ok := mujica.PasteImagePreview(c, images).Removed(); ok {\n\t\tfmt.Println(\"remove\", id)\n\t}\n}\nui.Render(view, 400, 300, 1)"},
Code: "pic := ui.MustParseSVG([]byte(`<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"4\" height=\"3\"/>`))\nimages := []chat.PastedImage{{ID: \"p1\", Source: pic, Name: \"screenshot\"}}\nview := func(c *ui.Context) {\n\tcore.Use(c, core.Settings{})\n\tif id, ok := chat.PasteImagePreview(c, images).Removed(); ok {\n\t\tfmt.Println(\"remove\", id)\n\t}\n}\nui.Render(view, 400, 300, 1)"},
{ID: "224", Name: "DragDropOverlay", ZhName: "拖放遮罩", Category: chatinputCategory, Demo: chatinputDemoDragDropOverlay,
Keys: "内容中的操作按其自身键盘规则访问;文件拖入由操作系统驱动。",
Code: "view := func(c *ui.Context) {\n\tmujica.Use(c, mujica.Settings{})\n\timages := func(path string) bool { return strings.HasSuffix(path, \".png\") }\n\tr := mujica.DragDropOverlay(c, mujica.DragDropOverlayOptions{Accept: images}, func() {\n\t\tui.Text(c, \"Drag images into the chat\")\n\t})\n\tfor _, path := range r.Accepted() {\n\t\tfmt.Println(\"attach\", path)\n\t}\n}\nui.Render(view, 400, 300, 1)"},
Code: "view := func(c *ui.Context) {\n\tcore.Use(c, core.Settings{})\n\timages := func(path string) bool { return strings.HasSuffix(path, \".png\") }\n\tr := chat.DragDropOverlay(c, chat.DragDropOverlayOptions{Accept: images}, func() {\n\t\tui.Text(c, \"Drag images into the chat\")\n\t})\n\tfor _, path := range r.Accepted() {\n\t\tfmt.Println(\"attach\", path)\n\t}\n}\nui.Render(view, 400, 300, 1)"},
{ID: "225", Name: "VoiceInputButton", ZhName: "语音输入按钮", Category: chatinputCategory, Demo: chatinputDemoVoiceInputButton,
Keys: "Tab 到按钮;Enter、Space 开始或停止。",
Code: "var rec mujica.VoiceInputOptions\nview := func(c *ui.Context) {\n\tmujica.Use(c, mujica.Settings{})\n\tif mujica.VoiceInputButton(c, rec).Changed() {\n\t\trec = mujica.VoiceInputOptions{Recording: !rec.Recording, Started: time.Now()}\n\t}\n}\nui.Render(view, 400, 300, 1)"},
Code: "var rec chat.VoiceInputOptions\nview := func(c *ui.Context) {\n\tcore.Use(c, core.Settings{})\n\tif chat.VoiceInputButton(c, rec).Changed() {\n\t\trec = chat.VoiceInputOptions{Recording: !rec.Recording, Started: time.Now()}\n\t}\n}\nui.Render(view, 400, 300, 1)"},
{ID: "226", Name: "VoiceWaveform", ZhName: "输入波形", Category: chatinputCategory, Demo: chatinputDemoVoiceWaveform,
Keys: "展示组件,沿用外层 Tab 顺序。",
Code: "levels := []float32{0.2, 0.6, 0.9, 0.4, 0.1}\nview := func(c *ui.Context) {\n\tmujica.Use(c, mujica.Settings{})\n\tmujica.VoiceWaveform(c, levels, mujica.VoiceWaveformOptions{})\n}\nui.Render(view, 400, 300, 1)"},
Code: "levels := []float32{0.2, 0.6, 0.9, 0.4, 0.1}\nview := func(c *ui.Context) {\n\tcore.Use(c, core.Settings{})\n\tchat.VoiceWaveform(c, levels, chat.VoiceWaveformOptions{})\n}\nui.Render(view, 400, 300, 1)"},
{ID: "227", Name: "SlashCommandMenu", ZhName: "斜杠命令菜单", Category: chatinputCategory, Demo: chatinputDemoSlashCommandMenu,
Keys: "编辑器内 Up/Down 选择,Enter 选中,Tab 补全,Escape 关闭。",
Code: "var prompt string\ncommands := []mujica.SlashCommand{{Name: \"summarize\", Description: \"Summarize the thread\"}}\nview := func(c *ui.Context) {\n\tmujica.Use(c, mujica.Settings{})\n\tcomposer := mujica.PromptComposer(c, &prompt, mujica.PromptComposerOptions{})\n\tif cmd, ok := mujica.SlashCommandMenu(c, composer, &prompt, commands).Chosen(); ok {\n\t\tfmt.Println(\"run\", cmd.Name)\n\t}\n}\nui.Render(view, 400, 300, 1)"},
Code: "var prompt string\ncommands := []chat.SlashCommand{{Name: \"summarize\", Description: \"Summarize the thread\"}}\nview := func(c *ui.Context) {\n\tcore.Use(c, core.Settings{})\n\tcomposer := chat.PromptComposer(c, &prompt, chat.PromptComposerOptions{})\n\tif cmd, ok := chat.SlashCommandMenu(c, composer, &prompt, commands).Chosen(); ok {\n\t\tfmt.Println(\"run\", cmd.Name)\n\t}\n}\nui.Render(view, 400, 300, 1)"},
{ID: "228", Name: "ContextMentionMenu", ZhName: "上下文提及菜单", Category: chatinputCategory, Demo: chatinputDemoContextMentionMenu,
Keys: "编辑器内 Up/Down 选择,Enter 或 Tab 附加,Escape 关闭。",
Code: "var prompt string\nvar found []mujica.ContextItem\nview := func(c *ui.Context) {\n\tmujica.Use(c, mujica.Settings{})\n\tcomposer := mujica.PromptComposer(c, &prompt, mujica.PromptComposerOptions{})\n\tr := mujica.ContextMentionMenu(c, composer, &prompt, found, mujica.ContextMentionOptions{})\n\tif r.QueryChanged() {\n\t\tfound = []mujica.ContextItem{{ID: \"f1\", Label: r.Query() + \".md\", Kind: mujica.ContextFile}}\n\t}\n\tif item, ok := r.Chosen(); ok {\n\t\tfmt.Println(\"attach\", item.ID)\n\t}\n}\nui.Render(view, 400, 300, 1)"},
Code: "var prompt string\nvar found []chat.ContextItem\nview := func(c *ui.Context) {\n\tcore.Use(c, core.Settings{})\n\tcomposer := chat.PromptComposer(c, &prompt, chat.PromptComposerOptions{})\n\tr := chat.ContextMentionMenu(c, composer, &prompt, found, chat.ContextMentionOptions{})\n\tif r.QueryChanged() {\n\t\tfound = []chat.ContextItem{{ID: \"f1\", Label: r.Query() + \".md\", Kind: chat.ContextFile}}\n\t}\n\tif item, ok := r.Chosen(); ok {\n\t\tfmt.Println(\"attach\", item.ID)\n\t}\n}\nui.Render(view, 400, 300, 1)"},
{ID: "229", Name: "ContextChips", ZhName: "上下文标签", Category: chatinputCategory, Demo: chatinputDemoContextChips,
Keys: "Tab 到移除与展开;Enter、Space 激活。",
Code: "items := []mujica.ContextItem{{ID: \"f1\", Label: \"guests.csv\", Kind: mujica.ContextFile}}\nview := func(c *ui.Context) {\n\tmujica.Use(c, mujica.Settings{})\n\tif id, ok := mujica.ContextChips(c, items, mujica.ContextChipsOptions{}).Removed(); ok {\n\t\tfmt.Println(\"detach\", id)\n\t}\n}\nui.Render(view, 400, 300, 1)"},
Code: "items := []chat.ContextItem{{ID: \"f1\", Label: \"guests.csv\", Kind: chat.ContextFile}}\nview := func(c *ui.Context) {\n\tcore.Use(c, core.Settings{})\n\tif id, ok := chat.ContextChips(c, items, chat.ContextChipsOptions{}).Removed(); ok {\n\t\tfmt.Println(\"detach\", id)\n\t}\n}\nui.Render(view, 400, 300, 1)"},
{ID: "230", Name: "ModelSelector", ZhName: "模型选择器", Category: chatinputCategory, Demo: chatinputDemoModelSelector,
Keys: "Tab 到选择器;Up/Down、Home/End 选择,Enter/Space 确认,Escape 关闭。",
Code: "model := \"herald\"\nmodels := []mujica.ModelOption{{ID: \"herald\", Name: \"Herald 2\", Capabilities: []string{\"Vision\"}, ContextTokens: 200000}}\nview := func(c *ui.Context) {\n\tmujica.Use(c, mujica.Settings{})\n\tif mujica.ModelSelector(c, &model, models, mujica.ModelSelectorOptions{}).Changed() {\n\t\tfmt.Println(\"model\", model)\n\t}\n}\nui.Render(view, 400, 300, 1)"},
Code: "model := \"herald\"\nmodels := []chat.ModelOption{{ID: \"herald\", Name: \"Herald 2\", Capabilities: []string{\"Vision\"}, ContextTokens: 200000}}\nview := func(c *ui.Context) {\n\tcore.Use(c, core.Settings{})\n\tif chat.ModelSelector(c, &model, models, chat.ModelSelectorOptions{}).Changed() {\n\t\tfmt.Println(\"model\", model)\n\t}\n}\nui.Render(view, 400, 300, 1)"},
{ID: "231", Name: "ModeSelector", ZhName: "模式选择器", Category: chatinputCategory, Demo: chatinputDemoModeSelector,
Keys: "Tab 进入模式组;Left/Right 切换模式。",
Code: "mode := mujica.ModeChat\nview := func(c *ui.Context) {\n\tmujica.Use(c, mujica.Settings{})\n\tif mujica.ModeSelector(c, &mode, mujica.ModeSelectorOptions{}).Changed() {\n\t\tfmt.Println(\"mode\", mode)\n\t}\n}\nui.Render(view, 400, 300, 1)"},
Code: "mode := chat.ModeChat\nview := func(c *ui.Context) {\n\tcore.Use(c, core.Settings{})\n\tif chat.ModeSelector(c, &mode, chat.ModeSelectorOptions{}).Changed() {\n\t\tfmt.Println(\"mode\", mode)\n\t}\n}\nui.Render(view, 400, 300, 1)"},
{ID: "232", Name: "ToolToggleMenu", ZhName: "工具开关菜单", Category: chatinputCategory, Demo: chatinputDemoToolToggleMenu,
Keys: "Tab 到菜单按钮;Enter/Space 打开;Tab 到开关与授权;Escape 关闭。",
Code: "web := true\nview := func(c *ui.Context) {\n\tmujica.Use(c, mujica.Settings{})\n\ttools := []mujica.ToolItem{{ID: \"web\", Label: \"Web search\", On: &web}}\n\tif id, ok := mujica.ToolToggleMenu(c, tools).Changed(); ok {\n\t\tfmt.Println(\"toggled\", id)\n\t}\n}\nui.Render(view, 400, 300, 1)"},
Code: "web := true\nview := func(c *ui.Context) {\n\tcore.Use(c, core.Settings{})\n\ttools := []chat.ToolItem{{ID: \"web\", Label: \"Web search\", On: &web}}\n\tif id, ok := chat.ToolToggleMenu(c, tools).Changed(); ok {\n\t\tfmt.Println(\"toggled\", id)\n\t}\n}\nui.Render(view, 400, 300, 1)"},
{ID: "233", Name: "ParameterPanel", ZhName: "参数面板", Category: chatinputCategory, Demo: chatinputDemoParameterPanel,
Keys: "Tab 到滑块与重置;方向键按 Step 调整,Home/End 到范围端点;Enter/Space 重置。",
Code: "temperature := 1.0\nview := func(c *ui.Context) {\n\tmujica.Use(c, mujica.Settings{})\n\tparams := []mujica.ModelParam{{Label: \"Temperature\", Value: &temperature, Min: 0, Max: 2, Step: 0.1, Default: 1}}\n\tmujica.ParameterPanel(c, params)\n}\nui.Render(view, 400, 300, 1)"},
Code: "temperature := 1.0\nview := func(c *ui.Context) {\n\tcore.Use(c, core.Settings{})\n\tparams := []chat.ModelParam{{Label: \"Temperature\", Value: &temperature, Min: 0, Max: 2, Step: 0.1, Default: 1}}\n\tchat.ParameterPanel(c, params)\n}\nui.Render(view, 400, 300, 1)"},
{ID: "234", Name: "TokenCounter", ZhName: "Token计数", Category: chatinputCategory, Demo: chatinputDemoTokenCounter,
Keys: "展示组件,沿用外层 Tab 顺序。",
Code: "view := func(c *ui.Context) {\n\tmujica.Use(c, mujica.Settings{})\n\tmujica.TokenCounter(c, 1234, 8000)\n}\nui.Render(view, 400, 300, 1)"},
Code: "view := func(c *ui.Context) {\n\tcore.Use(c, core.Settings{})\n\tchat.TokenCounter(c, 1234, 8000)\n}\nui.Render(view, 400, 300, 1)"},
{ID: "235", Name: "ContextWindowMeter", ZhName: "上下文容量", Category: chatinputCategory, Demo: chatinputDemoContextWindowMeter,
Keys: "展示组件,沿用外层 Tab 顺序。",
Code: "segments := []mujica.ContextSegment{{Label: \"System\", Tokens: 1200}, {Label: \"History\", Tokens: 58000}}\nview := func(c *ui.Context) {\n\tmujica.Use(c, mujica.Settings{})\n\tmujica.ContextWindowMeter(c, segments, 128000, mujica.ContextWindowMeterOptions{})\n}\nui.Render(view, 400, 300, 1)"},
Code: "segments := []chat.ContextSegment{{Label: \"System\", Tokens: 1200}, {Label: \"History\", Tokens: 58000}}\nview := func(c *ui.Context) {\n\tcore.Use(c, core.Settings{})\n\tchat.ContextWindowMeter(c, segments, 128000, chat.ContextWindowMeterOptions{})\n}\nui.Render(view, 400, 300, 1)"},
{ID: "236", Name: "SendButton", ZhName: "发送按钮", Category: chatinputCategory, Demo: chatinputDemoSendButton,
Keys: "Tab 到按钮;Enter、Space 激活;快捷键提示由调用方绑定。",
Code: "streaming := false\nview := func(c *ui.Context) {\n\tmujica.Use(c, mujica.Settings{})\n\tr := mujica.SendButton(c, mujica.SendButtonOptions{Streaming: streaming, Shortcut: \"Enter\"})\n\tif r.Sent() || r.Stopped() {\n\t\tstreaming = r.Sent()\n\t}\n}\nui.Render(view, 400, 300, 1)"},
Code: "streaming := false\nview := func(c *ui.Context) {\n\tcore.Use(c, core.Settings{})\n\tr := chat.SendButton(c, chat.SendButtonOptions{Streaming: streaming, Shortcut: \"Enter\"})\n\tif r.Sent() || r.Stopped() {\n\t\tstreaming = r.Sent()\n\t}\n}\nui.Render(view, 400, 300, 1)"},
{ID: "237", Name: "CitationBadge", ZhName: "引用编号", Category: chatinputCategory, Demo: chatinputDemoCitationBadge,
Keys: "Tab 到引用;Enter、Space 定位;悬停或键盘焦点 500 毫秒后预览;Escape 关闭预览。",
Code: "var cites mujica.CitationState\nsrc := mujica.Source{Title: \"The Palace of Versailles\", URL: \"https://example.com/versailles\"}\nview := func(c *ui.Context) {\n\tmujica.Use(c, mujica.Settings{})\n\tif mujica.CitationBadge(c, 1, src, &cites).Changed() {\n\t\tfmt.Println(\"show source\", cites.Active)\n\t}\n}\nui.Render(view, 400, 300, 1)"},
Code: "var cites chat.CitationState\nsrc := chat.Source{Title: \"The Palace of Versailles\", URL: \"https://example.com/versailles\"}\nview := func(c *ui.Context) {\n\tcore.Use(c, core.Settings{})\n\tif chat.CitationBadge(c, 1, src, &cites).Changed() {\n\t\tfmt.Println(\"show source\", cites.Active)\n\t}\n}\nui.Render(view, 400, 300, 1)"},
{ID: "238", Name: "SourceCard", ZhName: "来源卡片", Category: chatinputCategory, Demo: chatinputDemoSourceCard,
Keys: "Tab 到卡片;Enter、Space 请求打开。",
Code: "var cites mujica.CitationState\nsrc := mujica.Source{Title: \"Hall of Mirrors\", URL: \"https://example.org/hall\", Snippet: \"Seventeen arches.\"}\nview := func(c *ui.Context) {\n\tmujica.Use(c, mujica.Settings{})\n\tif mujica.SourceCard(c, 1, src, &cites).Clicked() {\n\t\tfmt.Println(\"open\", src.URL)\n\t}\n}\nui.Render(view, 400, 300, 1)"},
Code: "var cites chat.CitationState\nsrc := chat.Source{Title: \"Hall of Mirrors\", URL: \"https://example.org/hall\", Snippet: \"Seventeen arches.\"}\nview := func(c *ui.Context) {\n\tcore.Use(c, core.Settings{})\n\tif chat.SourceCard(c, 1, src, &cites).Clicked() {\n\t\tfmt.Println(\"open\", src.URL)\n\t}\n}\nui.Render(view, 400, 300, 1)"},
{ID: "239", Name: "SourcesPanel", ZhName: "来源面板", Category: chatinputCategory, Demo: chatinputDemoSourcesPanel,
Keys: "Tab 到可见来源;Enter、Space 请求打开;滚轮滚动;Active 变化定位来源。",
Code: "var cites mujica.CitationState\nsources := []mujica.Source{{Title: \"Hall of Mirrors\", URL: \"https://example.org/hall\"}}\nview := func(c *ui.Context) {\n\tmujica.Use(c, mujica.Settings{})\n\tif n, ok := mujica.SourcesPanel(c, sources, &cites).Opened(); ok {\n\t\tfmt.Println(\"open\", sources[n-1].URL)\n\t}\n}\nui.Render(view, 400, 300, 1)"},
Code: "var cites chat.CitationState\nsources := []chat.Source{{Title: \"Hall of Mirrors\", URL: \"https://example.org/hall\"}}\nview := func(c *ui.Context) {\n\tcore.Use(c, core.Settings{})\n\tif n, ok := chat.SourcesPanel(c, sources, &cites).Opened(); ok {\n\t\tfmt.Println(\"open\", sources[n-1].URL)\n\t}\n}\nui.Render(view, 400, 300, 1)"},
{ID: "240", Name: "SearchProgress", ZhName: "搜索进度", Category: chatinputCategory, Demo: chatinputDemoSearchProgress,
Keys: "Tab 到折叠标题;Enter、Space 展开或收起。",
Code: "view := func(c *ui.Context) {\n\tmujica.Use(c, mujica.Settings{})\n\tstages := []mujica.SearchStage{{Label: \"Searching the web\", Done: true}, {Label: \"Reading sources\"}}\n\tmujica.SearchProgress(c, mujica.SearchProgressOptions{Query: \"Versailles\", Read: 3, Total: 8, Stages: stages})\n}\nui.Render(view, 400, 300, 1)"},
Code: "view := func(c *ui.Context) {\n\tcore.Use(c, core.Settings{})\n\tstages := []chat.SearchStage{{Label: \"Searching the web\", Done: true}, {Label: \"Reading sources\"}}\n\tchat.SearchProgress(c, chat.SearchProgressOptions{Query: \"Versailles\", Read: 3, Total: 8, Stages: stages})\n}\nui.Render(view, 400, 300, 1)"},
}
+25 -22
View File
@@ -7,7 +7,10 @@ import (
"strings"
"github.com/egoist/mygo/ui"
mujica "mujicaui"
"github.com/ZacharyZhang-NY/MujicaUI/chat"
"github.com/ZacharyZhang-NY/MujicaUI/core"
"github.com/ZacharyZhang-NY/MujicaUI/input"
)
func chatinputDemoPromptComposer(c *ui.Context, log func(string)) {
@@ -23,20 +26,20 @@ func chatinputDemoPromptComposer(c *ui.Context, log func(string)) {
choiceFlag(c, &s.attached, "Attachment", log)
}, func() {
ui.Column(c).Width(520).Children(func() {
r := mujica.PromptComposer(c, &s.text, mujica.PromptComposerOptions{Label: "Prompt", Placeholder: "Ask the herald", Disabled: s.disabled, CmdEnterSends: s.cmd, Composing: s.composing,
r := chat.PromptComposer(c, &s.text, chat.PromptComposerOptions{Label: "Prompt", Placeholder: "Ask the herald", Disabled: s.disabled, CmdEnterSends: s.cmd, Composing: s.composing,
Attachments: func() {
if s.attached && mujica.AttachmentChip(c, mujica.Attachment{Name: "guests.csv", Size: 4096}).Removed() {
if s.attached && chat.AttachmentChip(c, chat.Attachment{Name: "guests.csv", Size: 4096}).Removed() {
s.attached = false
log("remove attachment")
}
},
Tools: func() {
if _, ok := mujica.ToolToggleMenu(c, []mujica.ToolItem{{ID: "web", Label: "Web search", On: &s.web}}).Changed(); ok {
if _, ok := chat.ToolToggleMenu(c, []chat.ToolItem{{ID: "web", Label: "Web search", On: &s.web}}).Changed(); ok {
log("toggle web search")
}
},
Actions: func() {
r := mujica.SendButton(c, mujica.SendButtonOptions{Streaming: s.streaming, Disabled: strings.TrimSpace(s.text) == "" || s.composing, Shortcut: "Enter"})
r := chat.SendButton(c, chat.SendButtonOptions{Streaming: s.streaming, Disabled: strings.TrimSpace(s.text) == "" || s.composing, Shortcut: "Enter"})
if r.Sent() {
s.streaming = true
s.text = ""
@@ -70,10 +73,10 @@ func chatinputDemoAttachmentChip(c *ui.Context, log func(string)) {
choiceFlag(c, &s.upload, "Uploading", log)
choiceFlag(c, &s.fail, "Failed", log)
choiceFlag(c, &s.disabled, "Disabled", log)
if mujica.Slider(c, &s.progress, mujica.SliderOptions{Min: 0, Max: 1, Step: 0.1, Label: "Upload progress"}).Changed() {
if input.Slider(c, &s.progress, input.SliderOptions{Min: 0, Max: 1, Step: 0.1, Label: "Upload progress"}).Changed() {
log(fmt.Sprint("progress ", s.progress))
}
if mujica.Button(c, "Restore attachment", mujica.ButtonOptions{}).Clicked() {
if input.Button(c, "Restore attachment", input.ButtonOptions{}).Clicked() {
s.removed = false
log("restore")
}
@@ -83,11 +86,11 @@ func chatinputDemoAttachmentChip(c *ui.Context, log func(string)) {
ui.Text(c, "Attachment removed")
return
}
a := mujica.Attachment{Name: "guests.csv", Size: 4096, Uploading: s.upload, Progress: s.progress}
a := chat.Attachment{Name: "guests.csv", Size: 4096, Uploading: s.upload, Progress: s.progress}
if s.fail {
a.Err = errors.New("upload rejected")
}
r := mujica.AttachmentChip(c, a)
r := chat.AttachmentChip(c, a)
if r.Removed() {
s.removed = true
log("remove")
@@ -102,30 +105,30 @@ func chatinputDemoAttachmentChip(c *ui.Context, log func(string)) {
})
}
func chatinputPictures() []mujica.PastedImage {
var out []mujica.PastedImage
func chatinputPictures() []chat.PastedImage {
var out []chat.PastedImage
for i, col := range []string{"#7D2034", "#355E80"} {
svg := fmt.Sprintf(`<svg xmlns="http://www.w3.org/2000/svg" width="72" height="72"><rect width="72" height="72" fill="%s"/><circle cx="36" cy="36" r="18" fill="#F2EBDD"/></svg>`, col)
out = append(out, mujica.PastedImage{ID: fmt.Sprint(i), Name: fmt.Sprintf("Picture %d", i+1), Source: ui.MustParseSVG([]byte(svg))})
out = append(out, chat.PastedImage{ID: fmt.Sprint(i), Name: fmt.Sprintf("Picture %d", i+1), Source: ui.MustParseSVG([]byte(svg))})
}
return out
}
func chatinputDemoPasteImagePreview(c *ui.Context, log func(string)) {
type state struct {
images []mujica.PastedImage
images []chat.PastedImage
disabled bool
}
s := ui.Local(c.Root(), "223", func() state { return state{images: chatinputPictures()} })
choiceDemo(c, func() {
choiceFlag(c, &s.disabled, "Disabled", log)
if mujica.Button(c, "Restore images", mujica.ButtonOptions{}).Clicked() {
if input.Button(c, "Restore images", input.ButtonOptions{}).Clicked() {
s.images = chatinputPictures()
log("restore images")
}
}, func() {
ui.Column(c).Padding(12).Disabled(s.disabled).Children(func() {
if id, ok := mujica.PasteImagePreview(c, s.images).Removed(); ok {
if id, ok := chat.PasteImagePreview(c, s.images).Removed(); ok {
for i, im := range s.images {
if im.ID == id {
s.images = append(s.images[:i], s.images[i+1:]...)
@@ -150,8 +153,8 @@ func chatinputDemoDragDropOverlay(c *ui.Context, log func(string)) {
choiceFlag(c, &s.disabled, "Disabled", log)
}, func() {
ui.Column(c).Width(520).Height(180).Disabled(s.disabled).Children(func() {
r := mujica.DragDropOverlay(c, mujica.DragDropOverlayOptions{Accept: func(p string) bool { return !s.imagesOnly || strings.EqualFold(filepath.Ext(p), ".png") }}, func() {
ui.Box(c).Fill().Border(1, mujica.Tokens(c).ControlBorder).Center().Children(func() { ui.Text(c, "Drop files here") })
r := chat.DragDropOverlay(c, chat.DragDropOverlayOptions{Accept: func(p string) bool { return !s.imagesOnly || strings.EqualFold(filepath.Ext(p), ".png") }}, func() {
ui.Box(c).Fill().Border(1, core.Tokens(c).ControlBorder).Center().Children(func() { ui.Text(c, "Drop files here") })
})
r.Element.Fill()
if n := len(r.Accepted()); n > 0 {
@@ -168,16 +171,16 @@ func chatinputDemoDragDropOverlay(c *ui.Context, log func(string)) {
}
func chatinputDemoVoiceInputButton(c *ui.Context, log func(string)) {
s := ui.Local(c.Root(), "225", func() mujica.VoiceInputOptions { return mujica.VoiceInputOptions{} })
s := ui.Local(c.Root(), "225", func() chat.VoiceInputOptions { return chat.VoiceInputOptions{} })
choiceDemo(c, func() {
choiceFlag(c, &s.Disabled, "Disabled", log)
if mujica.Button(c, "Permission failure", mujica.ButtonOptions{}).Clicked() {
if input.Button(c, "Permission failure", input.ButtonOptions{}).Clicked() {
s.Recording = false
s.Denied = true
log("permission denied")
}
}, func() {
if mujica.VoiceInputButton(c, *s).Changed() {
if chat.VoiceInputButton(c, *s).Changed() {
s.Recording = !s.Recording
s.Denied = false
s.Started = c.Now()
@@ -195,7 +198,7 @@ func chatinputDemoVoiceWaveform(c *ui.Context, log func(string)) {
s := ui.Local(c.Root(), "226", func() state { return state{level: 0.6} })
choiceDemo(c, func() {
choiceFlag(c, &s.silent, "Silent", log)
if mujica.Slider(c, &s.level, mujica.SliderOptions{Min: 0, Max: 1, Step: 0.1, Label: "Input level"}).Changed() {
if input.Slider(c, &s.level, input.SliderOptions{Min: 0, Max: 1, Step: 0.1, Label: "Input level"}).Changed() {
log(fmt.Sprint("level ", s.level))
}
}, func() {
@@ -205,6 +208,6 @@ func chatinputDemoVoiceWaveform(c *ui.Context, log func(string)) {
levels[i] = float32(s.level) * float32(i%7+1) / 7
}
}
mujica.VoiceWaveform(c, levels, mujica.VoiceWaveformOptions{})
chat.VoiceWaveform(c, levels, chat.VoiceWaveformOptions{})
})
}
+23 -20
View File
@@ -2,9 +2,12 @@ package main
import (
"fmt"
"github.com/egoist/mygo/ui"
mujica "mujicaui"
"strings"
"github.com/egoist/mygo/ui"
"github.com/ZacharyZhang-NY/MujicaUI/chat"
"github.com/ZacharyZhang-NY/MujicaUI/input"
)
func chatinputDemoSlashCommandMenu(c *ui.Context, log func(string)) {
@@ -18,13 +21,13 @@ func chatinputDemoSlashCommandMenu(c *ui.Context, log func(string)) {
choiceFlag(c, &s.empty, "Empty commands", log)
}, func() {
ui.Column(c).Width(520).Children(func() {
p := mujica.PromptComposer(c, &s.text, mujica.PromptComposerOptions{Label: "Command prompt", Disabled: s.disabled})
commands := []mujica.SlashCommand{{Name: "summarize", Description: "Summarize the thread"}, {Name: "search", Description: "Search the web"}, {Name: "translate", Description: "Translate the message"}}
p := chat.PromptComposer(c, &s.text, chat.PromptComposerOptions{Label: "Command prompt", Disabled: s.disabled})
commands := []chat.SlashCommand{{Name: "summarize", Description: "Summarize the thread"}, {Name: "search", Description: "Search the web"}, {Name: "translate", Description: "Translate the message"}}
if s.empty {
commands = nil
}
before := s.text
if cmd, ok := mujica.SlashCommandMenu(c, p, &s.text, commands).Chosen(); ok {
if cmd, ok := chat.SlashCommandMenu(c, p, &s.text, commands).Chosen(); ok {
log("command " + cmd.Name)
} else if before != s.text {
log("complete command")
@@ -39,14 +42,14 @@ func chatinputDemoSlashCommandMenu(c *ui.Context, log func(string)) {
})
}
func chatinputContextItems() []mujica.ContextItem {
return []mujica.ContextItem{{ID: "file", Label: "guests.csv", Kind: mujica.ContextFile}, {ID: "doc", Label: "Court protocol", Kind: mujica.ContextDoc}, {ID: "web", Label: "Versailles", Detail: "example.com", Kind: mujica.ContextWeb}}
func chatinputContextItems() []chat.ContextItem {
return []chat.ContextItem{{ID: "file", Label: "guests.csv", Kind: chat.ContextFile}, {ID: "doc", Label: "Court protocol", Kind: chat.ContextDoc}, {ID: "web", Label: "Versailles", Detail: "example.com", Kind: chat.ContextWeb}}
}
func chatinputDemoContextMentionMenu(c *ui.Context, log func(string)) {
type state struct {
text, query string
items []mujica.ContextItem
items []chat.ContextItem
loading, disabled bool
}
s := ui.Local(c.Root(), "228", func() state { return state{text: "@", items: chatinputContextItems()} })
@@ -55,12 +58,12 @@ func chatinputDemoContextMentionMenu(c *ui.Context, log func(string)) {
choiceFlag(c, &s.disabled, "Disabled", log)
}, func() {
ui.Column(c).Width(520).Children(func() {
p := mujica.PromptComposer(c, &s.text, mujica.PromptComposerOptions{Label: "Context prompt", Disabled: s.disabled})
p := chat.PromptComposer(c, &s.text, chat.PromptComposerOptions{Label: "Context prompt", Disabled: s.disabled})
items := s.items
if s.loading {
items = nil
}
r := mujica.ContextMentionMenu(c, p, &s.text, items, mujica.ContextMentionOptions{Loading: s.loading})
r := chat.ContextMentionMenu(c, p, &s.text, items, chat.ContextMentionOptions{Loading: s.loading})
if r.QueryChanged() {
s.query = r.Query()
s.items = nil
@@ -87,23 +90,23 @@ func chatinputDemoContextMentionMenu(c *ui.Context, log func(string)) {
func chatinputDemoContextChips(c *ui.Context, log func(string)) {
type state struct {
items []mujica.ContextItem
items []chat.ContextItem
max float64
disabled bool
}
s := ui.Local(c.Root(), "229", func() state { return state{items: chatinputContextItems(), max: 2} })
choiceDemo(c, func() {
choiceFlag(c, &s.disabled, "Disabled", log)
if mujica.NumberInput(c, &s.max, mujica.NumberInputOptions{Min: 1, Max: 5, Step: 1, Label: "Visible chips"}).Changed() {
if input.NumberInput(c, &s.max, input.NumberInputOptions{Min: 1, Max: 5, Step: 1, Label: "Visible chips"}).Changed() {
log(fmt.Sprint("max ", s.max))
}
if mujica.Button(c, "Restore context", mujica.ButtonOptions{}).Clicked() {
if input.Button(c, "Restore context", input.ButtonOptions{}).Clicked() {
s.items = chatinputContextItems()
log("restore context")
}
}, func() {
ui.Column(c).Width(520).Disabled(s.disabled).Children(func() {
if id, ok := mujica.ContextChips(c, s.items, mujica.ContextChipsOptions{Max: int(s.max)}).Removed(); ok {
if id, ok := chat.ContextChips(c, s.items, chat.ContextChipsOptions{Max: int(s.max)}).Removed(); ok {
for i, it := range s.items {
if it.ID == id {
s.items = append(s.items[:i], s.items[i+1:]...)
@@ -126,11 +129,11 @@ func chatinputDemoModelSelector(c *ui.Context, log func(string)) {
choiceFlag(c, &s.disabled, "Disabled", log)
choiceFlag(c, &s.unavailable, "Restricted model", log)
}, func() {
models := []mujica.ModelOption{{ID: "herald", Name: "Herald 2", Capabilities: []string{"Vision", "Tools"}, ContextTokens: 200000}, {ID: "page", Name: "Page Mini", Capabilities: []string{"Fast"}, ContextTokens: 32000}, {ID: "oracle", Name: "Oracle", ContextTokens: 1000000}}
models := []chat.ModelOption{{ID: "herald", Name: "Herald 2", Capabilities: []string{"Vision", "Tools"}, ContextTokens: 200000}, {ID: "page", Name: "Page Mini", Capabilities: []string{"Fast"}, ContextTokens: 32000}, {ID: "oracle", Name: "Oracle", ContextTokens: 1000000}}
if s.unavailable {
models[2].Unavailable = "Needs the Court plan"
}
if mujica.ModelSelector(c, &s.model, models, mujica.ModelSelectorOptions{Disabled: s.disabled}).Changed() {
if chat.ModelSelector(c, &s.model, models, chat.ModelSelectorOptions{Disabled: s.disabled}).Changed() {
log("model " + s.model)
}
})
@@ -138,12 +141,12 @@ func chatinputDemoModelSelector(c *ui.Context, log func(string)) {
func chatinputDemoModeSelector(c *ui.Context, log func(string)) {
type state struct {
mode mujica.ChatMode
mode chat.ChatMode
icons, disabled bool
}
s := ui.Local(c.Root(), "231", func() state { return state{} })
choiceDemo(c, func() { choiceFlag(c, &s.icons, "Icons only", log); choiceFlag(c, &s.disabled, "Disabled", log) }, func() {
if mujica.ModeSelector(c, &s.mode, mujica.ModeSelectorOptions{IconsOnly: s.icons, Disabled: s.disabled}).Changed() {
if chat.ModeSelector(c, &s.mode, chat.ModeSelectorOptions{IconsOnly: s.icons, Disabled: s.disabled}).Changed() {
log(fmt.Sprint("mode ", s.mode))
}
})
@@ -154,7 +157,7 @@ func chatinputDemoToolToggleMenu(c *ui.Context, log func(string)) {
s := ui.Local(c.Root(), "232", func() state { return state{web: true} })
choiceDemo(c, func() { choiceFlag(c, &s.authorized, "Authorized", log); choiceFlag(c, &s.disabled, "Disabled", log) }, func() {
ui.Column(c).Disabled(s.disabled).Children(func() {
r := mujica.ToolToggleMenu(c, []mujica.ToolItem{{ID: "web", Label: "Web search", On: &s.web}, {ID: "code", Label: "Code execution", On: &s.code}, {ID: "mail", Label: "Mail", On: &s.mail, NeedsAuth: true, Authorized: s.authorized}})
r := chat.ToolToggleMenu(c, []chat.ToolItem{{ID: "web", Label: "Web search", On: &s.web}, {ID: "code", Label: "Code execution", On: &s.code}, {ID: "mail", Label: "Mail", On: &s.mail, NeedsAuth: true, Authorized: s.authorized}})
if id, ok := r.Changed(); ok {
log("toggle " + id)
}
@@ -174,7 +177,7 @@ func chatinputDemoParameterPanel(c *ui.Context, log func(string)) {
s := ui.Local(c.Root(), "233", func() state { return state{temp: 0.7, top: 1, length: 2048} })
choiceDemo(c, func() { choiceFlag(c, &s.disabled, "Disabled", log) }, func() {
ui.Column(c).Width(420).Disabled(s.disabled).Children(func() {
if mujica.ParameterPanel(c, []mujica.ModelParam{{Label: "Temperature", Value: &s.temp, Min: 0, Max: 2, Step: 0.1, Default: 1}, {Label: "Top-p", Value: &s.top, Min: 0, Max: 1, Step: 0.05, Default: 1}, {Label: "Max tokens", Value: &s.length, Min: 256, Max: 8192, Step: 256, Default: 2048}}).Changed() {
if chat.ParameterPanel(c, []chat.ModelParam{{Label: "Temperature", Value: &s.temp, Min: 0, Max: 2, Step: 0.1, Default: 1}, {Label: "Top-p", Value: &s.top, Min: 0, Max: 1, Step: 0.05, Default: 1}, {Label: "Max tokens", Value: &s.length, Min: 256, Max: 8192, Step: 256, Default: 2048}}).Changed() {
log(fmt.Sprintf("parameters %.1f / %.2f / %.0f", s.temp, s.top, s.length))
}
})
+29 -26
View File
@@ -2,44 +2,47 @@ package main
import (
"fmt"
"github.com/egoist/mygo/ui"
mujica "mujicaui"
"time"
"github.com/egoist/mygo/ui"
"github.com/ZacharyZhang-NY/MujicaUI/chat"
"github.com/ZacharyZhang-NY/MujicaUI/input"
)
func chatinputDemoTokenCounter(c *ui.Context, log func(string)) {
s := ui.Local(c.Root(), "234", func() float64 { return 1234 })
choiceDemo(c, func() {
if mujica.NumberInput(c, s, mujica.NumberInputOptions{Min: 0, Max: 12000, Step: 100, Label: "Estimated tokens"}).Changed() {
if input.NumberInput(c, s, input.NumberInputOptions{Min: 0, Max: 12000, Step: 100, Label: "Estimated tokens"}).Changed() {
log(fmt.Sprint("estimate ", *s))
}
}, func() {
mujica.TokenCounter(c, int(*s), 8000)
mujica.TokenCounter(c, 7500, 8000)
mujica.TokenCounter(c, 9100, 8000)
chat.TokenCounter(c, int(*s), 8000)
chat.TokenCounter(c, 7500, 8000)
chat.TokenCounter(c, 9100, 8000)
})
}
func chatinputDemoContextWindowMeter(c *ui.Context, log func(string)) {
s := ui.Local(c.Root(), "235", func() float64 { return 58000 })
choiceDemo(c, func() {
if mujica.NumberInput(c, s, mujica.NumberInputOptions{Min: 0, Max: 150000, Step: 1000, Label: "History tokens"}).Changed() {
if input.NumberInput(c, s, input.NumberInputOptions{Min: 0, Max: 150000, Step: 1000, Label: "History tokens"}).Changed() {
log(fmt.Sprint("history ", *s))
}
}, func() {
ui.Column(c).Width(520).Children(func() {
mujica.ContextWindowMeter(c, []mujica.ContextSegment{{Label: "System", Tokens: 1200}, {Label: "History", Tokens: int(*s)}, {Label: "Attachments", Tokens: 24000}}, 128000, mujica.ContextWindowMeterOptions{})
chat.ContextWindowMeter(c, []chat.ContextSegment{{Label: "System", Tokens: 1200}, {Label: "History", Tokens: int(*s)}, {Label: "Attachments", Tokens: 24000}}, 128000, chat.ContextWindowMeterOptions{})
})
})
}
func chatinputDemoSendButton(c *ui.Context, log func(string)) {
s := ui.Local(c.Root(), "236", func() mujica.SendButtonOptions { return mujica.SendButtonOptions{Shortcut: "Enter"} })
s := ui.Local(c.Root(), "236", func() chat.SendButtonOptions { return chat.SendButtonOptions{Shortcut: "Enter"} })
choiceDemo(c, func() {
choiceFlag(c, &s.Streaming, "Streaming", log)
choiceFlag(c, &s.Disabled, "Disabled send", log)
}, func() {
r := mujica.SendButton(c, *s)
r := chat.SendButton(c, *s)
if r.Sent() {
s.Streaming = true
log("send")
@@ -51,13 +54,13 @@ func chatinputDemoSendButton(c *ui.Context, log func(string)) {
})
}
func chatinputSources() []mujica.Source {
return []mujica.Source{{Title: "The Palace of Versailles", URL: "https://example.com/versailles", Site: "example.com", Snippet: "Seat of the court from 1682.", Published: time.Date(2025, 5, 4, 0, 0, 0, 0, time.UTC)}, {Title: "Hall of Mirrors", URL: "https://example.org/hall", Site: "example.org", Snippet: "Seventeen arches face the garden."}, {Title: "Court etiquette", URL: "https://example.net/etiquette", Snippet: "The ranks of the court."}}
func chatinputSources() []chat.Source {
return []chat.Source{{Title: "The Palace of Versailles", URL: "https://example.com/versailles", Site: "example.com", Snippet: "Seat of the court from 1682.", Published: time.Date(2025, 5, 4, 0, 0, 0, 0, time.UTC)}, {Title: "Hall of Mirrors", URL: "https://example.org/hall", Site: "example.org", Snippet: "Seventeen arches face the garden."}, {Title: "Court etiquette", URL: "https://example.net/etiquette", Snippet: "The ranks of the court."}}
}
func chatinputDemoCitationBadge(c *ui.Context, log func(string)) {
type state struct {
cites mujica.CitationState
cites chat.CitationState
disabled bool
}
s := ui.Local(c.Root(), "237", func() state { return state{} })
@@ -65,13 +68,13 @@ func chatinputDemoCitationBadge(c *ui.Context, log func(string)) {
ui.Row(c).Disabled(s.disabled).Gap(8).AlignItems(ui.Center).Children(func() {
ui.Text(c, "The court moved in 1682")
for i, src := range chatinputSources() {
if mujica.CitationBadge(c, i+1, src, &s.cites).Changed() {
if chat.CitationBadge(c, i+1, src, &s.cites).Changed() {
log(fmt.Sprint("locate source ", s.cites.Active))
}
}
})
ui.Column(c).Width(360).Height(260).Disabled(s.disabled).Children(func() {
if n, ok := mujica.SourcesPanel(c, chatinputSources(), &s.cites).Opened(); ok {
if n, ok := chat.SourcesPanel(c, chatinputSources(), &s.cites).Opened(); ok {
log(fmt.Sprint("open source ", n))
}
})
@@ -80,7 +83,7 @@ func chatinputDemoCitationBadge(c *ui.Context, log func(string)) {
func chatinputDemoSourceCard(c *ui.Context, log func(string)) {
type state struct {
cites mujica.CitationState
cites chat.CitationState
selected, disabled bool
}
s := ui.Local(c.Root(), "238", func() state { return state{} })
@@ -90,7 +93,7 @@ func chatinputDemoSourceCard(c *ui.Context, log func(string)) {
s.cites.Active = 1
}
ui.Column(c).Width(400).Disabled(s.disabled).Children(func() {
if mujica.SourceCard(c, 1, chatinputSources()[0], &s.cites).Clicked() {
if chat.SourceCard(c, 1, chatinputSources()[0], &s.cites).Clicked() {
log("open source 1")
}
})
@@ -99,26 +102,26 @@ func chatinputDemoSourceCard(c *ui.Context, log func(string)) {
func chatinputDemoSourcesPanel(c *ui.Context, log func(string)) {
type state struct {
cites mujica.CitationState
sources []mujica.Source
cites chat.CitationState
sources []chat.Source
disabled, empty, many bool
}
s := ui.Local(c.Root(), "239", func() state { return state{sources: chatinputSources()} })
choiceDemo(c, func() {
choiceFlag(c, &s.disabled, "Disabled", log)
choiceFlag(c, &s.empty, "Empty", log)
if mujica.Switch(c, &s.many, "100,000 sources", mujica.SwitchOptions{}).Changed() {
if input.Switch(c, &s.many, "100,000 sources", input.SwitchOptions{}).Changed() {
s.sources = chatinputSources()
s.cites.Active = 0
if s.many {
s.sources = make([]mujica.Source, 100000)
s.sources = make([]chat.Source, 100000)
for i := range s.sources {
s.sources[i] = mujica.Source{Title: fmt.Sprintf("Archive %d", i+1), URL: "https://example.com/archive"}
s.sources[i] = chat.Source{Title: fmt.Sprintf("Archive %d", i+1), URL: "https://example.com/archive"}
}
}
log(fmt.Sprint("sources ", len(s.sources)))
}
if mujica.Button(c, "Locate last source", mujica.ButtonOptions{Disabled: s.empty}).Clicked() {
if input.Button(c, "Locate last source", input.ButtonOptions{Disabled: s.empty}).Clicked() {
s.cites.Active = len(s.sources)
log("locate last source")
}
@@ -129,7 +132,7 @@ func chatinputDemoSourcesPanel(c *ui.Context, log func(string)) {
s.cites.Active = 0
}
ui.Column(c).Width(400).Height(320).Disabled(s.disabled).Children(func() {
if n, ok := mujica.SourcesPanel(c, sources, &s.cites).Opened(); ok {
if n, ok := chat.SourcesPanel(c, sources, &s.cites).Opened(); ok {
log(fmt.Sprint("open source ", n))
}
})
@@ -145,12 +148,12 @@ func chatinputDemoSearchProgress(c *ui.Context, log func(string)) {
choiceDemo(c, func() {
choiceFlag(c, &s.done, "Done", log)
choiceFlag(c, &s.disabled, "Disabled", log)
if mujica.NumberInput(c, &s.read, mujica.NumberInputOptions{Min: 0, Max: 8, Step: 1, Label: "Read sources"}).Changed() {
if input.NumberInput(c, &s.read, input.NumberInputOptions{Min: 0, Max: 8, Step: 1, Label: "Read sources"}).Changed() {
log(fmt.Sprint("read ", s.read))
}
}, func() {
ui.Column(c).Width(440).Disabled(s.disabled).Children(func() {
if mujica.SearchProgress(c, mujica.SearchProgressOptions{Query: "Versailles 1682", Read: int(s.read), Total: 8, Done: s.done, Stages: []mujica.SearchStage{{Label: "Planning queries", Done: true}, {Label: "Searching the web", Done: true}, {Label: "Reading sources", Done: s.done}}}).Changed() {
if chat.SearchProgress(c, chat.SearchProgressOptions{Query: "Versailles 1682", Read: int(s.read), Total: 8, Done: s.done, Stages: []chat.SearchStage{{Label: "Planning queries", Done: true}, {Label: "Searching the web", Done: true}, {Label: "Reading sources", Done: s.done}}}).Changed() {
log("toggle search details")
}
})
+5 -5
View File
@@ -8,10 +8,10 @@ import (
"github.com/egoist/mygo/ui"
mujica "mujicaui"
"github.com/ZacharyZhang-NY/MujicaUI/core"
)
func chatinputDemoTester(t *testing.T, id string, mode mujica.Mode) (*ui.Tester, *[]string) {
func chatinputDemoTester(t *testing.T, id string, mode core.Mode) (*ui.Tester, *[]string) {
t.Helper()
var p *page
for i := range chatinputPages {
@@ -25,7 +25,7 @@ func chatinputDemoTester(t *testing.T, id string, mode mujica.Mode) (*ui.Tester,
}
events := new([]string)
tt := ui.NewTester(func(c *ui.Context) {
mujica.Use(c, mujica.Settings{Mode: mode})
core.Use(c, core.Settings{Mode: mode})
ui.Scroll(c).Fill().Padding(24).Gap(16).Children(func() { p.Demo(c, func(s string) { *events = append(*events, s) }) })
}, 900, 800)
tt.SetPreferences(ui.Preferences{ReduceMotion: true, TextScale: 1})
@@ -40,7 +40,7 @@ func TestChatinputDemosRender(t *testing.T) {
if p.Category != "Chat Input" || p.Code == "" || p.Keys == "" {
t.Errorf("incomplete page %s", p.ID)
}
for _, mode := range []mujica.Mode{mujica.Light, mujica.Dark} {
for _, mode := range []core.Mode{core.Light, core.Dark} {
tt, _ := chatinputDemoTester(t, p.ID, mode)
if tt.HasText("panic") {
t.Errorf("%s: panic shown", p.ID)
@@ -69,7 +69,7 @@ func TestChatinputDemosEvents(t *testing.T) {
{"237", "Source 1: The Palace of Versailles"}, {"238", "Open The Palace of Versailles"},
{"239", "Locate last source"}, {"240", "Searching for “Versailles 1682”"},
} {
tt, events := chatinputDemoTester(t, tc.id, mujica.Light)
tt, events := chatinputDemoTester(t, tc.id, core.Light)
if err := tt.Click(tc.click); err != nil {
t.Fatalf("%s: %v", tc.id, err)
}
+45 -42
View File
@@ -7,7 +7,10 @@ import (
"github.com/egoist/mygo/ui"
mujica "mujicaui"
"github.com/ZacharyZhang-NY/MujicaUI/chat"
"github.com/ZacharyZhang-NY/MujicaUI/core"
"github.com/ZacharyZhang-NY/MujicaUI/display"
"github.com/ZacharyZhang-NY/MujicaUI/input"
)
const chatmsgCategory = "Chat Messages"
@@ -26,7 +29,7 @@ var chatmsgReplies = []string{
type chatmsgDemo struct {
ids []string
texts []string
list mujica.MessageListState
list chat.MessageListState
next int
sidebar bool
dated bool
@@ -54,27 +57,27 @@ func (d *chatmsgDemo) day(i int) time.Time {
// row shows message i as a bubble, users and the assistant taking turns.
func (d *chatmsgDemo) row(c *ui.Context, i int) {
role := mujica.MessageAssistant
role := chat.MessageAssistant
if i%2 == 0 {
role = mujica.MessageUser
role = chat.MessageUser
}
mujica.MessageBubble(c, role, mujica.MessageBubbleOptions{}, func() { mujica.MarkdownView(c, d.texts[i]) })
chat.MessageBubble(c, role, chat.MessageBubbleOptions{}, func() { chat.MarkdownView(c, d.texts[i]) })
}
// chatmsgControls adds messages to a demo chat and logs it.
func chatmsgControls(c *ui.Context, log func(string), d *chatmsgDemo) {
ui.Row(c).Gap(8).Children(func() {
if mujica.Button(c, "Add message", mujica.ButtonOptions{Variant: mujica.Secondary}).Clicked() {
if input.Button(c, "Add message", input.ButtonOptions{Variant: input.Secondary}).Clicked() {
d.add()
log(fmt.Sprintf("message %s added", d.ids[len(d.ids)-1]))
}
if mujica.Button(c, "Add 10", mujica.ButtonOptions{Variant: mujica.Secondary}).Clicked() {
if input.Button(c, "Add 10", input.ButtonOptions{Variant: input.Secondary}).Clicked() {
for range 10 {
d.add()
}
log(fmt.Sprintf("10 messages added, %d in all", len(d.ids)))
}
if mujica.Button(c, "Jump to latest", mujica.ButtonOptions{Variant: mujica.Ghost}).Clicked() {
if input.Button(c, "Jump to latest", input.ButtonOptions{Variant: input.Ghost}).Clicked() {
d.list.ScrollToEnd()
log("scrolled to end")
}
@@ -84,8 +87,8 @@ func chatmsgControls(c *ui.Context, log func(string), d *chatmsgDemo) {
var chatmsgListPages = []page{
{ID: "201", Name: "ChatContainer", ZhName: "对话容器", Category: chatmsgCategory,
Keys: "Tab 在侧栏、消息与输入区之间移动;消息区滚轮与 PageUp/PageDown 滚动;上翻后出现回到最新按钮。",
Code: `mujica.ChatContainer(c, &chat, len(msgs), mujica.ChatContainerOptions{
List: mujica.MessageListOptions{ID: func(i int) string { return msgs[i] }},
Code: `chat.ChatContainer(c, &thread, len(msgs), chat.ChatContainerOptions{
List: chat.MessageListOptions{ID: func(i int) string { return msgs[i] }},
Input: func() { ui.Text(c, "Composer goes here") },
}, func(i int) { ui.Text(c, msgs[i]) })`,
Demo: func(c *ui.Context, log func(string)) {
@@ -96,11 +99,11 @@ var chatmsgListPages = []page{
chatmsgControls(c, log, d)
})
inputSection(c, "Live", func() {
ui.Box(c).Height(420).Border(1, mujica.Tokens(c).Border).Clip().Children(func() {
opts := mujica.ChatContainerOptions{
List: mujica.MessageListOptions{ID: func(i int) string { return d.ids[i] }},
ui.Box(c).Height(420).Border(1, core.Tokens(c).Border).Clip().Children(func() {
opts := chat.ChatContainerOptions{
List: chat.MessageListOptions{ID: func(i int) string { return d.ids[i] }},
Input: func() {
r := mujica.TextInput(c, draft, mujica.TextInputOptions{Label: "Message", Placeholder: "Write to the herald"})
r := input.TextInput(c, draft, input.TextInputOptions{Label: "Message", Placeholder: "Write to the herald"})
if r.Submitted() && strings.TrimSpace(*draft) != "" {
d.add()
d.texts[len(d.texts)-1] = *draft
@@ -118,7 +121,7 @@ var chatmsgListPages = []page{
})
}
}
r := mujica.ChatContainer(c, &d.list, len(d.ids), opts, func(i int) { d.row(c, i) })
r := chat.ChatContainer(c, &d.list, len(d.ids), opts, func(i int) { d.row(c, i) })
if r.Unread() > 0 {
ui.Text(c, fmt.Sprintf("%d unread", r.Unread())).Absolute().Top(8).Right(12)
}
@@ -127,7 +130,7 @@ var chatmsgListPages = []page{
}},
{ID: "202", Name: "MessageList", ZhName: "消息列表", Category: chatmsgCategory,
Keys: "滚轮与方向键滚动;上翻后停止跟随;Tab 到回到最新按钮,Enter 回到底部。",
Code: `r := mujica.MessageList(c, &list, len(ids), mujica.MessageListOptions{
Code: `r := chat.MessageList(c, &list, len(ids), chat.MessageListOptions{
ID: func(i int) string { return ids[i] },
}, func(i int) { ui.Text(c, "Message "+ids[i]) })
fmt.Println("unread", r.Unread())`,
@@ -137,7 +140,7 @@ fmt.Println("unread", r.Unread())`,
inputFlags(c, log, map[string]*bool{"Date separators": &d.dated}, "Date separators")
chatmsgControls(c, log, d)
ui.Row(c).Children(func() {
if mujica.Button(c, "Load 100,000", mujica.ButtonOptions{Variant: mujica.Secondary}).Clicked() {
if input.Button(c, "Load 100,000", input.ButtonOptions{Variant: input.Secondary}).Clicked() {
for len(d.ids) < 100000 {
d.add()
}
@@ -146,77 +149,77 @@ fmt.Println("unread", r.Unread())`,
})
})
inputSection(c, "Live", func() {
opts := mujica.MessageListOptions{ID: func(i int) string { return d.ids[i] }, Label: func(i int) string { return d.texts[i] }}
opts := chat.MessageListOptions{ID: func(i int) string { return d.ids[i] }, Label: func(i int) string { return d.texts[i] }}
if d.dated {
opts.Date = d.day
}
ui.Column(c).Height(360).Border(1, mujica.Tokens(c).Border).Children(func() {
r := mujica.MessageList(c, &d.list, len(d.ids), opts, func(i int) { d.row(c, i) })
ui.Column(c).Height(360).Border(1, core.Tokens(c).Border).Children(func() {
r := chat.MessageList(c, &d.list, len(d.ids), opts, func(i int) { d.row(c, i) })
ui.Text(c, fmt.Sprintf("%d messages, %d unread, at end %v", len(d.ids), r.Unread(), d.list.AtEnd())).Padding(8)
})
})
}},
{ID: "203", Name: "MessageBubble", ZhName: "消息气泡", Category: chatmsgCategory,
Keys: "气泡不获取焦点;内容中的链接与按钮按 Tab 顺序访问。",
Code: `mujica.MessageBubble(c, mujica.MessageAssistant, mujica.MessageBubbleOptions{Name: "Herald"}, func() {
Code: `chat.MessageBubble(c, chat.MessageAssistant, chat.MessageBubbleOptions{Name: "Herald"}, func() {
ui.Text(c, "The ball begins at nine.")
})`,
Demo: func(c *ui.Context, log func(string)) {
type state struct {
role mujica.MessageRole
role chat.MessageRole
avatar, named bool
width float64
}
s := ui.Local(c.Root(), "203", func() state { return state{role: mujica.MessageAssistant, avatar: true, named: true, width: 360} })
roles := []mujica.RadioItem[mujica.MessageRole]{{Value: mujica.MessageUser, Label: "User"}, {Value: mujica.MessageAssistant, Label: "Assistant"},
{Value: mujica.MessageSystem, Label: "System"}, {Value: mujica.MessageError, Label: "Error"}}
s := ui.Local(c.Root(), "203", func() state { return state{role: chat.MessageAssistant, avatar: true, named: true, width: 360} })
roles := []input.RadioItem[chat.MessageRole]{{Value: chat.MessageUser, Label: "User"}, {Value: chat.MessageAssistant, Label: "Assistant"},
{Value: chat.MessageSystem, Label: "System"}, {Value: chat.MessageError, Label: "Error"}}
inputSection(c, "Parameters", func() {
if mujica.RadioGroup(c, &s.role, roles, mujica.RadioGroupOptions{Horizontal: true, Label: "Role"}).Changed() {
if input.RadioGroup(c, &s.role, roles, input.RadioGroupOptions{Horizontal: true, Label: "Role"}).Changed() {
log(fmt.Sprintf("role %d", s.role))
}
inputFlags(c, log, map[string]*bool{"Avatar": &s.avatar, "Name": &s.named}, "Avatar", "Name")
if mujica.NumberInput(c, &s.width, mujica.NumberInputOptions{Min: 160, Max: 640, Step: 40, Label: "MaxWidth"}).Changed() {
if input.NumberInput(c, &s.width, input.NumberInputOptions{Min: 160, Max: 640, Step: 40, Label: "MaxWidth"}).Changed() {
log(fmt.Sprintf("MaxWidth %v", s.width))
}
})
inputSection(c, "Live", func() {
opts := mujica.MessageBubbleOptions{MaxWidth: float32(s.width)}
opts := chat.MessageBubbleOptions{MaxWidth: float32(s.width)}
if s.named {
opts.Name = "Herald"
}
if s.avatar {
opts.Avatar = func() { mujica.Avatar(c, "Herald", mujica.AvatarOptions{}) }
opts.Avatar = func() { display.Avatar(c, "Herald", display.AvatarOptions{}) }
}
mujica.MessageBubble(c, s.role, opts, func() { ui.Text(c, chatmsgReplies[0]+" "+chatmsgReplies[2]) })
chat.MessageBubble(c, s.role, opts, func() { ui.Text(c, chatmsgReplies[0]+" "+chatmsgReplies[2]) })
})
inputSection(c, "States", func() {
for _, r := range roles {
mujica.MessageBubble(c, r.Value, mujica.MessageBubbleOptions{Name: r.Label}, func() { ui.Text(c, r.Label+" message") })
chat.MessageBubble(c, r.Value, chat.MessageBubbleOptions{Name: r.Label}, func() { ui.Text(c, r.Label+" message") })
}
})
}},
{ID: "204", Name: "MessageHeader", ZhName: "消息头", Category: chatmsgCategory, Keys: "不获取焦点。",
Code: `mujica.MessageHeader(c, mujica.MessageHeaderOptions{Model: "herald-2", Time: time.Now(), Status: mujica.MessageSent})`,
Code: `chat.MessageHeader(c, chat.MessageHeaderOptions{Model: "herald-2", Time: time.Now(), Status: chat.MessageSent})`,
Demo: func(c *ui.Context, log func(string)) {
status := ui.Local(c.Root(), "204", func() mujica.MessageStatus { return mujica.MessageSent })
items := []mujica.RadioItem[mujica.MessageStatus]{{Value: mujica.MessageNoStatus, Label: "None"}, {Value: mujica.MessageSending, Label: "Sending"},
{Value: mujica.MessageStreaming, Label: "Streaming"}, {Value: mujica.MessageSent, Label: "Sent"}, {Value: mujica.MessageFailed, Label: "Failed"}}
status := ui.Local(c.Root(), "204", func() chat.MessageStatus { return chat.MessageSent })
items := []input.RadioItem[chat.MessageStatus]{{Value: chat.MessageNoStatus, Label: "None"}, {Value: chat.MessageSending, Label: "Sending"},
{Value: chat.MessageStreaming, Label: "Streaming"}, {Value: chat.MessageSent, Label: "Sent"}, {Value: chat.MessageFailed, Label: "Failed"}}
inputSection(c, "Parameters", func() {
if mujica.RadioGroup(c, status, items, mujica.RadioGroupOptions{Horizontal: true, Label: "Status"}).Changed() {
if input.RadioGroup(c, status, items, input.RadioGroupOptions{Horizontal: true, Label: "Status"}).Changed() {
log(fmt.Sprintf("status %d", *status))
}
})
inputSection(c, "Live", func() {
mujica.MessageHeader(c, mujica.MessageHeaderOptions{Model: "herald-2", Time: time.Now(), Status: *status})
chat.MessageHeader(c, chat.MessageHeaderOptions{Model: "herald-2", Time: time.Now(), Status: *status})
})
inputSection(c, "States", func() {
for _, it := range items[1:] {
mujica.MessageHeader(c, mujica.MessageHeaderOptions{Model: "herald-2", Time: time.Now(), Status: it.Value})
chat.MessageHeader(c, chat.MessageHeaderOptions{Model: "herald-2", Time: time.Now(), Status: it.Value})
}
})
}},
{ID: "205", Name: "StreamingText", ZhName: "流式文本", Category: chatmsgCategory, Keys: "不获取焦点;减少动画时直接显示全部文本。",
Code: `mujica.StreamingText(c, reply, mujica.StreamingTextOptions{Streaming: true, Smooth: true})`,
Code: `chat.StreamingText(c, reply, chat.StreamingTextOptions{Streaming: true, Smooth: true})`,
Demo: func(c *ui.Context, log func(string)) {
type state struct {
text string
@@ -228,11 +231,11 @@ fmt.Println("unread", r.Unread())`,
const full = "By order of the Queen, the ball begins at nine. Guests arrive by the east door, and carriages wait in the lower court until midnight."
inputSection(c, "Parameters", func() {
inputFlags(c, log, map[string]*bool{"Smooth": &s.smooth, "Streaming cursor": &s.streaming, "Stream": &s.auto}, "Smooth", "Streaming cursor", "Stream")
if mujica.NumberInput(c, &s.rate, mujica.NumberInputOptions{Min: 10, Max: 400, Step: 10, Label: "Rate"}).Changed() {
if input.NumberInput(c, &s.rate, input.NumberInputOptions{Min: 10, Max: 400, Step: 10, Label: "Rate"}).Changed() {
log(fmt.Sprintf("Rate %v", s.rate))
}
ui.Row(c).Children(func() {
if mujica.Button(c, "Restart", mujica.ButtonOptions{Variant: mujica.Secondary}).Clicked() {
if input.Button(c, "Restart", input.ButtonOptions{Variant: input.Secondary}).Clicked() {
s.text = ""
log("stream restarted")
}
@@ -255,7 +258,7 @@ fmt.Println("unread", r.Unread())`,
c.After(150 * time.Millisecond)
}
inputSection(c, "Live", func() {
mujica.StreamingText(c, s.text, mujica.StreamingTextOptions{Streaming: s.streaming && len(s.text) < len(full), Smooth: s.smooth, Rate: s.rate})
chat.StreamingText(c, s.text, chat.StreamingTextOptions{Streaming: s.streaming && len(s.text) < len(full), Smooth: s.smooth, Rate: s.rate})
})
}},
}
+48 -46
View File
@@ -6,12 +6,14 @@ import (
"github.com/egoist/mygo/ui"
mujica "mujicaui"
"github.com/ZacharyZhang-NY/MujicaUI/chat"
"github.com/ZacharyZhang-NY/MujicaUI/core"
"github.com/ZacharyZhang-NY/MujicaUI/input"
)
var chatmsgActionPages = []page{
{ID: "211", Name: "AudioMessage", ZhName: "语音消息", Category: chatmsgCategory, Keys: "Tab 到播放按钮与波形;波形上 Left/Right 跳转,Home、End 到首尾。",
Code: `r := mujica.AudioMessage(c, mujica.AudioMessageOptions{Duration: time.Minute, Position: pos})
Code: `r := chat.AudioMessage(c, chat.AudioMessageOptions{Duration: time.Minute, Position: pos})
if d, ok := r.Seek(); ok {
pos = d
}`,
@@ -34,7 +36,7 @@ if d, ok := r.Seek(); ok {
}
s.at = c.Now()
inputSection(c, "Live", func() {
r := mujica.AudioMessage(c, mujica.AudioMessageOptions{Duration: length, Position: s.pos, Playing: s.playing, Waveform: chatmsgWave})
r := chat.AudioMessage(c, chat.AudioMessageOptions{Duration: length, Position: s.pos, Playing: s.playing, Waveform: chatmsgWave})
if r.Toggled() {
s.playing = !s.playing
if s.pos >= length {
@@ -49,13 +51,13 @@ if d, ok := r.Seek(); ok {
})
inputSection(c, "States", func() {
ui.Column(c).Disabled(true).Children(func() {
mujica.AudioMessage(c, mujica.AudioMessageOptions{Duration: length, Waveform: chatmsgWave})
chat.AudioMessage(c, chat.AudioMessageOptions{Duration: length, Waveform: chatmsgWave})
})
})
}},
{ID: "212", Name: "LinkPreviewCard", ZhName: "链接预览卡片", Category: chatmsgCategory, Keys: "Tab 聚焦卡片,Enter 或 Space 打开。",
Code: `p := mujica.LinkPreview{URL: "https://example.com", Title: "The Palace", Site: "example.com"}
if mujica.LinkPreviewCard(c, p).Clicked() {
Code: `p := chat.LinkPreview{URL: "https://example.com", Title: "The Palace", Site: "example.com"}
if chat.LinkPreviewCard(c, p).Clicked() {
fmt.Println("open", p.URL)
}`,
Demo: func(c *ui.Context, log func(string)) {
@@ -63,66 +65,66 @@ if mujica.LinkPreviewCard(c, p).Clicked() {
inputSection(c, "Parameters", func() {
inputFlags(c, log, map[string]*bool{"Thumbnail": thumb}, "Thumbnail")
})
p := mujica.LinkPreview{URL: "https://example.com/versailles", Title: "The Palace of Versailles", Site: "example.com",
p := chat.LinkPreview{URL: "https://example.com/versailles", Title: "The Palace of Versailles", Site: "example.com",
Description: "A royal château in the Île-de-France region, seat of the French court from 1682 until the Revolution."}
if *thumb {
p.Thumbnail = chatmsgPicture("#AD9164")
}
inputSection(c, "Live", func() {
if mujica.LinkPreviewCard(c, p).Clicked() {
if chat.LinkPreviewCard(c, p).Clicked() {
log("open " + p.URL)
}
if mujica.LinkPreviewCard(c, mujica.LinkPreview{URL: "https://example.com/hall", Title: "Hall of Mirrors"}).Clicked() {
if chat.LinkPreviewCard(c, chat.LinkPreview{URL: "https://example.com/hall", Title: "Hall of Mirrors"}).Clicked() {
log("open https://example.com/hall")
}
})
}},
{ID: "213", Name: "QuoteReply", ZhName: "引用回复", Category: chatmsgCategory, Keys: "Tab 聚焦,Enter 或 Space 跳转。",
Code: `if mujica.QuoteReply(c, "Marie", "Let them eat cake.").Clicked() {
Code: `if chat.QuoteReply(c, "Marie", "Let them eat cake.").Clicked() {
fmt.Println("jump to the quoted message")
}`,
Demo: func(c *ui.Context, log func(string)) {
inputSection(c, "Live", func() {
for _, q := range [][2]string{{"Marie", "Let them eat cake."}, {"Herald", chatmsgMarkdown}} {
if mujica.QuoteReply(c, q[0], q[1]).Clicked() {
if chat.QuoteReply(c, q[0], q[1]).Clicked() {
log("jump to " + q[0] + ": " + chatmsgSnippet(q[1], 24))
}
}
})
}},
{ID: "214", Name: "MessageActions", ZhName: "消息操作", Category: chatmsgCategory, Keys: "Tab 进入工具栏,Left/Right 移动,Enter 或 Space 触发。",
Code: `if a, ok := mujica.MessageActions(c, mujica.MessageAssistant, mujica.MessageActionsOptions{Feedback: &rating}).Action(); ok {
Code: `if a, ok := chat.MessageActions(c, chat.MessageAssistant, chat.MessageActionsOptions{Feedback: &rating}).Action(); ok {
fmt.Println("action", a)
}`,
Demo: func(c *ui.Context, log func(string)) {
type state struct {
role mujica.MessageRole
rating mujica.MessageFeedback
role chat.MessageRole
rating chat.MessageFeedback
rated bool
}
s := ui.Local(c.Root(), "214", func() state { return state{role: mujica.MessageAssistant, rated: true} })
names := map[mujica.MessageAction]string{mujica.ActionCopy: "copy", mujica.ActionRegenerate: "regenerate", mujica.ActionEdit: "edit",
mujica.ActionLike: "like", mujica.ActionDislike: "dislike", mujica.ActionSpeak: "speak", mujica.ActionShare: "share"}
s := ui.Local(c.Root(), "214", func() state { return state{role: chat.MessageAssistant, rated: true} })
names := map[chat.MessageAction]string{chat.ActionCopy: "copy", chat.ActionRegenerate: "regenerate", chat.ActionEdit: "edit",
chat.ActionLike: "like", chat.ActionDislike: "dislike", chat.ActionSpeak: "speak", chat.ActionShare: "share"}
inputSection(c, "Parameters", func() {
roles := []mujica.RadioItem[mujica.MessageRole]{{Value: mujica.MessageUser, Label: "User"}, {Value: mujica.MessageAssistant, Label: "Assistant"},
{Value: mujica.MessageSystem, Label: "System"}, {Value: mujica.MessageError, Label: "Error"}}
if mujica.RadioGroup(c, &s.role, roles, mujica.RadioGroupOptions{Horizontal: true, Label: "Role"}).Changed() {
roles := []input.RadioItem[chat.MessageRole]{{Value: chat.MessageUser, Label: "User"}, {Value: chat.MessageAssistant, Label: "Assistant"},
{Value: chat.MessageSystem, Label: "System"}, {Value: chat.MessageError, Label: "Error"}}
if input.RadioGroup(c, &s.role, roles, input.RadioGroupOptions{Horizontal: true, Label: "Role"}).Changed() {
log(fmt.Sprintf("role %d", s.role))
}
inputFlags(c, log, map[string]*bool{"Rating": &s.rated}, "Rating")
})
inputSection(c, "Live", func() {
opts := mujica.MessageActionsOptions{}
opts := chat.MessageActionsOptions{}
if s.rated {
opts.Feedback = &s.rating
}
if a, ok := mujica.MessageActions(c, s.role, opts).Action(); ok {
if a, ok := chat.MessageActions(c, s.role, opts).Action(); ok {
log(fmt.Sprintf("action %s, rating %d", names[a], s.rating))
}
})
}},
{ID: "215", Name: "MessageEditor", ZhName: "消息编辑器", Category: chatmsgCategory, Keys: "Cmd/Ctrl+Enter 保存,Escape 取消。",
Code: `if mujica.MessageEditor(c, &draft, mujica.MessageEditorOptions{Original: "Hello"}).Saved() {
Code: `if chat.MessageEditor(c, &draft, chat.MessageEditorOptions{Original: "Hello"}).Saved() {
fmt.Println("new branch from", draft)
}`,
Demo: func(c *ui.Context, log func(string)) {
@@ -137,19 +139,19 @@ if mujica.LinkPreviewCard(c, p).Clicked() {
})
inputSection(c, "Live", func() {
if !s.editing {
mujica.MessageBubble(c, mujica.MessageUser, mujica.MessageBubbleOptions{}, func() { ui.Text(c, s.branches[s.index]) })
chat.MessageBubble(c, chat.MessageUser, chat.MessageBubbleOptions{}, func() { ui.Text(c, s.branches[s.index]) })
ui.Row(c).Gap(8).Justify(ui.End).Children(func() {
if mujica.BranchNavigator(c, &s.index, len(s.branches)).Changed() {
if chat.BranchNavigator(c, &s.index, len(s.branches)).Changed() {
log(fmt.Sprintf("branch %d", s.index+1))
}
if mujica.Button(c, "Edit", mujica.ButtonOptions{Variant: mujica.Ghost}).Clicked() {
if input.Button(c, "Edit", input.ButtonOptions{Variant: input.Ghost}).Clicked() {
s.editing, s.draft = true, s.branches[s.index]
log("editing")
}
})
return
}
r := mujica.MessageEditor(c, &s.draft, mujica.MessageEditorOptions{Original: s.branches[s.index]})
r := chat.MessageEditor(c, &s.draft, chat.MessageEditorOptions{Original: s.branches[s.index]})
if r.Saved() {
s.branches = append(s.branches, s.draft)
s.index, s.editing = len(s.branches)-1, false
@@ -162,7 +164,7 @@ if mujica.LinkPreviewCard(c, p).Clicked() {
})
}},
{ID: "216", Name: "BranchNavigator", ZhName: "分支切换", Category: chatmsgCategory, Keys: "Tab 聚焦,Left/Right 切换,Home、End 到首末。",
Code: `if mujica.BranchNavigator(c, &branch, 3).Changed() {
Code: `if chat.BranchNavigator(c, &branch, 3).Changed() {
fmt.Println("show branch", branch)
}`,
Demo: func(c *ui.Context, log func(string)) {
@@ -173,7 +175,7 @@ if mujica.LinkPreviewCard(c, p).Clicked() {
}
s := ui.Local(c.Root(), "216", func() state { return state{index: 1, count: 3} })
inputSection(c, "Parameters", func() {
if mujica.NumberInput(c, &s.count, mujica.NumberInputOptions{Min: 1, Max: 9, StepperOnly: true, Label: "Branches"}).Changed() {
if input.NumberInput(c, &s.count, input.NumberInputOptions{Min: 1, Max: 9, StepperOnly: true, Label: "Branches"}).Changed() {
s.index = min(s.index, int(s.count)-1)
log(fmt.Sprintf("%v branches", s.count))
}
@@ -181,39 +183,39 @@ if mujica.LinkPreviewCard(c, p).Clicked() {
})
inputSection(c, "Live", func() {
ui.Row(c).Disabled(s.disabled).Children(func() {
if mujica.BranchNavigator(c, &s.index, int(s.count)).Changed() {
if chat.BranchNavigator(c, &s.index, int(s.count)).Changed() {
log(fmt.Sprintf("branch %d of %v", s.index+1, s.count))
}
})
})
}},
{ID: "217", Name: "ErrorMessage", ZhName: "错误消息", Category: chatmsgCategory, Keys: "Tab 到错误文本(可选择复制)与重试按钮。",
Code: `opts := mujica.ErrorMessageOptions{RetryAt: time.Now().Add(30 * time.Second)}
if mujica.ErrorMessage(c, "429 Too Many Requests", opts).Retried() {
Code: `opts := chat.ErrorMessageOptions{RetryAt: time.Now().Add(30 * time.Second)}
if chat.ErrorMessage(c, "429 Too Many Requests", opts).Retried() {
fmt.Println("send again")
}`,
Demo: func(c *ui.Context, log func(string)) {
until := ui.Local(c.Root(), "217", func() time.Time { return time.Time{} })
inputSection(c, "Parameters", func() {
ui.Row(c).Children(func() {
if mujica.Button(c, "Rate limit for 10 s", mujica.ButtonOptions{Variant: mujica.Secondary}).Clicked() {
if input.Button(c, "Rate limit for 10 s", input.ButtonOptions{Variant: input.Secondary}).Clicked() {
*until = c.Now().Add(10 * time.Second)
log("rate limited for 10 s")
}
})
})
inputSection(c, "Live", func() {
opts := mujica.ErrorMessageOptions{RetryAt: *until}
if mujica.ErrorMessage(c, `429 Too Many Requests: {"type":"rate_limit_error","message":"Number of requests exceeded"}`, opts).Retried() {
opts := chat.ErrorMessageOptions{RetryAt: *until}
if chat.ErrorMessage(c, `429 Too Many Requests: {"type":"rate_limit_error","message":"Number of requests exceeded"}`, opts).Retried() {
log("retry")
}
if mujica.ErrorMessage(c, "dial tcp 10.0.0.7:443: connection refused", mujica.ErrorMessageOptions{Title: "Could not reach the server"}).Retried() {
if chat.ErrorMessage(c, "dial tcp 10.0.0.7:443: connection refused", chat.ErrorMessageOptions{Title: "Could not reach the server"}).Retried() {
log("retry connection")
}
})
}},
{ID: "218", Name: "ThinkingBlock", ZhName: "思考过程", Category: chatmsgCategory, Keys: "Tab 聚焦标题,Enter 或 Space 展开与折叠。",
Code: `mujica.ThinkingBlock(c, &open, mujica.ThinkingBlockOptions{Elapsed: 12 * time.Second}, func() {
Code: `chat.ThinkingBlock(c, &open, chat.ThinkingBlockOptions{Elapsed: 12 * time.Second}, func() {
ui.Text(c, "Weigh the guest list.")
})`,
Demo: func(c *ui.Context, log func(string)) {
@@ -229,7 +231,7 @@ if mujica.ErrorMessage(c, "429 Too Many Requests", opts).Retried() {
label = "Finish thinking"
}
ui.Row(c).Children(func() {
if mujica.Button(c, label, mujica.ButtonOptions{Variant: mujica.Secondary}).Clicked() {
if input.Button(c, label, input.ButtonOptions{Variant: input.Secondary}).Clicked() {
if s.thinking {
s.elapsed = c.Now().Sub(s.started)
} else {
@@ -241,8 +243,8 @@ if mujica.ErrorMessage(c, "429 Too Many Requests", opts).Retried() {
})
})
inputSection(c, "Live", func() {
opts := mujica.ThinkingBlockOptions{Thinking: s.thinking, Started: s.started, Elapsed: s.elapsed}
if mujica.ThinkingBlock(c, &s.open, opts, func() {
opts := chat.ThinkingBlockOptions{Thinking: s.thinking, Started: s.started, Elapsed: s.elapsed}
if chat.ThinkingBlock(c, &s.open, opts, func() {
ui.Text(c, "Forty guests, thirty-six seats. The envoys outrank the counts; four counts dine in the antechamber.")
}).Changed() {
log(fmt.Sprintf("open %v", s.open))
@@ -251,7 +253,7 @@ if mujica.ErrorMessage(c, "429 Too Many Requests", opts).Retried() {
}},
{ID: "219", Name: "ScrollToBottomButton", ZhName: "回到底部按钮", Category: chatmsgCategory, Keys: "Tab 聚焦,Enter 或 Space 回到底部。",
Code: `ui.Box(c).Fill().Children(func() {
if mujica.ScrollToBottomButton(c, !list.AtEnd(), 3).Clicked() {
if chat.ScrollToBottomButton(c, !list.AtEnd(), 3).Clicked() {
list.ScrollToEnd()
}
})`,
@@ -263,13 +265,13 @@ if mujica.ErrorMessage(c, "429 Too Many Requests", opts).Retried() {
s := ui.Local(c.Root(), "219", func() state { return state{away: true, unread: 3} })
inputSection(c, "Parameters", func() {
inputFlags(c, log, map[string]*bool{"Away from bottom": &s.away}, "Away from bottom")
if mujica.NumberInput(c, &s.unread, mujica.NumberInputOptions{Min: 0, Max: 99, Label: "Unread"}).Changed() {
if input.NumberInput(c, &s.unread, input.NumberInputOptions{Min: 0, Max: 99, Label: "Unread"}).Changed() {
log(fmt.Sprintf("unread %v", s.unread))
}
})
inputSection(c, "Live", func() {
ui.Box(c).Height(120).Background(mujica.Tokens(c).Surface).Border(1, mujica.Tokens(c).Border).Children(func() {
if mujica.ScrollToBottomButton(c, s.away, int(s.unread)).Clicked() {
ui.Box(c).Height(120).Background(core.Tokens(c).Surface).Border(1, core.Tokens(c).Border).Children(func() {
if chat.ScrollToBottomButton(c, s.away, int(s.unread)).Clicked() {
s.away, s.unread = false, 0
log("back to the bottom")
}
@@ -277,7 +279,7 @@ if mujica.ErrorMessage(c, "429 Too Many Requests", opts).Retried() {
})
}},
{ID: "220", Name: "TypingIndicator", ZhName: "正在输入", Category: chatmsgCategory, Keys: "不获取焦点;减少动画时显示静态文字。",
Code: `mujica.TypingIndicator(c, "Herald")`,
Code: `chat.TypingIndicator(c, "Herald")`,
Demo: func(c *ui.Context, log func(string)) {
typing := ui.Local(c.Root(), "220", func() bool { return true })
inputSection(c, "Parameters", func() {
@@ -285,7 +287,7 @@ if mujica.ErrorMessage(c, "429 Too Many Requests", opts).Retried() {
})
inputSection(c, "Live", func() {
if *typing {
mujica.TypingIndicator(c, "Herald")
chat.TypingIndicator(c, "Herald")
}
})
}},
+23 -22
View File
@@ -8,7 +8,8 @@ import (
"github.com/egoist/mygo/ui"
mujica "mujicaui"
"github.com/ZacharyZhang-NY/MujicaUI/chat"
"github.com/ZacharyZhang-NY/MujicaUI/input"
)
const chatmsgMarkdown = "## The Decree\n\nBy order of **the Queen**, *all* guests use `the east door`. See [the protocol](https://example.com/protocol).\n\n" +
@@ -24,7 +25,7 @@ var chatmsgColors = []string{"#7D2034", "#AD9164", "#355E80", "#326247", "#80571
var chatmsgBlockPages = []page{
{ID: "206", Name: "MarkdownView", ZhName: "Markdown 视图", Category: chatmsgCategory, Keys: "Tab 到链接,Enter 激活;代码块与表格按钮按 Tab 顺序访问。",
Code: `if url, ok := mujica.MarkdownView(c, "# Decree\n\nSee **the guide** at [the court](https://example.com).").Link(); ok {
Code: `if url, ok := chat.MarkdownView(c, "# Decree\n\nSee **the guide** at [the court](https://example.com).").Link(); ok {
fmt.Println("open", url)
}`,
Demo: func(c *ui.Context, log func(string)) {
@@ -36,12 +37,12 @@ var chatmsgBlockPages = []page{
s := ui.Local(c.Root(), "206", func() state { return state{src: chatmsgMarkdown} })
inputSection(c, "Parameters", func() {
ui.Row(c).Children(func() {
if mujica.Button(c, "Stream it again", mujica.ButtonOptions{Variant: mujica.Secondary}).Clicked() {
if input.Button(c, "Stream it again", input.ButtonOptions{Variant: input.Secondary}).Clicked() {
s.src, s.stream = "", true
log("streaming started")
}
})
mujica.TextArea(c, &s.src, mujica.TextAreaOptions{Label: "Markdown source", MaxHeight: 160})
input.TextArea(c, &s.src, input.TextAreaOptions{Label: "Markdown source", MaxHeight: 160})
})
if s.stream && len(s.src) < len(chatmsgMarkdown) && c.Now().Sub(s.at) > 40*time.Millisecond {
s.src = chatmsgMarkdown[:min(len(chatmsgMarkdown), len(s.src)+6)]
@@ -55,13 +56,13 @@ var chatmsgBlockPages = []page{
c.After(40 * time.Millisecond)
}
inputSection(c, "Live", func() {
if url, ok := mujica.MarkdownView(c, s.src).Link(); ok {
if url, ok := chat.MarkdownView(c, s.src).Link(); ok {
log("link " + url)
}
})
}},
{ID: "207", Name: "CodeBlock", ZhName: "代码块", Category: chatmsgCategory, Keys: "Tab 到换行、复制、折叠按钮;Space 或 Enter 触发。",
Code: `mujica.CodeBlock(c, "func bow() {}", mujica.CodeBlockOptions{Language: "go", LineNumbers: true})`,
Code: `chat.CodeBlock(c, "func bow() {}", chat.CodeBlockOptions{Language: "go", LineNumbers: true})`,
Demo: func(c *ui.Context, log func(string)) {
type state struct {
lang string
@@ -74,25 +75,25 @@ var chatmsgBlockPages = []page{
"sql": "SELECT name, seat FROM guests WHERE rank > 2 ORDER BY seat LIMIT 10; -- the envoys",
}
inputSection(c, "Parameters", func() {
langs := []mujica.RadioItem[string]{{Value: "go", Label: "Go"}, {Value: "python", Label: "Python"}, {Value: "sql", Label: "SQL"}}
if mujica.RadioGroup(c, &s.lang, langs, mujica.RadioGroupOptions{Horizontal: true, Label: "Language"}).Changed() {
langs := []input.RadioItem[string]{{Value: "go", Label: "Go"}, {Value: "python", Label: "Python"}, {Value: "sql", Label: "SQL"}}
if input.RadioGroup(c, &s.lang, langs, input.RadioGroupOptions{Horizontal: true, Label: "Language"}).Changed() {
log("language " + s.lang)
}
inputFlags(c, log, map[string]*bool{"LineNumbers": &s.numbers, "Wrap at first": &s.wrap}, "LineNumbers", "Wrap at first")
})
inputSection(c, "Live", func() {
ui.Column(c).Key(s.lang + fmt.Sprint(s.wrap)).Children(func() {
if mujica.CodeBlock(c, samples[s.lang], mujica.CodeBlockOptions{Language: s.lang, LineNumbers: s.numbers, Wrap: s.wrap}).Copied() {
if chat.CodeBlock(c, samples[s.lang], chat.CodeBlockOptions{Language: s.lang, LineNumbers: s.numbers, Wrap: s.wrap}).Copied() {
log("code copied")
}
})
})
inputSection(c, "States", func() {
mujica.CodeBlock(c, samples["go"], mujica.CodeBlockOptions{Language: "go", Collapsed: true})
chat.CodeBlock(c, samples["go"], chat.CodeBlockOptions{Language: "go", Collapsed: true})
})
}},
{ID: "208", Name: "TableBlock", ZhName: "表格块", Category: chatmsgCategory, Keys: "Tab 到复制按钮,Enter 或 Space 复制为 TSV。",
Code: `if mujica.TableBlock(c, []string{"Guest", "Seat"}, [][]string{{"Marie", "1"}}, mujica.TableBlockOptions{}).Copied() {
Code: `if chat.TableBlock(c, []string{"Guest", "Seat"}, [][]string{{"Marie", "1"}}, chat.TableBlockOptions{}).Copied() {
fmt.Println("copied as TSV")
}`,
Demo: func(c *ui.Context, log func(string)) {
@@ -110,14 +111,14 @@ var chatmsgBlockPages = []page{
}
inputSection(c, "Live", func() {
ui.Column(c).MaxWidth(520).Children(func() {
if mujica.TableBlock(c, header, rows, mujica.TableBlockOptions{Align: []ui.Align{ui.Start, ui.Start, ui.End}}).Copied() {
if chat.TableBlock(c, header, rows, chat.TableBlockOptions{Align: []ui.Align{ui.Start, ui.Start, ui.End}}).Copied() {
log("table copied as TSV")
}
})
})
}},
{ID: "209", Name: "ImageGrid", ZhName: "图片网格", Category: chatmsgCategory, Keys: "Tab 聚焦图片,Enter 打开查看器;查看器内 Left/Right 切换,Escape 关闭。",
Code: `if i, ok := mujica.ImageGrid(c, []mujica.GridImage{{Source: pic, Alt: "Portrait"}}).Retried(); ok {
Code: `if i, ok := chat.ImageGrid(c, []chat.GridImage{{Source: pic, Alt: "Portrait"}}).Retried(); ok {
fmt.Println("reload", i)
}`,
Demo: func(c *ui.Context, log func(string)) {
@@ -127,30 +128,30 @@ var chatmsgBlockPages = []page{
}
s := ui.Local(c.Root(), "209", func() state { return state{n: 5} })
inputSection(c, "Parameters", func() {
if mujica.NumberInput(c, &s.n, mujica.NumberInputOptions{Min: 1, Max: 9, Label: "Images", StepperOnly: true}).Changed() {
if input.NumberInput(c, &s.n, input.NumberInputOptions{Min: 1, Max: 9, Label: "Images", StepperOnly: true}).Changed() {
log(fmt.Sprintf("%v images", s.n))
}
inputFlags(c, log, map[string]*bool{"Last loading": &s.loading, "Second failed": &s.fail}, "Last loading", "Second failed")
})
imgs := make([]mujica.GridImage, int(s.n))
imgs := make([]chat.GridImage, int(s.n))
for i := range imgs {
imgs[i] = mujica.GridImage{Source: chatmsgPicture(chatmsgColors[i]), Alt: fmt.Sprintf("Portrait %d", i+1)}
imgs[i] = chat.GridImage{Source: chatmsgPicture(chatmsgColors[i]), Alt: fmt.Sprintf("Portrait %d", i+1)}
}
if s.loading {
imgs[len(imgs)-1] = mujica.GridImage{Alt: "Loading portrait", Loading: true}
imgs[len(imgs)-1] = chat.GridImage{Alt: "Loading portrait", Loading: true}
}
if s.fail && len(imgs) > 1 {
imgs[1] = mujica.GridImage{Alt: "Lost portrait", Err: errors.New("404")}
imgs[1] = chat.GridImage{Alt: "Lost portrait", Err: errors.New("404")}
}
inputSection(c, "Live", func() {
if i, ok := mujica.ImageGrid(c, imgs).Retried(); ok {
if i, ok := chat.ImageGrid(c, imgs).Retried(); ok {
s.fail = false
log(fmt.Sprintf("retry image %d", i))
}
})
}},
{ID: "210", Name: "FileMessage", ZhName: "文件消息", Category: chatmsgCategory, Keys: "Tab 到文件与下载按钮,Enter 或 Space 触发。",
Code: `r := mujica.FileMessage(c, mujica.FileMessageOptions{Name: "treaty.pdf", Size: 2621440})
Code: `r := chat.FileMessage(c, chat.FileMessageOptions{Name: "treaty.pdf", Size: 2621440})
if r.Downloaded() {
fmt.Println("save treaty.pdf")
}`,
@@ -161,9 +162,9 @@ if r.Downloaded() {
})
inputSection(c, "Live", func() {
ui.Column(c).Gap(8).Disabled(*disabled).Children(func() {
for _, f := range []mujica.FileMessageOptions{{Name: "treaty.pdf", Size: 2621440}, {Name: "portrait.png", Size: 812000},
for _, f := range []chat.FileMessageOptions{{Name: "treaty.pdf", Size: 2621440}, {Name: "portrait.png", Size: 812000},
{Name: "ledger.zip", Size: 73400320}, {Name: "decree.go", Size: 1840}, {Name: "notes", Size: 12}} {
r := mujica.FileMessage(c, f)
r := chat.FileMessage(c, f)
if r.Opened() {
log("open " + f.Name)
}
+5 -5
View File
@@ -9,11 +9,11 @@ import (
"github.com/egoist/mygo/ui"
mujica "mujicaui"
"github.com/ZacharyZhang-NY/MujicaUI/core"
)
// chatmsgDemoTester runs the demo of page id and returns its tester and event log.
func chatmsgDemoTester(t *testing.T, id string, mode mujica.Mode) (*ui.Tester, *[]string) {
func chatmsgDemoTester(t *testing.T, id string, mode core.Mode) (*ui.Tester, *[]string) {
t.Helper()
i := slices.IndexFunc(chatmsgPages, func(p page) bool { return p.ID == id })
if i < 0 {
@@ -21,7 +21,7 @@ func chatmsgDemoTester(t *testing.T, id string, mode mujica.Mode) (*ui.Tester, *
}
events := &[]string{}
tt := ui.NewTester(func(c *ui.Context) {
mujica.Use(c, mujica.Settings{Mode: mode})
core.Use(c, core.Settings{Mode: mode})
ui.Scroll(c).Fill().Padding(24).Gap(16).Children(func() {
chatmsgPages[i].Demo(c, func(s string) { *events = append(*events, s) })
})
@@ -37,7 +37,7 @@ func TestChatmsgDemosRender(t *testing.T) {
}
dir := os.Getenv("CHATMSG_DEMO_DIR")
for _, p := range chatmsgPages {
for _, m := range []mujica.Mode{mujica.Light, mujica.Dark} {
for _, m := range []core.Mode{core.Light, core.Dark} {
tt, _ := chatmsgDemoTester(t, p.ID, m)
if len(tt.Texts()) == 0 {
t.Errorf("%s: demo shows nothing", p.ID)
@@ -61,7 +61,7 @@ func TestChatmsgDemosLogEvents(t *testing.T) {
for id, click := range map[string]string{
"202": "Add message", "207": "Copy code", "214": "Regenerate", "216": "Next branch", "217": "Retry",
} {
tt, events := chatmsgDemoTester(t, id, mujica.Light)
tt, events := chatmsgDemoTester(t, id, core.Light)
if err := tt.Click(click); err != nil {
t.Fatalf("%s: %v", id, err)
}
+57 -57
View File
@@ -4,23 +4,23 @@ const chatsessionCategory = "Chat Sessions"
var chatsessionPages = []page{
{ID: "241", Name: "ConversationList", ZhName: "会话列表", Category: chatsessionCategory, Demo: chatDemoConversationList,
Keys: "Up/Down choose a conversation, skipping group headers; typing jumps; Enter opens.", Code: `var state mujica.ConversationListState
convs := []mujica.ChatConversation{
Keys: "Up/Down choose a conversation, skipping group headers; typing jumps; Enter opens.", Code: `var state chat.ConversationListState
convs := []chat.ChatConversation{
{ID: "1", Title: "Court etiquette", Updated: time.Now()},
{ID: "2", Title: "Royal letters", Updated: time.Now().AddDate(0, 0, -30), Pinned: true},
}
view := func(c *ui.Context) {
mujica.Use(c, mujica.Settings{})
if mujica.ConversationList(c, &state, convs, mujica.ConversationListOptions{Label: "History"}, nil).Changed() {
core.Use(c, core.Settings{})
if chat.ConversationList(c, &state, convs, chat.ConversationListOptions{Label: "History"}, nil).Changed() {
log.Println("open", state.Selected)
}
}
ui.Render(view, 400, 300, 1)`},
{ID: "242", Name: "ConversationItem", ZhName: "会话条目", Category: chatsessionCategory, Demo: chatDemoConversationItem,
Keys: "Enter opens; F2 renames in place (Enter keeps, Escape cancels); Delete asks to delete; the ellipsis opens the menu.", Code: `conv := mujica.ChatConversation{ID: "1", Title: "Court etiquette", Updated: time.Now()}
Keys: "Enter opens; F2 renames in place (Enter keeps, Escape cancels); Delete asks to delete; the ellipsis opens the menu.", Code: `conv := chat.ChatConversation{ID: "1", Title: "Court etiquette", Updated: time.Now()}
view := func(c *ui.Context) {
mujica.Use(c, mujica.Settings{})
v := mujica.ConversationItem(c, &conv, mujica.ConversationItemOptions{})
core.Use(c, core.Settings{})
v := chat.ConversationItem(c, &conv, chat.ConversationItemOptions{})
if v.Deleted {
log.Println("delete", conv.ID)
}
@@ -28,43 +28,43 @@ view := func(c *ui.Context) {
ui.Render(view, 400, 300, 1)`},
{ID: "243", Name: "ConversationSearch", ZhName: "会话搜索", Category: chatsessionCategory, Demo: chatDemoConversationSearch,
Keys: "Type to search; Down moves into the results; Up/Down move; Enter opens; Escape clears.", Code: `query := "seat"
hits := []mujica.ConversationSearchHit{{ID: "1", Title: "Ballroom seating plan"}}
hits := []chat.ConversationSearchHit{{ID: "1", Title: "Ballroom seating plan"}}
view := func(c *ui.Context) {
mujica.Use(c, mujica.Settings{})
v := mujica.ConversationSearch(c, &query, hits, mujica.ConversationSearchOptions{})
core.Use(c, core.Settings{})
v := chat.ConversationSearch(c, &query, hits, chat.ConversationSearchOptions{})
if v.QueryChanged {
log.Println("search", query)
}
}
ui.Render(view, 400, 300, 1)`},
{ID: "244", Name: "ProjectList", ZhName: "项目列表", Category: chatsessionCategory, Demo: chatDemoProjectList,
Keys: "Up/Down move through folders, projects and conversations; Right/Left show or hide a project's conversations; the ellipsis menu moves a conversation.", Code: `var state mujica.ProjectListState
projects := []mujica.ChatProject{{ID: "p1", Name: "Coronation", Folder: "Court"}}
convs := func(id string) []mujica.ChatConversation {
return []mujica.ChatConversation{{ID: "1", Title: "Menu"}}
Keys: "Up/Down move through folders, projects and conversations; Right/Left show or hide a project's conversations; the ellipsis menu moves a conversation.", Code: `var state chat.ProjectListState
projects := []chat.ChatProject{{ID: "p1", Name: "Coronation", Folder: "Court"}}
convs := func(id string) []chat.ChatConversation {
return []chat.ChatConversation{{ID: "1", Title: "Menu"}}
}
view := func(c *ui.Context) {
mujica.Use(c, mujica.Settings{})
if v := mujica.ProjectList(c, &state, projects, convs, mujica.ProjectListOptions{}); v.Moved {
core.Use(c, core.Settings{})
if v := chat.ProjectList(c, &state, projects, convs, chat.ProjectListOptions{}); v.Moved {
log.Println("move", v.MoveID, "to", v.MoveTo)
}
}
ui.Render(view, 400, 300, 1)`},
{ID: "245", Name: "ProjectKnowledgePanel", ZhName: "项目知识面板", Category: chatsessionCategory, Demo: chatDemoProjectKnowledgePanel,
Keys: "Tab reaches Add files and the file list; Up/Down move between remove buttons; Enter removes.", Code: `files := []mujica.KnowledgeFile{{ID: "1", Name: "etiquette.pdf", Tokens: 12000}}
Keys: "Tab reaches Add files and the file list; Up/Down move between remove buttons; Enter removes.", Code: `files := []chat.KnowledgeFile{{ID: "1", Name: "etiquette.pdf", Tokens: 12000}}
view := func(c *ui.Context) {
mujica.Use(c, mujica.Settings{})
v := mujica.ProjectKnowledgePanel(c, files, mujica.ProjectKnowledgePanelOptions{Capacity: 200000})
core.Use(c, core.Settings{})
v := chat.ProjectKnowledgePanel(c, files, chat.ProjectKnowledgePanelOptions{Capacity: 200000})
if v.Add {
log.Println("pick files")
}
}
ui.Render(view, 400, 300, 1)`},
{ID: "246", Name: "WelcomeScreen", ZhName: "欢迎页", Category: chatsessionCategory, Demo: chatDemoWelcomeScreen,
Keys: "Tab moves through the cards' examples and the prompt slot; Enter picks.", Code: `cards := []mujica.CapabilityCard{{Title: "Write", Examples: []string{"Draft a decree"}}}
Keys: "Tab moves through the cards' examples and the prompt slot; Enter picks.", Code: `cards := []chat.CapabilityCard{{Title: "Write", Examples: []string{"Draft a decree"}}}
view := func(c *ui.Context) {
mujica.Use(c, mujica.Settings{})
if v := mujica.WelcomeScreen(c, mujica.WelcomeScreenOptions{Greeting: "Good evening", Cards: cards}); v.Example != "" {
core.Use(c, core.Settings{})
if v := chat.WelcomeScreen(c, chat.WelcomeScreenOptions{Greeting: "Good evening", Cards: cards}); v.Example != "" {
log.Println("ask", v.Example)
}
}
@@ -72,55 +72,55 @@ ui.Render(view, 400, 300, 1)`},
{ID: "247", Name: "SuggestionChips", ZhName: "建议标签", Category: chatsessionCategory, Demo: chatDemoSuggestionChips,
Keys: "Tab enters the chips; Left/Right move; Enter or Space picks.", Code: `input := ""
view := func(c *ui.Context) {
mujica.Use(c, mujica.Settings{})
mujica.SuggestionChips(c, &input, []string{"Plan a ball", "Write a sonnet"}, mujica.SuggestionChipsOptions{})
core.Use(c, core.Settings{})
chat.SuggestionChips(c, &input, []string{"Plan a ball", "Write a sonnet"}, chat.SuggestionChipsOptions{})
}
ui.Render(view, 400, 300, 1)`},
{ID: "248", Name: "PromptLibrary", ZhName: "提示模板库", Category: chatsessionCategory, Demo: chatDemoPromptLibrary,
Keys: "Tab moves between categories, search, templates and fields; Up/Down move in a column; Enter picks.", Code: `var state mujica.PromptLibraryState
templates := []mujica.PromptTemplate{{ID: "1", Category: "Letters", Title: "Invitation", Body: "Invite {{guest}}."}}
Keys: "Tab moves between categories, search, templates and fields; Up/Down move in a column; Enter picks.", Code: `var state chat.PromptLibraryState
templates := []chat.PromptTemplate{{ID: "1", Category: "Letters", Title: "Invitation", Body: "Invite {{guest}}."}}
view := func(c *ui.Context) {
mujica.Use(c, mujica.Settings{})
if v := mujica.PromptLibrary(c, &state, templates, mujica.PromptLibraryOptions{}); v.Inserted != "" {
core.Use(c, core.Settings{})
if v := chat.PromptLibrary(c, &state, templates, chat.PromptLibraryOptions{}); v.Inserted != "" {
log.Println("insert", v.Inserted)
}
}
ui.Render(view, 400, 300, 1)`},
{ID: "249", Name: "CapabilityCards", ZhName: "能力卡片", Category: chatsessionCategory, Demo: chatDemoCapabilityCards,
Keys: "Tab moves between cards; Up/Down move among a card's examples; Enter picks.", Code: `cards := []mujica.CapabilityCard{{Title: "Plan", Icon: icons.Must("calendar"), Examples: []string{"Plan a ball"}}}
Keys: "Tab moves between cards; Up/Down move among a card's examples; Enter picks.", Code: `cards := []chat.CapabilityCard{{Title: "Plan", Icon: icons.Must("calendar"), Examples: []string{"Plan a ball"}}}
view := func(c *ui.Context) {
mujica.Use(c, mujica.Settings{})
if v := mujica.CapabilityCards(c, cards, mujica.CapabilityCardsOptions{}); v.Example != "" {
core.Use(c, core.Settings{})
if v := chat.CapabilityCards(c, cards, chat.CapabilityCardsOptions{}); v.Example != "" {
log.Println("ask", v.Example)
}
}
ui.Render(view, 400, 300, 1)`},
{ID: "250", Name: "FeedbackForm", ZhName: "反馈表单", Category: chatsessionCategory, Demo: chatDemoFeedbackForm,
Keys: "Left/Right move among reasons, Space toggles; Escape cancels.", Code: `var value mujica.FeedbackValue
Keys: "Left/Right move among reasons, Space toggles; Escape cancels.", Code: `var value chat.FeedbackValue
view := func(c *ui.Context) {
mujica.Use(c, mujica.Settings{})
if mujica.FeedbackForm(c, &value, mujica.FeedbackFormOptions{Rating: mujica.FeedbackDown}).Submitted {
core.Use(c, core.Settings{})
if chat.FeedbackForm(c, &value, chat.FeedbackFormOptions{Rating: chat.FeedbackDown}).Submitted {
log.Println("reasons", value.Reasons)
}
}
ui.Render(view, 400, 300, 1)`},
{ID: "251", Name: "DateSeparator", ZhName: "日期分隔", Category: chatsessionCategory, Demo: chatDemoDateSeparator,
Keys: "Not focusable; read as the date.", Code: `view := func(c *ui.Context) {
mujica.Use(c, mujica.Settings{})
mujica.DateSeparator(c, mujica.DateOf(time.Now()), mujica.DateSeparatorOptions{})
core.Use(c, core.Settings{})
chat.DateSeparator(c, core.DateOf(time.Now()), chat.DateSeparatorOptions{})
}
ui.Render(view, 400, 300, 1)`},
{ID: "252", Name: "ThinkingIndicator", ZhName: "思考指示", Category: chatsessionCategory, Demo: chatDemoThinkingIndicator,
Keys: "Not focusable; read as a status.", Code: `view := func(c *ui.Context) {
mujica.Use(c, mujica.Settings{})
mujica.ThinkingIndicator(c, mujica.ThinkingIndicatorOptions{Phase: "Searching the archives"})
core.Use(c, core.Settings{})
chat.ThinkingIndicator(c, chat.ThinkingIndicatorOptions{Phase: "Searching the archives"})
}
ui.Render(view, 400, 300, 1)`},
{ID: "253", Name: "StopGeneratingButton", ZhName: "停止生成按钮", Category: chatsessionCategory, Demo: chatDemoStopGeneratingButton,
Keys: "Enter or Space stops; Cmd+. (Ctrl+. elsewhere) stops from anywhere.", Code: `generating := true
view := func(c *ui.Context) {
mujica.Use(c, mujica.Settings{})
if mujica.StopGeneratingButton(c, mujica.StopGeneratingButtonOptions{Generating: generating}).Changed() {
core.Use(c, core.Settings{})
if chat.StopGeneratingButton(c, chat.StopGeneratingButtonOptions{Generating: generating}).Changed() {
generating = false
}
}
@@ -128,57 +128,57 @@ ui.Render(view, 400, 300, 1)`},
{ID: "254", Name: "SystemPromptEditor", ZhName: "系统提示编辑器", Category: chatsessionCategory, Demo: chatDemoSystemPromptEditor,
Keys: "Cmd+S (Ctrl+S elsewhere) saves.", Code: `prompt, saved := "You are helpful.", "You are helpful."
view := func(c *ui.Context) {
mujica.Use(c, mujica.Settings{})
if mujica.SystemPromptEditor(c, &prompt, mujica.SystemPromptEditorOptions{Default: "You are helpful.", Saved: saved}).Save {
core.Use(c, core.Settings{})
if chat.SystemPromptEditor(c, &prompt, chat.SystemPromptEditorOptions{Default: "You are helpful.", Saved: saved}).Save {
saved = prompt
}
}
ui.Render(view, 400, 300, 1)`},
{ID: "255", Name: "CostEstimator", ZhName: "费用估算", Category: chatsessionCategory, Demo: chatDemoCostEstimator,
Keys: "Not focusable; read as the estimated cost.", Code: `view := func(c *ui.Context) {
mujica.Use(c, mujica.Settings{})
mujica.CostEstimator(c, mujica.CostEstimatorOptions{InputTokens: 12000, OutputTokens: 3400, InputPrice: 3, OutputPrice: 15})
core.Use(c, core.Settings{})
chat.CostEstimator(c, chat.CostEstimatorOptions{InputTokens: 12000, OutputTokens: 3400, InputPrice: 3, OutputPrice: 15})
}
ui.Render(view, 400, 300, 1)`},
{ID: "256", Name: "SharedConversationView", ZhName: "分享会话视图", Category: chatsessionCategory, Demo: chatDemoSharedConversationView,
Keys: "Tab reaches Copy link and the messages; message text is selectable.", Code: `msgs := []mujica.SessionMessage{{Role: mujica.SessionUser, Text: "Hello"}, {Role: mujica.SessionAssistant, Text: "Good day."}}
Keys: "Tab reaches Copy link and the messages; message text is selectable.", Code: `msgs := []chat.SessionMessage{{Role: chat.SessionUser, Text: "Hello"}, {Role: chat.SessionAssistant, Text: "Good day."}}
view := func(c *ui.Context) {
mujica.Use(c, mujica.Settings{})
if mujica.SharedConversationView(c, msgs, mujica.SharedConversationViewOptions{Title: "Greeting", SharedBy: "Ann"}).Changed() {
core.Use(c, core.Settings{})
if chat.SharedConversationView(c, msgs, chat.SharedConversationViewOptions{Title: "Greeting", SharedBy: "Ann"}).Changed() {
c.WriteClipboard("https://example.com/share/1")
}
}
ui.Render(view, 400, 300, 1)`},
{ID: "257", Name: "ConversationExport", ZhName: "会话导出", Category: chatsessionCategory, Demo: chatDemoConversationExport,
Keys: "Arrows choose a format and range; Tab reaches Export.", Code: `var state mujica.ConversationExportState
msgs := []mujica.SessionMessage{{Role: mujica.SessionUser, Text: "Hello"}}
Keys: "Arrows choose a format and range; Tab reaches Export.", Code: `var state chat.ConversationExportState
msgs := []chat.SessionMessage{{Role: chat.SessionUser, Text: "Hello"}}
view := func(c *ui.Context) {
mujica.Use(c, mujica.Settings{})
if v := mujica.ConversationExport(c, &state, msgs, mujica.ConversationExportOptions{Title: "Greeting"}); v.Exported {
core.Use(c, core.Settings{})
if v := chat.ConversationExport(c, &state, msgs, chat.ConversationExportOptions{Title: "Greeting"}); v.Exported {
log.Println(len(v.Content), "bytes to save")
}
}
ui.Render(view, 400, 300, 1)`},
{ID: "258", Name: "RegenerateMenu", ZhName: "重新生成菜单", Category: chatsessionCategory, Demo: chatDemoRegenerateMenu,
Keys: "Enter, Space or Down open; Up/Down move; Right opens the model list; Escape closes.", Code: `view := func(c *ui.Context) {
mujica.Use(c, mujica.Settings{})
if v := mujica.RegenerateMenu(c, mujica.RegenerateMenuOptions{Models: []string{"Opus", "Sonnet"}, Current: "Opus"}); v.Chosen {
core.Use(c, core.Settings{})
if v := chat.RegenerateMenu(c, chat.RegenerateMenuOptions{Models: []string{"Opus", "Sonnet"}, Current: "Opus"}); v.Chosen {
log.Println("retry", v.Choice.Kind, v.Choice.Model, v.Choice.Variant)
}
}
ui.Render(view, 400, 300, 1)`},
{ID: "259", Name: "ArtifactCard", ZhName: "产物卡片", Category: chatsessionCategory, Demo: chatDemoArtifactCard,
Keys: "Enter or Space opens the artifact panel.", Code: `view := func(c *ui.Context) {
mujica.Use(c, mujica.Settings{})
if mujica.ArtifactCard(c, mujica.ArtifactCardOptions{Title: "seating.go", Detail: "Go · 120 lines"}).Changed() {
core.Use(c, core.Settings{})
if chat.ArtifactCard(c, chat.ArtifactCardOptions{Title: "seating.go", Detail: "Go · 120 lines"}).Changed() {
log.Println("open artifact panel")
}
}
ui.Render(view, 400, 300, 1)`},
{ID: "260", Name: "ChatEmptyState", ZhName: "会话空状态", Category: chatsessionCategory, Demo: chatDemoChatEmptyState,
Keys: "Tab reaches the action; Enter runs it.", Code: `view := func(c *ui.Context) {
mujica.Use(c, mujica.Settings{})
if mujica.ChatEmptyState(c, mujica.ChatEmptyStateOptions{Kind: mujica.ChatEmptyOffline}).Action {
core.Use(c, core.Settings{})
if chat.ChatEmptyState(c, chat.ChatEmptyStateOptions{Kind: chat.ChatEmptyOffline}).Action {
log.Println("reconnect")
}
}
+36 -35
View File
@@ -7,14 +7,15 @@ import (
"github.com/egoist/mygo/ui"
mujica "mujicaui"
"mujicaui/icons"
"github.com/ZacharyZhang-NY/MujicaUI/chat"
"github.com/ZacharyZhang-NY/MujicaUI/icons"
"github.com/ZacharyZhang-NY/MujicaUI/input"
)
// chatHistory is the gallery's sample conversations, dated from now.
func chatHistory(now time.Time) []mujica.ChatConversation {
func chatHistory(now time.Time) []chat.ChatConversation {
day := 24 * time.Hour
return []mujica.ChatConversation{
return []chat.ChatConversation{
{ID: "a", Title: "Court etiquette notes", Updated: now.Add(-2 * time.Hour)},
{ID: "b", Title: "Ballroom seating plan", Updated: now.Add(-day)},
{ID: "c", Title: "Harpsichord repair", Updated: now.Add(-4 * day)},
@@ -24,17 +25,17 @@ func chatHistory(now time.Time) []mujica.ChatConversation {
}
}
func chatSampleMessages(now time.Time) []mujica.SessionMessage {
return []mujica.SessionMessage{
{Role: mujica.SessionUser, Text: "How should guests be seated at the banquet?", Time: now.Add(-10 * time.Minute)},
{Role: mujica.SessionAssistant, Text: "Seat by rank: the host at the head, honored guests to the right.", Time: now.Add(-9 * time.Minute)},
{Role: mujica.SessionUser, Author: "Lady Ann", Text: "And the musicians?", Time: now.Add(-5 * time.Minute)},
{Role: mujica.SessionAssistant, Text: "Place the quartet behind a screen near the gallery.", Time: now.Add(-4 * time.Minute)},
func chatSampleMessages(now time.Time) []chat.SessionMessage {
return []chat.SessionMessage{
{Role: chat.SessionUser, Text: "How should guests be seated at the banquet?", Time: now.Add(-10 * time.Minute)},
{Role: chat.SessionAssistant, Text: "Seat by rank: the host at the head, honored guests to the right.", Time: now.Add(-9 * time.Minute)},
{Role: chat.SessionUser, Author: "Lady Ann", Text: "And the musicians?", Time: now.Add(-5 * time.Minute)},
{Role: chat.SessionAssistant, Text: "Place the quartet behind a screen near the gallery.", Time: now.Add(-4 * time.Minute)},
}
}
func chatSampleCards() []mujica.CapabilityCard {
return []mujica.CapabilityCard{
func chatSampleCards() []chat.CapabilityCard {
return []chat.CapabilityCard{
{Title: "Write", Description: "Letters, speeches and verse.", Icon: icons.Must("file-text"), Examples: []string{"Draft a royal decree", "Write a sonnet"}},
{Title: "Plan", Description: "Banquets, seating and schedules.", Icon: icons.Must("calendar"), Examples: []string{"Plan a ball for 200 guests"}},
{Title: "Research", Description: "History, heraldry and music.", Icon: icons.Must("search"), Examples: []string{"Explain the minuet"}},
@@ -43,19 +44,19 @@ func chatSampleCards() []mujica.CapabilityCard {
func chatDemoConversationList(c *ui.Context, log func(string)) {
type state struct {
list mujica.ConversationListState
items []mujica.ChatConversation
list chat.ConversationListState
items []chat.ChatConversation
many, rich bool
disabled bool
}
s := *ui.Local(c.Root(), "241", func() *state { return &state{items: chatHistory(time.Now())} })
choiceDemo(c, func() {
if mujica.Switch(c, &s.many, "100,000 conversations", mujica.SwitchOptions{}).Changed() {
if input.Switch(c, &s.many, "100,000 conversations", input.SwitchOptions{}).Changed() {
s.items = chatHistory(time.Now())
if s.many {
now := time.Now()
for i := range 100000 {
s.items = append(s.items, mujica.ChatConversation{ID: fmt.Sprint("n", i), Title: fmt.Sprintf("Archive %d", i), Updated: now.Add(-time.Duration(i) * 7 * time.Minute)})
s.items = append(s.items, chat.ChatConversation{ID: fmt.Sprint("n", i), Title: fmt.Sprintf("Archive %d", i), Updated: now.Add(-time.Duration(i) * 7 * time.Minute)})
}
}
log(fmt.Sprintf("conversations = %d", len(s.items)))
@@ -63,17 +64,17 @@ func chatDemoConversationList(c *ui.Context, log func(string)) {
choiceFlag(c, &s.rich, "ConversationItem rows", log)
choiceFlag(c, &s.disabled, "Disabled", log)
}, func() {
var row func(conv mujica.ChatConversation)
var row func(conv chat.ChatConversation)
if s.rich {
row = func(conv mujica.ChatConversation) {
v := mujica.ConversationItem(c, &conv, mujica.ConversationItemOptions{Selected: conv.ID == s.list.Selected})
row = func(conv chat.ChatConversation) {
v := chat.ConversationItem(c, &conv, chat.ConversationItemOptions{Selected: conv.ID == s.list.Selected})
if v.Opened {
s.list.Selected = conv.ID
log("open " + conv.Title)
}
}
}
v := mujica.ConversationList(c, &s.list, s.items, mujica.ConversationListOptions{Label: "History", Disabled: s.disabled}, row)
v := chat.ConversationList(c, &s.list, s.items, chat.ConversationListOptions{Label: "History", Disabled: s.disabled}, row)
v.Element.Width(380).Height(380)
if v.Changed() {
log("select " + s.list.Selected)
@@ -86,7 +87,7 @@ func chatDemoConversationList(c *ui.Context, log func(string)) {
func chatDemoConversationItem(c *ui.Context, log func(string)) {
type state struct {
convs []mujica.ChatConversation
convs []chat.ChatConversation
selected, disabled bool
}
s := *ui.Local(c.Root(), "242", func() *state { return &state{convs: chatHistory(time.Now())[:3]} })
@@ -98,7 +99,7 @@ func chatDemoConversationItem(c *ui.Context, log func(string)) {
for i := range s.convs {
conv := &s.convs[i]
ui.Row(c).Key(conv.ID).Children(func() {
v := mujica.ConversationItem(c, conv, mujica.ConversationItemOptions{Selected: s.selected && i == 0, Disabled: s.disabled})
v := chat.ConversationItem(c, conv, chat.ConversationItemOptions{Selected: s.selected && i == 0, Disabled: s.disabled})
switch {
case v.Opened:
log("open " + conv.Title)
@@ -122,7 +123,7 @@ func chatDemoConversationSearch(c *ui.Context, log func(string)) {
type state struct {
query, asked string
at time.Time
hits []mujica.ConversationSearchHit
hits []chat.ConversationSearchHit
fail, slow bool
disabled bool
}
@@ -137,7 +138,7 @@ func chatDemoConversationSearch(c *ui.Context, log func(string)) {
s.asked, s.hits, loading = s.query, nil, false
for _, conv := range chatHistory(time.Now()) {
if q := strings.ToLower(s.query); q != "" && strings.Contains(strings.ToLower(conv.Title), q) {
s.hits = append(s.hits, mujica.ConversationSearchHit{ID: conv.ID, Title: conv.Title, Snippet: "…mentions " + strings.ToLower(conv.Title) + "…"})
s.hits = append(s.hits, chat.ConversationSearchHit{ID: conv.ID, Title: conv.Title, Snippet: "…mentions " + strings.ToLower(conv.Title) + "…"})
}
}
log(fmt.Sprintf("results for %q: %d", s.query, len(s.hits)))
@@ -147,7 +148,7 @@ func chatDemoConversationSearch(c *ui.Context, log func(string)) {
errText = "The archive did not answer."
}
ui.Column(c).Width(420).Children(func() {
v := mujica.ConversationSearch(c, &s.query, s.hits, mujica.ConversationSearchOptions{Loading: loading, Error: errText, Disabled: s.disabled, Label: "Search conversations"})
v := chat.ConversationSearch(c, &s.query, s.hits, chat.ConversationSearchOptions{Loading: loading, Error: errText, Disabled: s.disabled, Label: "Search conversations"})
if v.QueryChanged {
s.at = c.Now()
log(fmt.Sprintf("query %q", s.query))
@@ -165,21 +166,21 @@ func chatDemoConversationSearch(c *ui.Context, log func(string)) {
func chatDemoProjectList(c *ui.Context, log func(string)) {
type state struct {
list mujica.ProjectListState
list chat.ProjectListState
owner map[string]string
disabled bool
}
s := *ui.Local(c.Root(), "244", func() *state {
return &state{owner: map[string]string{"a": "p1", "b": "p1", "c": "p3", "f": "p2"}}
})
projects := []mujica.ChatProject{{ID: "p1", Name: "Coronation", Folder: "Court"}, {ID: "p2", Name: "Gardens", Folder: "Court"}, {ID: "p3", Name: "Music"}}
projects := []chat.ChatProject{{ID: "p1", Name: "Coronation", Folder: "Court"}, {ID: "p2", Name: "Gardens", Folder: "Court"}, {ID: "p3", Name: "Music"}}
choiceDemo(c, func() {
choiceFlag(c, &s.disabled, "Disabled", log)
}, func() {
ui.Row(c).Gap(24).AlignItems(ui.Start).Children(func() {
ui.Column(c).Width(380).Children(func() {
convs := func(id string) []mujica.ChatConversation {
var out []mujica.ChatConversation
convs := func(id string) []chat.ChatConversation {
var out []chat.ChatConversation
for _, conv := range chatHistory(time.Now()) {
if s.owner[conv.ID] == id {
out = append(out, conv)
@@ -187,7 +188,7 @@ func chatDemoProjectList(c *ui.Context, log func(string)) {
}
return out
}
v := mujica.ProjectList(c, &s.list, projects, convs, mujica.ProjectListOptions{Label: "Projects", Disabled: s.disabled})
v := chat.ProjectList(c, &s.list, projects, convs, chat.ProjectListOptions{Label: "Projects", Disabled: s.disabled})
switch {
case v.Moved:
s.owner[v.MoveID] = v.MoveTo
@@ -200,7 +201,7 @@ func chatDemoProjectList(c *ui.Context, log func(string)) {
})
choiceLabeled(c, "Drag onto a project", func() {
conv := chatHistory(time.Now())[3]
v := mujica.ConversationItem(c, &conv, mujica.ConversationItemOptions{Draggable: true})
v := chat.ConversationItem(c, &conv, chat.ConversationItemOptions{Draggable: true})
v.Element.Width(300)
if v.Opened {
log("open " + conv.Title)
@@ -212,13 +213,13 @@ func chatDemoProjectList(c *ui.Context, log func(string)) {
func chatDemoProjectKnowledgePanel(c *ui.Context, log func(string)) {
type state struct {
files []mujica.KnowledgeFile
files []chat.KnowledgeFile
next int
small bool
disabled bool
}
s := *ui.Local(c.Root(), "245", func() *state {
return &state{files: []mujica.KnowledgeFile{{ID: "1", Name: "etiquette.pdf", Tokens: 120000}, {ID: "2", Name: "menu.md", Tokens: 30000}}}
return &state{files: []chat.KnowledgeFile{{ID: "1", Name: "etiquette.pdf", Tokens: 120000}, {ID: "2", Name: "menu.md", Tokens: 30000}}}
})
choiceDemo(c, func() {
choiceFlag(c, &s.small, "Capacity 150,000", log)
@@ -229,10 +230,10 @@ func chatDemoProjectKnowledgePanel(c *ui.Context, log func(string)) {
capacity = 150000
}
ui.Column(c).Width(440).Children(func() {
v := mujica.ProjectKnowledgePanel(c, s.files, mujica.ProjectKnowledgePanelOptions{Capacity: capacity, Disabled: s.disabled})
v := chat.ProjectKnowledgePanel(c, s.files, chat.ProjectKnowledgePanelOptions{Capacity: capacity, Disabled: s.disabled})
if v.Add {
s.next++
s.files = append(s.files, mujica.KnowledgeFile{ID: fmt.Sprint("n", s.next), Name: fmt.Sprintf("notes-%d.txt", s.next), Tokens: 15000})
s.files = append(s.files, chat.KnowledgeFile{ID: fmt.Sprint("n", s.next), Name: fmt.Sprintf("notes-%d.txt", s.next), Tokens: 15000})
log("add (the demo invents a file)")
}
if v.Removed != "" {
+42 -39
View File
@@ -6,7 +6,10 @@ import (
"github.com/egoist/mygo/ui"
mujica "mujicaui"
"github.com/ZacharyZhang-NY/MujicaUI/agent"
"github.com/ZacharyZhang-NY/MujicaUI/chat"
"github.com/ZacharyZhang-NY/MujicaUI/core"
"github.com/ZacharyZhang-NY/MujicaUI/input"
)
func chatDemoWelcomeScreen(c *ui.Context, log func(string)) {
@@ -19,14 +22,14 @@ func chatDemoWelcomeScreen(c *ui.Context, log func(string)) {
choiceFlag(c, &s.cards, "Capability cards", log)
choiceFlag(c, &s.disabled, "Disabled", log)
}, func() {
var cards []mujica.CapabilityCard
var cards []chat.CapabilityCard
if s.cards {
cards = chatSampleCards()
}
ui.Column(c).Width(860).Children(func() {
v := mujica.WelcomeScreen(c, mujica.WelcomeScreenOptions{Greeting: "Good afternoon", Subtitle: "How may I serve the court?", Cards: cards, Disabled: s.disabled,
v := chat.WelcomeScreen(c, chat.WelcomeScreenOptions{Greeting: "Good afternoon", Subtitle: "How may I serve the court?", Cards: cards, Disabled: s.disabled,
Prompts: func() {
if mujica.SuggestionChips(c, &s.input, []string{"Plan a ball", "Write a sonnet"}, mujica.SuggestionChipsOptions{Disabled: s.disabled}).Chosen != "" {
if chat.SuggestionChips(c, &s.input, []string{"Plan a ball", "Write a sonnet"}, chat.SuggestionChipsOptions{Disabled: s.disabled}).Chosen != "" {
log(fmt.Sprintf("fill %q", s.input))
}
}})
@@ -47,8 +50,8 @@ func chatDemoSuggestionChips(c *ui.Context, log func(string)) {
choiceFlag(c, &s.send, "Send directly", log)
choiceFlag(c, &s.disabled, "Disabled", log)
}, func() {
v := mujica.SuggestionChips(c, &s.input, []string{"Plan a ball", "Write a sonnet", "Explain the minuet"},
mujica.SuggestionChipsOptions{Send: s.send, Label: "Suggestions", Disabled: s.disabled})
v := chat.SuggestionChips(c, &s.input, []string{"Plan a ball", "Write a sonnet", "Explain the minuet"},
chat.SuggestionChipsOptions{Send: s.send, Label: "Suggestions", Disabled: s.disabled})
switch {
case v.Sent:
log(fmt.Sprintf("send %q", v.Chosen))
@@ -56,18 +59,18 @@ func chatDemoSuggestionChips(c *ui.Context, log func(string)) {
log(fmt.Sprintf("fill %q", v.Chosen))
}
ui.Column(c).Width(420).Children(func() {
mujica.TextInput(c, &s.input, mujica.TextInputOptions{Label: "Message", Placeholder: "Message"})
input.TextInput(c, &s.input, input.TextInputOptions{Label: "Message", Placeholder: "Message"})
})
})
}
func chatDemoPromptLibrary(c *ui.Context, log func(string)) {
type state struct {
lib mujica.PromptLibraryState
lib chat.PromptLibraryState
disabled bool
}
s := *ui.Local(c.Root(), "248", func() *state { return &state{} })
templates := []mujica.PromptTemplate{
templates := []chat.PromptTemplate{
{ID: "t1", Category: "Letters", Title: "Invitation", Body: "Write an invitation to {{guest}} for the {{event}}."},
{ID: "t2", Category: "Letters", Title: "Thank-you note", Body: "Thank {{host}} for the evening."},
{ID: "t3", Category: "Music", Title: "Recital program", Body: "List pieces for a {{instrument}} recital."},
@@ -77,7 +80,7 @@ func chatDemoPromptLibrary(c *ui.Context, log func(string)) {
choiceFlag(c, &s.disabled, "Disabled", log)
}, func() {
before := s.lib.Selected
v := mujica.PromptLibrary(c, &s.lib, templates, mujica.PromptLibraryOptions{Label: "Prompt library", Disabled: s.disabled})
v := chat.PromptLibrary(c, &s.lib, templates, chat.PromptLibraryOptions{Label: "Prompt library", Disabled: s.disabled})
if s.lib.Selected != before {
log("preview " + s.lib.Selected)
}
@@ -101,7 +104,7 @@ func chatDemoCapabilityCards(c *ui.Context, log func(string)) {
cols = 1
}
ui.Column(c).Width(860).Children(func() {
if v := mujica.CapabilityCards(c, chatSampleCards(), mujica.CapabilityCardsOptions{Columns: cols, Disabled: s.disabled}); v.Example != "" {
if v := chat.CapabilityCards(c, chatSampleCards(), chat.CapabilityCardsOptions{Columns: cols, Disabled: s.disabled}); v.Example != "" {
log(fmt.Sprintf("ask %q", v.Example))
}
})
@@ -110,7 +113,7 @@ func chatDemoCapabilityCards(c *ui.Context, log func(string)) {
func chatDemoFeedbackForm(c *ui.Context, log func(string)) {
type state struct {
value mujica.FeedbackValue
value chat.FeedbackValue
down, submitting bool
disabled bool
at time.Time
@@ -120,22 +123,22 @@ func chatDemoFeedbackForm(c *ui.Context, log func(string)) {
choiceFlag(c, &s.down, "Thumbs down", log)
choiceFlag(c, &s.disabled, "Disabled", log)
}, func() {
rating := mujica.FeedbackUp
rating := chat.FeedbackUp
if s.down {
rating = mujica.FeedbackDown
rating = chat.FeedbackDown
}
if s.submitting && choiceDue(c, s.at) {
s.submitting, s.value = false, mujica.FeedbackValue{}
s.submitting, s.value = false, chat.FeedbackValue{}
log("feedback sent")
}
ui.Column(c).Width(480).Children(func() {
v := mujica.FeedbackForm(c, &s.value, mujica.FeedbackFormOptions{Rating: rating, Submitting: s.submitting, Disabled: s.disabled})
v := chat.FeedbackForm(c, &s.value, chat.FeedbackFormOptions{Rating: rating, Submitting: s.submitting, Disabled: s.disabled})
if v.Submitted {
s.submitting, s.at = true, c.Now()
log(fmt.Sprintf("submit reasons %q, %d characters", s.value.Reasons, len(s.value.Comment)))
}
if v.Cancelled {
s.value = mujica.FeedbackValue{}
s.value = chat.FeedbackValue{}
log("cancel")
}
})
@@ -143,13 +146,13 @@ func chatDemoFeedbackForm(c *ui.Context, log func(string)) {
}
func chatDemoDateSeparator(c *ui.Context, log func(string)) {
today := mujica.DateOf(time.Now())
today := core.DateOf(time.Now())
choiceDemo(c, func() {
ui.Text(c, "No parameters; the language follows the window.")
}, func() {
ui.Column(c).Width(520).Children(func() {
for _, d := range []int{0, -1, -12} {
mujica.DateSeparator(c, today.AddDays(d), mujica.DateSeparatorOptions{})
chat.DateSeparator(c, today.AddDays(d), chat.DateSeparatorOptions{})
}
})
})
@@ -160,12 +163,12 @@ func chatDemoThinkingIndicator(c *ui.Context, log func(string)) {
s := *ui.Local(c.Root(), "252", func() *state { return &state{} })
phases := []string{"", "Searching the archives", "Reading 3 letters", "Composing a reply"}
choiceDemo(c, func() {
if mujica.Button(c, "Next phase", mujica.ButtonOptions{Variant: mujica.Secondary}).Clicked() {
if input.Button(c, "Next phase", input.ButtonOptions{Variant: input.Secondary}).Clicked() {
s.phase = (s.phase + 1) % len(phases)
log(fmt.Sprintf("phase %q", phases[s.phase]))
}
}, func() {
mujica.ThinkingIndicator(c, mujica.ThinkingIndicatorOptions{Phase: phases[s.phase]})
chat.ThinkingIndicator(c, chat.ThinkingIndicatorOptions{Phase: phases[s.phase]})
})
}
@@ -176,7 +179,7 @@ func chatDemoStopGeneratingButton(c *ui.Context, log func(string)) {
}
s := *ui.Local(c.Root(), "253", func() *state { return &state{} })
choiceDemo(c, func() {
if mujica.Button(c, "Generate", mujica.ButtonOptions{Disabled: s.generating}).Clicked() {
if input.Button(c, "Generate", input.ButtonOptions{Disabled: s.generating}).Clicked() {
s.generating, s.at = true, c.Now()
log("generate")
}
@@ -188,12 +191,12 @@ func chatDemoStopGeneratingButton(c *ui.Context, log func(string)) {
} else if s.generating {
c.After(time.Second)
}
if mujica.StopGeneratingButton(c, mujica.StopGeneratingButtonOptions{Generating: s.generating, Disabled: s.disabled}).Changed() {
if chat.StopGeneratingButton(c, chat.StopGeneratingButtonOptions{Generating: s.generating, Disabled: s.disabled}).Changed() {
s.generating = false
log("stop")
}
if !s.generating {
ui.Text(c, "Idle: the button is hidden.").TextColor(mujica.Tokens(c).TextMuted)
ui.Text(c, "Idle: the button is hidden.").TextColor(core.Tokens(c).TextMuted)
}
})
}
@@ -221,7 +224,7 @@ func chatDemoSystemPromptEditor(c *ui.Context, log func(string)) {
limit = 60
}
ui.Column(c).Width(520).Children(func() {
v := mujica.SystemPromptEditor(c, &s.value, mujica.SystemPromptEditorOptions{Default: "You are helpful.", Saved: s.saved,
v := chat.SystemPromptEditor(c, &s.value, chat.SystemPromptEditorOptions{Default: "You are helpful.", Saved: s.saved,
MaxLength: limit, Saving: s.saving, Disabled: s.disabled})
if v.Save {
s.saving, s.at = true, c.Now()
@@ -243,7 +246,7 @@ func chatDemoCostEstimator(c *ui.Context, log func(string)) {
label string
}{{&s.in, "Input tokens"}, {&s.out, "Output tokens"}, {&s.inPrice, "Input $/M"}, {&s.outPrice, "Output $/M"}} {
choiceLabeled(c, f.label, func() {
r := mujica.NumberInput(c, f.v, mujica.NumberInputOptions{Min: 0, Max: 1e7, Step: 100, Label: f.label})
r := input.NumberInput(c, f.v, input.NumberInputOptions{Min: 0, Max: 1e7, Step: 100, Label: f.label})
r.Element.Width(150)
if r.Changed() {
log(fmt.Sprintf("%s = %v", f.label, *f.v))
@@ -252,7 +255,7 @@ func chatDemoCostEstimator(c *ui.Context, log func(string)) {
}
}, func() {
ui.Column(c).Width(420).Children(func() {
mujica.CostEstimator(c, mujica.CostEstimatorOptions{InputTokens: int(s.in), OutputTokens: int(s.out), InputPrice: s.inPrice, OutputPrice: s.outPrice})
chat.CostEstimator(c, chat.CostEstimatorOptions{InputTokens: int(s.in), OutputTokens: int(s.out), InputPrice: s.inPrice, OutputPrice: s.outPrice})
})
})
}
@@ -264,7 +267,7 @@ func chatDemoSharedConversationView(c *ui.Context, log func(string)) {
choiceFlag(c, &s.disabled, "Disabled", log)
}, func() {
ui.Column(c).Width(600).Height(420).Children(func() {
if mujica.SharedConversationView(c, chatSampleMessages(time.Now()), mujica.SharedConversationViewOptions{Title: "Banquet seating",
if chat.SharedConversationView(c, chatSampleMessages(time.Now()), chat.SharedConversationViewOptions{Title: "Banquet seating",
SharedBy: "Lady Ann", SharedAt: time.Now(), Disabled: s.disabled}).Changed() {
c.WriteClipboard("https://example.com/share/banquet")
log("copy link")
@@ -275,7 +278,7 @@ func chatDemoSharedConversationView(c *ui.Context, log func(string)) {
func chatDemoConversationExport(c *ui.Context, log func(string)) {
type state struct {
export mujica.ConversationExportState
export chat.ConversationExportState
disabled bool
}
s := *ui.Local(c.Root(), "257", func() *state { return &state{} })
@@ -283,7 +286,7 @@ func chatDemoConversationExport(c *ui.Context, log func(string)) {
choiceFlag(c, &s.disabled, "Disabled", log)
}, func() {
ui.Column(c).Width(580).Children(func() {
v := mujica.ConversationExport(c, &s.export, chatSampleMessages(time.Now()), mujica.ConversationExportOptions{Title: "Banquet seating",
v := chat.ConversationExport(c, &s.export, chatSampleMessages(time.Now()), chat.ConversationExportOptions{Title: "Banquet seating",
Selected: []int{1, 3}, Disabled: s.disabled})
if v.Exported {
log(fmt.Sprintf("export %d bytes as format %d (the caller writes the file)", len(v.Content), v.Format))
@@ -298,7 +301,7 @@ func chatDemoRegenerateMenu(c *ui.Context, log func(string)) {
choiceDemo(c, func() {
choiceFlag(c, &s.disabled, "Disabled", log)
}, func() {
v := mujica.RegenerateMenu(c, mujica.RegenerateMenuOptions{Models: []string{"Opus", "Sonnet", "Haiku"}, Current: "Opus", Disabled: s.disabled})
v := chat.RegenerateMenu(c, chat.RegenerateMenuOptions{Models: []string{"Opus", "Sonnet", "Haiku"}, Current: "Opus", Disabled: s.disabled})
if v.Chosen {
log(fmt.Sprintf("retry kind %d model %q variant %q", v.Choice.Kind, v.Choice.Model, v.Choice.Variant))
}
@@ -312,13 +315,13 @@ func chatDemoArtifactCard(c *ui.Context, log func(string)) {
choiceFlag(c, &s.streaming, "Streaming", log)
choiceFlag(c, &s.disabled, "Disabled", log)
}, func() {
for _, a := range []mujica.ArtifactCardOptions{
{Kind: mujica.ArtifactCode, Title: "seating.go", Detail: "Go · 120 lines"},
{Kind: mujica.ArtifactDocument, Title: "Royal decree", Detail: "2 pages"},
{Kind: mujica.ArtifactWeb, Title: "Ball invitation"},
for _, a := range []chat.ArtifactCardOptions{
{Kind: agent.ArtifactCode, Title: "seating.go", Detail: "Go · 120 lines"},
{Kind: agent.ArtifactDocument, Title: "Royal decree", Detail: "2 pages"},
{Kind: agent.ArtifactWeb, Title: "Ball invitation"},
} {
a.Streaming, a.Disabled = s.streaming, s.disabled
if mujica.ArtifactCard(c, a).Changed() {
if chat.ArtifactCard(c, a).Changed() {
log("open " + a.Title)
}
}
@@ -329,11 +332,11 @@ func chatDemoChatEmptyState(c *ui.Context, log func(string)) {
type state struct{ kind int }
s := *ui.Local(c.Root(), "260", func() *state { return &state{} })
choiceDemo(c, func() {
mujica.RadioGroup(c, &s.kind, []mujica.RadioItem[int]{{Value: 0, Label: "New"}, {Value: 1, Label: "No results"}, {Value: 2, Label: "Offline"}},
mujica.RadioGroupOptions{Horizontal: true, Label: "Kind"})
input.RadioGroup(c, &s.kind, []input.RadioItem[int]{{Value: 0, Label: "New"}, {Value: 1, Label: "No results"}, {Value: 2, Label: "Offline"}},
input.RadioGroupOptions{Horizontal: true, Label: "Kind"})
}, func() {
ui.Column(c).Width(420).Children(func() {
if mujica.ChatEmptyState(c, mujica.ChatEmptyStateOptions{Kind: mujica.ChatEmptyKind(s.kind), Query: "minuet"}).Action {
if chat.ChatEmptyState(c, chat.ChatEmptyStateOptions{Kind: chat.ChatEmptyKind(s.kind), Query: "minuet"}).Action {
log(fmt.Sprintf("action for kind %d", s.kind))
}
})
+84 -83
View File
@@ -8,9 +8,10 @@ import (
"github.com/egoist/mygo/ui"
mujica "mujicaui"
"mujicaui/icons"
"mujicaui/theme"
"github.com/ZacharyZhang-NY/MujicaUI/core"
"github.com/ZacharyZhang-NY/MujicaUI/icons"
"github.com/ZacharyZhang-NY/MujicaUI/input"
"github.com/ZacharyZhang-NY/MujicaUI/theme"
)
const choiceCategory = "Choices"
@@ -18,104 +19,104 @@ const choiceCategory = "Choices"
var choicePages = []page{
{ID: "031", Name: "Switch", ZhName: "开关", Category: choiceCategory, Demo: switchDemo, Code: `wifi := true
view := func(c *ui.Context) {
mujica.Use(c, mujica.Settings{})
if mujica.Switch(c, &wifi, "Wi-Fi", mujica.SwitchOptions{}).Changed() {
core.Use(c, core.Settings{})
if input.Switch(c, &wifi, "Wi-Fi", input.SwitchOptions{}).Changed() {
log.Println("Wi-Fi:", wifi)
}
}`, Keys: "Tab focuses the switch; Space turns it over."},
{ID: "032", Name: "Toggle", ZhName: "切换按钮", Category: choiceCategory, Demo: toggleDemo, Code: `bold := false
view := func(c *ui.Context) {
mujica.Use(c, mujica.Settings{})
mujica.Toggle(c, &bold, "", mujica.ToggleOptions{Icon: icons.Must("bold"), Label: "Bold"})
core.Use(c, core.Settings{})
input.Toggle(c, &bold, "", input.ToggleOptions{Icon: icons.Must("bold"), Label: "Bold"})
}`, Keys: "Tab focuses the toggle; Space or Enter presses or releases it."},
{ID: "033", Name: "ToggleGroup", ZhName: "切换按钮组", Category: choiceCategory, Demo: toggleGroupDemo, Code: `align := []string{"left"}
items := []mujica.ToggleItem[string]{
items := []input.ToggleItem[string]{
{Value: "left", Label: "Left"},
{Value: "center", Label: "Center"},
{Value: "right", Label: "Right"},
}
view := func(c *ui.Context) {
mujica.Use(c, mujica.Settings{})
mujica.ToggleGroup(c, &align, items, mujica.ToggleGroupOptions{Label: "Align"})
core.Use(c, core.Settings{})
input.ToggleGroup(c, &align, items, input.ToggleGroupOptions{Label: "Align"})
}`, Keys: "Tab enters the group at the first enabled item, later at the last focused one; Left/Right (Up/Down when vertical), Home and End move; Space presses."},
{ID: "034", Name: "Slider", ZhName: "滑块", Category: choiceCategory, Demo: sliderDemo, Code: `volume := 40.0
view := func(c *ui.Context) {
mujica.Use(c, mujica.Settings{})
mujica.Slider(c, &volume, mujica.SliderOptions{Step: 5, ShowValue: true, Label: "Volume"})
core.Use(c, core.Settings{})
input.Slider(c, &volume, input.SliderOptions{Step: 5, ShowValue: true, Label: "Volume"})
}`, Keys: "Arrows move one step, Home and End go to the ends; the move applies when the key is released."},
{ID: "035", Name: "RangeSlider", ZhName: "范围滑块", Category: choiceCategory, Demo: rangeSliderDemo, Code: `low, high := 20.0, 80.0
view := func(c *ui.Context) {
mujica.Use(c, mujica.Settings{})
mujica.RangeSlider(c, &low, &high, mujica.RangeSliderOptions{Step: 10, Label: "Price"})
core.Use(c, core.Settings{})
input.RangeSlider(c, &low, &high, input.RangeSliderOptions{Step: 10, Label: "Price"})
}`, Keys: "Tab moves between the two thumbs; arrows move one step; Home and End go as far as the other thumb allows."},
{ID: "036", Name: "Select", ZhName: "选择器", Category: choiceCategory, Demo: selectDemo, Code: `plan := ""
options := []mujica.SelectOption[string]{
options := []input.SelectOption[string]{
{Value: "free", Label: "Free"},
{Value: "pro", Label: "Pro"},
{Value: "team", Label: "Team", Disabled: true},
}
view := func(c *ui.Context) {
mujica.Use(c, mujica.Settings{})
mujica.Select(c, &plan, options, mujica.SelectOptions{Clearable: true, Label: "Plan"})
core.Use(c, core.Settings{})
input.Select(c, &plan, options, input.SelectOptions{Clearable: true, Label: "Plan"})
}`, Keys: "Enter, Space, Up or Down open; arrows, Home, End move; Enter chooses; Escape closes; typing letters on the closed select jumps to a match; Backspace/Delete clear when Clearable."},
{ID: "037", Name: "MultiSelect", ZhName: "多选选择器", Category: choiceCategory, Demo: multiSelectDemo, Code: `tags := []int{1}
options := []mujica.SelectOption[int]{
options := []input.SelectOption[int]{
{Value: 1, Label: "Rose"},
{Value: 2, Label: "Lily"},
{Value: 3, Label: "Iris"},
}
view := func(c *ui.Context) {
mujica.Use(c, mujica.Settings{})
mujica.MultiSelect(c, &tags, options, mujica.MultiSelectOptions{Max: 2, Label: "Flowers"})
core.Use(c, core.Settings{})
input.MultiSelect(c, &tags, options, input.MultiSelectOptions{Max: 2, Label: "Flowers"})
}`, Keys: "Enter, Space or Down open with the search focused; Down enters the list; Up/Down move; Space checks; Escape closes; Backspace on the field removes the last tag."},
{ID: "038", Name: "Combobox", ZhName: "组合框", Category: choiceCategory, Demo: comboboxDemo, Code: `font := ""
fonts := []string{"Garamond", "Georgia", "Didot"}
view := func(c *ui.Context) {
mujica.Use(c, mujica.Settings{})
r := mujica.Combobox(c, &font, fonts, mujica.ComboboxOptions{AllowCustom: true, Label: "Font"})
core.Use(c, core.Settings{})
r := input.Combobox(c, &font, fonts, input.ComboboxOptions{AllowCustom: true, Label: "Font"})
if r.QueryChanged() {
log.Println("search:", r.Query())
}
}`, Keys: "Typing filters; Down opens or moves; Up moves; Enter chooses; Escape closes; Tab leaves (keeping typed text with AllowCustom)."},
{ID: "039", Name: "Cascader", ZhName: "级联选择", Category: choiceCategory, Demo: cascaderDemo, Code: `var path []string
level := func(p []string) mujica.CascaderLevel {
level := func(p []string) input.CascaderLevel {
if len(p) == 0 {
return mujica.CascaderLevel{Nodes: []mujica.CascaderNode{{Value: "fr", Label: "France"}}}
return input.CascaderLevel{Nodes: []input.CascaderNode{{Value: "fr", Label: "France"}}}
}
return mujica.CascaderLevel{Nodes: []mujica.CascaderNode{{Value: "paris", Label: "Paris", Leaf: true}}}
return input.CascaderLevel{Nodes: []input.CascaderNode{{Value: "paris", Label: "Paris", Leaf: true}}}
}
view := func(c *ui.Context) {
mujica.Use(c, mujica.Settings{})
mujica.Cascader(c, &path, mujica.CascaderOptions{Level: level, Label: "City"})
core.Use(c, core.Settings{})
input.Cascader(c, &path, input.CascaderOptions{Level: level, Label: "City"})
}`, Keys: "Enter, Space or Down open; Up/Down move in a column; Right opens a branch; Left goes back; Enter chooses; Escape closes."},
{ID: "040", Name: "TreeSelect", ZhName: "树选择", Category: choiceCategory, Demo: treeSelectDemo, Code: `var roles []string
nodes := []mujica.TreeSelectNode{
{ID: "court", Label: "Court", Children: []mujica.TreeSelectNode{
nodes := []input.TreeSelectNode{
{ID: "court", Label: "Court", Children: []input.TreeSelectNode{
{ID: "king", Label: "King"},
{ID: "queen", Label: "Queen"},
}},
}
view := func(c *ui.Context) {
mujica.Use(c, mujica.Settings{})
mujica.TreeSelect(c, &roles, nodes, mujica.TreeSelectOptions{Multiple: true, Label: "Roles"})
core.Use(c, core.Settings{})
input.TreeSelect(c, &roles, nodes, input.TreeSelectOptions{Multiple: true, Label: "Roles"})
}`, Keys: "Enter, Space or Down open with the search focused; Down enters the tree; Up/Down move; Right expands or enters; Left collapses or goes to the parent; Space checks; Escape closes."},
}
// choiceDemo lays out a demo: parameter controls, then the samples.
func choiceDemo(c *ui.Context, controls, samples func()) {
k := mujica.Tokens(c)
k := core.Tokens(c)
ui.Column(c).Gap(20).AlignItems(ui.Start).Children(func() {
ui.Text(c, "Parameters").Font(theme.SerifFont).FontSize(mujica.FontSize(c, theme.H3Size)).TextColor(k.Text)
ui.Text(c, "Parameters").Font(theme.SerifFont).FontSize(core.FontSize(c, theme.H3Size)).TextColor(k.Text)
ui.Row(c).Gap(20).Wrap().AlignItems(ui.Center).Children(controls)
ui.Box(c).Height(1).FillWidth().Background(k.Ornament)
ui.Text(c, "States").Font(theme.SerifFont).FontSize(mujica.FontSize(c, theme.H3Size)).TextColor(k.Text)
ui.Text(c, "States").Font(theme.SerifFont).FontSize(core.FontSize(c, theme.H3Size)).TextColor(k.Text)
ui.Column(c).Gap(16).AlignItems(ui.Start).Children(samples)
})
}
// choiceFlag is a parameter switch that logs its change.
func choiceFlag(c *ui.Context, on *bool, label string, log func(string)) {
if mujica.Switch(c, on, label, mujica.SwitchOptions{}).Changed() {
if input.Switch(c, on, label, input.SwitchOptions{}).Changed() {
log(fmt.Sprintf("%s = %v", label, *on))
}
}
@@ -123,7 +124,7 @@ func choiceFlag(c *ui.Context, on *bool, label string, log func(string)) {
// choiceLabeled shows a caption above a sample.
func choiceLabeled(c *ui.Context, caption string, fn func()) {
ui.Column(c).Gap(6).Children(func() {
ui.Text(c, caption).FontSize(mujica.FontSize(c, theme.CaptionSize)).TextColor(mujica.Tokens(c).TextMuted)
ui.Text(c, caption).FontSize(core.FontSize(c, theme.CaptionSize)).TextColor(core.Tokens(c).TextMuted)
fn()
})
}
@@ -136,12 +137,12 @@ func switchDemo(c *ui.Context, log func(string)) {
choiceFlag(c, &s.disabled, "Disabled", log)
choiceFlag(c, &s.loading, "Loading", log)
}, func() {
if mujica.Switch(c, &s.wifi, "Wi-Fi", mujica.SwitchOptions{Disabled: s.disabled, Loading: s.loading}).Changed() {
if input.Switch(c, &s.wifi, "Wi-Fi", input.SwitchOptions{Disabled: s.disabled, Loading: s.loading}).Changed() {
log(fmt.Sprintf("Wi-Fi changed: %v", s.wifi))
}
// A caller applying the change: loading for a moment, then done.
at := ui.Local(c.Root(), "031-at", func() time.Time { return time.Time{} })
if mujica.Switch(c, &s.pending, "Backup (saves for 1 s)", mujica.SwitchOptions{Loading: !at.IsZero()}).Changed() {
if input.Switch(c, &s.pending, "Backup (saves for 1 s)", input.SwitchOptions{Loading: !at.IsZero()}).Changed() {
*at = c.Now()
log(fmt.Sprintf("Backup requested: %v", s.pending))
}
@@ -155,8 +156,8 @@ func switchDemo(c *ui.Context, log func(string)) {
}
off, on := false, true
ui.Row(c).Gap(24).Children(func() {
mujica.Switch(c, &off, "Disabled off", mujica.SwitchOptions{Disabled: true})
mujica.Switch(c, &on, "Disabled on", mujica.SwitchOptions{Disabled: true})
input.Switch(c, &off, "Disabled off", input.SwitchOptions{Disabled: true})
input.Switch(c, &on, "Disabled on", input.SwitchOptions{Disabled: true})
})
})
}
@@ -173,12 +174,12 @@ func toggleDemo(c *ui.Context, log func(string)) {
on *bool
label, ico string
}{{&s.bold, "Bold", "bold"}, {&s.italic, "Italic", "italic"}, {&s.underline, "Underline", "underline"}} {
if mujica.Toggle(c, t.on, "", mujica.ToggleOptions{Icon: icons.Must(t.ico), Label: t.label, Disabled: s.disabled}).Changed() {
if input.Toggle(c, t.on, "", input.ToggleOptions{Icon: icons.Must(t.ico), Label: t.label, Disabled: s.disabled}).Changed() {
log(fmt.Sprintf("%s: %v", t.label, *t.on))
}
}
})
if mujica.Toggle(c, &s.bold, "Bold text", mujica.ToggleOptions{Disabled: s.disabled}).Changed() {
if input.Toggle(c, &s.bold, "Bold text", input.ToggleOptions{Disabled: s.disabled}).Changed() {
log(fmt.Sprintf("Bold: %v", s.bold))
}
})
@@ -190,12 +191,12 @@ func toggleGroupDemo(c *ui.Context, log func(string)) {
multiple, allowEmpty, vertical, disabled bool
}
s := *ui.Local(c.Root(), "033", func() *state { return &state{value: []string{"left"}} })
items := []mujica.ToggleItem[string]{
items := []input.ToggleItem[string]{
{Value: "left", Label: "Left"}, {Value: "center", Label: "Center"},
{Value: "right", Label: "Right"}, {Value: "justify", Label: "Justify", Disabled: true},
}
choiceDemo(c, func() {
if mujica.Switch(c, &s.multiple, "Multiple", mujica.SwitchOptions{}).Changed() {
if input.Switch(c, &s.multiple, "Multiple", input.SwitchOptions{}).Changed() {
s.value = s.value[:min(len(s.value), 1)]
log(fmt.Sprintf("Multiple = %v", s.multiple))
}
@@ -203,11 +204,11 @@ func toggleGroupDemo(c *ui.Context, log func(string)) {
choiceFlag(c, &s.vertical, "Vertical", log)
choiceFlag(c, &s.disabled, "Disabled", log)
}, func() {
opts := mujica.ToggleGroupOptions{Multiple: s.multiple, AllowEmpty: s.allowEmpty, Vertical: s.vertical, Disabled: s.disabled, Label: "Align"}
if mujica.ToggleGroup(c, &s.value, items, opts).Changed() {
opts := input.ToggleGroupOptions{Multiple: s.multiple, AllowEmpty: s.allowEmpty, Vertical: s.vertical, Disabled: s.disabled, Label: "Align"}
if input.ToggleGroup(c, &s.value, items, opts).Changed() {
log(fmt.Sprintf("value: %v", s.value))
}
ui.Text(c, fmt.Sprintf("Value: %v", s.value)).TextColor(mujica.Tokens(c).TextMuted)
ui.Text(c, fmt.Sprintf("Value: %v", s.value)).TextColor(core.Tokens(c).TextMuted)
})
}
@@ -222,24 +223,24 @@ func sliderDemo(c *ui.Context, log func(string)) {
choiceFlag(c, &s.showValue, "ShowValue", log)
choiceFlag(c, &s.disabled, "Disabled", log)
choiceLabeled(c, fmt.Sprintf("Step %v", s.step), func() {
if mujica.Slider(c, &s.step, mujica.SliderOptions{Max: 25, Step: 5, Label: "Step"}).Changed() {
if input.Slider(c, &s.step, input.SliderOptions{Max: 25, Step: 5, Label: "Step"}).Changed() {
log(fmt.Sprintf("Step = %v", s.step))
}
})
}, func() {
var marks []mujica.SliderMark
var marks []input.SliderMark
if s.marks {
for v := 0.0; v <= 100; v += 25 {
marks = append(marks, mujica.SliderMark{Value: v, Label: fmt.Sprint(v)})
marks = append(marks, input.SliderMark{Value: v, Label: fmt.Sprint(v)})
}
}
ui.Column(c).Width(360).Children(func() {
opts := mujica.SliderOptions{Step: s.step, Marks: marks, ShowValue: s.showValue, Disabled: s.disabled, Label: "Volume"}
if mujica.Slider(c, &s.value, opts).Changed() {
opts := input.SliderOptions{Step: s.step, Marks: marks, ShowValue: s.showValue, Disabled: s.disabled, Label: "Volume"}
if input.Slider(c, &s.value, opts).Changed() {
log(fmt.Sprintf("Volume: %v", s.value))
}
})
ui.Text(c, fmt.Sprintf("Value: %v", s.value)).TextColor(mujica.Tokens(c).TextMuted)
ui.Text(c, fmt.Sprintf("Value: %v", s.value)).TextColor(core.Tokens(c).TextMuted)
})
}
@@ -254,18 +255,18 @@ func rangeSliderDemo(c *ui.Context, log func(string)) {
choiceFlag(c, &s.showValue, "ShowValue", log)
choiceFlag(c, &s.disabled, "Disabled", log)
}, func() {
var marks []mujica.SliderMark
var marks []input.SliderMark
if s.marks {
marks = []mujica.SliderMark{{Value: 0, Label: "£0"}, {Value: 500, Label: "£500"}, {Value: 1000, Label: "£1000"}}
marks = []input.SliderMark{{Value: 0, Label: "£0"}, {Value: 500, Label: "£500"}, {Value: 1000, Label: "£1000"}}
}
ui.Column(c).Width(360).Children(func() {
opts := mujica.RangeSliderOptions{Max: 1000, Step: 50, Marks: marks, ShowValue: s.showValue, Disabled: s.disabled, Label: "Price",
opts := input.RangeSliderOptions{Max: 1000, Step: 50, Marks: marks, ShowValue: s.showValue, Disabled: s.disabled, Label: "Price",
Format: func(v float64) string { return fmt.Sprintf("£%.0f", v) }}
if mujica.RangeSlider(c, &s.low, &s.high, opts).Changed() {
if input.RangeSlider(c, &s.low, &s.high, opts).Changed() {
log(fmt.Sprintf("Price: %v–%v", s.low, s.high))
}
})
ui.Text(c, fmt.Sprintf("Range: £%.0f – £%.0f", s.low, s.high)).TextColor(mujica.Tokens(c).TextMuted)
ui.Text(c, fmt.Sprintf("Range: £%.0f – £%.0f", s.low, s.high)).TextColor(core.Tokens(c).TextMuted)
})
}
@@ -281,7 +282,7 @@ func choiceDue(c *ui.Context, at time.Time) bool {
return true
}
var choiceFruits = []mujica.SelectOption[string]{
var choiceFruits = []input.SelectOption[string]{
{Value: "apple", Label: "Apple"}, {Value: "banana", Label: "Banana"}, {Value: "blueberry", Label: "Blueberry"},
{Value: "cherry", Label: "Cherry", Disabled: true}, {Value: "date", Label: "Date"}, {Value: "fig", Label: "Fig"},
}
@@ -302,12 +303,12 @@ func selectDemo(c *ui.Context, log func(string)) {
options = nil
}
choiceLabeled(c, "Fruit", func() {
if mujica.Select(c, &s.value, options, mujica.SelectOptions{Clearable: s.clearable, Disabled: s.disabled, Label: "Fruit"}).Changed() {
if input.Select(c, &s.value, options, input.SelectOptions{Clearable: s.clearable, Disabled: s.disabled, Label: "Fruit"}).Changed() {
log(fmt.Sprintf("Fruit: %q", s.value))
}
})
choiceLabeled(c, "Placeholder", func() {
if mujica.Select(c, &s.picked, choiceFruits, mujica.SelectOptions{Placeholder: "Pick a fruit", Clearable: true}).Changed() {
if input.Select(c, &s.picked, choiceFruits, input.SelectOptions{Placeholder: "Pick a fruit", Clearable: true}).Changed() {
log(fmt.Sprintf("Picked: %q", s.picked))
}
})
@@ -329,7 +330,7 @@ func multiSelectDemo(c *ui.Context, log func(string)) {
limit = 3
}
choiceLabeled(c, "Fruits", func() {
r := mujica.MultiSelect(c, &s.values, choiceFruits, mujica.MultiSelectOptions{Max: limit, MaxTags: 2, Disabled: s.disable, Label: "Fruits"})
r := input.MultiSelect(c, &s.values, choiceFruits, input.MultiSelectOptions{Max: limit, MaxTags: 2, Disabled: s.disable, Label: "Fruits"})
r.Element.Width(320)
if r.Changed() {
log(fmt.Sprintf("Fruits: %v", s.values))
@@ -368,7 +369,7 @@ func comboboxDemo(c *ui.Context, log func(string)) {
options, loading = s.results, s.asked != s.query
}
choiceLabeled(c, "Typeface", func() {
r := mujica.Combobox(c, &s.value, options, mujica.ComboboxOptions{AllowCustom: s.custom, ExternalFilter: s.remote, Loading: loading, Label: "Typeface"})
r := input.Combobox(c, &s.value, options, input.ComboboxOptions{AllowCustom: s.custom, ExternalFilter: s.remote, Loading: loading, Label: "Typeface"})
if r.QueryChanged() {
s.query, s.at = r.Query(), c.Now()
log(fmt.Sprintf("query: %q", s.query))
@@ -379,13 +380,13 @@ func comboboxDemo(c *ui.Context, log func(string)) {
})
disabled := "Didot"
choiceLabeled(c, "Disabled", func() {
mujica.Combobox(c, &disabled, choiceFonts, mujica.ComboboxOptions{Disabled: true})
input.Combobox(c, &disabled, choiceFonts, input.ComboboxOptions{Disabled: true})
})
})
}
// choiceAtlas is the cascader demo's pretend server data.
var choiceAtlas = map[string][]mujica.CascaderNode{
var choiceAtlas = map[string][]input.CascaderNode{
"": {{Value: "eu", Label: "Europe"}, {Value: "as", Label: "Asia"}, {Value: "aq", Label: "Antarctica", Disabled: true}},
"eu": {{Value: "fr", Label: "France"}, {Value: "it", Label: "Italy"}},
"as": {{Value: "jp", Label: "Japan"}, {Value: "cn", Label: "China"}},
@@ -398,34 +399,34 @@ var choiceAtlas = map[string][]mujica.CascaderNode{
func cascaderDemo(c *ui.Context, log func(string)) {
type state struct {
path []string
levels map[string]mujica.CascaderLevel
levels map[string]input.CascaderLevel
pending map[string]time.Time
anyLevel, fail, ds bool
}
s := *ui.Local(c.Root(), "039", func() *state {
return &state{levels: map[string]mujica.CascaderLevel{"": {Nodes: choiceAtlas[""]}}, pending: map[string]time.Time{}}
return &state{levels: map[string]input.CascaderLevel{"": {Nodes: choiceAtlas[""]}}, pending: map[string]time.Time{}}
})
for key, at := range s.pending {
if choiceDue(c, at) {
delete(s.pending, key)
if s.fail {
s.levels[key] = mujica.CascaderLevel{Status: mujica.CascaderFailed, Err: errors.New("server busy")}
s.levels[key] = input.CascaderLevel{Status: input.CascaderFailed, Err: errors.New("server busy")}
log(fmt.Sprintf("load %q failed", key))
continue
}
s.levels[key] = mujica.CascaderLevel{Nodes: choiceAtlas[key]}
s.levels[key] = input.CascaderLevel{Nodes: choiceAtlas[key]}
log(fmt.Sprintf("loaded %q", key))
}
}
level := func(p []string) mujica.CascaderLevel {
level := func(p []string) input.CascaderLevel {
if lv, ok := s.levels[strings.Join(p, "/")]; ok {
return lv
}
return mujica.CascaderLevel{Status: mujica.CascaderIdle}
return input.CascaderLevel{Status: input.CascaderIdle}
}
load := func(p []string) {
key := strings.Join(p, "/")
s.levels[key] = mujica.CascaderLevel{Status: mujica.CascaderLoading}
s.levels[key] = input.CascaderLevel{Status: input.CascaderLoading}
s.pending[key] = c.Now()
log(fmt.Sprintf("load %q", key))
}
@@ -435,8 +436,8 @@ func cascaderDemo(c *ui.Context, log func(string)) {
choiceFlag(c, &s.ds, "Disabled", log)
}, func() {
choiceLabeled(c, "City (children load on demand)", func() {
opts := mujica.CascaderOptions{Level: level, Load: load, AnyLevel: s.anyLevel, Disabled: s.ds, Label: "City"}
r := mujica.Cascader(c, &s.path, opts)
opts := input.CascaderOptions{Level: level, Load: load, AnyLevel: s.anyLevel, Disabled: s.ds, Label: "City"}
r := input.Cascader(c, &s.path, opts)
r.Element.Width(320)
if r.Changed() {
log(fmt.Sprintf("path: %v", s.path))
@@ -445,12 +446,12 @@ func cascaderDemo(c *ui.Context, log func(string)) {
})
}
var choiceRoles = []mujica.TreeSelectNode{
{ID: "court", Label: "Court", Children: []mujica.TreeSelectNode{
var choiceRoles = []input.TreeSelectNode{
{ID: "court", Label: "Court", Children: []input.TreeSelectNode{
{ID: "king", Label: "King"}, {ID: "queen", Label: "Queen"}, {ID: "jester", Label: "Jester", Disabled: true},
{ID: "guard", Label: "Royal guard", Children: []mujica.TreeSelectNode{{ID: "captain", Label: "Captain"}, {ID: "archer", Label: "Archer"}}},
{ID: "guard", Label: "Royal guard", Children: []input.TreeSelectNode{{ID: "captain", Label: "Captain"}, {ID: "archer", Label: "Archer"}}},
}},
{ID: "church", Label: "Church", Children: []mujica.TreeSelectNode{{ID: "bishop", Label: "Bishop"}, {ID: "monk", Label: "Monk"}}},
{ID: "church", Label: "Church", Children: []input.TreeSelectNode{{ID: "bishop", Label: "Bishop"}, {ID: "monk", Label: "Monk"}}},
{ID: "merchant", Label: "Merchant"},
}
@@ -461,20 +462,20 @@ func treeSelectDemo(c *ui.Context, log func(string)) {
}
s := *ui.Local(c.Root(), "040", func() *state { return &state{sel: []string{"queen", "bishop"}, multiple: true} })
choiceDemo(c, func() {
if mujica.Switch(c, &s.multiple, "Multiple", mujica.SwitchOptions{}).Changed() {
if input.Switch(c, &s.multiple, "Multiple", input.SwitchOptions{}).Changed() {
s.sel = s.sel[:min(len(s.sel), 1)]
log(fmt.Sprintf("Multiple = %v", s.multiple))
}
choiceFlag(c, &s.independent, "Independent rule", log)
choiceFlag(c, &s.disabled, "Disabled", log)
}, func() {
rule := mujica.TreeSelectCascade
rule := input.TreeSelectCascade
if s.independent {
rule = mujica.TreeSelectIndependent
rule = input.TreeSelectIndependent
}
choiceLabeled(c, "Roles", func() {
opts := mujica.TreeSelectOptions{Multiple: s.multiple, Rule: rule, MaxTags: 2, Disabled: s.disabled, Label: "Roles"}
r := mujica.TreeSelect(c, &s.sel, choiceRoles, opts)
opts := input.TreeSelectOptions{Multiple: s.multiple, Rule: rule, MaxTags: 2, Disabled: s.disabled, Label: "Roles"}
r := input.TreeSelect(c, &s.sel, choiceRoles, opts)
r.Element.Width(320)
if r.Changed() {
log(fmt.Sprintf("Roles: %v", s.sel))
+2 -2
View File
@@ -6,7 +6,7 @@ import (
"github.com/egoist/mygo/ui"
mujica "mujicaui"
"github.com/ZacharyZhang-NY/MujicaUI/core"
)
// choiceDemoTester runs the demo of page id at text scale and returns its tester and event log.
@@ -18,7 +18,7 @@ func choiceDemoTester(t *testing.T, id string, scale float32) (*ui.Tester, *[]st
}
events := &[]string{}
tt := ui.NewTester(func(c *ui.Context) {
mujica.Use(c, mujica.Settings{Mode: mujica.Light})
core.Use(c, core.Settings{Mode: core.Light})
ui.Scroll(c).Fill().Padding(24).Children(func() {
choicePages[i].Demo(c, func(s string) { *events = append(*events, s) })
})
+30 -28
View File
@@ -7,7 +7,9 @@ import (
"github.com/egoist/mygo/ui"
mujica "mujicaui"
"github.com/ZacharyZhang-NY/MujicaUI/code"
"github.com/ZacharyZhang-NY/MujicaUI/core"
"github.com/ZacharyZhang-NY/MujicaUI/input"
)
const codeCategory = "Terminal & Code"
@@ -57,7 +59,7 @@ var codePages = []page{
// codeDemoCheck is a switch that logs its changes.
func codeDemoCheck(c *ui.Context, v *bool, label string, log func(string)) {
if mujica.Switch(c, v, label, mujica.SwitchOptions{}).Changed() {
if input.Switch(c, v, label, input.SwitchOptions{}).Changed() {
log(fmt.Sprintf("%s: %v", label, *v))
}
}
@@ -68,7 +70,7 @@ func codeDemoControls(c *ui.Context, fn func()) {
func codeTerminalDemo(c *ui.Context, log func(string)) {
type state struct {
term mujica.TerminalState
term code.TerminalState
line string
disabled bool
}
@@ -80,13 +82,13 @@ func codeTerminalDemo(c *ui.Context, log func(string)) {
st := *s
codeDemoControls(c, func() {
codeDemoCheck(c, &st.disabled, "Disabled", log)
if mujica.Button(c, "Clear screen", mujica.ButtonOptions{Variant: mujica.Secondary}).Clicked() {
if input.Button(c, "Clear screen", input.ButtonOptions{Variant: input.Secondary}).Clicked() {
st.term.Write("\x1b[2J\x1b[H$ ")
log("clear screen")
}
})
// A tiny echo shell stands in for the PTY.
mujica.Terminal(c, &st.term, mujica.TerminalOptions{Disabled: st.disabled,
code.Terminal(c, &st.term, code.TerminalOptions{Disabled: st.disabled,
Input: func(data string) {
log(fmt.Sprintf("input %q", data))
for _, r := range data {
@@ -128,27 +130,27 @@ func codeShell(line string) string {
func codeTerminalTabsDemo(c *ui.Context, log func(string)) {
type state struct {
tabs []mujica.TerminalTab
tabs []code.TerminalTab
active string
next int
disabled bool
}
s := ui.Local(c.Root(), "322", func() *state {
return &state{tabs: []mujica.TerminalTab{{ID: "1", Title: "zsh"}, {ID: "2", Title: "build", Busy: true}, {ID: "3", Title: "logs"}}, active: "1", next: 4}
return &state{tabs: []code.TerminalTab{{ID: "1", Title: "zsh"}, {ID: "2", Title: "build", Busy: true}, {ID: "3", Title: "logs"}}, active: "1", next: 4}
})
st := *s
codeDemoControls(c, func() {
codeDemoCheck(c, &st.disabled, "Disabled", log)
if mujica.Button(c, "New terminal", mujica.ButtonOptions{Variant: mujica.Secondary}).Clicked() {
if input.Button(c, "New terminal", input.ButtonOptions{Variant: input.Secondary}).Clicked() {
id := fmt.Sprint(st.next)
st.next++
st.tabs = append(st.tabs, mujica.TerminalTab{ID: id, Title: "shell " + id})
st.tabs = append(st.tabs, code.TerminalTab{ID: id, Title: "shell " + id})
st.active = id
log("new " + id)
}
})
r := mujica.TerminalTabs(c, &st.tabs, &st.active, mujica.TerminalTabsOptions{Disabled: st.disabled}, func(id string) {
ui.Text(c, "Terminal "+id+" content").TextColor(mujica.Tokens(c).TextMuted)
r := code.TerminalTabs(c, &st.tabs, &st.active, code.TerminalTabsOptions{Disabled: st.disabled}, func(id string) {
ui.Text(c, "Terminal "+id+" content").TextColor(core.Tokens(c).TextMuted)
})
r.Element.Width(560)
if r.Changed {
@@ -164,8 +166,8 @@ func codeTerminalTabsDemo(c *ui.Context, log func(string)) {
func codeTerminalSearchDemo(c *ui.Context, log func(string)) {
type state struct {
term mujica.TerminalState
search mujica.TerminalSearchState
term code.TerminalState
search code.TerminalSearchState
disabled bool
}
s := ui.Local(c.Root(), "323", func() *state {
@@ -182,62 +184,62 @@ func codeTerminalSearchDemo(c *ui.Context, log func(string)) {
st := *s
codeDemoControls(c, func() { codeDemoCheck(c, &st.disabled, "Disabled", log) })
ui.Column(c).Width(600).Gap(8).Children(func() {
if mujica.TerminalSearch(c, &st.term, &st.search, mujica.TerminalSearchOptions{Disabled: st.disabled}).Changed() {
if code.TerminalSearch(c, &st.term, &st.search, code.TerminalSearchOptions{Disabled: st.disabled}).Changed() {
log(fmt.Sprintf("query %q, match %d", st.search.Query, st.search.Current+1))
}
mujica.Terminal(c, &st.term, mujica.TerminalOptions{}).Height(220)
code.Terminal(c, &st.term, code.TerminalOptions{}).Height(220)
})
}
func codeCommandHistoryDemo(c *ui.Context, log func(string)) {
type state struct {
entries []mujica.HistoryEntry
s mujica.CommandHistoryState
entries []code.HistoryEntry
s code.CommandHistoryState
disabled bool
}
s := ui.Local(c.Root(), "324", func() *state {
at := time.Date(2026, 10, 5, 9, 0, 0, 0, time.UTC)
var e []mujica.HistoryEntry
var e []code.HistoryEntry
for i, cmd := range []string{"git pull", "go build ./...", "go test ./...", "make seal", "ls -la", "git status"} {
e = append(e, mujica.HistoryEntry{Command: cmd, Time: at.Add(time.Duration(i) * time.Minute)})
e = append(e, code.HistoryEntry{Command: cmd, Time: at.Add(time.Duration(i) * time.Minute)})
}
e[3].ExitCode = 2
return &state{entries: e}
})
st := *s
codeDemoControls(c, func() { codeDemoCheck(c, &st.disabled, "Disabled", log) })
r := mujica.CommandHistory(c, st.entries, &st.s, mujica.CommandHistoryOptions{Disabled: st.disabled})
r := code.CommandHistory(c, st.entries, &st.s, code.CommandHistoryOptions{Disabled: st.disabled})
r.Element.Size(480, 300)
if r.Replayed != nil {
log("replay " + r.Replayed.Command)
st.entries = append(st.entries, mujica.HistoryEntry{Command: r.Replayed.Command, Time: time.Now()})
st.entries = append(st.entries, code.HistoryEntry{Command: r.Replayed.Command, Time: time.Now()})
}
}
func codeLogViewerDemo(c *ui.Context, log func(string)) {
type state struct {
lines []mujica.LogLine
s mujica.LogViewerState
lines []code.LogLine
s code.LogViewerState
disabled bool
}
s := ui.Local(c.Root(), "325", func() *state {
at := time.Date(2026, 10, 5, 9, 0, 0, 0, time.UTC)
lines := make([]mujica.LogLine, 100_000)
lines := make([]code.LogLine, 100_000)
words := []string{"ledger synced", "seal applied", "herald dispatched", "archive rotated"}
for i := range lines {
lines[i] = mujica.LogLine{Time: at.Add(time.Duration(i) * time.Second), Level: mujica.LogLevel(i % 7 % 4), Text: fmt.Sprintf("%s #%d", words[i%4], i)}
lines[i] = code.LogLine{Time: at.Add(time.Duration(i) * time.Second), Level: code.LogLevel(i % 7 % 4), Text: fmt.Sprintf("%s #%d", words[i%4], i)}
}
return &state{lines: lines}
})
st := *s
codeDemoControls(c, func() {
codeDemoCheck(c, &st.disabled, "Disabled", log)
if mujica.Button(c, "Append line", mujica.ButtonOptions{Variant: mujica.Secondary}).Clicked() {
st.lines = append(st.lines, mujica.LogLine{Time: time.Now(), Level: mujica.LogWarn, Text: fmt.Sprintf("late seal #%d", len(st.lines))})
if input.Button(c, "Append line", input.ButtonOptions{Variant: input.Secondary}).Clicked() {
st.lines = append(st.lines, code.LogLine{Time: time.Now(), Level: code.LogWarn, Text: fmt.Sprintf("late seal #%d", len(st.lines))})
log("append")
}
})
r := mujica.LogViewer(c, st.lines, &st.s, mujica.LogViewerOptions{Disabled: st.disabled})
r := code.LogViewer(c, st.lines, &st.s, code.LogViewerOptions{Disabled: st.disabled})
r.Element.Size(720, 360)
if r.Changed() {
log(fmt.Sprintf("hidden %v, follow %v", st.s.Hidden, st.s.Follow))
+60 -57
View File
@@ -6,12 +6,15 @@ import (
"github.com/egoist/mygo/ui"
mujica "mujicaui"
"github.com/ZacharyZhang-NY/MujicaUI/code"
"github.com/ZacharyZhang-NY/MujicaUI/core"
"github.com/ZacharyZhang-NY/MujicaUI/data"
"github.com/ZacharyZhang-NY/MujicaUI/input"
)
func codeSymbolDemo(c *ui.Context, log func(string)) {
type state struct {
s mujica.SymbolOutlineState
s code.SymbolOutlineState
cursor float64
disabled bool
}
@@ -20,19 +23,19 @@ func codeSymbolDemo(c *ui.Context, log func(string)) {
codeDemoControls(c, func() {
codeDemoCheck(c, &st.disabled, "Disabled", log)
ui.Text(c, fmt.Sprintf("Cursor line %.0f", st.cursor))
if mujica.Slider(c, &st.cursor, mujica.SliderOptions{Min: 1, Max: 34, Step: 1}).Changed() {
if input.Slider(c, &st.cursor, input.SliderOptions{Min: 1, Max: 34, Step: 1}).Changed() {
log(fmt.Sprintf("cursor %.0f", st.cursor))
}
})
symbols := []mujica.Symbol{
{Name: "Decree", Kind: mujica.SymbolType, Line: 6, EndLine: 9, Detail: "struct", Children: []mujica.Symbol{
{Name: "ID", Kind: mujica.SymbolField, Line: 7, EndLine: 7, Detail: "int"},
{Name: "Title", Kind: mujica.SymbolField, Line: 8, EndLine: 8, Detail: "string"},
symbols := []code.Symbol{
{Name: "Decree", Kind: code.SymbolType, Line: 6, EndLine: 9, Detail: "struct", Children: []code.Symbol{
{Name: "ID", Kind: code.SymbolField, Line: 7, EndLine: 7, Detail: "int"},
{Name: "Title", Kind: code.SymbolField, Line: 8, EndLine: 8, Detail: "string"},
}},
{Name: "Seal", Kind: mujica.SymbolFunction, Line: 13, EndLine: 21, Detail: "(d Decree) (string, error)"},
{Name: "Herald", Kind: mujica.SymbolFunction, Line: 23, EndLine: 31, Detail: "(ds []Decree) int"},
{Name: "Seal", Kind: code.SymbolFunction, Line: 13, EndLine: 21, Detail: "(d Decree) (string, error)"},
{Name: "Herald", Kind: code.SymbolFunction, Line: 23, EndLine: 31, Detail: "(ds []Decree) int"},
}
r := mujica.SymbolOutline(c, symbols, &st.s, mujica.SymbolOutlineOptions{CursorLine: int(st.cursor), Disabled: st.disabled})
r := code.SymbolOutline(c, symbols, &st.s, code.SymbolOutlineOptions{CursorLine: int(st.cursor), Disabled: st.disabled})
r.Element.Size(420, 320)
if r.Jumped != nil {
log(fmt.Sprintf("jump to %s, line %d", r.Jumped.Name, r.Jumped.Line))
@@ -43,33 +46,33 @@ func codeCompletionDemo(c *ui.Context, log func(string)) {
type state struct {
text string
open bool
s mujica.CompletionMenuState
s code.CompletionMenuState
disabled bool
}
s := ui.Local(c.Root(), "334", func() *state { return &state{} })
st := *s
codeDemoControls(c, func() { codeDemoCheck(c, &st.disabled, "Disabled", log) })
all := []mujica.Completion{
{Label: "Seal", Kind: mujica.SymbolFunction, Detail: "func(d Decree) (string, error)", Doc: "Seal stamps a decree with the royal wax."},
{Label: "Search", Kind: mujica.SymbolFunction, Detail: "func(q string) []Decree"},
{Label: "Season", Kind: mujica.SymbolConstant, Detail: "= 4"},
{Label: "Herald", Kind: mujica.SymbolFunction, Detail: "func(ds []Decree) int"},
{Label: "Decree", Kind: mujica.SymbolType, Detail: "struct", Doc: "Decree is an order of the court."},
all := []code.Completion{
{Label: "Seal", Kind: code.SymbolFunction, Detail: "func(d Decree) (string, error)", Doc: "Seal stamps a decree with the royal wax."},
{Label: "Search", Kind: code.SymbolFunction, Detail: "func(q string) []Decree"},
{Label: "Season", Kind: code.SymbolConstant, Detail: "= 4"},
{Label: "Herald", Kind: code.SymbolFunction, Detail: "func(ds []Decree) int"},
{Label: "Decree", Kind: code.SymbolType, Detail: "struct", Doc: "Decree is an order of the court."},
}
word := st.text[strings.LastIndexAny(st.text, " .(")+1:]
var items []mujica.Completion
var items []code.Completion
for _, it := range all {
if word != "" && strings.HasPrefix(strings.ToLower(it.Label), strings.ToLower(word)) {
items = append(items, it)
}
}
ui.Column(c).Width(420).Gap(8).Children(func() {
ui.Text(c, "Type “se” or “de” to see suggestions.").TextColor(mujica.Tokens(c).TextMuted)
in := mujica.TextInput(c, &st.text, mujica.TextInputOptions{Label: "Code", Placeholder: "court.", Disabled: st.disabled})
ui.Text(c, "Type “se” or “de” to see suggestions.").TextColor(core.Tokens(c).TextMuted)
in := input.TextInput(c, &st.text, input.TextInputOptions{Label: "Code", Placeholder: "court.", Disabled: st.disabled})
if in.Changed() {
st.open = len(items) > 0
}
r := mujica.CompletionMenu(c, in.Input, &st.open, items, &st.s, mujica.CompletionMenuOptions{Disabled: st.disabled})
r := code.CompletionMenu(c, in.Input, &st.open, items, &st.s, code.CompletionMenuOptions{Disabled: st.disabled})
if r.Accepted != nil {
st.text = st.text[:len(st.text)-len(word)] + r.Accepted.Label
log("accept " + r.Accepted.Label)
@@ -79,37 +82,37 @@ func codeCompletionDemo(c *ui.Context, log func(string)) {
func codeDebugDemo(c *ui.Context, log func(string)) {
type state struct {
dbg mujica.DebugState
dbg code.DebugState
disabled bool
}
s := ui.Local(c.Root(), "335", func() *state { return &state{dbg: mujica.DebugPaused} })
s := ui.Local(c.Root(), "335", func() *state { return &state{dbg: code.DebugPaused} })
st := *s
names := []string{"Stopped", "Running", "Paused"}
codeDemoControls(c, func() {
codeDemoCheck(c, &st.disabled, "Disabled", log)
ui.Text(c, "State: "+names[st.dbg])
})
a := mujica.DebugToolbar(c, st.dbg, mujica.DebugToolbarOptions{Disabled: st.disabled}).Action
a := code.DebugToolbar(c, st.dbg, code.DebugToolbarOptions{Disabled: st.disabled}).Action
switch a {
case mujica.DebugNone:
case code.DebugNone:
return
case mujica.DebugContinue, mujica.DebugRestart:
st.dbg = mujica.DebugRunning
case mujica.DebugPause, mujica.DebugStepOver, mujica.DebugStepInto, mujica.DebugStepOut:
st.dbg = mujica.DebugPaused
case mujica.DebugStop:
st.dbg = mujica.DebugStopped
case code.DebugContinue, code.DebugRestart:
st.dbg = code.DebugRunning
case code.DebugPause, code.DebugStepOver, code.DebugStepInto, code.DebugStepOut:
st.dbg = code.DebugPaused
case code.DebugStop:
st.dbg = code.DebugStopped
}
log(fmt.Sprintf("action %d, now %s", a, names[st.dbg]))
}
func codeBreakpointDemo(c *ui.Context, log func(string)) {
type state struct {
bps []mujica.Breakpoint
bps []code.Breakpoint
disabled bool
}
s := ui.Local(c.Root(), "336", func() *state {
return &state{bps: []mujica.Breakpoint{
return &state{bps: []code.Breakpoint{
{ID: "1", File: "court/seal.go", Line: 14, Enabled: true},
{ID: "2", File: "court/ledger.go", Line: 40, Condition: "n > 3"},
{ID: "3", File: "main.go", Line: 9, Enabled: true},
@@ -117,7 +120,7 @@ func codeBreakpointDemo(c *ui.Context, log func(string)) {
})
st := *s
codeDemoControls(c, func() { codeDemoCheck(c, &st.disabled, "Disabled", log) })
r := mujica.BreakpointList(c, &st.bps, mujica.BreakpointListOptions{Disabled: st.disabled})
r := code.BreakpointList(c, &st.bps, code.BreakpointListOptions{Disabled: st.disabled})
r.Element.Size(480, 240)
for _, e := range []struct{ what, id string }{{"toggled", r.Toggled}, {"condition", r.Edited}, {"open", r.Opened}} {
if e.id != "" {
@@ -128,21 +131,21 @@ func codeBreakpointDemo(c *ui.Context, log func(string)) {
func codeCallStackDemo(c *ui.Context, log func(string)) {
type state struct {
s mujica.CallStackState
s code.CallStackState
disabled bool
}
s := ui.Local(c.Root(), "337", func() *state { return &state{s: mujica.CallStackState{Current: "f1"}} })
s := ui.Local(c.Root(), "337", func() *state { return &state{s: code.CallStackState{Current: "f1"}} })
st := *s
codeDemoControls(c, func() { codeDemoCheck(c, &st.disabled, "Disabled", log) })
threads := []mujica.DebugThread{
{ID: "t1", Name: "main", Frames: []mujica.StackFrame{
threads := []code.DebugThread{
{ID: "t1", Name: "main", Frames: []code.StackFrame{
{ID: "f1", Name: "court.Seal", File: "court/seal.go", Line: 14},
{ID: "f2", Name: "court.Herald", File: "court/seal.go", Line: 27},
{ID: "f3", Name: "main.main", File: "main.go", Line: 9},
}},
{ID: "t2", Name: "ledger writer", Frames: []mujica.StackFrame{{ID: "f4", Name: "ledger.Flush", File: "ledger/write.go", Line: 51}}},
{ID: "t2", Name: "ledger writer", Frames: []code.StackFrame{{ID: "f4", Name: "ledger.Flush", File: "ledger/write.go", Line: 51}}},
}
r := mujica.CallStack(c, threads, &st.s, mujica.CallStackOptions{Disabled: st.disabled})
r := code.CallStack(c, threads, &st.s, code.CallStackOptions{Disabled: st.disabled})
r.Element.Size(440, 300)
if r.Changed() {
log("frame " + st.s.Current)
@@ -151,13 +154,13 @@ func codeCallStackDemo(c *ui.Context, log func(string)) {
func codeVariablesDemo(c *ui.Context, log func(string)) {
type state struct {
vars []mujica.Variable
s mujica.VariablesPanelState
vars []code.Variable
s code.VariablesPanelState
disabled bool
}
s := ui.Local(c.Root(), "338", func() *state {
return &state{vars: []mujica.Variable{
{Name: "d", Type: "court.Decree", Value: "{…}", Children: []mujica.Variable{
return &state{vars: []code.Variable{
{Name: "d", Type: "court.Decree", Value: "{…}", Children: []code.Variable{
{Name: "ID", Value: "42", Type: "int", Changed: true},
{Name: "Title", Value: `"Of seals"`, Type: "string"},
}},
@@ -167,7 +170,7 @@ func codeVariablesDemo(c *ui.Context, log func(string)) {
})
st := *s
codeDemoControls(c, func() { codeDemoCheck(c, &st.disabled, "Disabled", log) })
r := mujica.VariablesPanel(c, st.vars, &st.s, mujica.VariablesPanelOptions{Disabled: st.disabled})
r := code.VariablesPanel(c, st.vars, &st.s, code.VariablesPanelOptions{Disabled: st.disabled})
r.Element.Size(440, 260)
if r.Edited != nil {
log(fmt.Sprintf("set %s = %s", r.Edited.Path, r.Edited.Value))
@@ -176,7 +179,7 @@ func codeVariablesDemo(c *ui.Context, log func(string)) {
}
// codeDemoSet writes value at the dotted path.
func codeDemoSet(vars []mujica.Variable, path []string, value string) {
func codeDemoSet(vars []code.Variable, path []string, value string) {
for i := range vars {
if vars[i].Name != path[0] {
continue
@@ -191,14 +194,14 @@ func codeDemoSet(vars []mujica.Variable, path []string, value string) {
func codeHexDemo(c *ui.Context, log func(string)) {
type state struct {
s mujica.HexViewerState
s code.HexViewerState
disabled bool
}
s := ui.Local(c.Root(), "339", func() *state { return &state{} })
st := *s
codeDemoControls(c, func() { codeDemoCheck(c, &st.disabled, "Disabled", log) })
data := []byte(strings.Join(codeDemoSource, "\n"))
r := mujica.HexViewer(c, data, &st.s, mujica.HexViewerOptions{Disabled: st.disabled})
r := code.HexViewer(c, data, &st.s, code.HexViewerOptions{Disabled: st.disabled})
r.Element.Size(720, 300)
if r.Changed() {
a, b := st.s.Selection()
@@ -208,7 +211,7 @@ func codeHexDemo(c *ui.Context, log func(string)) {
func codeFlamegraphDemo(c *ui.Context, log func(string)) {
type state struct {
s mujica.FlamegraphState
s code.FlamegraphState
status string
disabled bool
}
@@ -216,22 +219,22 @@ func codeFlamegraphDemo(c *ui.Context, log func(string)) {
st := *s
codeDemoControls(c, func() {
codeDemoCheck(c, &st.disabled, "Disabled", log)
opts := []mujica.SelectOption[string]{{Value: "Ready", Label: "Ready"}, {Value: "Loading", Label: "Loading"}, {Value: "Failed", Label: "Failed"}}
if mujica.Select(c, &st.status, opts, mujica.SelectOptions{Label: "Status"}).Changed() {
opts := []input.SelectOption[string]{{Value: "Ready", Label: "Ready"}, {Value: "Loading", Label: "Loading"}, {Value: "Failed", Label: "Failed"}}
if input.Select(c, &st.status, opts, input.SelectOptions{Label: "Status"}).Changed() {
log("status " + st.status)
}
})
root := &mujica.FlameNode{Name: "main", Value: 240, Children: []mujica.FlameNode{
{Name: "court.Herald", Value: 150, Children: []mujica.FlameNode{
{Name: "court.Seal", Value: 110, Children: []mujica.FlameNode{{Name: "wax.Melt", Value: 70}, {Name: "ink.Dry", Value: 25}}},
root := &code.FlameNode{Name: "main", Value: 240, Children: []code.FlameNode{
{Name: "court.Herald", Value: 150, Children: []code.FlameNode{
{Name: "court.Seal", Value: 110, Children: []code.FlameNode{{Name: "wax.Melt", Value: 70}, {Name: "ink.Dry", Value: 25}}},
{Name: "ledger.Write", Value: 30},
}},
{Name: "archive.Load", Value: 60, Children: []mujica.FlameNode{{Name: "json.Decode", Value: 45}}},
{Name: "archive.Load", Value: 60, Children: []code.FlameNode{{Name: "json.Decode", Value: 45}}},
{Name: "gc", Value: 20},
}}
status := map[string]mujica.DataStatus{"Ready": mujica.DataReady, "Loading": mujica.DataLoading, "Failed": mujica.DataFailed}[st.status]
status := map[string]data.DataStatus{"Ready": data.DataReady, "Loading": data.DataLoading, "Failed": data.DataFailed}[st.status]
ui.Column(c).Width(680).Children(func() {
if mujica.Flamegraph(c, root, &st.s, mujica.FlamegraphOptions{Unit: "ms", Status: status, Error: "Profile could not be read", Disabled: st.disabled}).Changed() {
if code.Flamegraph(c, root, &st.s, code.FlamegraphOptions{Unit: "ms", Status: status, Error: "Profile could not be read", Disabled: st.disabled}).Changed() {
log(fmt.Sprintf("zoom %v focus %v", st.s.Zoom, st.s.Focus))
}
})
+79 -79
View File
@@ -2,22 +2,22 @@ package main
// Snippets are the bodies of the Example functions in example_code_test.go.
const codeTerminalCode = `var term mujica.TerminalState
const codeTerminalCode = `var term code.TerminalState
term.Write("\x1b[1m~/court\x1b[0m $ ls\r\n\x1b[34mdecrees\x1b[0m ledger.txt\r\n$ ")
view := func(c *ui.Context) {
mujica.Use(c, mujica.Settings{})
mujica.Terminal(c, &term, mujica.TerminalOptions{
core.Use(c, core.Settings{})
code.Terminal(c, &term, code.TerminalOptions{
Input: func(data string) { fmt.Printf("to pty: %q\n", data) },
Resized: func(cols, rows int) { fmt.Println("pty size", cols, rows) },
}).Size(380, 200)
}
ui.Render(view, 400, 300, 1)`
const codeTerminalTabsCode = `tabs := []mujica.TerminalTab{{ID: "a", Title: "zsh"}, {ID: "b", Title: "build", Busy: true}}
const codeTerminalTabsCode = `tabs := []code.TerminalTab{{ID: "a", Title: "zsh"}, {ID: "b", Title: "build", Busy: true}}
active := "a"
view := func(c *ui.Context) {
mujica.Use(c, mujica.Settings{})
r := mujica.TerminalTabs(c, &tabs, &active, mujica.TerminalTabsOptions{}, func(id string) {
core.Use(c, core.Settings{})
r := code.TerminalTabs(c, &tabs, &active, code.TerminalTabsOptions{}, func(id string) {
ui.Text(c, "terminal "+id)
})
if r.Closed != "" {
@@ -26,23 +26,23 @@ view := func(c *ui.Context) {
}
ui.Render(view, 400, 300, 1)`
const codeTerminalSearchCode = `var term mujica.TerminalState
const codeTerminalSearchCode = `var term code.TerminalState
term.Write("build ok\r\nerror: missing seal\r\n")
var search mujica.TerminalSearchState
var search code.TerminalSearchState
view := func(c *ui.Context) {
mujica.Use(c, mujica.Settings{})
core.Use(c, core.Settings{})
ui.Column(c).Gap(8).Children(func() {
mujica.TerminalSearch(c, &term, &search, mujica.TerminalSearchOptions{})
mujica.Terminal(c, &term, mujica.TerminalOptions{}).Height(160)
code.TerminalSearch(c, &term, &search, code.TerminalSearchOptions{})
code.Terminal(c, &term, code.TerminalOptions{}).Height(160)
})
}
ui.Render(view, 400, 300, 1)`
const codeCommandHistoryCode = `history := []mujica.HistoryEntry{{Command: "git status"}, {Command: "go test ./...", ExitCode: 1}}
var state mujica.CommandHistoryState
const codeCommandHistoryCode = `history := []code.HistoryEntry{{Command: "git status"}, {Command: "go test ./...", ExitCode: 1}}
var state code.CommandHistoryState
view := func(c *ui.Context) {
mujica.Use(c, mujica.Settings{})
r := mujica.CommandHistory(c, history, &state, mujica.CommandHistoryOptions{})
core.Use(c, core.Settings{})
r := code.CommandHistory(c, history, &state, code.CommandHistoryOptions{})
r.Element.Height(240)
if r.Replayed != nil {
fmt.Println("run", r.Replayed.Command)
@@ -50,19 +50,19 @@ view := func(c *ui.Context) {
}
ui.Render(view, 400, 300, 1)`
const codeLogViewerCode = `lines := []mujica.LogLine{{Level: mujica.LogInfo, Text: "court opened"}, {Level: mujica.LogError, Text: "seal missing"}}
var state mujica.LogViewerState
const codeLogViewerCode = `lines := []code.LogLine{{Level: code.LogInfo, Text: "court opened"}, {Level: code.LogError, Text: "seal missing"}}
var state code.LogViewerState
view := func(c *ui.Context) {
mujica.Use(c, mujica.Settings{})
mujica.LogViewer(c, lines, &state, mujica.LogViewerOptions{}).Element.Height(240)
core.Use(c, core.Settings{})
code.LogViewer(c, lines, &state, code.LogViewerOptions{}).Element.Height(240)
}
ui.Render(view, 400, 300, 1)`
const codeProcessListCode = `procs := []mujica.Process{{PID: 7, Name: "archivist", CPU: 42, Memory: 50 << 20}, {PID: 9, Name: "herald", CPU: 3, Memory: 8 << 20}}
var state mujica.ProcessListState
const codeProcessListCode = `procs := []code.Process{{PID: 7, Name: "archivist", CPU: 42, Memory: 50 << 20}, {PID: 9, Name: "herald", CPU: 3, Memory: 8 << 20}}
var state code.ProcessListState
view := func(c *ui.Context) {
mujica.Use(c, mujica.Settings{})
r := mujica.ProcessList(c, procs, &state, mujica.ProcessListOptions{})
core.Use(c, core.Settings{})
r := code.ProcessList(c, procs, &state, code.ProcessListOptions{})
r.Element.Height(240)
if r.End != nil {
fmt.Println("kill", r.End.PID)
@@ -71,38 +71,38 @@ view := func(c *ui.Context) {
ui.Render(view, 400, 300, 1)`
const codeCodeViewerCode = `lines := []string{"func Seal(d string) int {", "\treturn len(d)", "}"}
var state mujica.CodeViewerState
var state code.CodeViewerState
view := func(c *ui.Context) {
mujica.Use(c, mujica.Settings{})
mujica.CodeViewer(c, lines, &state, mujica.CodeViewerOptions{Language: "go"}).Element.Height(240)
core.Use(c, core.Settings{})
code.CodeViewer(c, lines, &state, code.CodeViewerOptions{Language: "go"}).Element.Height(240)
}
ui.Render(view, 400, 300, 1)`
const codeMinimapCode = `lines := []string{"package court", "", "func Seal() {}", "func Herald() {}"}
view := mujica.MinimapView{Top: 0, Visible: 2}
view := code.MinimapView{Top: 0, Visible: 2}
draw := func(c *ui.Context) {
mujica.Use(c, mujica.Settings{})
if mujica.Minimap(c, lines, &view, mujica.MinimapOptions{Language: "go"}).Changed() {
core.Use(c, core.Settings{})
if code.Minimap(c, lines, &view, code.MinimapOptions{Language: "go"}).Changed() {
fmt.Println("scroll editor to", view.Top)
}
}
ui.Render(draw, 400, 300, 1)`
const codeFindWidgetCode = `text := "the seal and the seal"
state := mujica.FindState{Query: "seal", Replacement: "signet", ShowReplace: true}
state := code.FindState{Query: "seal", Replacement: "signet", ShowReplace: true}
view := func(c *ui.Context) {
mujica.Use(c, mujica.Settings{})
if mujica.FindWidget(c, &text, &state, mujica.FindWidgetOptions{}).Replaced > 0 {
core.Use(c, core.Settings{})
if code.FindWidget(c, &text, &state, code.FindWidgetOptions{}).Replaced > 0 {
fmt.Println(text)
}
}
ui.Render(view, 400, 300, 1)`
const codeSearchPanelCode = `results := []mujica.SearchFileResult{{Path: "court/seal.go", Matches: []mujica.SearchMatch{{Line: 12, Text: "func Seal() {", Start: 5, End: 9}}}}
state := mujica.SearchPanelState{Query: "seal"}
const codeSearchPanelCode = `results := []code.SearchFileResult{{Path: "court/seal.go", Matches: []code.SearchMatch{{Line: 12, Text: "func Seal() {", Start: 5, End: 9}}}}
state := code.SearchPanelState{Query: "seal"}
view := func(c *ui.Context) {
mujica.Use(c, mujica.Settings{})
r := mujica.SearchPanel(c, results, &state, mujica.SearchPanelOptions{})
core.Use(c, core.Settings{})
r := code.SearchPanel(c, results, &state, code.SearchPanelOptions{})
r.Element.Height(280)
if r.Opened != nil {
fmt.Println("open", r.Opened.Path, r.Opened.Line)
@@ -110,11 +110,11 @@ view := func(c *ui.Context) {
}
ui.Render(view, 400, 300, 1)`
const codeProblemsPanelCode = `problems := []mujica.Problem{{Severity: mujica.ProblemError, File: "court/seal.go", Line: 12, Col: 5, Message: "undefined: Decree"}}
var state mujica.ProblemsPanelState
const codeProblemsPanelCode = `problems := []code.Problem{{Severity: code.ProblemError, File: "court/seal.go", Line: 12, Col: 5, Message: "undefined: Decree"}}
var state code.ProblemsPanelState
view := func(c *ui.Context) {
mujica.Use(c, mujica.Settings{})
r := mujica.ProblemsPanel(c, problems, &state, mujica.ProblemsPanelOptions{})
core.Use(c, core.Settings{})
r := code.ProblemsPanel(c, problems, &state, code.ProblemsPanelOptions{})
r.Element.Height(240)
if r.Opened != nil {
fmt.Println("go to", r.Opened.File, r.Opened.Line)
@@ -122,11 +122,11 @@ view := func(c *ui.Context) {
}
ui.Render(view, 400, 300, 1)`
const codeOutputPanelCode = `channels := []mujica.OutputChannel{{Name: "Build", Lines: []string{"compiling court", "\x1b[32mok\x1b[0m"}}}
var state mujica.OutputPanelState
const codeOutputPanelCode = `channels := []code.OutputChannel{{Name: "Build", Lines: []string{"compiling court", "\x1b[32mok\x1b[0m"}}}
var state code.OutputPanelState
view := func(c *ui.Context) {
mujica.Use(c, mujica.Settings{})
r := mujica.OutputPanel(c, channels, &state, mujica.OutputPanelOptions{})
core.Use(c, core.Settings{})
r := code.OutputPanel(c, channels, &state, code.OutputPanelOptions{})
r.Element.Height(240)
if r.Cleared != "" {
channels[state.Channel].Lines = nil
@@ -134,13 +134,13 @@ view := func(c *ui.Context) {
}
ui.Render(view, 400, 300, 1)`
const codeSymbolOutlineCode = `symbols := []mujica.Symbol{{Name: "Court", Kind: mujica.SymbolType, Line: 3, EndLine: 40, Children: []mujica.Symbol{
{Name: "Seal", Kind: mujica.SymbolMethod, Line: 10, EndLine: 20},
const codeSymbolOutlineCode = `symbols := []code.Symbol{{Name: "Court", Kind: code.SymbolType, Line: 3, EndLine: 40, Children: []code.Symbol{
{Name: "Seal", Kind: code.SymbolMethod, Line: 10, EndLine: 20},
}}}
var state mujica.SymbolOutlineState
var state code.SymbolOutlineState
view := func(c *ui.Context) {
mujica.Use(c, mujica.Settings{})
r := mujica.SymbolOutline(c, symbols, &state, mujica.SymbolOutlineOptions{CursorLine: 12})
core.Use(c, core.Settings{})
r := code.SymbolOutline(c, symbols, &state, code.SymbolOutlineOptions{CursorLine: 12})
r.Element.Height(240)
if r.Jumped != nil {
fmt.Println("go to line", r.Jumped.Line)
@@ -150,33 +150,33 @@ ui.Render(view, 400, 300, 1)`
const codeCompletionMenuCode = `text := "se"
open := true
items := []mujica.Completion{{Label: "Seal", Kind: mujica.SymbolMethod, Detail: "func()"}, {Label: "Search", Kind: mujica.SymbolFunction}}
var state mujica.CompletionMenuState
items := []code.Completion{{Label: "Seal", Kind: code.SymbolMethod, Detail: "func()"}, {Label: "Search", Kind: code.SymbolFunction}}
var state code.CompletionMenuState
view := func(c *ui.Context) {
mujica.Use(c, mujica.Settings{})
in := mujica.TextInput(c, &text, mujica.TextInputOptions{Label: "Code"})
if r := mujica.CompletionMenu(c, in.Input, &open, items, &state, mujica.CompletionMenuOptions{}); r.Accepted != nil {
core.Use(c, core.Settings{})
in := input.TextInput(c, &text, input.TextInputOptions{Label: "Code"})
if r := code.CompletionMenu(c, in.Input, &open, items, &state, code.CompletionMenuOptions{}); r.Accepted != nil {
text = r.Accepted.Label
}
}
ui.Render(view, 400, 300, 1)`
const codeDebugToolbarCode = `state := mujica.DebugPaused
const codeDebugToolbarCode = `state := code.DebugPaused
view := func(c *ui.Context) {
mujica.Use(c, mujica.Settings{})
switch mujica.DebugToolbar(c, state, mujica.DebugToolbarOptions{}).Action {
case mujica.DebugContinue:
state = mujica.DebugRunning
case mujica.DebugStop:
state = mujica.DebugStopped
core.Use(c, core.Settings{})
switch code.DebugToolbar(c, state, code.DebugToolbarOptions{}).Action {
case code.DebugContinue:
state = code.DebugRunning
case code.DebugStop:
state = code.DebugStopped
}
}
ui.Render(view, 400, 300, 1)`
const codeBreakpointListCode = `bps := []mujica.Breakpoint{{ID: "a", File: "court/seal.go", Line: 12, Enabled: true}, {ID: "b", File: "main.go", Line: 9, Condition: "n > 3"}}
const codeBreakpointListCode = `bps := []code.Breakpoint{{ID: "a", File: "court/seal.go", Line: 12, Enabled: true}, {ID: "b", File: "main.go", Line: 9, Condition: "n > 3"}}
view := func(c *ui.Context) {
mujica.Use(c, mujica.Settings{})
r := mujica.BreakpointList(c, &bps, mujica.BreakpointListOptions{})
core.Use(c, core.Settings{})
r := code.BreakpointList(c, &bps, code.BreakpointListOptions{})
r.Element.Height(200)
if r.Toggled != "" || r.Edited != "" {
fmt.Println("sync breakpoints with the debugger")
@@ -184,14 +184,14 @@ view := func(c *ui.Context) {
}
ui.Render(view, 400, 300, 1)`
const codeCallStackCode = `threads := []mujica.DebugThread{{ID: "t1", Name: "main", Frames: []mujica.StackFrame{
const codeCallStackCode = `threads := []code.DebugThread{{ID: "t1", Name: "main", Frames: []code.StackFrame{
{ID: "f1", Name: "court.Seal", File: "court/seal.go", Line: 14},
{ID: "f2", Name: "main.main", File: "main.go", Line: 9},
}}}
state := mujica.CallStackState{Current: "f1"}
state := code.CallStackState{Current: "f1"}
view := func(c *ui.Context) {
mujica.Use(c, mujica.Settings{})
r := mujica.CallStack(c, threads, &state, mujica.CallStackOptions{})
core.Use(c, core.Settings{})
r := code.CallStack(c, threads, &state, code.CallStackOptions{})
r.Element.Height(200)
if r.Changed() {
fmt.Println("show frame", state.Current)
@@ -199,11 +199,11 @@ view := func(c *ui.Context) {
}
ui.Render(view, 400, 300, 1)`
const codeVariablesPanelCode = `vars := []mujica.Variable{{Name: "decree", Value: "{…}", Children: []mujica.Variable{{Name: "id", Value: "42", Changed: true}}}, {Name: "count", Value: "7"}}
var state mujica.VariablesPanelState
const codeVariablesPanelCode = `vars := []code.Variable{{Name: "decree", Value: "{…}", Children: []code.Variable{{Name: "id", Value: "42", Changed: true}}}, {Name: "count", Value: "7"}}
var state code.VariablesPanelState
view := func(c *ui.Context) {
mujica.Use(c, mujica.Settings{})
r := mujica.VariablesPanel(c, vars, &state, mujica.VariablesPanelOptions{})
core.Use(c, core.Settings{})
r := code.VariablesPanel(c, vars, &state, code.VariablesPanelOptions{})
r.Element.Height(200)
if r.Edited != nil {
fmt.Println("set", r.Edited.Path, "=", r.Edited.Value)
@@ -212,19 +212,19 @@ view := func(c *ui.Context) {
ui.Render(view, 400, 300, 1)`
const codeHexViewerCode = `data := []byte("MUJICA court ledger\x00\x01")
var state mujica.HexViewerState
var state code.HexViewerState
view := func(c *ui.Context) {
mujica.Use(c, mujica.Settings{})
mujica.HexViewer(c, data, &state, mujica.HexViewerOptions{}).Element.Height(160)
core.Use(c, core.Settings{})
code.HexViewer(c, data, &state, code.HexViewerOptions{}).Element.Height(160)
}
ui.Render(view, 400, 300, 1)`
const codeFlamegraphCode = `root := &mujica.FlameNode{Name: "main", Value: 100, Children: []mujica.FlameNode{
const codeFlamegraphCode = `root := &code.FlameNode{Name: "main", Value: 100, Children: []code.FlameNode{
{Name: "court.Seal", Value: 60}, {Name: "ledger.Write", Value: 30},
}}
var state mujica.FlamegraphState
var state code.FlamegraphState
view := func(c *ui.Context) {
mujica.Use(c, mujica.Settings{})
mujica.Flamegraph(c, root, &state, mujica.FlamegraphOptions{Unit: "ms"})
core.Use(c, core.Settings{})
code.Flamegraph(c, root, &state, code.FlamegraphOptions{Unit: "ms"})
}
ui.Render(view, 400, 300, 1)`
+42 -40
View File
@@ -6,28 +6,30 @@ import (
"github.com/egoist/mygo/ui"
mujica "mujicaui"
"github.com/ZacharyZhang-NY/MujicaUI/code"
"github.com/ZacharyZhang-NY/MujicaUI/core"
"github.com/ZacharyZhang-NY/MujicaUI/input"
)
func codeProcessListDemo(c *ui.Context, log func(string)) {
type state struct {
procs []mujica.Process
s mujica.ProcessListState
procs []code.Process
s code.ProcessListState
disabled bool
}
s := ui.Local(c.Root(), "326", func() *state {
return &state{procs: []mujica.Process{
return &state{procs: []code.Process{
{PID: 101, Name: "chancellor", CPU: 3.5, Memory: 200 << 20}, {PID: 7, Name: "archivist", CPU: 42.1, Memory: 50 << 20},
{PID: 330, Name: "herald", CPU: 0.2, Memory: 2 << 30}, {PID: 412, Name: "scribe", CPU: 12.8, Memory: 96 << 20},
}}
})
st := *s
codeDemoControls(c, func() { codeDemoCheck(c, &st.disabled, "Disabled", log) })
r := mujica.ProcessList(c, st.procs, &st.s, mujica.ProcessListOptions{Disabled: st.disabled})
r := code.ProcessList(c, st.procs, &st.s, code.ProcessListOptions{Disabled: st.disabled})
r.Element.Size(560, 260)
if r.End != nil {
log(fmt.Sprintf("end %s (PID %d)", r.End.Name, r.End.PID))
var kept []mujica.Process
var kept []code.Process
for _, p := range st.procs {
if p.PID != r.End.PID {
kept = append(kept, p)
@@ -71,7 +73,7 @@ func Herald(ds []Decree) int {
func codeCodeViewerDemo(c *ui.Context, log func(string)) {
type state struct {
s mujica.CodeViewerState
s code.CodeViewerState
lang string
disabled bool
}
@@ -79,16 +81,16 @@ func codeCodeViewerDemo(c *ui.Context, log func(string)) {
st := *s
codeDemoControls(c, func() {
codeDemoCheck(c, &st.disabled, "Disabled", log)
langs := []mujica.SelectOption[string]{{Value: "go", Label: "Go"}, {Value: "", Label: "Plain"}}
if mujica.Select(c, &st.lang, langs, mujica.SelectOptions{Label: "Language"}).Changed() {
langs := []input.SelectOption[string]{{Value: "go", Label: "Go"}, {Value: "", Label: "Plain"}}
if input.Select(c, &st.lang, langs, input.SelectOptions{Label: "Language"}).Changed() {
log("language " + st.lang)
}
if mujica.Button(c, "Go to line 20", mujica.ButtonOptions{Variant: mujica.Secondary}).Clicked() {
if input.Button(c, "Go to line 20", input.ButtonOptions{Variant: input.Secondary}).Clicked() {
st.s.GoTo(19)
log("go to 20")
}
})
r := mujica.CodeViewer(c, codeDemoSource, &st.s, mujica.CodeViewerOptions{Language: st.lang, Label: "Source", Disabled: st.disabled})
r := code.CodeViewer(c, codeDemoSource, &st.s, code.CodeViewerOptions{Language: st.lang, Label: "Source", Disabled: st.disabled})
r.Element.Size(600, 360)
if r.Changed() {
log(fmt.Sprintf("line %d", st.s.Line()+1))
@@ -97,8 +99,8 @@ func codeCodeViewerDemo(c *ui.Context, log func(string)) {
func codeMinimapDemo(c *ui.Context, log func(string)) {
type state struct {
s mujica.CodeViewerState
view mujica.MinimapView
s code.CodeViewerState
view code.MinimapView
lines []string
disabled bool
}
@@ -112,10 +114,10 @@ func codeMinimapDemo(c *ui.Context, log func(string)) {
st := *s
codeDemoControls(c, func() { codeDemoCheck(c, &st.disabled, "Disabled", log) })
first, last := st.s.Visible()
st.view = mujica.MinimapView{Top: first, Visible: max(last-first+1, 1)}
st.view = code.MinimapView{Top: first, Visible: max(last-first+1, 1)}
ui.Row(c).Gap(8).Height(360).AlignItems(ui.Stretch).Children(func() {
mujica.CodeViewer(c, st.lines, &st.s, mujica.CodeViewerOptions{Language: "go"}).Element.Width(520)
if mujica.Minimap(c, st.lines, &st.view, mujica.MinimapOptions{Language: "go", Disabled: st.disabled}).Changed() {
code.CodeViewer(c, st.lines, &st.s, code.CodeViewerOptions{Language: "go"}).Element.Width(520)
if code.Minimap(c, st.lines, &st.view, code.MinimapOptions{Language: "go", Disabled: st.disabled}).Changed() {
st.s.ScrollTo(st.view.Top)
log(fmt.Sprintf("jump to line %d", st.view.Top+1))
}
@@ -125,11 +127,11 @@ func codeMinimapDemo(c *ui.Context, log func(string)) {
func codeFindWidgetDemo(c *ui.Context, log func(string)) {
type state struct {
text string
s mujica.FindState
s code.FindState
readOnly, disabled bool
}
s := ui.Local(c.Root(), "329", func() *state {
return &state{text: "The seal of the court; seals are kept sealed. Seal 42 and seal 7.", s: mujica.FindState{Query: "seal"}}
return &state{text: "The seal of the court; seals are kept sealed. Seal 42 and seal 7.", s: code.FindState{Query: "seal"}}
})
st := *s
codeDemoControls(c, func() {
@@ -137,7 +139,7 @@ func codeFindWidgetDemo(c *ui.Context, log func(string)) {
codeDemoCheck(c, &st.disabled, "Disabled", log)
})
ui.Column(c).Width(620).Gap(12).Children(func() {
r := mujica.FindWidget(c, &st.text, &st.s, mujica.FindWidgetOptions{ReadOnly: st.readOnly, Disabled: st.disabled})
r := code.FindWidget(c, &st.text, &st.s, code.FindWidgetOptions{ReadOnly: st.readOnly, Disabled: st.disabled})
if r.Moved {
log(fmt.Sprintf("%d matches, at %d", len(st.s.Matches()), st.s.Current+1))
}
@@ -148,7 +150,7 @@ func codeFindWidgetDemo(c *ui.Context, log func(string)) {
at := 0
for i, m := range st.s.Matches() {
spans = append(spans, ui.Span{Text: st.text[at:m[0]]})
sp := ui.Span{Text: st.text[m[0]:m[1]], Background: mujica.Tokens(c).Selection, Underline: true}
sp := ui.Span{Text: st.text[m[0]:m[1]], Background: core.Tokens(c).Selection, Underline: true}
if i == st.s.Current {
sp.Weight = 700
}
@@ -162,11 +164,11 @@ func codeFindWidgetDemo(c *ui.Context, log func(string)) {
func codeSearchPanelDemo(c *ui.Context, log func(string)) {
type state struct {
s mujica.SearchPanelState
results []mujica.SearchFileResult
s code.SearchPanelState
results []code.SearchFileResult
disabled bool
}
s := ui.Local(c.Root(), "330", func() *state { return &state{s: mujica.SearchPanelState{Query: "seal"}} })
s := ui.Local(c.Root(), "330", func() *state { return &state{s: code.SearchPanelState{Query: "seal"}} })
st := *s
codeDemoControls(c, func() { codeDemoCheck(c, &st.disabled, "Disabled", log) })
// The demo searches its own two files.
@@ -174,7 +176,7 @@ func codeSearchPanelDemo(c *ui.Context, log func(string)) {
if st.results == nil || st.s.Query != "" && len(st.results) == 0 {
st.results = codeDemoSearch(files, st.s.Query, st.s.Include, st.s.Exclude)
}
r := mujica.SearchPanel(c, st.results, &st.s, mujica.SearchPanelOptions{Disabled: st.disabled})
r := code.SearchPanel(c, st.results, &st.s, code.SearchPanelOptions{Disabled: st.disabled})
r.Element.Size(560, 380)
if r.Changed {
st.results = codeDemoSearch(files, st.s.Query, st.s.Include, st.s.Exclude)
@@ -186,8 +188,8 @@ func codeSearchPanelDemo(c *ui.Context, log func(string)) {
}
// codeDemoSearch finds q in the demo files.
func codeDemoSearch(files map[string][]string, q, include, exclude string) []mujica.SearchFileResult {
out := []mujica.SearchFileResult{}
func codeDemoSearch(files map[string][]string, q, include, exclude string) []code.SearchFileResult {
out := []code.SearchFileResult{}
if q == "" {
return out
}
@@ -195,10 +197,10 @@ func codeDemoSearch(files map[string][]string, q, include, exclude string) []muj
if include != "" && !strings.Contains(path, strings.Trim(include, "*")) || exclude != "" && strings.Contains(path, strings.Trim(exclude, "*")) {
continue
}
f := mujica.SearchFileResult{Path: path}
f := code.SearchFileResult{Path: path}
for i, line := range files[path] {
if at := strings.Index(strings.ToLower(line), strings.ToLower(q)); at >= 0 {
f.Matches = append(f.Matches, mujica.SearchMatch{Line: i + 1, Text: line, Start: at, End: at + len(q)})
f.Matches = append(f.Matches, code.SearchMatch{Line: i + 1, Text: line, Start: at, End: at + len(q)})
}
}
if len(f.Matches) > 0 {
@@ -210,19 +212,19 @@ func codeDemoSearch(files map[string][]string, q, include, exclude string) []muj
func codeProblemsDemo(c *ui.Context, log func(string)) {
type state struct {
s mujica.ProblemsPanelState
s code.ProblemsPanelState
disabled bool
}
s := ui.Local(c.Root(), "331", func() *state { return &state{} })
st := *s
codeDemoControls(c, func() { codeDemoCheck(c, &st.disabled, "Disabled", log) })
problems := []mujica.Problem{
{Severity: mujica.ProblemError, File: "court/seal.go", Line: 12, Col: 5, Message: "undefined: Decree", Source: "compiler"},
{Severity: mujica.ProblemWarning, File: "court/seal.go", Line: 30, Col: 2, Message: "unused variable wax", Source: "vet"},
{Severity: mujica.ProblemInfo, File: "docs/ledger.md", Line: 4, Col: 1, Message: "line is longer than 80", Source: "lint"},
{Severity: mujica.ProblemError, File: "main.go", Line: 9, Col: 14, Message: "missing return", Source: "compiler"},
problems := []code.Problem{
{Severity: code.ProblemError, File: "court/seal.go", Line: 12, Col: 5, Message: "undefined: Decree", Source: "compiler"},
{Severity: code.ProblemWarning, File: "court/seal.go", Line: 30, Col: 2, Message: "unused variable wax", Source: "vet"},
{Severity: code.ProblemInfo, File: "docs/ledger.md", Line: 4, Col: 1, Message: "line is longer than 80", Source: "lint"},
{Severity: code.ProblemError, File: "main.go", Line: 9, Col: 14, Message: "missing return", Source: "compiler"},
}
r := mujica.ProblemsPanel(c, problems, &st.s, mujica.ProblemsPanelOptions{Disabled: st.disabled})
r := code.ProblemsPanel(c, problems, &st.s, code.ProblemsPanelOptions{Disabled: st.disabled})
r.Element.Size(620, 320)
if r.Opened != nil {
log(fmt.Sprintf("go to %s:%d:%d", r.Opened.File, r.Opened.Line, r.Opened.Col))
@@ -231,8 +233,8 @@ func codeProblemsDemo(c *ui.Context, log func(string)) {
func codeOutputDemo(c *ui.Context, log func(string)) {
type state struct {
channels []mujica.OutputChannel
s mujica.OutputPanelState
channels []code.OutputChannel
s code.OutputPanelState
disabled bool
}
s := ui.Local(c.Root(), "332", func() *state {
@@ -240,19 +242,19 @@ func codeOutputDemo(c *ui.Context, log func(string)) {
for i := range 300 {
build = append(build, fmt.Sprintf("compiling unit %d of 300", i+1))
}
return &state{channels: []mujica.OutputChannel{{Name: "Build", Lines: build},
return &state{channels: []code.OutputChannel{{Name: "Build", Lines: build},
{Name: "Tests", Lines: []string{"\x1b[32mPASS\x1b[0m court/seal", "\x1b[31mFAIL\x1b[0m court/ledger: expected 3 seals", "\x1b[1mok\x1b[0m 2 of 3 packages"}}}}
})
st := *s
codeDemoControls(c, func() {
codeDemoCheck(c, &st.disabled, "Disabled", log)
if mujica.Button(c, "Write line", mujica.ButtonOptions{Variant: mujica.Secondary}).Clicked() {
if input.Button(c, "Write line", input.ButtonOptions{Variant: input.Secondary}).Clicked() {
ch := &st.channels[st.s.Channel]
ch.Lines = append(ch.Lines, fmt.Sprintf("line %d", len(ch.Lines)+1))
log("write to " + ch.Name)
}
})
r := mujica.OutputPanel(c, st.channels, &st.s, mujica.OutputPanelOptions{Disabled: st.disabled})
r := code.OutputPanel(c, st.channels, &st.s, code.OutputPanelOptions{Disabled: st.disabled})
r.Element.Size(560, 300)
if r.Changed {
log("channel " + st.channels[st.s.Channel].Name)
+12 -11
View File
@@ -3,8 +3,9 @@ package main
import (
"github.com/egoist/mygo/ui"
mujica "mujicaui"
"mujicaui/theme"
"github.com/ZacharyZhang-NY/MujicaUI/core"
"github.com/ZacharyZhang-NY/MujicaUI/data"
"github.com/ZacharyZhang-NY/MujicaUI/theme"
)
const dataCategory = "Data"
@@ -39,18 +40,18 @@ var dataPages = []page{
// 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(mujica.FontSize(c, theme.H3Size))
ui.Text(c, title).Font(theme.SerifFont).FontSize(core.FontSize(c, 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)
k := core.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(mujica.FontSize(c, theme.CaptionSize)).TextColor(k.TextMuted)
ui.Text(c, name).FontSize(core.FontSize(c, theme.CaptionSize)).TextColor(k.TextMuted)
body()
})
})
@@ -64,18 +65,18 @@ func dataControls(c *ui.Context, fn func()) {
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) {
func dataStatus(s string) (data.DataStatus, bool) {
switch s {
case "Ready":
return mujica.DataReady, true
return data.DataReady, true
case "Loading":
return mujica.DataLoading, false
return data.DataLoading, false
case "Loading more":
return mujica.DataLoadingMore, true
return data.DataLoadingMore, true
case "Failed":
return mujica.DataFailed, true
return data.DataFailed, true
case "Empty":
return mujica.DataReady, false
return data.DataReady, false
}
panic("gallery: unknown status " + s)
}
+35 -35
View File
@@ -3,9 +3,9 @@ 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{})
core.Use(c, core.Settings{})
data.Card(c, data.CardOptions{Title: "Archive", Decorated: true, Footer: func() {
input.Button(c, "Open", input.ButtonOptions{})
}}, func() {
ui.Text(c, "Letters of the court, sealed in wax.")
}).Width(280)
@@ -13,10 +13,10 @@ const dataCardCode = `view := func(c *ui.Context) {
ui.Render(view, 400, 300, 1)`
const dataListCode = `names := []string{"Matins", "Lauds", "Prime", "Terce"}
var state mujica.ListState[string]
var state data.ListState[string]
view := func(c *ui.Context) {
mujica.Use(c, mujica.Settings{})
v := mujica.List(c, &state, len(names), mujica.ListOptions[string]{
core.Use(c, core.Settings{})
v := data.List(c, &state, len(names), data.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]) })
@@ -34,10 +34,10 @@ const dataTableCode = `type relic struct {
Age int
}
rows := []relic{{1, "Censer", 300}, {2, "Altar", 120}, {3, "Bell", 80}}
var state mujica.DataTableState[int]
opts := mujica.DataTableOptions[relic, int]{
var state data.DataTableState[int]
opts := data.DataTableOptions[relic, int]{
Key: func(r relic) int { return r.ID },
Columns: []mujica.DataColumn[relic]{
Columns: []data.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) }},
@@ -47,16 +47,16 @@ opts := mujica.DataTableOptions[relic, int]{
},
}
view := func(c *ui.Context) {
mujica.Use(c, mujica.Settings{})
mujica.DataTable(c, &state, rows, opts).Size(360, 240)
core.Use(c, core.Settings{})
data.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]
var state data.TreeState[string]
view := func(c *ui.Context) {
mujica.Use(c, mujica.Settings{})
mujica.Tree(c, &state, mujica.TreeOptions[string]{
core.Use(c, core.Settings{})
data.Tree(c, &state, data.TreeOptions[string]{
Roots: []string{"Nave", "Sacristy"},
Children: func(n string) []string { return kids[n] },
Label: func(n string) string { return n },
@@ -66,14 +66,14 @@ 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]
var state data.TreeTableState[string]
view := func(c *ui.Context) {
mujica.Use(c, mujica.Settings{})
mujica.TreeTable(c, &state, mujica.TreeTableOptions[string]{
core.Use(c, core.Settings{})
data.TreeTable(c, &state, data.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]{
Columns: []data.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,
@@ -85,59 +85,59 @@ 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{
core.Use(c, core.Settings{})
data.Accordion(c, &open, []data.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{})
}, data.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() {
core.Use(c, core.Settings{})
data.Collapsible(c, &open, data.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},
core.Use(c, core.Settings{})
data.Timeline(c, []data.TimelineItem{
{Time: "06:00", Title: "Matins sung", Status: data.TimelineDone},
{Time: "09:00", Title: "Relics moved", Content: "From the crypt to the altar.", Status: data.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{
core.Use(c, core.Settings{})
data.DescriptionList(c, []data.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{})
}, data.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{
core.Use(c, core.Settings{})
data.Statistic(c, 12840, data.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 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\tcore.Use(c, core.Settings{})\n\tdata.ImageViewer(c, &open, &index, []data.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) {
core.Use(c, core.Settings{})
data.Carousel(c, &index, 3, data.CarouselOptions{Label: "Windows", Autoplay: true}, func(i int) {
ui.Text(c, fmt.Sprintf("Rose window %d", i+1)).Padding(24)
}).Size(360, 220)
}
+27 -25
View File
@@ -9,7 +9,9 @@ import (
"github.com/egoist/mygo/ui"
mujica "mujicaui"
"github.com/ZacharyZhang-NY/MujicaUI/core"
"github.com/ZacharyZhang-NY/MujicaUI/data"
"github.com/ZacharyZhang-NY/MujicaUI/input"
)
var dataOffices = []string{"Matins", "Lauds", "Prime", "Terce", "Sext", "None", "Vespers", "Compline"}
@@ -18,7 +20,7 @@ func dataListDemo(c *ui.Context, log func(string)) {
type state struct {
big, grid, multiple bool
status, query string
list mujica.ListState[int]
list data.ListState[int]
}
s := ui.Local(c.Root(), "070", func() state { return state{status: "Ready"} })
n := 24
@@ -39,7 +41,7 @@ func dataListDemo(c *ui.Context, log func(string)) {
})
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() {
if input.Button(c, "Find", input.ButtonOptions{Variant: input.Secondary}).Clicked() {
for i := range n {
if strings.Contains(strings.ToLower(label(i)), strings.ToLower(s.query)) {
s.list.ScrollTo(i)
@@ -49,13 +51,13 @@ func dataListDemo(c *ui.Context, log func(string)) {
}
}
})
v := mujica.List(c, &s.list, n, mujica.ListOptions[int]{
v := data.List(c, &s.list, n, data.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)
ui.Text(c, "Office").TextColor(core.Tokens(c).TextMuted)
}
})
v.Element.Size(480, 320)
@@ -95,7 +97,7 @@ func dataTableDemo(c *ui.Context, log func(string)) {
big, multiple bool
status string
small, large []dataRelic
table mujica.DataTableState[int]
table data.DataTableState[int]
}
s := ui.Local(c.Root(), "071", func() state { return state{status: "Ready", small: dataRelics(60)} })
rows := s.small
@@ -109,8 +111,8 @@ func dataTableDemo(c *ui.Context, log func(string)) {
if !show {
rows = nil
}
k := mujica.Tokens(c)
cols := []mujica.DataColumn[dataRelic]{
k := core.Tokens(c)
cols := []data.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 },
@@ -135,7 +137,7 @@ func dataTableDemo(c *ui.Context, log func(string)) {
ui.Select(c, &s.status, dataStatuses)
ui.TextInput(c, &s.table.Filter).Width(180).Placeholder("Filter")
})
v := mujica.DataTable(c, &s.table, rows, mujica.DataTableOptions[dataRelic, int]{
v := data.DataTable(c, &s.table, rows, data.DataTableOptions[dataRelic, int]{
Columns: cols, Key: func(r dataRelic) int { return r.ID }, Multiple: s.multiple, Status: st,
})
v.Element.Size(620, 360)
@@ -160,7 +162,7 @@ func dataTableDemo(c *ui.Context, log func(string)) {
// dataLazy simulates children that arrive 600 ms after they are asked for.
type dataLazy struct {
kids map[string][]string
status map[string]mujica.DataStatus
status map[string]data.DataStatus
due map[string]time.Time
failed map[string]bool
}
@@ -169,14 +171,14 @@ 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},
status: map[string]data.DataStatus{"Crypt": data.DataUnloaded, "Archive": data.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.status[n] = data.DataLoading
d.due[n] = c.Now().Add(600 * time.Millisecond)
log("load " + n)
}
@@ -191,10 +193,10 @@ func (d *dataLazy) tick(c *ui.Context) {
}
delete(d.due, n)
if n == "Archive" && !d.failed[n] {
d.failed[n], d.status[n] = true, mujica.DataFailed
d.failed[n], d.status[n] = true, data.DataFailed
continue
}
d.status[n] = mujica.DataReady
d.status[n] = data.DataReady
d.kids[n] = []string{n + " north", n + " south"}
}
}
@@ -203,14 +205,14 @@ func dataTreeDemo(c *ui.Context, log func(string)) {
type state struct {
big, multiple bool
lazy *dataLazy
tree mujica.TreeState[string]
bigTree mujica.TreeState[string]
tree data.TreeState[string]
bigTree data.TreeState[string]
}
s := ui.Local(c.Root(), "072", func() state { return state{lazy: newDataLazy()} })
s.lazy.tick(c)
opts := mujica.TreeOptions[string]{
opts := data.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] },
Label: func(n string) string { return n }, ItemStatus: func(n string) data.DataStatus { return s.lazy.status[n] },
Load: s.lazy.load(c, log), Multiple: s.multiple,
}
ts := &s.tree
@@ -223,7 +225,7 @@ func dataTreeDemo(c *ui.Context, log func(string)) {
ui.Checkbox(c, &s.big, "100,000 nodes")
ui.Checkbox(c, &s.multiple, "Multiple")
})
v := mujica.Tree(c, ts, opts)
v := data.Tree(c, ts, opts)
v.Element.Size(420, 340)
if v.Changed() {
n, _ := ts.Selected()
@@ -237,12 +239,12 @@ func dataTreeDemo(c *ui.Context, log func(string)) {
}
// dataBigTree has 100 roots of 999 children: 100,000 nodes, built as they open.
func dataBigTree(multiple bool) mujica.TreeOptions[string] {
func dataBigTree(multiple bool) data.TreeOptions[string] {
roots := make([]string, 100)
for i := range roots {
roots[i] = fmt.Sprintf("Wing %d", i+1)
}
return mujica.TreeOptions[string]{
return data.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, "·") {
@@ -271,7 +273,7 @@ func dataTreeTableDemo(c *ui.Context, log func(string)) {
type state struct {
multiple bool
lazy *dataLazy
table mujica.TreeTableState[string]
table data.TreeTableState[string]
}
s := ui.Local(c.Root(), "073", func() state { return state{lazy: newDataLazy()} })
s.lazy.tick(c)
@@ -279,11 +281,11 @@ func dataTreeTableDemo(c *ui.Context, log func(string)) {
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]{
v := data.TreeTable(c, &s.table, data.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] },
Label: func(n string) string { return n }, ItemStatus: func(n string) data.DataStatus { return s.lazy.status[n] },
Load: s.lazy.load(c, log), Multiple: s.multiple,
Columns: []mujica.TreeColumn[string]{
Columns: []data.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)) }},
+31 -29
View File
@@ -7,7 +7,9 @@ import (
"github.com/egoist/mygo/ui"
mujica "mujicaui"
"github.com/ZacharyZhang-NY/MujicaUI/core"
"github.com/ZacharyZhang-NY/MujicaUI/data"
"github.com/ZacharyZhang-NY/MujicaUI/input"
)
func dataCardDemo(c *ui.Context, log func(string)) {
@@ -23,15 +25,15 @@ func dataCardDemo(c *ui.Context, log func(string)) {
ui.Checkbox(c, &s.footer, "Footer")
})
card := func(decorated bool) {
opts := mujica.CardOptions{Title: s.title, Decorated: decorated}
opts := data.CardOptions{Title: s.title, Decorated: decorated}
if s.footer {
opts.Footer = func() {
if mujica.Button(c, "Open", mujica.ButtonOptions{Variant: mujica.Secondary}).Clicked() {
if input.Button(c, "Open", input.ButtonOptions{Variant: input.Secondary}).Clicked() {
log(fmt.Sprintf("open clicked (decorated=%v)", decorated))
}
}
}
mujica.Card(c, opts, func() {
data.Card(c, opts, func() {
ui.Text(c, "Letters of the court, sealed in wine-red wax and kept in the north vault.")
}).Width(280)
}
@@ -53,21 +55,21 @@ func dataAccordionDemo(c *ui.Context, log func(string)) {
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() {
if input.Button(c, "Close all", input.ButtonOptions{Variant: input.Ghost}).Clicked() {
s.open = nil
log("closed all from outside")
}
})
r := mujica.Accordion(c, &s.open, []mujica.AccordionItem{
r := data.Accordion(c, &s.open, []data.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() {
if mujica.Button(c, "Ring", mujica.ButtonOptions{}).Clicked() {
if input.Button(c, "Ring", input.ButtonOptions{}).Clicked() {
log("ring the bells")
}
}},
}, mujica.AccordionOptions{Multiple: s.multiple, KeepOne: s.keepOne})
}, data.AccordionOptions{Multiple: s.multiple, KeepOne: s.keepOne})
r.Element.Width(420)
if r.Changed() {
log(fmt.Sprintf("open %v", s.open))
@@ -80,15 +82,15 @@ func dataCollapsibleDemo(c *ui.Context, log func(string)) {
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() {
if input.Button(c, "Open from outside", input.ButtonOptions{Variant: input.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() {
if data.Collapsible(c, &s.open, data.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.")
if mujica.Button(c, "Light candles", mujica.ButtonOptions{Variant: mujica.Outline}).Clicked() {
if input.Button(c, "Light candles", input.ButtonOptions{Variant: input.Outline}).Clicked() {
log("light candles")
}
}).Changed() {
@@ -98,25 +100,25 @@ func dataCollapsibleDemo(c *ui.Context, log func(string)) {
}
func dataTimelineDemo(c *ui.Context, log func(string)) {
type state struct{ items []mujica.TimelineItem }
type state struct{ items []data.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,
return state{items: []data.TimelineItem{
{Time: "06:00", Title: "Matins sung", Content: "The choir gathered before dawn.", Status: data.TimelineDone},
{Time: "09:00", Title: "Relics moved", Status: data.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: "12:00", Title: "Bell cracked", Status: data.TimelineError},
{Time: "15:00", Title: "Storm warning", Status: data.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"})
if input.Button(c, "Add event", input.ButtonOptions{Variant: input.Secondary}).Clicked() {
s.items = append(s.items, data.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) })
ui.Column(c).Width(420).Children(func() { data.Timeline(c, s.items) })
})
}
@@ -134,12 +136,12 @@ func dataDescriptionDemo(c *ui.Context, log func(string)) {
})
cols, _ := strconv.Atoi(s.columns)
ui.Column(c).Width(float32(s.width)).Children(func() {
if i, ok := mujica.DescriptionList(c, []mujica.DescriptionItem{
if i, ok := data.DescriptionList(c, []data.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 {
}, data.DescriptionListOptions{Columns: cols}).Copied(); ok {
log(fmt.Sprintf("copied item %d", i))
}
})
@@ -166,22 +168,22 @@ func dataStatisticDemo(c *ui.Context, log func(string)) {
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,
data.Statistic(c, s.value, data.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})
data.Statistic(c, s.value/1000, data.StatisticOptions{Title: "Tithe", Prefix: "£", Suffix: "k", Precision: p})
})
})
})
}
var dataPictures = func() []mujica.ViewerImage {
var dataPictures = func() []data.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{
return []data.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"},
@@ -195,7 +197,7 @@ func dataViewerDemo(c *ui.Context, log func(string)) {
}
s := ui.Local(c.Root(), "079", func() state { return state{} })
dataSection(c, "ImageViewer", func() {
k := mujica.Tokens(c)
k := core.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 {
@@ -208,7 +210,7 @@ func dataViewerDemo(c *ui.Context, log func(string)) {
}
})
wasOpen := s.open
if mujica.ImageViewer(c, &s.open, &s.index, dataPictures).Changed() {
if data.ImageViewer(c, &s.open, &s.index, dataPictures).Changed() {
log(fmt.Sprintf("image %d", s.index+1))
}
if wasOpen && !s.open {
@@ -234,7 +236,7 @@ func dataCarouselDemo(c *ui.Context, log func(string)) {
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) {
v := data.Carousel(c, &s.index, len(dataPictures), data.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.Element.Size(520, 300)
+2 -2
View File
@@ -6,7 +6,7 @@ import (
"github.com/egoist/mygo/ui"
mujica "mujicaui"
"github.com/ZacharyZhang-NY/MujicaUI/core"
)
// dataDemo runs the demo of page id and returns its tester and event log.
@@ -18,7 +18,7 @@ func dataDemo(t *testing.T, id string) (*ui.Tester, *[]string) {
}
events := &[]string{}
tt := ui.NewTester(func(c *ui.Context) {
mujica.Use(c, mujica.Settings{})
core.Use(c, core.Settings{})
ui.Scroll(c).Fill().Padding(24).Children(func() {
dataPages[i].Demo(c, func(s string) { *events = append(*events, s) })
})
+85 -84
View File
@@ -6,8 +6,9 @@ import (
"github.com/egoist/mygo/ui"
mujica "mujicaui"
"mujicaui/theme"
"github.com/ZacharyZhang-NY/MujicaUI/core"
"github.com/ZacharyZhang-NY/MujicaUI/datetime"
"github.com/ZacharyZhang-NY/MujicaUI/theme"
)
const dtCategory = "Date & Time"
@@ -33,20 +34,20 @@ var datetimePages = []page{
// dtSection lays out a captioned block of a demo.
func dtSection(c *ui.Context, title string, fn func()) {
k := mujica.Tokens(c)
k := core.Tokens(c)
ui.Column(c).Gap(8).Children(func() {
ui.Text(c, title).Font(theme.SerifFont).FontSize(mujica.FontSize(c, theme.H3Size)).TextColor(k.Text)
ui.Text(c, title).Font(theme.SerifFont).FontSize(core.FontSize(c, theme.H3Size)).TextColor(k.Text)
fn()
})
}
// dtStates lays labeled variants side by side.
func dtStates(c *ui.Context, fn func(state func(name string, body func()))) {
k := mujica.Tokens(c)
k := core.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(mujica.FontSize(c, theme.CaptionSize)).TextColor(k.TextMuted)
ui.Text(c, name).FontSize(core.FontSize(c, theme.CaptionSize)).TextColor(k.TextMuted)
body()
})
})
@@ -59,10 +60,10 @@ func dtControls(c *ui.Context, fn func()) {
var dtWeekStarts = []string{"Locale", "Sunday", "Monday", "Saturday"}
func dtWeekStart(s string) mujica.WeekStart {
func dtWeekStart(s string) datetime.WeekStart {
for i, n := range dtWeekStarts {
if n == s {
return mujica.WeekStart(i)
return datetime.WeekStart(i)
}
}
panic("gallery: unknown week start " + s)
@@ -70,11 +71,11 @@ func dtWeekStart(s string) mujica.WeekStart {
func dtCalendarDemo(c *ui.Context, log func(string)) {
type state struct {
day mujica.Date
day core.Date
weekStart string
weekends, bound, frozen bool
}
s := ui.Local(c.Root(), "051", func() state { return state{day: mujica.DateOf(time.Now()), weekStart: "Locale"} })
s := ui.Local(c.Root(), "051", func() state { return state{day: core.DateOf(time.Now()), weekStart: "Locale"} })
dtSection(c, "Calendar", func() {
dtControls(c, func() {
ui.Select(c, &s.weekStart, dtWeekStarts)
@@ -82,16 +83,16 @@ func dtCalendarDemo(c *ui.Context, log func(string)) {
ui.Checkbox(c, &s.bound, "Only this month")
ui.Checkbox(c, &s.frozen, "Disabled")
})
opts := mujica.CalendarOptions{WeekStart: dtWeekStart(s.weekStart), Disabled: s.frozen, Label: "Calendar demo"}
opts := datetime.CalendarOptions{WeekStart: dtWeekStart(s.weekStart), Disabled: s.frozen, Label: "Calendar demo"}
if s.weekends {
opts.DisableDate = func(d mujica.Date) bool { return d.Weekday() == time.Saturday || d.Weekday() == time.Sunday }
opts.DisableDate = func(d core.Date) bool { return d.Weekday() == time.Saturday || d.Weekday() == time.Sunday }
}
if s.bound {
now := mujica.DateOf(time.Now())
opts.Min = mujica.Date{Year: now.Year, Month: now.Month, Day: 1}
now := core.DateOf(time.Now())
opts.Min = core.Date{Year: now.Year, Month: now.Month, Day: 1}
opts.Max = opts.Min.AddMonths(1).AddDays(-1)
}
if mujica.Calendar(c, &s.day, opts).Changed() {
if datetime.Calendar(c, &s.day, opts).Changed() {
log("Calendar changed: " + s.day.String())
}
})
@@ -99,11 +100,11 @@ func dtCalendarDemo(c *ui.Context, log func(string)) {
func dtDatePickerDemo(c *ui.Context, log func(string)) {
type state struct {
a, b, d mujica.Date
a, b, d core.Date
clearable, slash bool
}
s := ui.Local(c.Root(), "052", func() state {
t := mujica.DateOf(time.Now())
t := core.DateOf(time.Now())
return state{a: t, d: t, clearable: true}
})
dtSection(c, "DatePicker", func() {
@@ -115,24 +116,24 @@ func dtDatePickerDemo(c *ui.Context, log func(string)) {
if s.slash {
layout = "02/01/2006"
}
now := mujica.DateOf(time.Now())
now := core.DateOf(time.Now())
dtStates(c, func(state func(string, func())) {
state("Default", func() {
if mujica.DatePicker(c, &s.a, mujica.DatePickerOptions{Label: "Due date", Layout: layout, Clearable: s.clearable}).Changed() {
if datetime.DatePicker(c, &s.a, datetime.DatePickerOptions{Label: "Due date", Layout: layout, Clearable: s.clearable}).Changed() {
log("DatePicker changed: " + s.a.String())
}
})
state("Next 30 days only", func() {
opts := mujica.DatePickerOptions{Label: "Delivery", Layout: layout, Clearable: s.clearable, Min: now, Max: now.AddDays(30)}
if mujica.DatePicker(c, &s.b, opts).Changed() {
opts := datetime.DatePickerOptions{Label: "Delivery", Layout: layout, Clearable: s.clearable, Min: now, Max: now.AddDays(30)}
if datetime.DatePicker(c, &s.b, opts).Changed() {
log("DatePicker (bounded) changed: " + s.b.String())
}
})
state("Read-only", func() {
mujica.DatePicker(c, &s.d, mujica.DatePickerOptions{Label: "Signed on", Layout: layout, ReadOnly: true})
datetime.DatePicker(c, &s.d, datetime.DatePickerOptions{Label: "Signed on", Layout: layout, ReadOnly: true})
})
state("Disabled", func() {
mujica.DatePicker(c, &s.d, mujica.DatePickerOptions{Label: "Locked", Layout: layout, Disabled: true})
datetime.DatePicker(c, &s.d, datetime.DatePickerOptions{Label: "Locked", Layout: layout, Disabled: true})
})
})
})
@@ -140,7 +141,7 @@ func dtDatePickerDemo(c *ui.Context, log func(string)) {
func dtDateRangeDemo(c *ui.Context, log func(string)) {
type state struct {
a mujica.DateRange
a datetime.DateRange
noTuesdays bool
frozen bool
fromThisWeek bool
@@ -152,14 +153,14 @@ func dtDateRangeDemo(c *ui.Context, log func(string)) {
ui.Checkbox(c, &s.fromThisWeek, "Start from today")
ui.Checkbox(c, &s.frozen, "Disabled")
})
opts := mujica.DateRangePickerOptions{Label: "Stay", Disabled: s.frozen}
opts := datetime.DateRangePickerOptions{Label: "Stay", Disabled: s.frozen}
if s.noTuesdays {
opts.DisableDate = func(d mujica.Date) bool { return d.Weekday() == time.Tuesday }
opts.DisableDate = func(d core.Date) bool { return d.Weekday() == time.Tuesday }
}
if s.fromThisWeek {
opts.Min = mujica.DateOf(time.Now())
opts.Min = core.DateOf(time.Now())
}
r := mujica.DateRangePicker(c, &s.a, opts)
r := datetime.DateRangePicker(c, &s.a, opts)
if r.Changed() {
log(fmt.Sprintf("DateRangePicker changed: %v – %v (%d days)", s.a.Start, s.a.End, s.a.Days()))
}
@@ -171,12 +172,12 @@ func dtDateRangeDemo(c *ui.Context, log func(string)) {
func dtTimePickerDemo(c *ui.Context, log func(string)) {
type state struct {
a, b mujica.TimeOfDay
a, b datetime.TimeOfDay
seconds, hour12 bool
step string
}
s := ui.Local(c.Root(), "054", func() state {
return state{a: mujica.TimeOfDay{Hour: 9, Minute: 30}, b: mujica.TimeOfDay{Hour: 10}, step: "1"}
return state{a: datetime.TimeOfDay{Hour: 9, Minute: 30}, b: datetime.TimeOfDay{Hour: 10}, step: "1"}
})
dtSection(c, "TimePicker", func() {
dtControls(c, func() {
@@ -189,31 +190,31 @@ func dtTimePickerDemo(c *ui.Context, log func(string)) {
if _, err := fmt.Sscan(s.step, &step); err != nil {
panic(err)
}
base := mujica.TimePickerOptions{Seconds: s.seconds, Hour12: s.hour12, MinuteStep: step}
base := datetime.TimePickerOptions{Seconds: s.seconds, Hour12: s.hour12, MinuteStep: step}
dtStates(c, func(state func(string, func())) {
state("Any time", func() {
o := base
o.Label = "Alarm"
if mujica.TimePicker(c, &s.a, o).Changed() {
if datetime.TimePicker(c, &s.a, o).Changed() {
log("TimePicker changed: " + s.a.String())
}
})
state("Office hours 09:00–18:00", func() {
o := base
o.Label, o.Min, o.Max = "Meeting", mujica.TimeOfDay{Hour: 9}, mujica.TimeOfDay{Hour: 18}
if mujica.TimePicker(c, &s.b, o).Changed() {
o.Label, o.Min, o.Max = "Meeting", datetime.TimeOfDay{Hour: 9}, datetime.TimeOfDay{Hour: 18}
if datetime.TimePicker(c, &s.b, o).Changed() {
log("TimePicker (bounded) changed: " + s.b.String())
}
})
state("Read-only", func() {
o := base
o.Label, o.ReadOnly = "Logged at", true
mujica.TimePicker(c, &s.a, o)
datetime.TimePicker(c, &s.a, o)
})
state("Disabled", func() {
o := base
o.Label, o.Disabled = "Locked", true
mujica.TimePicker(c, &s.a, o)
datetime.TimePicker(c, &s.a, o)
})
})
})
@@ -221,21 +222,21 @@ func dtTimePickerDemo(c *ui.Context, log func(string)) {
func dtTimeRangeDemo(c *ui.Context, log func(string)) {
type state struct {
v mujica.TimeRange
v datetime.TimeRange
overnight bool
}
s := ui.Local(c.Root(), "055", func() state {
return state{v: mujica.TimeRange{Start: mujica.TimeOfDay{Hour: 9}, End: mujica.TimeOfDay{Hour: 17}}}
return state{v: datetime.TimeRange{Start: datetime.TimeOfDay{Hour: 9}, End: datetime.TimeOfDay{Hour: 17}}}
})
dtSection(c, "TimeRangePicker", func() {
dtControls(c, func() {
if ui.Checkbox(c, &s.overnight, "Allow overnight").Changed() && !s.overnight && s.v.Overnight() {
s.v = mujica.TimeRange{Start: s.v.End, End: s.v.Start}
s.v = datetime.TimeRange{Start: s.v.End, End: s.v.Start}
log("TimeRangePicker swapped ends: overnight turned off")
}
})
opts := mujica.TimeRangePickerOptions{Label: "Opening hours", AllowOvernight: s.overnight, MinuteStep: 15}
if mujica.TimeRangePicker(c, &s.v, opts).Changed() {
opts := datetime.TimeRangePickerOptions{Label: "Opening hours", AllowOvernight: s.overnight, MinuteStep: 15}
if datetime.TimeRangePicker(c, &s.v, opts).Changed() {
log(fmt.Sprintf("TimeRangePicker changed: %v – %v overnight=%v", s.v.Start, s.v.End, s.v.Overnight()))
}
})
@@ -260,22 +261,22 @@ func dtDateTimeDemo(c *ui.Context, log func(string)) {
if err != nil {
panic(err)
}
r := mujica.DateTimePicker(c, &s.v, mujica.DateTimePickerOptions{Label: "Meeting", Location: loc, Hour12: s.hour12})
r := datetime.DateTimePicker(c, &s.v, datetime.DateTimePickerOptions{Label: "Meeting", Location: loc, Hour12: s.hour12})
if r.Changed() {
log("DateTimePicker changed: " + s.v.Format(time.RFC3339))
}
if r.Canceled() {
log("DateTimePicker canceled")
}
k := mujica.Tokens(c)
k := core.Tokens(c)
ui.Text(c, "Try 2026-03-08 02:30 in America/New_York: the picker reports the DST gap.").
FontSize(mujica.FontSize(c, theme.CaptionSize)).TextColor(k.TextMuted)
FontSize(core.FontSize(c, theme.CaptionSize)).TextColor(k.TextMuted)
})
}
func dtMonthDemo(c *ui.Context, log func(string)) {
type state struct {
v mujica.YearMonth
v datetime.YearMonth
clearable, past bool
}
s := ui.Local(c.Root(), "057", func() state { return state{clearable: true} })
@@ -284,23 +285,23 @@ func dtMonthDemo(c *ui.Context, log func(string)) {
ui.Checkbox(c, &s.clearable, "Clearable")
ui.Checkbox(c, &s.past, "Past months only")
})
opts := mujica.MonthPickerOptions{Label: "Billing month", Clearable: s.clearable,
DisableMonth: func(m mujica.YearMonth) bool { return m.Month == time.February }}
opts := datetime.MonthPickerOptions{Label: "Billing month", Clearable: s.clearable,
DisableMonth: func(m datetime.YearMonth) bool { return m.Month == time.February }}
if s.past {
now := time.Now()
opts.Max = mujica.YearMonth{Year: now.Year(), Month: now.Month()}
opts.Max = datetime.YearMonth{Year: now.Year(), Month: now.Month()}
}
dtStates(c, func(state func(string, func())) {
state("February unavailable", func() {
if mujica.MonthPicker(c, &s.v, opts).Changed() {
if datetime.MonthPicker(c, &s.v, opts).Changed() {
log("MonthPicker changed: " + s.v.String())
}
})
state("Read-only", func() {
mujica.MonthPicker(c, &s.v, mujica.MonthPickerOptions{Label: "Closed month", ReadOnly: true})
datetime.MonthPicker(c, &s.v, datetime.MonthPickerOptions{Label: "Closed month", ReadOnly: true})
})
state("Disabled", func() {
mujica.MonthPicker(c, &s.v, mujica.MonthPickerOptions{Label: "Locked", Disabled: true})
datetime.MonthPicker(c, &s.v, datetime.MonthPickerOptions{Label: "Locked", Disabled: true})
})
})
})
@@ -314,19 +315,19 @@ func dtYearDemo(c *ui.Context, log func(string)) {
s := ui.Local(c.Root(), "058", func() state { return state{v: time.Now().Year(), clearable: true} })
dtSection(c, "YearPicker", func() {
dtControls(c, func() { ui.Checkbox(c, &s.clearable, "Clearable") })
opts := mujica.YearPickerOptions{Label: "Vintage", Min: 1990, Max: time.Now().Year(), Clearable: s.clearable,
opts := datetime.YearPickerOptions{Label: "Vintage", Min: 1990, Max: time.Now().Year(), Clearable: s.clearable,
DisableYear: func(y int) bool { return y == 2020 }}
dtStates(c, func(state func(string, func())) {
state("1990 to now, 2020 unavailable", func() {
if mujica.YearPicker(c, &s.v, opts).Changed() {
if datetime.YearPicker(c, &s.v, opts).Changed() {
log(fmt.Sprintf("YearPicker changed: %d", s.v))
}
})
state("Read-only", func() {
mujica.YearPicker(c, &s.v, mujica.YearPickerOptions{Label: "Founded", ReadOnly: true})
datetime.YearPicker(c, &s.v, datetime.YearPickerOptions{Label: "Founded", ReadOnly: true})
})
state("Disabled", func() {
mujica.YearPicker(c, &s.v, mujica.YearPickerOptions{Label: "Locked", Disabled: true})
datetime.YearPicker(c, &s.v, datetime.YearPickerOptions{Label: "Locked", Disabled: true})
})
})
})
@@ -334,47 +335,47 @@ func dtYearDemo(c *ui.Context, log func(string)) {
// Code snippets mirror example_datetime_test.go.
const (
dtCalendarCode = `day := mujica.Date{Year: 2026, Month: time.October, Day: 5}
dtCalendarCode = `day := core.Date{Year: 2026, Month: time.October, Day: 5}
view := func(c *ui.Context) {
mujica.Use(c, mujica.Settings{})
if mujica.Calendar(c, &day, mujica.CalendarOptions{WeekStart: mujica.WeekStartMonday}).Changed() {
core.Use(c, core.Settings{})
if datetime.Calendar(c, &day, datetime.CalendarOptions{WeekStart: datetime.WeekStartMonday}).Changed() {
fmt.Println("chosen", day)
}
}
ui.Render(view, 400, 300, 1)`
dtDatePickerCode = `due := mujica.Date{Year: 2026, Month: time.October, Day: 5}
dtDatePickerCode = `due := core.Date{Year: 2026, Month: time.October, Day: 5}
view := func(c *ui.Context) {
mujica.Use(c, mujica.Settings{})
opts := mujica.DatePickerOptions{Label: "Due date", Clearable: true,
Min: mujica.Date{Year: 2026, Month: time.January, Day: 1}}
if mujica.DatePicker(c, &due, opts).Changed() {
core.Use(c, core.Settings{})
opts := datetime.DatePickerOptions{Label: "Due date", Clearable: true,
Min: core.Date{Year: 2026, Month: time.January, Day: 1}}
if datetime.DatePicker(c, &due, opts).Changed() {
fmt.Println("due", due)
}
}
ui.Render(view, 400, 300, 1)`
dtDateRangeCode = `var stay mujica.DateRange
dtDateRangeCode = `var stay datetime.DateRange
view := func(c *ui.Context) {
mujica.Use(c, mujica.Settings{})
r := mujica.DateRangePicker(c, &stay, mujica.DateRangePickerOptions{Label: "Stay"})
core.Use(c, core.Settings{})
r := datetime.DateRangePicker(c, &stay, datetime.DateRangePickerOptions{Label: "Stay"})
if r.Changed() {
fmt.Println("stay", stay.Start, stay.End, stay.Days())
}
}
ui.Render(view, 400, 300, 1)`
dtTimePickerCode = `alarm := mujica.TimeOfDay{Hour: 7, Minute: 30}
dtTimePickerCode = `alarm := datetime.TimeOfDay{Hour: 7, Minute: 30}
view := func(c *ui.Context) {
mujica.Use(c, mujica.Settings{})
opts := mujica.TimePickerOptions{Label: "Alarm", Hour12: true, MinuteStep: 5}
if mujica.TimePicker(c, &alarm, opts).Changed() {
core.Use(c, core.Settings{})
opts := datetime.TimePickerOptions{Label: "Alarm", Hour12: true, MinuteStep: 5}
if datetime.TimePicker(c, &alarm, opts).Changed() {
fmt.Println("alarm", alarm)
}
}
ui.Render(view, 400, 300, 1)`
dtTimeRangeCode = `shift := mujica.TimeRange{Start: mujica.TimeOfDay{Hour: 22}, End: mujica.TimeOfDay{Hour: 6}}
dtTimeRangeCode = `shift := datetime.TimeRange{Start: datetime.TimeOfDay{Hour: 22}, End: datetime.TimeOfDay{Hour: 6}}
view := func(c *ui.Context) {
mujica.Use(c, mujica.Settings{})
opts := mujica.TimeRangePickerOptions{Label: "Night shift", AllowOvernight: true}
if mujica.TimeRangePicker(c, &shift, opts).Changed() {
core.Use(c, core.Settings{})
opts := datetime.TimeRangePickerOptions{Label: "Night shift", AllowOvernight: true}
if datetime.TimeRangePicker(c, &shift, opts).Changed() {
fmt.Println("shift", shift.Start, shift.End, shift.Overnight())
}
}
@@ -385,27 +386,27 @@ if err != nil {
}
var meeting time.Time
view := func(c *ui.Context) {
mujica.Use(c, mujica.Settings{})
opts := mujica.DateTimePickerOptions{Label: "Meeting", Location: tokyo, MinuteStep: 15}
if mujica.DateTimePicker(c, &meeting, opts).Changed() {
core.Use(c, core.Settings{})
opts := datetime.DateTimePickerOptions{Label: "Meeting", Location: tokyo, MinuteStep: 15}
if datetime.DateTimePicker(c, &meeting, opts).Changed() {
fmt.Println("meeting", meeting)
}
}
ui.Render(view, 400, 300, 1)`
dtMonthCode = `var month mujica.YearMonth
dtMonthCode = `var month datetime.YearMonth
view := func(c *ui.Context) {
mujica.Use(c, mujica.Settings{})
opts := mujica.MonthPickerOptions{Label: "Billing month", Clearable: true}
if mujica.MonthPicker(c, &month, opts).Changed() {
core.Use(c, core.Settings{})
opts := datetime.MonthPickerOptions{Label: "Billing month", Clearable: true}
if datetime.MonthPicker(c, &month, opts).Changed() {
fmt.Println("month", month)
}
}
ui.Render(view, 400, 300, 1)`
dtYearCode = `year := 2026
view := func(c *ui.Context) {
mujica.Use(c, mujica.Settings{})
opts := mujica.YearPickerOptions{Label: "Vintage", Min: 1990, Max: 2030}
if mujica.YearPicker(c, &year, opts).Changed() {
core.Use(c, core.Settings{})
opts := datetime.YearPickerOptions{Label: "Vintage", Min: 1990, Max: 2030}
if datetime.YearPicker(c, &year, opts).Changed() {
fmt.Println("year", year)
}
}
+52 -52
View File
@@ -1,19 +1,19 @@
package main
const devStatCardCode = `value := "12,480"
opts := mujica.StatCardOptions{Label: "Requests", Change: 0.12, HasChange: true, Trend: []float64{3, 5, 4, 8}}
opts := devtools.StatCardOptions{Label: "Requests", Change: 0.12, HasChange: true, Trend: []float64{3, 5, 4, 8}}
view := func(c *ui.Context) {
mujica.Use(c, mujica.Settings{})
if mujica.StatCard(c, value, opts).Changed() {
core.Use(c, core.Settings{})
if devtools.StatCard(c, value, opts).Changed() {
fmt.Println("retry")
}
}
ui.Render(view, 720, 480, 1)`
const devAlertListCode = `alerts := []mujica.AlertItem{{ID: "cpu", Title: "CPU usage high", Level: mujica.AlertWarning, Source: "worker", At: time.Date(2026, 10, 5, 9, 0, 0, 0, time.UTC)}}
const devAlertListCode = `alerts := []devtools.AlertItem{{ID: "cpu", Title: "CPU usage high", Level: devtools.AlertWarning, Source: "worker", At: time.Date(2026, 10, 5, 9, 0, 0, 0, time.UTC)}}
view := func(c *ui.Context) {
mujica.Use(c, mujica.Settings{})
r := mujica.AlertList(c, alerts, mujica.AlertListOptions{})
core.Use(c, core.Settings{})
r := devtools.AlertList(c, alerts, devtools.AlertListOptions{})
if r.Ack != "" {
fmt.Println("ack", r.Ack)
}
@@ -23,17 +23,17 @@ view := func(c *ui.Context) {
}
ui.Render(view, 720, 480, 1)`
const devIncidentCardCode = `incident := mujica.Incident{Title: "API latency", Status: mujica.IncidentMonitoring, Impact: mujica.ImpactMinor, Updates: []mujica.IncidentUpdate{{Status: mujica.IncidentIdentified, Text: "Slow query located"}, {Status: mujica.IncidentMonitoring, Text: "Recovery observed"}}}
const devIncidentCardCode = `incident := devtools.Incident{Title: "API latency", Status: devtools.IncidentMonitoring, Impact: devtools.ImpactMinor, Updates: []devtools.IncidentUpdate{{Status: devtools.IncidentIdentified, Text: "Slow query located"}, {Status: devtools.IncidentMonitoring, Text: "Recovery observed"}}}
view := func(c *ui.Context) {
mujica.Use(c, mujica.Settings{})
mujica.IncidentCard(c, incident)
core.Use(c, core.Settings{})
devtools.IncidentCard(c, incident)
}
ui.Render(view, 720, 480, 1)`
const devUptimeBarCode = `days := []mujica.UptimeDay{{Date: mujica.Date{Year: 2026, Month: 10, Day: 1}, Uptime: 1}, {Date: mujica.Date{Year: 2026, Month: 10, Day: 2}, Uptime: 0.91, Note: "Recovery"}}
const devUptimeBarCode = `days := []devtools.UptimeDay{{Date: core.Date{Year: 2026, Month: 10, Day: 1}, Uptime: 1}, {Date: core.Date{Year: 2026, Month: 10, Day: 2}, Uptime: 0.91, Note: "Recovery"}}
view := func(c *ui.Context) {
mujica.Use(c, mujica.Settings{})
r := mujica.UptimeBar(c, days, mujica.UptimeBarOptions{Frame: mujica.ChartFrame{Label: "API uptime"}})
core.Use(c, core.Settings{})
r := devtools.UptimeBar(c, days, devtools.UptimeBarOptions{Frame: chart.ChartFrame{Label: "API uptime"}})
if r.Changed() {
fmt.Println("day", r.Point)
}
@@ -43,46 +43,46 @@ view := func(c *ui.Context) {
}
ui.Render(view, 720, 480, 1)`
const devServiceStatusCode = `services := []mujica.ServiceItem{{Name: "API", Health: mujica.ServiceOperational}, {Name: "Search", Health: mujica.ServiceDegraded, Note: "Slow queries"}}
const devServiceStatusCode = `services := []devtools.ServiceItem{{Name: "API", Health: devtools.ServiceOperational}, {Name: "Search", Health: devtools.ServiceDegraded, Note: "Slow queries"}}
view := func(c *ui.Context) {
mujica.Use(c, mujica.Settings{})
mujica.ServiceStatus(c, services, mujica.ServiceStatusOptions{})
core.Use(c, core.Settings{})
devtools.ServiceStatus(c, services, devtools.ServiceStatusOptions{})
}
ui.Render(view, 720, 480, 1)`
const devLogStreamCode = `lines := []mujica.LogLine{{Level: mujica.LogInfo, Text: "server ready"}, {Level: mujica.LogWarn, Text: "slow query"}}
var state mujica.LogStreamState
const devLogStreamCode = `lines := []code.LogLine{{Level: code.LogInfo, Text: "server ready"}, {Level: code.LogWarn, Text: "slow query"}}
var state devtools.LogStreamState
view := func(c *ui.Context) {
mujica.Use(c, mujica.Settings{})
if mujica.LogStream(c, lines, &state, mujica.LogStreamOptions{}).Changed() {
core.Use(c, core.Settings{})
if devtools.LogStream(c, lines, &state, devtools.LogStreamOptions{}).Changed() {
fmt.Println("filter changed")
}
}
ui.Render(view, 720, 480, 1)`
const devTraceWaterfallCode = `spans := []mujica.WaterfallSpan{{ID: "root", Name: "GET /api", Service: "gateway", Duration: 200 * time.Millisecond}, {ID: "db", Parent: "root", Name: "Query", Service: "database", Start: 20 * time.Millisecond, Duration: 80 * time.Millisecond}}
const devTraceWaterfallCode = `spans := []devtools.WaterfallSpan{{ID: "root", Name: "GET /api", Service: "gateway", Duration: 200 * time.Millisecond}, {ID: "db", Parent: "root", Name: "Query", Service: "database", Start: 20 * time.Millisecond, Duration: 80 * time.Millisecond}}
view := func(c *ui.Context) {
mujica.Use(c, mujica.Settings{})
if mujica.TraceWaterfall(c, spans, mujica.TraceWaterfallOptions{}).Changed() {
core.Use(c, core.Settings{})
if devtools.TraceWaterfall(c, spans, devtools.TraceWaterfallOptions{}).Changed() {
fmt.Println("span selected")
}
}
ui.Render(view, 720, 480, 1)`
const devResourceGaugeCode = `readings := []mujica.ResourceReading{{Name: "CPU", Used: 45, Total: 100}, {Name: "Memory", Used: 6.4, Total: 8, Unit: "GB"}, {Name: "Disk", Used: 470, Total: 500, Unit: "GB"}}
const devResourceGaugeCode = `readings := []devtools.ResourceReading{{Name: "CPU", Used: 45, Total: 100}, {Name: "Memory", Used: 6.4, Total: 8, Unit: "GB"}, {Name: "Disk", Used: 470, Total: 500, Unit: "GB"}}
view := func(c *ui.Context) {
mujica.Use(c, mujica.Settings{})
if mujica.ResourceGauge(c, readings, mujica.ResourceGaugeOptions{}).Changed() {
core.Use(c, core.Settings{})
if devtools.ResourceGauge(c, readings, devtools.ResourceGaugeOptions{}).Changed() {
fmt.Println("retry")
}
}
ui.Render(view, 720, 480, 1)`
const devSystemMonitorCode = `times := []time.Time{time.Date(2026, 10, 5, 9, 0, 0, 0, time.UTC), time.Date(2026, 10, 5, 9, 0, 5, 0, time.UTC)}
metrics := []mujica.MonitorMetric{{Name: "CPU", Unit: "%", Samples: []float64{42, 51}}, {Name: "Memory", Unit: "GB", Samples: []float64{3.2, 3.4}}}
metrics := []devtools.MonitorMetric{{Name: "CPU", Unit: "%", Samples: []float64{42, 51}}, {Name: "Memory", Unit: "GB", Samples: []float64{3.2, 3.4}}}
view := func(c *ui.Context) {
mujica.Use(c, mujica.Settings{})
if mujica.SystemMonitor(c, times, metrics, mujica.SystemMonitorOptions{}).Changed() {
core.Use(c, core.Settings{})
if devtools.SystemMonitor(c, times, metrics, devtools.SystemMonitorOptions{}).Changed() {
fmt.Println("retry")
}
}
@@ -90,8 +90,8 @@ ui.Render(view, 720, 480, 1)`
const devRefreshIntervalSelectorCode = `interval := 30 * time.Second
view := func(c *ui.Context) {
mujica.Use(c, mujica.Settings{})
r := mujica.RefreshIntervalSelector(c, &interval, mujica.RefreshIntervalSelectorOptions{})
core.Use(c, core.Settings{})
r := devtools.RefreshIntervalSelector(c, &interval, devtools.RefreshIntervalSelectorOptions{})
if r.Refresh {
fmt.Println("refresh")
}
@@ -101,21 +101,21 @@ view := func(c *ui.Context) {
}
ui.Render(view, 720, 480, 1)`
const devDashboardFilterBarCode = `var filters mujica.DashboardFilters
opts := mujica.DashboardFilterBarOptions{Envs: []string{"production", "staging"}, Tags: []string{"api", "db"}}
const devDashboardFilterBarCode = `var filters devtools.DashboardFilters
opts := devtools.DashboardFilterBarOptions{Envs: []string{"production", "staging"}, Tags: []string{"api", "db"}}
view := func(c *ui.Context) {
mujica.Use(c, mujica.Settings{})
if mujica.DashboardFilterBar(c, &filters, opts).Changed() {
core.Use(c, core.Settings{})
if devtools.DashboardFilterBar(c, &filters, opts).Changed() {
fmt.Println("filters changed")
}
}
ui.Render(view, 720, 480, 1)`
const devJsonViewerCode = `data := ` + "`" + `{"server":{"port":8080},"ready":true}` + "`" + `
var state mujica.JsonViewerState
var state devtools.JsonViewerState
view := func(c *ui.Context) {
mujica.Use(c, mujica.Settings{})
r := mujica.JsonViewer(c, data, &state, mujica.JsonViewerOptions{})
core.Use(c, core.Settings{})
r := devtools.JsonViewer(c, data, &state, devtools.JsonViewerOptions{})
if r.Copied != "" {
fmt.Println("path copied")
}
@@ -124,29 +124,29 @@ ui.Render(view, 720, 480, 1)`
const devJsonEditorCode = `text := ` + "`" + `{"port":8080,"ready":true}` + "`" + `
view := func(c *ui.Context) {
mujica.Use(c, mujica.Settings{})
r := mujica.JsonEditor(c, &text, mujica.JsonEditorOptions{})
core.Use(c, core.Settings{})
r := devtools.JsonEditor(c, &text, devtools.JsonEditorOptions{})
if r.Changed {
fmt.Println("valid", r.Valid)
}
}
ui.Render(view, 720, 480, 1)`
const devSchemaTreeCode = `roots := []mujica.SchemaNode{{Name: "app", Children: []mujica.SchemaNode{{Name: "users", Kind: mujica.SchemaTable, Children: []mujica.SchemaNode{{Name: "id", Kind: mujica.SchemaColumn, Detail: "bigint"}, {Name: "users_pk", Kind: mujica.SchemaIndex, Detail: "(id)"}}}}}}
var state mujica.SchemaTreeState
const devSchemaTreeCode = `roots := []devtools.SchemaNode{{Name: "app", Children: []devtools.SchemaNode{{Name: "users", Kind: devtools.SchemaTable, Children: []devtools.SchemaNode{{Name: "id", Kind: devtools.SchemaColumn, Detail: "bigint"}, {Name: "users_pk", Kind: devtools.SchemaIndex, Detail: "(id)"}}}}}}
var state devtools.SchemaTreeState
view := func(c *ui.Context) {
mujica.Use(c, mujica.Settings{})
if mujica.SchemaTree(c, &state, roots, mujica.SchemaTreeOptions{}).Changed() {
core.Use(c, core.Settings{})
if devtools.SchemaTree(c, &state, roots, devtools.SchemaTreeOptions{}).Changed() {
path, _ := state.Selected()
fmt.Println("selected", path)
}
}
ui.Render(view, 720, 480, 1)`
const devApiRequestBuilderCode = `request := mujica.ApiRequest{Method: "POST", URL: "https://example.com/api", Headers: []mujica.KeyValue{{Key: "Accept", Value: "application/json", Enabled: true}}}
const devApiRequestBuilderCode = `request := devtools.ApiRequest{Method: "POST", URL: "https://example.com/api", Headers: []devtools.KeyValue{{Key: "Accept", Value: "application/json", Enabled: true}}}
view := func(c *ui.Context) {
mujica.Use(c, mujica.Settings{})
r := mujica.ApiRequestBuilder(c, &request, mujica.ApiRequestBuilderOptions{})
core.Use(c, core.Settings{})
r := devtools.ApiRequestBuilder(c, &request, devtools.ApiRequestBuilderOptions{})
if r.Send {
fmt.Println("send", request.Method)
}
@@ -156,19 +156,19 @@ view := func(c *ui.Context) {
}
ui.Render(view, 720, 480, 1)`
const devResponseViewerCode = `response := &mujica.ApiResponse{Status: 200, Duration: 182 * time.Millisecond, Size: 32, Body: ` + "`" + `{"ready":true}` + "`" + `, Headers: []mujica.KeyValue{{Key: "Content-Type", Value: "application/json"}}}
const devResponseViewerCode = `response := &devtools.ApiResponse{Status: 200, Duration: 182 * time.Millisecond, Size: 32, Body: ` + "`" + `{"ready":true}` + "`" + `, Headers: []devtools.KeyValue{{Key: "Content-Type", Value: "application/json"}}}
view := func(c *ui.Context) {
mujica.Use(c, mujica.Settings{})
if mujica.ResponseViewer(c, response, mujica.ResponseViewerOptions{}).Changed() {
core.Use(c, core.Settings{})
if devtools.ResponseViewer(c, response, devtools.ResponseViewerOptions{}).Changed() {
fmt.Println("retry")
}
}
ui.Render(view, 720, 480, 1)`
const devKeyValueInputCode = `rows := []mujica.KeyValue{{Key: "Accept", Value: "application/json", Enabled: true}}
const devKeyValueInputCode = `rows := []devtools.KeyValue{{Key: "Accept", Value: "application/json", Enabled: true}}
view := func(c *ui.Context) {
mujica.Use(c, mujica.Settings{})
if mujica.KeyValueInput(c, &rows, mujica.KeyValueInputOptions{}).Changed() {
core.Use(c, core.Settings{})
if devtools.KeyValueInput(c, &rows, devtools.KeyValueInputOptions{}).Changed() {
fmt.Println("rows", len(rows))
}
}
+21 -20
View File
@@ -6,18 +6,19 @@ import (
"github.com/egoist/mygo/ui"
mujica "mujicaui"
"github.com/ZacharyZhang-NY/MujicaUI/devtools"
"github.com/ZacharyZhang-NY/MujicaUI/input"
)
func devJsonViewerDemo(c *ui.Context, log func(string)) {
dtSection(c, "JsonViewer", func() {
s := devControls(c, "453", log, false)
type state struct {
tree mujica.JsonViewerState
tree devtools.JsonViewerState
invalid bool
}
st := ui.Local(c.Root(), "453", func() state { return state{} })
if mujica.Checkbox(c, &st.invalid, "Invalid JSON", mujica.CheckboxOptions{}).Changed() {
if input.Checkbox(c, &st.invalid, "Invalid JSON", input.CheckboxOptions{}).Changed() {
log("JSON validity changed")
}
devPreview(c, s, func() {
@@ -28,7 +29,7 @@ func devJsonViewerDemo(c *ui.Context, log func(string)) {
if s.empty {
data = `{}`
}
if mujica.JsonViewer(c, data, &st.tree, mujica.JsonViewerOptions{}).Copied != "" {
if devtools.JsonViewer(c, data, &st.tree, devtools.JsonViewerOptions{}).Copied != "" {
log("path copied")
}
})
@@ -39,12 +40,12 @@ func devJsonEditorDemo(c *ui.Context, log func(string)) {
dtSection(c, "JsonEditor", func() {
s := devControls(c, "454", log, false)
text := ui.Local(c.Root(), "454", func() string { return `{"port":8080,"ready":true}` })
if mujica.Button(c, "Invalid sample", mujica.ButtonOptions{}).Clicked() {
if input.Button(c, "Invalid sample", input.ButtonOptions{}).Clicked() {
*text = "{\n \"port\": [1,2,]\n}"
log("invalid sample loaded")
}
devPreview(c, s, func() {
r := mujica.JsonEditor(c, text, mujica.JsonEditorOptions{})
r := devtools.JsonEditor(c, text, devtools.JsonEditorOptions{})
if r.Changed {
log(fmt.Sprintf("JSON edited; valid=%t", r.Valid))
}
@@ -56,18 +57,18 @@ func devSchemaTreeDemo(c *ui.Context, log func(string)) {
dtSection(c, "SchemaTree", func() {
s := devControls(c, "455", log, false)
type state struct {
tree mujica.SchemaTreeState
roots []mujica.SchemaNode
tree devtools.SchemaTreeState
roots []devtools.SchemaNode
}
st := ui.Local(c.Root(), "455", func() state {
return state{roots: []mujica.SchemaNode{{Name: "app", Children: []mujica.SchemaNode{{Name: "users", Kind: mujica.SchemaTable, Children: []mujica.SchemaNode{{Name: "id", Kind: mujica.SchemaColumn, Detail: "bigint"}, {Name: "name", Kind: mujica.SchemaColumn, Detail: "text"}, {Name: "users_pk", Kind: mujica.SchemaIndex, Detail: "(id)"}}}, {Name: "active_users", Kind: mujica.SchemaView}}}}}
return state{roots: []devtools.SchemaNode{{Name: "app", Children: []devtools.SchemaNode{{Name: "users", Kind: devtools.SchemaTable, Children: []devtools.SchemaNode{{Name: "id", Kind: devtools.SchemaColumn, Detail: "bigint"}, {Name: "name", Kind: devtools.SchemaColumn, Detail: "text"}, {Name: "users_pk", Kind: devtools.SchemaIndex, Detail: "(id)"}}}, {Name: "active_users", Kind: devtools.SchemaView}}}}}
})
devPreview(c, s, func() {
roots := st.roots
if s.empty {
roots = nil
}
if mujica.SchemaTree(c, &st.tree, roots, mujica.SchemaTreeOptions{}).Changed() {
if devtools.SchemaTree(c, &st.tree, roots, devtools.SchemaTreeOptions{}).Changed() {
log("schema node selected")
}
})
@@ -77,15 +78,15 @@ func devSchemaTreeDemo(c *ui.Context, log func(string)) {
func devApiRequestBuilderDemo(c *ui.Context, log func(string)) {
dtSection(c, "ApiRequestBuilder", func() {
s := devControls(c, "456", log, false)
request := ui.Local(c.Root(), "456", func() mujica.ApiRequest {
return mujica.ApiRequest{URL: "https://example.com/api", Headers: []mujica.KeyValue{{Key: "Accept", Value: "application/json", Enabled: true}}}
request := ui.Local(c.Root(), "456", func() devtools.ApiRequest {
return devtools.ApiRequest{URL: "https://example.com/api", Headers: []devtools.KeyValue{{Key: "Accept", Value: "application/json", Enabled: true}}}
})
sending := ui.Local(c.Root(), "456-sending", func() bool { return false })
if mujica.Checkbox(c, sending, "Sending", mujica.CheckboxOptions{}).Changed() {
if input.Checkbox(c, sending, "Sending", input.CheckboxOptions{}).Changed() {
log("send state changed")
}
devPreview(c, s, func() {
r := mujica.ApiRequestBuilder(c, request, mujica.ApiRequestBuilderOptions{Sending: *sending})
r := devtools.ApiRequestBuilder(c, request, devtools.ApiRequestBuilderOptions{Sending: *sending})
if r.Send {
log("send requested")
}
@@ -100,18 +101,18 @@ func devResponseViewerDemo(c *ui.Context, log func(string)) {
dtSection(c, "ResponseViewer", func() {
s := devControls(c, "457", log, true)
text := ui.Local(c.Root(), "457", func() bool { return false })
if mujica.Checkbox(c, text, "Text body", mujica.CheckboxOptions{}).Changed() {
if input.Checkbox(c, text, "Text body", input.CheckboxOptions{}).Changed() {
log("response format changed")
}
devPreview(c, s, func() {
resp := &mujica.ApiResponse{Status: 200, Duration: 182 * time.Millisecond, Size: 2048, Body: `{"ready":true,"requests":1248}`, Headers: []mujica.KeyValue{{Key: "Content-Type", Value: "application/json"}, {Key: "X-Trace", Value: "demo"}}}
resp := &devtools.ApiResponse{Status: 200, Duration: 182 * time.Millisecond, Size: 2048, Body: `{"ready":true,"requests":1248}`, Headers: []devtools.KeyValue{{Key: "Content-Type", Value: "application/json"}, {Key: "X-Trace", Value: "demo"}}}
if *text {
resp.Body = "Response received successfully."
}
if s.empty {
resp = nil
}
if mujica.ResponseViewer(c, resp, mujica.ResponseViewerOptions{Height: 260, Status: s.status, Error: "Connection refused"}).Changed() {
if devtools.ResponseViewer(c, resp, devtools.ResponseViewerOptions{Height: 260, Status: s.status, Error: "Connection refused"}).Changed() {
log("retry requested")
}
})
@@ -121,11 +122,11 @@ func devResponseViewerDemo(c *ui.Context, log func(string)) {
func devKeyValueInputDemo(c *ui.Context, log func(string)) {
dtSection(c, "KeyValueInput", func() {
s := devControls(c, "458", log, false)
rows := ui.Local(c.Root(), "458", func() []mujica.KeyValue {
return []mujica.KeyValue{{Key: "Accept", Value: "application/json", Enabled: true}, {Key: "debug", Value: "true"}}
rows := ui.Local(c.Root(), "458", func() []devtools.KeyValue {
return []devtools.KeyValue{{Key: "Accept", Value: "application/json", Enabled: true}, {Key: "debug", Value: "true"}}
})
devPreview(c, s, func() {
if mujica.KeyValueInput(c, rows, mujica.KeyValueInputOptions{Label: "Headers"}).Changed() {
if devtools.KeyValueInput(c, rows, devtools.KeyValueInputOptions{Label: "Headers"}).Changed() {
log("pairs edited")
}
})
+33 -29
View File
@@ -6,31 +6,35 @@ import (
"github.com/egoist/mygo/ui"
mujica "mujicaui"
"github.com/ZacharyZhang-NY/MujicaUI/chart"
"github.com/ZacharyZhang-NY/MujicaUI/core"
"github.com/ZacharyZhang-NY/MujicaUI/data"
"github.com/ZacharyZhang-NY/MujicaUI/devtools"
"github.com/ZacharyZhang-NY/MujicaUI/input"
)
type devDemoSettings struct {
disabled bool
status mujica.DataStatus
status data.DataStatus
empty bool
}
func devControls(c *ui.Context, id string, log func(string), states bool) *devDemoSettings {
s := ui.Local(c.Root(), id+"-controls", func() devDemoSettings { return devDemoSettings{} })
dtControls(c, func() {
if mujica.Checkbox(c, &s.disabled, "Disabled", mujica.CheckboxOptions{}).Changed() {
if input.Checkbox(c, &s.disabled, "Disabled", input.CheckboxOptions{}).Changed() {
log("disabled changed")
}
switch id {
case "451", "454", "456", "458":
default:
if mujica.Checkbox(c, &s.empty, "Empty data", mujica.CheckboxOptions{}).Changed() {
if input.Checkbox(c, &s.empty, "Empty data", input.CheckboxOptions{}).Changed() {
log("data state changed")
}
}
if states {
options := []mujica.SelectOption[mujica.DataStatus]{{Value: mujica.DataReady, Label: "Ready"}, {Value: mujica.DataLoading, Label: "Loading"}, {Value: mujica.DataFailed, Label: "Failed"}}
if mujica.Select(c, &s.status, options, mujica.SelectOptions{Label: "Status"}).Changed() {
options := []input.SelectOption[data.DataStatus]{{Value: data.DataReady, Label: "Ready"}, {Value: data.DataLoading, Label: "Loading"}, {Value: data.DataFailed, Label: "Failed"}}
if input.Select(c, &s.status, options, input.SelectOptions{Label: "Status"}).Changed() {
log("status changed")
}
}
@@ -46,7 +50,7 @@ func devStatCardDemo(c *ui.Context, log func(string)) {
dtSection(c, "StatCard", func() {
s := devControls(c, "442", log, true)
lower := ui.Local(c.Root(), "442-lower", func() bool { return false })
if mujica.Checkbox(c, lower, "Lower is better", mujica.CheckboxOptions{}).Changed() {
if input.Checkbox(c, lower, "Lower is better", input.CheckboxOptions{}).Changed() {
log("change direction updated")
}
devPreview(c, s, func() {
@@ -55,7 +59,7 @@ func devStatCardDemo(c *ui.Context, log func(string)) {
if s.empty {
value, trend = "—", nil
}
if mujica.StatCard(c, value, mujica.StatCardOptions{Label: "Requests", Change: 0.12, HasChange: true, LowerIsBetter: *lower, Trend: trend, Status: s.status, Error: "Source unreachable"}).Changed() {
if devtools.StatCard(c, value, devtools.StatCardOptions{Label: "Requests", Change: 0.12, HasChange: true, LowerIsBetter: *lower, Trend: trend, Status: s.status, Error: "Source unreachable"}).Changed() {
log("retry requested")
}
})
@@ -65,15 +69,15 @@ func devStatCardDemo(c *ui.Context, log func(string)) {
func devAlertListDemo(c *ui.Context, log func(string)) {
dtSection(c, "AlertList", func() {
s := devControls(c, "443", log, false)
alerts := ui.Local(c.Root(), "443", func() []mujica.AlertItem {
return []mujica.AlertItem{{ID: "cpu", Title: "CPU usage high", Level: mujica.AlertWarning, Source: "worker", At: time.Now()}, {ID: "api", Title: "API unavailable", Level: mujica.AlertCritical, Source: "gateway", At: time.Now()}}
alerts := ui.Local(c.Root(), "443", func() []devtools.AlertItem {
return []devtools.AlertItem{{ID: "cpu", Title: "CPU usage high", Level: devtools.AlertWarning, Source: "worker", At: time.Now()}, {ID: "api", Title: "API unavailable", Level: devtools.AlertCritical, Source: "gateway", At: time.Now()}}
})
devPreview(c, s, func() {
shown := *alerts
if s.empty {
shown = nil
}
r := mujica.AlertList(c, shown, mujica.AlertListOptions{})
r := devtools.AlertList(c, shown, devtools.AlertListOptions{})
for i := range *alerts {
if r.Ack == (*alerts)[i].ID {
(*alerts)[i].Acked = true
@@ -91,17 +95,17 @@ func devAlertListDemo(c *ui.Context, log func(string)) {
func devIncidentCardDemo(c *ui.Context, log func(string)) {
dtSection(c, "IncidentCard", func() {
s := devControls(c, "444", log, false)
status := ui.Local(c.Root(), "444", func() mujica.IncidentStatus { return mujica.IncidentMonitoring })
choices := []mujica.SelectOption[mujica.IncidentStatus]{{Value: mujica.IncidentInvestigating, Label: "Investigating"}, {Value: mujica.IncidentIdentified, Label: "Identified"}, {Value: mujica.IncidentMonitoring, Label: "Monitoring"}, {Value: mujica.IncidentResolved, Label: "Resolved"}}
if mujica.Select(c, status, choices, mujica.SelectOptions{Label: "Incident status"}).Changed() {
status := ui.Local(c.Root(), "444", func() devtools.IncidentStatus { return devtools.IncidentMonitoring })
choices := []input.SelectOption[devtools.IncidentStatus]{{Value: devtools.IncidentInvestigating, Label: "Investigating"}, {Value: devtools.IncidentIdentified, Label: "Identified"}, {Value: devtools.IncidentMonitoring, Label: "Monitoring"}, {Value: devtools.IncidentResolved, Label: "Resolved"}}
if input.Select(c, status, choices, input.SelectOptions{Label: "Incident status"}).Changed() {
log("incident status changed")
}
devPreview(c, s, func() {
updates := []mujica.IncidentUpdate{{Status: mujica.IncidentIdentified, Text: "Slow query located"}, {Status: mujica.IncidentMonitoring, Text: "Recovery observed"}}
updates := []devtools.IncidentUpdate{{Status: devtools.IncidentIdentified, Text: "Slow query located"}, {Status: devtools.IncidentMonitoring, Text: "Recovery observed"}}
if s.empty {
updates = nil
}
mujica.IncidentCard(c, mujica.Incident{Title: "API latency", Status: *status, Impact: mujica.ImpactMinor, Updates: updates})
devtools.IncidentCard(c, devtools.Incident{Title: "API latency", Status: *status, Impact: devtools.ImpactMinor, Updates: updates})
})
})
}
@@ -109,11 +113,11 @@ func devIncidentCardDemo(c *ui.Context, log func(string)) {
func devUptimeBarDemo(c *ui.Context, log func(string)) {
dtSection(c, "UptimeBar", func() {
s := devControls(c, "445", log, true)
days := ui.Local(c.Root(), "445", func() []mujica.UptimeDay {
out := make([]mujica.UptimeDay, 30)
start := mujica.Date{Year: 2026, Month: 10, Day: 1}
days := ui.Local(c.Root(), "445", func() []devtools.UptimeDay {
out := make([]devtools.UptimeDay, 30)
start := core.Date{Year: 2026, Month: 10, Day: 1}
for i := range out {
out[i] = mujica.UptimeDay{Date: start.AddDays(i), Uptime: 1}
out[i] = devtools.UptimeDay{Date: start.AddDays(i), Uptime: 1}
}
out[7].Uptime, out[7].Note = 0.91, "2 h outage"
out[12].Uptime = 0.98
@@ -124,7 +128,7 @@ func devUptimeBarDemo(c *ui.Context, log func(string)) {
if s.empty {
data = nil
}
r := mujica.UptimeBar(c, data, mujica.UptimeBarOptions{Frame: mujica.ChartFrame{Label: "API uptime", Status: s.status, Error: "Source unreachable"}})
r := devtools.UptimeBar(c, data, devtools.UptimeBarOptions{Frame: chart.ChartFrame{Label: "API uptime", Status: s.status, Error: "Source unreachable"}})
if r.Changed() {
log(fmt.Sprintf("day %d", r.Point))
}
@@ -138,17 +142,17 @@ func devUptimeBarDemo(c *ui.Context, log func(string)) {
func devServiceStatusDemo(c *ui.Context, log func(string)) {
dtSection(c, "ServiceStatus", func() {
s := devControls(c, "446", log, false)
health := ui.Local(c.Root(), "446", func() mujica.ServiceHealth { return mujica.ServiceDegraded })
choices := []mujica.SelectOption[mujica.ServiceHealth]{{Value: mujica.ServiceOperational, Label: "Operational"}, {Value: mujica.ServiceDegraded, Label: "Degraded"}, {Value: mujica.ServiceOutage, Label: "Outage"}}
if mujica.Select(c, health, choices, mujica.SelectOptions{Label: "Search health"}).Changed() {
health := ui.Local(c.Root(), "446", func() devtools.ServiceHealth { return devtools.ServiceDegraded })
choices := []input.SelectOption[devtools.ServiceHealth]{{Value: devtools.ServiceOperational, Label: "Operational"}, {Value: devtools.ServiceDegraded, Label: "Degraded"}, {Value: devtools.ServiceOutage, Label: "Outage"}}
if input.Select(c, health, choices, input.SelectOptions{Label: "Search health"}).Changed() {
log("service health changed")
}
devPreview(c, s, func() {
data := []mujica.ServiceItem{{Name: "API", Health: mujica.ServiceOperational}, {Name: "Search", Health: *health, Note: "Search cluster"}}
data := []devtools.ServiceItem{{Name: "API", Health: devtools.ServiceOperational}, {Name: "Search", Health: *health, Note: "Search cluster"}}
if s.empty {
data = nil
}
mujica.ServiceStatus(c, data, mujica.ServiceStatusOptions{})
devtools.ServiceStatus(c, data, devtools.ServiceStatusOptions{})
})
})
}
@@ -157,15 +161,15 @@ func devResourceGaugeDemo(c *ui.Context, log func(string)) {
dtSection(c, "ResourceGauge", func() {
s := devControls(c, "449", log, true)
used := ui.Local(c.Root(), "449", func() float64 { return 45 })
if mujica.NumberInput(c, used, mujica.NumberInputOptions{Min: 0, Max: 100, Step: 5, Label: "CPU usage"}).Changed() {
if input.NumberInput(c, used, input.NumberInputOptions{Min: 0, Max: 100, Step: 5, Label: "CPU usage"}).Changed() {
log("resource reading changed")
}
devPreview(c, s, func() {
data := []mujica.ResourceReading{{Name: "CPU", Used: *used, Total: 100}, {Name: "Memory", Used: 6.4, Total: 8, Unit: "GB"}, {Name: "Disk", Used: 470, Total: 500, Unit: "GB"}}
data := []devtools.ResourceReading{{Name: "CPU", Used: *used, Total: 100}, {Name: "Memory", Used: 6.4, Total: 8, Unit: "GB"}, {Name: "Disk", Used: 470, Total: 500, Unit: "GB"}}
if s.empty {
data = nil
}
if mujica.ResourceGauge(c, data, mujica.ResourceGaugeOptions{Status: s.status, Error: "Source unreachable"}).Changed() {
if devtools.ResourceGauge(c, data, devtools.ResourceGaugeOptions{Status: s.status, Error: "Source unreachable"}).Changed() {
log("retry requested")
}
})
+22 -20
View File
@@ -6,21 +6,23 @@ import (
"github.com/egoist/mygo/ui"
mujica "mujicaui"
"github.com/ZacharyZhang-NY/MujicaUI/code"
"github.com/ZacharyZhang-NY/MujicaUI/devtools"
"github.com/ZacharyZhang-NY/MujicaUI/input"
)
func devLogStreamDemo(c *ui.Context, log func(string)) {
dtSection(c, "LogStream", func() {
s := devControls(c, "447", log, false)
type state struct {
lines []mujica.LogLine
stream mujica.LogStreamState
lines []code.LogLine
stream devtools.LogStreamState
}
st := ui.Local(c.Root(), "447", func() state {
return state{lines: []mujica.LogLine{{Level: mujica.LogInfo, Text: "server ready"}, {Level: mujica.LogWarn, Text: "slow query"}}}
return state{lines: []code.LogLine{{Level: code.LogInfo, Text: "server ready"}, {Level: code.LogWarn, Text: "slow query"}}}
})
if mujica.Button(c, "Append line", mujica.ButtonOptions{}).Clicked() {
st.lines = append(st.lines, mujica.LogLine{Time: c.Now(), Level: mujica.LogInfo, Text: fmt.Sprintf("request %d completed", len(st.lines))})
if input.Button(c, "Append line", input.ButtonOptions{}).Clicked() {
st.lines = append(st.lines, code.LogLine{Time: c.Now(), Level: code.LogInfo, Text: fmt.Sprintf("request %d completed", len(st.lines))})
log("line appended")
}
devPreview(c, s, func() {
@@ -28,7 +30,7 @@ func devLogStreamDemo(c *ui.Context, log func(string)) {
if s.empty {
data = nil
}
if mujica.LogStream(c, data, &st.stream, mujica.LogStreamOptions{}).Changed() {
if devtools.LogStream(c, data, &st.stream, devtools.LogStreamOptions{}).Changed() {
log("pause or level changed")
}
})
@@ -38,15 +40,15 @@ func devLogStreamDemo(c *ui.Context, log func(string)) {
func devTraceWaterfallDemo(c *ui.Context, log func(string)) {
dtSection(c, "TraceWaterfall", func() {
s := devControls(c, "448", log, false)
spans := ui.Local(c.Root(), "448", func() []mujica.WaterfallSpan {
return []mujica.WaterfallSpan{{ID: "root", Name: "GET /api", Service: "gateway", Duration: 320 * time.Millisecond}, {ID: "db", Parent: "root", Name: "Query", Service: "database", Start: 40 * time.Millisecond, Duration: 90 * time.Millisecond}, {ID: "pay", Parent: "root", Name: "Charge", Service: "payments", Start: 230 * time.Millisecond, Duration: 80 * time.Millisecond, Error: true}}
spans := ui.Local(c.Root(), "448", func() []devtools.WaterfallSpan {
return []devtools.WaterfallSpan{{ID: "root", Name: "GET /api", Service: "gateway", Duration: 320 * time.Millisecond}, {ID: "db", Parent: "root", Name: "Query", Service: "database", Start: 40 * time.Millisecond, Duration: 90 * time.Millisecond}, {ID: "pay", Parent: "root", Name: "Charge", Service: "payments", Start: 230 * time.Millisecond, Duration: 80 * time.Millisecond, Error: true}}
})
devPreview(c, s, func() {
data := *spans
if s.empty {
data = nil
}
if mujica.TraceWaterfall(c, data, mujica.TraceWaterfallOptions{}).Changed() {
if devtools.TraceWaterfall(c, data, devtools.TraceWaterfallOptions{}).Changed() {
log("span selected")
}
})
@@ -58,10 +60,10 @@ func devSystemMonitorDemo(c *ui.Context, log func(string)) {
s := devControls(c, "450", log, true)
type state struct {
times []time.Time
metrics []mujica.MonitorMetric
metrics []devtools.MonitorMetric
}
st := ui.Local(c.Root(), "450", func() state {
out := state{metrics: []mujica.MonitorMetric{{Name: "CPU", Unit: "%"}, {Name: "Memory", Unit: "GB"}}}
out := state{metrics: []devtools.MonitorMetric{{Name: "CPU", Unit: "%"}, {Name: "Memory", Unit: "GB"}}}
for i := range 20 {
out.times = append(out.times, time.Date(2026, 10, 5, 9, 0, i*5, 0, time.UTC))
out.metrics[0].Samples = append(out.metrics[0].Samples, 40+float64(i%7)*3)
@@ -69,7 +71,7 @@ func devSystemMonitorDemo(c *ui.Context, log func(string)) {
}
return out
})
if mujica.Button(c, "Append sample", mujica.ButtonOptions{}).Clicked() {
if input.Button(c, "Append sample", input.ButtonOptions{}).Clicked() {
st.times = append(append([]time.Time(nil), st.times...), st.times[len(st.times)-1].Add(5*time.Second))
for i := range st.metrics {
last := st.metrics[i].Samples[len(st.metrics[i].Samples)-1]
@@ -82,7 +84,7 @@ func devSystemMonitorDemo(c *ui.Context, log func(string)) {
if s.empty {
times, metrics = nil, nil
}
if mujica.SystemMonitor(c, times, metrics, mujica.SystemMonitorOptions{Status: s.status, Error: "Agent offline"}).Changed() {
if devtools.SystemMonitor(c, times, metrics, devtools.SystemMonitorOptions{Status: s.status, Error: "Agent offline"}).Changed() {
log("retry requested")
}
})
@@ -94,11 +96,11 @@ func devRefreshIntervalSelectorDemo(c *ui.Context, log func(string)) {
s := devControls(c, "451", log, false)
interval := ui.Local(c.Root(), "451", func() time.Duration { return 0 })
refreshing := ui.Local(c.Root(), "451-refreshing", func() bool { return false })
if mujica.Checkbox(c, refreshing, "Refreshing", mujica.CheckboxOptions{}).Changed() {
if input.Checkbox(c, refreshing, "Refreshing", input.CheckboxOptions{}).Changed() {
log("refresh state changed")
}
devPreview(c, s, func() {
r := mujica.RefreshIntervalSelector(c, interval, mujica.RefreshIntervalSelectorOptions{Refreshing: *refreshing})
r := devtools.RefreshIntervalSelector(c, interval, devtools.RefreshIntervalSelectorOptions{Refreshing: *refreshing})
if r.Refresh {
log("refresh requested")
}
@@ -112,14 +114,14 @@ func devRefreshIntervalSelectorDemo(c *ui.Context, log func(string)) {
func devDashboardFilterBarDemo(c *ui.Context, log func(string)) {
dtSection(c, "DashboardFilterBar", func() {
s := devControls(c, "452", log, false)
filters := ui.Local(c.Root(), "452", func() mujica.DashboardFilters { return mujica.DashboardFilters{} })
filters := ui.Local(c.Root(), "452", func() devtools.DashboardFilters { return devtools.DashboardFilters{} })
devPreview(c, s, func() {
opts := mujica.DashboardFilterBarOptions{Envs: []string{"production", "staging"}, Tags: []string{"api", "db", "web"}}
opts := devtools.DashboardFilterBarOptions{Envs: []string{"production", "staging"}, Tags: []string{"api", "db", "web"}}
if s.empty {
opts.Envs, opts.Tags = nil, nil
*filters = mujica.DashboardFilters{}
*filters = devtools.DashboardFilters{}
}
if mujica.DashboardFilterBar(c, filters, opts).Changed() {
if devtools.DashboardFilterBar(c, filters, opts).Changed() {
log("filters changed")
}
})
+5 -5
View File
@@ -5,12 +5,12 @@ import (
"github.com/egoist/mygo/ui"
mujica "mujicaui"
"github.com/ZacharyZhang-NY/MujicaUI/core"
)
func devPageTester(p page, mode mujica.Mode, events *[]string) *ui.Tester {
func devPageTester(p page, mode core.Mode, events *[]string) *ui.Tester {
return ui.NewTester(func(c *ui.Context) {
mujica.Use(c, mujica.Settings{Mode: mode})
core.Use(c, core.Settings{Mode: mode})
ui.Column(c).Padding(16).Children(func() { p.Demo(c, func(s string) { *events = append(*events, s) }) })
}, 1000, 800)
}
@@ -21,7 +21,7 @@ func TestDevtoolsPagesControlsAndThemes(t *testing.T) {
}
for _, p := range devtoolsPages {
t.Run(p.Name, func(t *testing.T) {
for _, mode := range []mujica.Mode{mujica.Light, mujica.Dark} {
for _, mode := range []core.Mode{core.Light, core.Dark} {
var events []string
tt := devPageTester(p, mode, &events)
if p.Category != "Dashboards & Dev Tools" || p.Code == "" || p.Keys == "" {
@@ -70,7 +70,7 @@ func TestDevtoolsDemoEvents(t *testing.T) {
continue
}
var events []string
tt := devPageTester(p, mujica.Light, &events)
tt := devPageTester(p, core.Light, &events)
if err := tt.Click(tc.label); err != nil {
t.Fatal(err)
}
+59 -57
View File
@@ -6,7 +6,9 @@ import (
"github.com/egoist/mygo/ui"
mujica "mujicaui"
"github.com/ZacharyZhang-NY/MujicaUI/core"
"github.com/ZacharyZhang-NY/MujicaUI/display"
"github.com/ZacharyZhang-NY/MujicaUI/input"
)
var displayPages = []page{
@@ -43,7 +45,7 @@ type textState struct {
func textDemo(c *ui.Context, log func(string)) {
s := ui.Local(c.Root(), "011", func() textState { return textState{maxLines: 0, selectable: true, name: "Coronation.md"} })
mujica.Heading(c, "Text", mujica.HeadingOptions{Subtitle: "换行、省略、选择复制与可编辑模式", Divider: true})
display.Heading(c, "Text", display.HeadingOptions{Subtitle: "换行、省略、选择复制与可编辑模式", Divider: true})
displaySection(c, "Parameters", func() {
displayControls(c, func() {
ui.Text(c, fmt.Sprintf("MaxLines %d", int(s.maxLines)))
@@ -55,12 +57,12 @@ func textDemo(c *ui.Context, log func(string)) {
})
displaySection(c, "Display", func() {
ui.Box(c).Width(360).Children(func() {
mujica.Text(c, "Velvet curtains fall over the long gallery. Every candle leans toward the open door, and the quartet plays on while the guests take their places beneath the chandeliers.",
mujica.TextOptions{MaxLines: int(s.maxLines), Selectable: s.selectable})
display.Text(c, "Velvet curtains fall over the long gallery. Every candle leans toward the open door, and the quartet plays on while the guests take their places beneath the chandeliers.",
display.TextOptions{MaxLines: int(s.maxLines), Selectable: s.selectable})
})
})
displaySection(c, "Editable", func() {
e := mujica.EditableText(c, &s.name, mujica.EditableTextOptions{Saving: s.saving, Error: s.saveErr, Label: "File name"})
e := display.EditableText(c, &s.name, display.EditableTextOptions{Saving: s.saving, Error: s.saveErr, Label: "File name"})
if e.Changed() {
if s.failNext {
s.saveErr = "Save failed: the name is already taken."
@@ -76,9 +78,9 @@ func textDemo(c *ui.Context, log func(string)) {
})
}
const textCode = `mujica.Text(c, "Order ID: 4821-AX", mujica.TextOptions{Selectable: true})
mujica.Text(c, "A long note that stops after two lines.", mujica.TextOptions{MaxLines: 2})
if mujica.EditableText(c, &name, mujica.EditableTextOptions{Error: saveErr}).Changed() {
const textCode = `display.Text(c, "Order ID: 4821-AX", display.TextOptions{Selectable: true})
display.Text(c, "A long note that stops after two lines.", display.TextOptions{MaxLines: 2})
if display.EditableText(c, &name, display.EditableTextOptions{Error: saveErr}).Changed() {
saveErr = "" // save name here; set saveErr on failure
}`
@@ -89,7 +91,7 @@ type headingState struct {
func headingDemo(c *ui.Context, log func(string)) {
s := ui.Local(c.Root(), "012", func() headingState { return headingState{level: 1, subtitle: true, divide: true} })
mujica.Heading(c, "Heading", mujica.HeadingOptions{Subtitle: "三级衬线标题、副标题与装饰分隔线", Divider: true})
display.Heading(c, "Heading", display.HeadingOptions{Subtitle: "三级衬线标题、副标题与装饰分隔线", Divider: true})
displaySection(c, "Parameters", func() {
displayControls(c, func() {
ui.Text(c, fmt.Sprintf("Level %d", int(s.level)))
@@ -105,17 +107,17 @@ func headingDemo(c *ui.Context, log func(string)) {
if s.subtitle {
sub = "Ceremonies of the winter season"
}
mujica.Heading(c, "The Grand Hall", mujica.HeadingOptions{Level: int(s.level), Subtitle: sub, Divider: s.divide})
display.Heading(c, "The Grand Hall", display.HeadingOptions{Level: int(s.level), Subtitle: sub, Divider: s.divide})
})
displaySection(c, "Levels", func() {
for l := 1; l <= 3; l++ {
mujica.Heading(c, fmt.Sprintf("Level %d · 宫廷", l), mujica.HeadingOptions{Level: l})
display.Heading(c, fmt.Sprintf("Level %d · 宫廷", l), display.HeadingOptions{Level: l})
}
})
}
const headingCode = `mujica.Heading(c, "The Grand Hall", mujica.HeadingOptions{Subtitle: "Winter ceremonies", Divider: true})
mujica.Heading(c, "Guest Ledger", mujica.HeadingOptions{Level: 2})`
const headingCode = `display.Heading(c, "The Grand Hall", display.HeadingOptions{Subtitle: "Winter ceremonies", Divider: true})
display.Heading(c, "Guest Ledger", display.HeadingOptions{Level: 2})`
type iconState struct {
name string
@@ -124,12 +126,12 @@ type iconState struct {
label bool
}
var iconTones = map[string]mujica.IconTone{"Inherit": mujica.IconInherit, "Muted": mujica.IconMuted, "Accent": mujica.IconAccent,
"Success": mujica.IconSuccess, "Warning": mujica.IconWarning, "Danger": mujica.IconDanger, "Info": mujica.IconInfo}
var iconTones = map[string]display.IconTone{"Inherit": display.IconInherit, "Muted": display.IconMuted, "Accent": display.IconAccent,
"Success": display.IconSuccess, "Warning": display.IconWarning, "Danger": display.IconDanger, "Info": display.IconInfo}
func iconDemo(c *ui.Context, log func(string)) {
s := ui.Local(c.Root(), "013", func() iconState { return iconState{name: "star", size: "24", tone: "Accent"} })
mujica.Heading(c, "Icon", mujica.HeadingOptions{Subtitle: "按名称取图标;尺寸与语义颜色;缺失时可见回退", Divider: true})
display.Heading(c, "Icon", display.HeadingOptions{Subtitle: "按名称取图标;尺寸与语义颜色;缺失时可见回退", Divider: true})
displaySection(c, "Parameters", func() {
displayControls(c, func() {
if ui.Select(c, &s.name, []string{"star", "bell", "settings", "moon", "sun", "no-such-icon"}).Changed() {
@@ -151,24 +153,24 @@ func iconDemo(c *ui.Context, log func(string)) {
if s.label {
label = "Preview icon"
}
mujica.Icon(c, s.name, mujica.IconOptions{Size: size, Tone: iconTones[s.tone], Label: label})
display.Icon(c, s.name, display.IconOptions{Size: size, Tone: iconTones[s.tone], Label: label})
})
displaySection(c, "Tones and fallback", func() {
ui.Row(c).Gap(14).Children(func() {
for _, t := range []string{"Inherit", "Muted", "Accent", "Success", "Warning", "Danger", "Info"} {
mujica.Icon(c, "circle-check", mujica.IconOptions{Size: 20, Tone: iconTones[t]}).Key(t)
display.Icon(c, "circle-check", display.IconOptions{Size: 20, Tone: iconTones[t]}).Key(t)
}
mujica.Icon(c, "missing-name", mujica.IconOptions{Size: 20})
display.Icon(c, "missing-name", display.IconOptions{Size: 20})
})
})
}
const iconCode = `ui.Row(c).Gap(8).Children(func() {
mujica.Icon(c, "star", mujica.IconOptions{})
mujica.Icon(c, "circle-check", mujica.IconOptions{Size: 20, Tone: mujica.IconSuccess, Label: "Done"})
display.Icon(c, "star", display.IconOptions{})
display.Icon(c, "circle-check", display.IconOptions{Size: 20, Tone: display.IconSuccess, Label: "Done"})
})`
var displayPeople = []mujica.AvatarItem{
var displayPeople = []display.AvatarItem{
{Name: "Sakiko Togawa"}, {Name: "Uika Misumi"}, {Name: "Mutsumi Wakaba"}, {Name: "Umiri Yahata"},
{Name: "Nyamu Yutenji"}, {Name: "Tomori Takamatsu"}, {Name: "Anon Chihaya"}, {Name: "Rana Kaname"},
}
@@ -177,7 +179,7 @@ type avatarGroupState struct{ max, count float64 }
func avatarGroupDemo(c *ui.Context, log func(string)) {
s := ui.Local(c.Root(), "016", func() avatarGroupState { return avatarGroupState{max: 3, count: 6} })
mujica.Heading(c, "AvatarGroup", mujica.HeadingOptions{Subtitle: "最大显示数量、剩余数量与完整名单", Divider: true})
display.Heading(c, "AvatarGroup", display.HeadingOptions{Subtitle: "最大显示数量、剩余数量与完整名单", Divider: true})
displaySection(c, "Parameters", func() {
displayControls(c, func() {
ui.Text(c, fmt.Sprintf("Max %d", int(s.max)))
@@ -191,9 +193,9 @@ func avatarGroupDemo(c *ui.Context, log func(string)) {
})
})
displaySection(c, "Preview", func() {
for i, g := range []mujica.AvatarGroupView{
mujica.AvatarGroup(c, displayPeople[:int(s.count)], mujica.AvatarGroupOptions{Max: int(s.max)}),
mujica.AvatarGroup(c, displayPeople, mujica.AvatarGroupOptions{Max: 4, Size: 24}),
for i, g := range []display.AvatarGroupView{
display.AvatarGroup(c, displayPeople[:int(s.count)], display.AvatarGroupOptions{Max: int(s.max)}),
display.AvatarGroup(c, displayPeople, display.AvatarGroupOptions{Max: 4, Size: 24}),
} {
if g.Toggled {
log(fmt.Sprintf("group %d open=%v", i+1, g.Open))
@@ -202,7 +204,7 @@ func avatarGroupDemo(c *ui.Context, log func(string)) {
})
}
const avatarGroupCode = `mujica.AvatarGroup(c, people, mujica.AvatarGroupOptions{Max: 3})`
const avatarGroupCode = `display.AvatarGroup(c, people, display.AvatarGroupOptions{Max: 3})`
type badgeState struct {
count, max float64
@@ -210,14 +212,14 @@ type badgeState struct {
tone, pos string
}
var badgeTones = map[string]mujica.BadgeTone{"Accent": mujica.BadgeAccent, "Neutral": mujica.BadgeNeutral, "Success": mujica.BadgeSuccess,
"Warning": mujica.BadgeWarning, "Danger": mujica.BadgeDanger, "Info": mujica.BadgeInfo}
var badgePositions = map[string]mujica.BadgePosition{"TopRight": mujica.BadgeTopRight, "TopLeft": mujica.BadgeTopLeft,
"BottomRight": mujica.BadgeBottomRight, "BottomLeft": mujica.BadgeBottomLeft}
var badgeTones = map[string]display.BadgeTone{"Accent": display.BadgeAccent, "Neutral": display.BadgeNeutral, "Success": display.BadgeSuccess,
"Warning": display.BadgeWarning, "Danger": display.BadgeDanger, "Info": display.BadgeInfo}
var badgePositions = map[string]display.BadgePosition{"TopRight": display.BadgeTopRight, "TopLeft": display.BadgeTopLeft,
"BottomRight": display.BadgeBottomRight, "BottomLeft": display.BadgeBottomLeft}
func badgeDemo(c *ui.Context, log func(string)) {
s := ui.Local(c.Root(), "017", func() badgeState { return badgeState{count: 120, max: 99, tone: "Accent", pos: "TopRight"} })
mujica.Heading(c, "Badge", mujica.HeadingOptions{Subtitle: "文字或计数、上限、位置与完整朗读", Divider: true})
display.Heading(c, "Badge", display.HeadingOptions{Subtitle: "文字或计数、上限、位置与完整朗读", Divider: true})
displaySection(c, "Parameters", func() {
displayControls(c, func() {
ui.Text(c, fmt.Sprintf("Count %d", int(s.count)))
@@ -233,24 +235,24 @@ func badgeDemo(c *ui.Context, log func(string)) {
}
})
})
opts := mujica.BadgeOptions{Text: s.text, Count: int(s.count), Max: int(s.max), Tone: badgeTones[s.tone], Label: "Unread"}
opts := display.BadgeOptions{Text: s.text, Count: int(s.count), Max: int(s.max), Tone: badgeTones[s.tone], Label: "Unread"}
displaySection(c, "Inline", func() {
ui.Row(c).Gap(8).AlignItems(ui.Center).Children(func() {
ui.Text(c, "Inbox")
mujica.Badge(c, opts, nil)
display.Badge(c, opts, nil)
})
})
displaySection(c, "Corner", func() {
opts.Position = badgePositions[s.pos]
ui.Box(c).Padding(10).Children(func() {
mujica.Badge(c, opts, func() { mujica.Avatar(c, "Sakiko Togawa", mujica.AvatarOptions{Size: 40}) })
display.Badge(c, opts, func() { display.Avatar(c, "Sakiko Togawa", display.AvatarOptions{Size: 40}) })
})
})
}
const badgeCode = `mujica.Badge(c, mujica.BadgeOptions{Text: "New", Tone: mujica.BadgeNeutral}, nil)
mujica.Badge(c, mujica.BadgeOptions{Count: unread, Label: "Unread", Position: mujica.BadgeTopRight}, func() {
mujica.Avatar(c, "Sakiko Togawa", mujica.AvatarOptions{})
const badgeCode = `display.Badge(c, display.BadgeOptions{Text: "New", Tone: display.BadgeNeutral}, nil)
display.Badge(c, display.BadgeOptions{Count: unread, Label: "Unread", Position: display.BadgeTopRight}, func() {
display.Avatar(c, "Sakiko Togawa", display.AvatarOptions{})
})`
type tagState struct {
@@ -262,14 +264,14 @@ func tagDemo(c *ui.Context, log func(string)) {
s := ui.Local(c.Root(), "018", func() tagState {
return tagState{tags: []string{"Baroque", "Gothic", "Rococo", "Court"}, icon: true, clickable: true, closable: true}
})
mujica.Heading(c, "Tag", mujica.HeadingOptions{Subtitle: "标签文字、图标;点击与关闭分离", Divider: true})
display.Heading(c, "Tag", display.HeadingOptions{Subtitle: "标签文字、图标;点击与关闭分离", Divider: true})
displaySection(c, "Parameters", func() {
displayControls(c, func() {
displayLog(log, ui.Checkbox(c, &s.icon, "Icon"), "icon", &s.icon)
displayLog(log, ui.Checkbox(c, &s.clickable, "Clickable"), "clickable", &s.clickable)
displayLog(log, ui.Checkbox(c, &s.closable, "Closable"), "closable", &s.closable)
displayLog(log, ui.Checkbox(c, &s.dis, "Disabled"), "dis", &s.dis)
if mujica.Button(c, "Reset tags", mujica.ButtonOptions{Variant: mujica.Ghost}).Clicked() {
if input.Button(c, "Reset tags", input.ButtonOptions{Variant: input.Ghost}).Clicked() {
s.tags = []string{"Baroque", "Gothic", "Rococo", "Court"}
log("reset")
}
@@ -283,9 +285,9 @@ func tagDemo(c *ui.Context, log func(string)) {
ui.Row(c).Gap(8).Wrap().Children(func() {
for i := 0; i < len(s.tags); i++ {
t := s.tags[i]
var v mujica.TagView
var v display.TagView
ui.Box(c).Key(t).Children(func() {
v = mujica.Tag(c, t, mujica.TagOptions{Icon: icon, Clickable: s.clickable, Closable: s.closable, Disabled: s.dis})
v = display.Tag(c, t, display.TagOptions{Icon: icon, Clickable: s.clickable, Closable: s.closable, Disabled: s.dis})
})
if v.Clicked {
log("clicked " + t)
@@ -301,9 +303,9 @@ func tagDemo(c *ui.Context, log func(string)) {
}
const tagCode = `for i, t := range tags {
var v mujica.TagView
var v display.TagView
ui.Box(c).Key(t).Children(func() {
v = mujica.Tag(c, t, mujica.TagOptions{Icon: "star", Clickable: true, Closable: true})
v = display.Tag(c, t, display.TagOptions{Icon: "star", Clickable: true, Closable: true})
})
if v.Closed {
tags = append(tags[:i:i], tags[i+1:]...)
@@ -317,32 +319,32 @@ type linkState struct {
func linkDemo(c *ui.Context, log func(string)) {
s := ui.Local(c.Root(), "019", func() linkState { return linkState{url: "https://example.com/charter"} })
mujica.Heading(c, "Link", mujica.HeadingOptions{Subtitle: "外部地址或应用内动作;键盘激活;打开失败反馈", Divider: true})
display.Heading(c, "Link", display.HeadingOptions{Subtitle: "外部地址或应用内动作;键盘激活;打开失败反馈", Divider: true})
displaySection(c, "Parameters", func() {
displayControls(c, func() {
displayLog(log, ui.TextInput(c, &s.url).Placeholder("URL (empty = in-app action)").Width(320), "url", &s.url)
if mujica.Button(c, "Simulate open failure", mujica.ButtonOptions{Variant: mujica.Secondary}).Clicked() {
if input.Button(c, "Simulate open failure", input.ButtonOptions{Variant: input.Secondary}).Clicked() {
s.external = "No application can open this link."
log("open failure set")
}
if mujica.Button(c, "Clear error", mujica.ButtonOptions{Variant: mujica.Ghost}).Clicked() {
if input.Button(c, "Clear error", input.ButtonOptions{Variant: input.Ghost}).Clicked() {
s.external = ""
log("open failure cleared")
}
})
})
displaySection(c, "Preview", func() {
if mujica.Link(c, "Read the court charter", mujica.LinkOptions{URL: s.url, Error: s.external}).Clicked() {
if display.Link(c, "Read the court charter", display.LinkOptions{URL: s.url, Error: s.external}).Clicked() {
log("activated " + s.url)
}
if mujica.Link(c, "Show the guest list", mujica.LinkOptions{}).Clicked() {
if display.Link(c, "Show the guest list", display.LinkOptions{}).Clicked() {
log("in-app action")
}
})
}
const linkCode = `mujica.Link(c, "Read the charter", mujica.LinkOptions{URL: "https://example.com/charter"})
if mujica.Link(c, "Show details", mujica.LinkOptions{}).Clicked() {
const linkCode = `display.Link(c, "Read the charter", display.LinkOptions{URL: "https://example.com/charter"})
if display.Link(c, "Show details", display.LinkOptions{}).Clicked() {
details = !details
}`
@@ -350,7 +352,7 @@ type kbdState struct{ combo string }
func kbdDemo(c *ui.Context, log func(string)) {
s := ui.Local(c.Root(), "020", func() kbdState { return kbdState{combo: "Mod+Shift+P"} })
mujica.Heading(c, "Kbd", mujica.HeadingOptions{Subtitle: "单键与组合键;按平台显示 Command 或 Ctrl", Divider: true})
display.Heading(c, "Kbd", display.HeadingOptions{Subtitle: "单键与组合键;按平台显示 Command 或 Ctrl", Divider: true})
displaySection(c, "Parameters", func() {
displayControls(c, func() {
if ui.TextInput(c, &s.combo).Placeholder("Keys joined by +").Width(220).Changed() {
@@ -366,20 +368,20 @@ func kbdDemo(c *ui.Context, log func(string)) {
}
}
if len(keys) > 0 {
mujica.Kbd(c, keys...)
display.Kbd(c, keys...)
}
})
displaySection(c, "Common", func() {
for _, row := range [][]string{{"Mod", "K"}, {"Mod", "Shift", "P"}, {"Alt", "Up"}, {"Esc"}, {"Ctrl", "Tab"}} {
ui.Row(c).Gap(12).AlignItems(ui.Center).Children(func() {
mujica.Kbd(c, row...)
ui.Text(c, strings.Join(row, " + ")).TextColor(mujica.Tokens(c).TextMuted)
display.Kbd(c, row...)
ui.Text(c, strings.Join(row, " + ")).TextColor(core.Tokens(c).TextMuted)
})
}
})
}
const kbdCode = `ui.Row(c).Gap(6).Children(func() {
mujica.Text(c, "Command palette", mujica.TextOptions{})
mujica.Kbd(c, "Mod", "Shift", "P")
display.Text(c, "Command palette", display.TextOptions{})
display.Kbd(c, "Mod", "Shift", "P")
})`
+19 -18
View File
@@ -6,7 +6,8 @@ import (
"github.com/egoist/mygo/ui"
mujica "mujicaui"
"github.com/ZacharyZhang-NY/MujicaUI/core"
"github.com/ZacharyZhang-NY/MujicaUI/display"
)
type imageState struct {
@@ -22,7 +23,7 @@ var displayPicture = func() *ui.SVG {
func imageDemo(c *ui.Context, log func(string)) {
s := ui.Local(c.Root(), "014", func() imageState { return imageState{crop: true} })
mujica.Heading(c, "Image", mujica.HeadingOptions{Subtitle: "位图与 SVG;包含或裁切;占位、加载、错误;替代说明", Divider: true})
display.Heading(c, "Image", display.HeadingOptions{Subtitle: "位图与 SVG;包含或裁切;占位、加载、错误;替代说明", Divider: true})
displaySection(c, "Parameters", func() {
displayControls(c, func() {
displayLog(log, ui.Checkbox(c, &s.crop, "Crop"), "crop", &s.crop)
@@ -42,7 +43,7 @@ func imageDemo(c *ui.Context, log func(string)) {
if s.empty {
src = nil
}
v := mujica.Image(c, src, mujica.ImageOptions{Width: 200, Height: 200, Crop: s.crop, Alt: "Gothic emblem", Loading: s.loading, Err: s.err})
v := display.Image(c, src, display.ImageOptions{Width: 200, Height: 200, Crop: s.crop, Alt: "Gothic emblem", Loading: s.loading, Err: s.err})
if v.Retry {
s.fail, s.err = false, nil
log("retry")
@@ -50,18 +51,18 @@ func imageDemo(c *ui.Context, log func(string)) {
})
displaySection(c, "States", func() {
ui.Row(c).Gap(12).Wrap().Children(func() {
mujica.Image(c, displayPicture, mujica.ImageOptions{Width: 120, Height: 120})
mujica.Image(c, displayPicture, mujica.ImageOptions{Width: 120, Height: 120, Crop: true})
mujica.Image(c, nil, mujica.ImageOptions{Width: 120, Height: 120})
mujica.Image(c, nil, mujica.ImageOptions{Width: 120, Height: 120, Loading: true})
if mujica.Image(c, nil, mujica.ImageOptions{Width: 160, Height: 140, Err: errors.New("404")}).Retry {
display.Image(c, displayPicture, display.ImageOptions{Width: 120, Height: 120})
display.Image(c, displayPicture, display.ImageOptions{Width: 120, Height: 120, Crop: true})
display.Image(c, nil, display.ImageOptions{Width: 120, Height: 120})
display.Image(c, nil, display.ImageOptions{Width: 120, Height: 120, Loading: true})
if display.Image(c, nil, display.ImageOptions{Width: 160, Height: 140, Err: errors.New("404")}).Retry {
log("retry (static example)")
}
})
})
}
const imageCode = `img := mujica.Image(c, photo, mujica.ImageOptions{
const imageCode = `img := display.Image(c, photo, display.ImageOptions{
Width: 160, Height: 120, Crop: true, Alt: "Hall of mirrors",
Loading: loading, Err: loadErr,
})
@@ -77,14 +78,14 @@ type avatarState struct {
var displayBitmap = func() *ui.Bitmap {
return ui.NewBitmap(ui.Render(func(c *ui.Context) {
mujica.Use(c, mujica.Settings{Mode: mujica.Dark})
core.Use(c, core.Settings{Mode: core.Dark})
ui.Box(c).Fill().Background(ui.Hex("#7D2034"))
}, 64, 64, 1))
}()
func avatarDemo(c *ui.Context, log func(string)) {
s := ui.Local(c.Root(), "015", func() avatarState { return avatarState{name: "Sakiko Togawa", size: 48} })
mujica.Heading(c, "Avatar", mujica.HeadingOptions{Subtitle: "图片、姓名首字母与默认图标回退", Divider: true})
display.Heading(c, "Avatar", display.HeadingOptions{Subtitle: "图片、姓名首字母与默认图标回退", Divider: true})
displaySection(c, "Parameters", func() {
displayControls(c, func() {
if ui.TextInput(c, &s.name).Width(200).Changed() {
@@ -100,19 +101,19 @@ func avatarDemo(c *ui.Context, log func(string)) {
if s.img {
img = displayBitmap
}
mujica.Avatar(c, s.name, mujica.AvatarOptions{Size: float32(s.size), Image: img})
display.Avatar(c, s.name, display.AvatarOptions{Size: float32(s.size), Image: img})
})
displaySection(c, "Fallbacks", func() {
ui.Row(c).Gap(12).AlignItems(ui.Center).Children(func() {
mujica.Avatar(c, "Photo", mujica.AvatarOptions{Image: displayBitmap, Size: 40})
mujica.Avatar(c, "Uika Misumi", mujica.AvatarOptions{Size: 40})
mujica.Avatar(c, "若叶睦", mujica.AvatarOptions{Size: 40})
mujica.Avatar(c, "", mujica.AvatarOptions{Size: 40})
display.Avatar(c, "Photo", display.AvatarOptions{Image: displayBitmap, Size: 40})
display.Avatar(c, "Uika Misumi", display.AvatarOptions{Size: 40})
display.Avatar(c, "若叶睦", display.AvatarOptions{Size: 40})
display.Avatar(c, "", display.AvatarOptions{Size: 40})
})
})
}
const avatarCode = `ui.Row(c).Gap(8).Children(func() {
mujica.Avatar(c, "Sakiko Togawa", mujica.AvatarOptions{Size: 40})
mujica.Avatar(c, "", mujica.AvatarOptions{})
display.Avatar(c, "Sakiko Togawa", display.AvatarOptions{Size: 40})
display.Avatar(c, "", display.AvatarOptions{})
})`
+2 -2
View File
@@ -5,13 +5,13 @@ import (
"github.com/egoist/mygo/ui"
mujica "mujicaui"
"github.com/ZacharyZhang-NY/MujicaUI/display"
)
// displaySection shows a titled block of a demo.
func displaySection(c *ui.Context, title string, fn func()) {
ui.Column(c).Gap(10).Margin(0, 0, 20, 0).Children(func() {
mujica.Heading(c, title, mujica.HeadingOptions{Level: 3})
display.Heading(c, title, display.HeadingOptions{Level: 3})
fn()
})
}
+55 -55
View File
@@ -5,9 +5,9 @@ var essentialsPages = []page{
ID: "481", Name: "SplitButton", ZhName: "拆分按钮 主操作 下拉", Category: "Essentials",
Demo: splitButtonDemo,
Code: `view := func(c *ui.Context) {
mujica.Use(c, mujica.Settings{})
r := mujica.SplitButton(c, "Save", mujica.SplitButtonOptions{}, func(m *mujica.PopupMenu) {
if m.Item("Save as…", mujica.PopupMenuItemOptions{}) {
core.Use(c, core.Settings{})
r := overlay.SplitButton(c, "Save", overlay.SplitButtonOptions{}, func(m *overlay.PopupMenu) {
if m.Item("Save as…", overlay.PopupMenuItemOptions{}) {
// ask for a file name
}
})
@@ -21,8 +21,8 @@ var essentialsPages = []page{
ID: "482", Name: "CopyButton", ZhName: "复制按钮 剪贴板", Category: "Essentials",
Demo: copyButtonDemo,
Code: `view := func(c *ui.Context) {
mujica.Use(c, mujica.Settings{})
mujica.CopyButton(c, "https://example.com/invite/42", mujica.CopyButtonOptions{Label: "Copy link"})
core.Use(c, core.Settings{})
input.CopyButton(c, "https://example.com/invite/42", input.CopyButtonOptions{Label: "Copy link"})
}`,
Keys: "Tab focuses; Enter or Space copies. The copied state is announced.",
},
@@ -30,8 +30,8 @@ var essentialsPages = []page{
ID: "483", Name: "HoldToConfirm", ZhName: "按住确认 危险操作", Category: "Essentials",
Demo: holdToConfirmDemo,
Code: `view := func(c *ui.Context) {
mujica.Use(c, mujica.Settings{})
if mujica.HoldToConfirm(c, "Delete project", mujica.HoldToConfirmOptions{}).Changed() {
core.Use(c, core.Settings{})
if input.HoldToConfirm(c, "Delete project", input.HoldToConfirmOptions{}).Changed() {
// delete the project
}
}`,
@@ -42,9 +42,9 @@ var essentialsPages = []page{
Demo: bulkActionBarDemo,
Code: `selected := 3
view := func(c *ui.Context) {
mujica.Use(c, mujica.Settings{})
r := mujica.BulkActionBar(c, selected, mujica.BulkActionBarOptions{}, func() {
mujica.Button(c, "Archive", mujica.ButtonOptions{Variant: mujica.Secondary})
core.Use(c, core.Settings{})
r := input.BulkActionBar(c, selected, input.BulkActionBarOptions{}, func() {
input.Button(c, "Archive", input.ButtonOptions{Variant: input.Secondary})
})
if r.Changed() {
selected = 0
@@ -57,8 +57,8 @@ view := func(c *ui.Context) {
Demo: scrubInputDemo,
Code: `opacity := 0.6
view := func(c *ui.Context) {
mujica.Use(c, mujica.Settings{})
mujica.ScrubInput(c, "Opacity", &opacity, mujica.ScrubInputOptions{Max: 1, Step: 0.1})
core.Use(c, core.Settings{})
input.ScrubInput(c, "Opacity", &opacity, input.ScrubInputOptions{Max: 1, Step: 0.1})
}`,
Keys: "The label is pointer-only; in the field, Up and Down step and typing sets the value.",
},
@@ -67,8 +67,8 @@ view := func(c *ui.Context) {
Demo: currencyInputDemo,
Code: `cents := int64(123450)
view := func(c *ui.Context) {
mujica.Use(c, mujica.Settings{})
mujica.CurrencyInput(c, &cents, mujica.CurrencyInputOptions{Symbol: "$", Label: "Price"})
core.Use(c, core.Settings{})
input.CurrencyInput(c, &cents, input.CurrencyInputOptions{Symbol: "$", Label: "Price"})
}`,
Keys: "Tab focuses the field, which shows plain digits while focused and groups thousands when left.",
},
@@ -77,22 +77,22 @@ view := func(c *ui.Context) {
Demo: maskedInputDemo,
Code: `phone := ""
view := func(c *ui.Context) {
mujica.Use(c, mujica.Settings{})
mujica.MaskedInput(c, &phone, mujica.MaskedInputOptions{Mask: "(999) 999-9999", Label: "Phone"})
core.Use(c, core.Settings{})
input.MaskedInput(c, &phone, input.MaskedInputOptions{Mask: "(999) 999-9999", Label: "Phone"})
}`,
Keys: "Arrows, Home and End move the caret; Backspace and Delete remove; paste keeps the characters that fit; Enter submits.",
},
{
ID: "488", Name: "MentionInput", ZhName: "提及输入 @ 候选", Category: "Essentials",
Demo: mentionInputDemo,
Code: `text, mentions := "", []mujica.Mention(nil)
people := []mujica.MentionCandidate{{ID: "u1", Name: "Alice"}, {ID: "u2", Name: "Bob"}}
Code: `text, mentions := "", []input.Mention(nil)
people := []input.MentionCandidate{{ID: "u1", Name: "Alice"}, {ID: "u2", Name: "Bob"}}
view := func(c *ui.Context) {
mujica.Use(c, mujica.Settings{})
mujica.MentionInput(c, &text, &mentions, mujica.MentionInputOptions{
core.Use(c, core.Settings{})
input.MentionInput(c, &text, &mentions, input.MentionInputOptions{
Label: "Comment",
Candidates: func(q string) []mujica.MentionCandidate {
var out []mujica.MentionCandidate
Candidates: func(q string) []input.MentionCandidate {
var out []input.MentionCandidate
for _, p := range people {
if strings.HasPrefix(strings.ToLower(p.Name), strings.ToLower(q)) {
out = append(out, p)
@@ -109,10 +109,10 @@ view := func(c *ui.Context) {
Demo: choiceChipsDemo,
Code: `var status []string
view := func(c *ui.Context) {
mujica.Use(c, mujica.Settings{})
mujica.ChoiceChips(c, &status, []mujica.ToggleItem[string]{
core.Use(c, core.Settings{})
input.ChoiceChips(c, &status, []input.ToggleItem[string]{
{Value: "open", Label: "Open"}, {Value: "closed", Label: "Closed"},
}, mujica.ChoiceChipsOptions{Multiple: true, Label: "Status"})
}, input.ChoiceChipsOptions{Multiple: true, Label: "Status"})
}`,
Keys: "The chips are one Tab stop; Left and Right move, skipping disabled chips; Space or Enter toggles.",
},
@@ -121,8 +121,8 @@ view := func(c *ui.Context) {
Demo: knobDemo,
Code: `gain := 35.0
view := func(c *ui.Context) {
mujica.Use(c, mujica.Settings{})
mujica.Knob(c, &gain, mujica.KnobOptions{Label: "Gain", Ticks: 11})
core.Use(c, core.Settings{})
input.Knob(c, &gain, input.KnobOptions{Label: "Gain", Ticks: 11})
}`,
Keys: "Arrows step the value; Home and End go to the ends; drag up or down with the pointer.",
},
@@ -131,8 +131,8 @@ view := func(c *ui.Context) {
Demo: verticalSliderDemo,
Code: `level := 40.0
view := func(c *ui.Context) {
mujica.Use(c, mujica.Settings{})
mujica.VerticalSlider(c, &level, mujica.VerticalSliderOptions{Step: 10, Label: "Level"})
core.Use(c, core.Settings{})
input.VerticalSlider(c, &level, input.VerticalSliderOptions{Step: 10, Label: "Level"})
}`,
Keys: "Up and Right raise the value, Down and Left lower it, Home and End go to the ends.",
},
@@ -141,23 +141,23 @@ view := func(c *ui.Context) {
Demo: colorPaletteDemo,
Code: `color := ui.Hex("#7D2034")
view := func(c *ui.Context) {
mujica.Use(c, mujica.Settings{})
mujica.ColorPalette(c, &color, []mujica.ColorSwatch{
core.Use(c, core.Settings{})
input.ColorPalette(c, &color, []input.ColorSwatch{
{Color: ui.Hex("#7D2034"), Name: "Wine"},
{Color: ui.Hex("#AD9164"), Name: "Old gold"},
{Color: ui.Hex("#355E80"), Name: "Slate blue"},
}, mujica.ColorPaletteOptions{Custom: true, Label: "Tag color"})
}, input.ColorPaletteOptions{Custom: true, Label: "Tag color"})
}`,
Keys: "The palette is one Tab stop; Left and Right move by one swatch, Up and Down by a row; Enter or Space chooses.",
},
{
ID: "493", Name: "SignaturePad", ZhName: "签名板 手写", Category: "Essentials",
Demo: signaturePadDemo,
Code: `var strokes [][]mujica.SignaturePoint
Code: `var strokes [][]input.SignaturePoint
view := func(c *ui.Context) {
mujica.Use(c, mujica.Settings{})
if mujica.SignaturePad(c, &strokes, mujica.SignaturePadOptions{}).Changed() {
_ = mujica.SignaturePath(strokes) // SVG path data to store
core.Use(c, core.Settings{})
if input.SignaturePad(c, &strokes, input.SignaturePadOptions{}).Changed() {
_ = input.SignaturePath(strokes) // SVG path data to store
}
}`,
Keys: "Tab focuses the pad; Delete or Backspace clears it. Drawing needs the pointer.",
@@ -167,9 +167,9 @@ view := func(c *ui.Context) {
Demo: bannerDemo,
Code: `open := true
view := func(c *ui.Context) {
mujica.Use(c, mujica.Settings{})
r := mujica.Banner(c, &open, "Maintenance tonight at 22:00.", mujica.BannerOptions{
Severity: mujica.SeverityWarning,
core.Use(c, core.Settings{})
r := input.Banner(c, &open, "Maintenance tonight at 22:00.", input.BannerOptions{
Severity: core.SeverityWarning,
Action: "Details",
})
if r.Action {
@@ -183,8 +183,8 @@ view := func(c *ui.Context) {
Demo: undoToastDemo,
Code: `open := true
view := func(c *ui.Context) {
mujica.Use(c, mujica.Settings{})
mujica.UndoToast(c, &open, "Note deleted", mujica.UndoToastOptions{
core.Use(c, core.Settings{})
feedback.UndoToast(c, &open, "Note deleted", feedback.UndoToastOptions{
OnUndo: func() {
// restore the note
},
@@ -197,8 +197,8 @@ view := func(c *ui.Context) {
Demo: masonryDemo,
Code: `titles := []string{"Sea", "Forest", "Desert", "Glacier"}
view := func(c *ui.Context) {
mujica.Use(c, mujica.Settings{})
mujica.Masonry(c, len(titles), mujica.MasonryOptions{
core.Use(c, core.Settings{})
input.Masonry(c, len(titles), input.MasonryOptions{
Key: func(i int) string { return titles[i] },
}, func(i int) {
ui.Text(c, titles[i])
@@ -211,11 +211,11 @@ view := func(c *ui.Context) {
Demo: resizablePanelGroupDemo,
Code: `var sizes []float32
view := func(c *ui.Context) {
mujica.Use(c, mujica.Settings{})
r := mujica.ResizablePanelGroup(c, &sizes, []mujica.ResizablePanel{
core.Use(c, core.Settings{})
r := input.ResizablePanelGroup(c, &sizes, []input.ResizablePanel{
{Label: "Files", Collapsible: true, Build: func() { ui.Text(c, "Files") }},
{Label: "Editor", Build: func() { ui.Text(c, "Editor") }},
}, mujica.ResizablePanelGroupOptions{})
}, input.ResizablePanelGroupOptions{})
if r.Committed {
// save sizes
}
@@ -225,13 +225,13 @@ view := func(c *ui.Context) {
{
ID: "498", Name: "DockPanel", ZhName: "停靠面板 标签", Category: "Essentials",
Demo: dockPanelDemo,
Code: `var layout mujica.DockLayout
Code: `var layout input.DockLayout
view := func(c *ui.Context) {
mujica.Use(c, mujica.Settings{})
r := mujica.DockPanel(c, &layout, []mujica.DockItem{
core.Use(c, core.Settings{})
r := input.DockPanel(c, &layout, []input.DockItem{
{ID: "files", Title: "Files", Build: func() { ui.Text(c, "Files") }},
{ID: "terminal", Title: "Terminal", Build: func() { ui.Text(c, "$") }},
}, mujica.DockPanelOptions{})
}, input.DockPanelOptions{})
if r.Changed() {
// save layout
}
@@ -242,20 +242,20 @@ view := func(c *ui.Context) {
ID: "499", Name: "QRCode", ZhName: "二维码", Category: "Essentials",
Demo: qrCodeDemo,
Code: `view := func(c *ui.Context) {
mujica.Use(c, mujica.Settings{})
mujica.QRCode(c, "https://example.com/mujica", mujica.QRCodeOptions{Level: mujica.QRQuartile})
core.Use(c, core.Settings{})
input.QRCode(c, "https://example.com/mujica", input.QRCodeOptions{Level: input.QRQuartile})
}`,
Keys: "Not focusable; assistive technology reads its name.",
},
{
ID: "500", Name: "ActivityFeed", ZhName: "活动流 动态", Category: "Essentials",
Demo: activityFeedDemo,
Code: `events := []mujica.Activity{
Code: `events := []feedback.Activity{
{ID: "1", Actor: "Lin", Action: "commented on", Object: "Quarterly report", Time: time.Now()},
}
view := func(c *ui.Context) {
mujica.Use(c, mujica.Settings{})
mujica.ActivityFeed(c, events, mujica.ActivityFeedOptions{}).Size(380, 280)
core.Use(c, core.Settings{})
feedback.ActivityFeed(c, events, feedback.ActivityFeedOptions{}).Size(380, 280)
}`,
Keys: "Scroll with the wheel or the scrollbar; only rows in view are built.",
},
+25 -24
View File
@@ -6,7 +6,8 @@ import (
"github.com/egoist/mygo/ui"
mujica "mujicaui"
"github.com/ZacharyZhang-NY/MujicaUI/input"
"github.com/ZacharyZhang-NY/MujicaUI/overlay"
)
func splitButtonDemo(c *ui.Context, log func(string)) {
@@ -20,14 +21,14 @@ func splitButtonDemo(c *ui.Context, log func(string)) {
})
})
feedbackSection(c, "Live", func() {
v := mujica.Primary
v := input.Primary
if s.variant {
v = mujica.Secondary
v = input.Secondary
}
ui.Row(c).Children(func() {
r := mujica.SplitButton(c, "Save", mujica.SplitButtonOptions{Variant: v, Disabled: s.disabled}, func(m *mujica.PopupMenu) {
r := overlay.SplitButton(c, "Save", overlay.SplitButtonOptions{Variant: v, Disabled: s.disabled}, func(m *overlay.PopupMenu) {
for _, it := range []string{"Save as…", "Save a copy", "Save all"} {
if m.Item(it, mujica.PopupMenuItemOptions{}) {
if m.Item(it, overlay.PopupMenuItemOptions{}) {
log("split button: menu " + it)
}
}
@@ -53,10 +54,10 @@ func copyButtonDemo(c *ui.Context, log func(string)) {
})
feedbackSection(c, "Live", func() {
ui.Row(c).Gap(12).Children(func() {
if mujica.CopyButton(c, s.text, mujica.CopyButtonOptions{Disabled: s.disabled}).Changed() {
if input.CopyButton(c, s.text, input.CopyButtonOptions{Disabled: s.disabled}).Changed() {
log(fmt.Sprintf("copy button: copied %d characters", len([]rune(s.text))))
}
if mujica.CopyButton(c, s.text, mujica.CopyButtonOptions{Label: "Copy link", Disabled: s.disabled}).Changed() {
if input.CopyButton(c, s.text, input.CopyButtonOptions{Label: "Copy link", Disabled: s.disabled}).Changed() {
log("copy button: copied with label")
}
})
@@ -82,7 +83,7 @@ func holdToConfirmDemo(c *ui.Context, log func(string)) {
feedbackSection(c, "Live (turn on reduced motion to see the countdown text)", func() {
ui.Row(c).Children(func() {
d := time.Duration(s.seconds * float64(time.Second))
if mujica.HoldToConfirm(c, "Delete project", mujica.HoldToConfirmOptions{Duration: d, Disabled: s.disabled}).Changed() {
if input.HoldToConfirm(c, "Delete project", input.HoldToConfirmOptions{Duration: d, Disabled: s.disabled}).Changed() {
s.deleted++
log("hold to confirm: confirmed")
}
@@ -113,11 +114,11 @@ func bulkActionBarDemo(c *ui.Context, log func(string)) {
n++
}
}
r := mujica.BulkActionBar(c, n, mujica.BulkActionBarOptions{Disabled: s.disabled}, func() {
if mujica.Button(c, "Archive", mujica.ButtonOptions{Variant: mujica.Secondary}).Clicked() {
r := input.BulkActionBar(c, n, input.BulkActionBarOptions{Disabled: s.disabled}, func() {
if input.Button(c, "Archive", input.ButtonOptions{Variant: input.Secondary}).Clicked() {
log(fmt.Sprintf("bulk bar: archive %d", n))
}
if mujica.Button(c, "Delete", mujica.ButtonOptions{Variant: mujica.Danger}).Clicked() {
if input.Button(c, "Delete", input.ButtonOptions{Variant: input.Danger}).Clicked() {
log(fmt.Sprintf("bulk bar: delete %d", n))
}
})
@@ -137,10 +138,10 @@ func scrubInputDemo(c *ui.Context, log func(string)) {
ui.Column(c).Gap(16).FillWidth().Children(func() {
ui.Checkbox(c, &s.disabled, "Disabled")
feedbackSection(c, "Drag a label sideways, or type", func() {
if mujica.ScrubInput(c, "Opacity", &s.opacity, mujica.ScrubInputOptions{Max: 1, Step: 0.05, Disabled: s.disabled}).Changed() {
if input.ScrubInput(c, "Opacity", &s.opacity, input.ScrubInputOptions{Max: 1, Step: 0.05, Disabled: s.disabled}).Changed() {
log(fmt.Sprintf("scrub: opacity %.2f", s.opacity))
}
if mujica.ScrubInput(c, "Size", &s.size, mujica.ScrubInputOptions{Min: 8, Max: 72, Disabled: s.disabled}).Changed() {
if input.ScrubInput(c, "Size", &s.size, input.ScrubInputOptions{Min: 8, Max: 72, Disabled: s.disabled}).Changed() {
log(fmt.Sprintf("scrub: size %.0f", s.size))
}
})
@@ -157,14 +158,14 @@ func currencyInputDemo(c *ui.Context, log func(string)) {
ui.Checkbox(c, &s.disabled, "Disabled")
feedbackSection(c, "Live", func() {
ui.Row(c).Gap(12).Children(func() {
if mujica.CurrencyInput(c, &s.cents, mujica.CurrencyInputOptions{Symbol: "$", Label: "Price", Disabled: s.disabled}).Changed() {
if input.CurrencyInput(c, &s.cents, input.CurrencyInputOptions{Symbol: "$", Label: "Price", Disabled: s.disabled}).Changed() {
log(fmt.Sprintf("currency: %d cents", s.cents))
}
if mujica.CurrencyInput(c, &s.yen, mujica.CurrencyInputOptions{Symbol: "¥", NoFraction: true, Label: "Yen", Disabled: s.disabled}).Changed() {
if input.CurrencyInput(c, &s.yen, input.CurrencyInputOptions{Symbol: "¥", NoFraction: true, Label: "Yen", Disabled: s.disabled}).Changed() {
log(fmt.Sprintf("currency: %d yen", s.yen))
}
})
ui.Text(c, "Stored as "+mujica.CurrencyFormat(s.cents, 2, true)+" and "+mujica.CurrencyFormat(s.yen, 0, true))
ui.Text(c, "Stored as "+input.CurrencyFormat(s.cents, 2, true)+" and "+input.CurrencyFormat(s.yen, 0, true))
})
})
}
@@ -186,7 +187,7 @@ func maskedInputDemo(c *ui.Context, log func(string)) {
ui.Checkbox(c, &s.disabled, "Disabled")
})
feedbackSection(c, "Live (try an input method: nothing commits until it does)", func() {
r := mujica.MaskedInput(c, &s.value, mujica.MaskedInputOptions{Mask: masks[s.mask], Label: "Masked field", Disabled: s.disabled})
r := input.MaskedInput(c, &s.value, input.MaskedInputOptions{Mask: masks[s.mask], Label: "Masked field", Disabled: s.disabled})
r.Element.Width(260)
if r.Changed() {
log(fmt.Sprintf("masked: %d characters filled", len([]rune(s.value))))
@@ -204,7 +205,7 @@ func choiceChipsDemo(c *ui.Context, log func(string)) {
multiple, disabled bool
}
s := ui.Local(c.Root(), "489", func() state { return state{multiple: true, value: []string{"open"}} })
items := []mujica.ToggleItem[string]{{Value: "open", Label: "Open"}, {Value: "draft", Label: "Draft"}, {Value: "review", Label: "In review"}, {Value: "closed", Label: "Closed", Disabled: true}, {Value: "merged", Label: "Merged"}}
items := []input.ToggleItem[string]{{Value: "open", Label: "Open"}, {Value: "draft", Label: "Draft"}, {Value: "review", Label: "In review"}, {Value: "closed", Label: "Closed", Disabled: true}, {Value: "merged", Label: "Merged"}}
ui.Column(c).Gap(16).FillWidth().Children(func() {
feedbackSection(c, "Parameters", func() {
ui.Row(c).Gap(16).Children(func() {
@@ -215,7 +216,7 @@ func choiceChipsDemo(c *ui.Context, log func(string)) {
})
})
feedbackSection(c, "Live", func() {
if mujica.ChoiceChips(c, &s.value, items, mujica.ChoiceChipsOptions{Multiple: s.multiple, Disabled: s.disabled, Label: "Status"}).Changed() {
if input.ChoiceChips(c, &s.value, items, input.ChoiceChipsOptions{Multiple: s.multiple, Disabled: s.disabled, Label: "Status"}).Changed() {
log(fmt.Sprintf("chips: %v", s.value))
}
})
@@ -231,10 +232,10 @@ func knobDemo(c *ui.Context, log func(string)) {
ui.Column(c).Gap(16).FillWidth().Children(func() {
ui.Checkbox(c, &s.disabled, "Disabled")
ui.Row(c).Gap(32).Children(func() {
if mujica.Knob(c, &s.gain, mujica.KnobOptions{Label: "Gain", Ticks: 11, Disabled: s.disabled}).Changed() {
if input.Knob(c, &s.gain, input.KnobOptions{Label: "Gain", Ticks: 11, Disabled: s.disabled}).Changed() {
log(fmt.Sprintf("knob: gain %.1f", s.gain))
}
if mujica.Knob(c, &s.mix, mujica.KnobOptions{Max: 1, Step: 0.1, Size: 72, Label: "Mix", Disabled: s.disabled}).Changed() {
if input.Knob(c, &s.mix, input.KnobOptions{Max: 1, Step: 0.1, Size: 72, Label: "Mix", Disabled: s.disabled}).Changed() {
log(fmt.Sprintf("knob: mix %.1f", s.mix))
}
})
@@ -254,11 +255,11 @@ func verticalSliderDemo(c *ui.Context, log func(string)) {
})
ui.Row(c).Gap(16).Children(func() {
for i := range s.levels {
opts := mujica.VerticalSliderOptions{Step: 10, ShowValue: s.showValue, Disabled: s.disable, Label: fmt.Sprintf("Band %d", i+1)}
opts := input.VerticalSliderOptions{Step: 10, ShowValue: s.showValue, Disabled: s.disable, Label: fmt.Sprintf("Band %d", i+1)}
if i == 0 {
opts.Marks = []mujica.SliderMark{{Value: 0, Label: "0"}, {Value: 50, Label: "50"}, {Value: 100, Label: "100"}}
opts.Marks = []input.SliderMark{{Value: 0, Label: "0"}, {Value: 50, Label: "50"}, {Value: 100, Label: "100"}}
}
if mujica.VerticalSlider(c, &s.levels[i], opts).Changed() {
if input.VerticalSlider(c, &s.levels[i], opts).Changed() {
log(fmt.Sprintf("vertical slider: band %d = %.0f", i+1, s.levels[i]))
}
}
+38 -36
View File
@@ -8,24 +8,26 @@ import (
"github.com/egoist/mygo/ui"
mujica "mujicaui"
"github.com/ZacharyZhang-NY/MujicaUI/core"
"github.com/ZacharyZhang-NY/MujicaUI/feedback"
"github.com/ZacharyZhang-NY/MujicaUI/input"
)
func mentionInputDemo(c *ui.Context, log func(string)) {
type state struct {
text string
mentions []mujica.Mention
mentions []input.Mention
disabled bool
}
people := []mujica.MentionCandidate{{ID: "u1", Name: "Alice"}, {ID: "u2", Name: "Albert"}, {ID: "u3", Name: "Bob"}, {ID: "u4", Name: "Sakiko"}, {ID: "u5", Name: "Mutsumi"}}
people := []input.MentionCandidate{{ID: "u1", Name: "Alice"}, {ID: "u2", Name: "Albert"}, {ID: "u3", Name: "Bob"}, {ID: "u4", Name: "Sakiko"}, {ID: "u5", Name: "Mutsumi"}}
s := ui.Local(c.Root(), "488", func() state { return state{} })
ui.Column(c).Gap(16).FillWidth().Children(func() {
ui.Checkbox(c, &s.disabled, "Disabled")
feedbackSection(c, "Type @ to mention someone", func() {
r := mujica.MentionInput(c, &s.text, &s.mentions, mujica.MentionInputOptions{
r := input.MentionInput(c, &s.text, &s.mentions, input.MentionInputOptions{
Placeholder: "Write a comment…", Label: "Comment", Disabled: s.disabled,
Candidates: func(q string) []mujica.MentionCandidate {
var out []mujica.MentionCandidate
Candidates: func(q string) []input.MentionCandidate {
var out []input.MentionCandidate
for _, p := range people {
if strings.HasPrefix(strings.ToLower(p.Name), strings.ToLower(q)) {
out = append(out, p)
@@ -54,7 +56,7 @@ func colorPaletteDemo(c *ui.Context, log func(string)) {
custom, disabled bool
}
s := ui.Local(c.Root(), "492", func() state { return state{color: ui.Hex("#7D2034"), custom: true} })
swatches := []mujica.ColorSwatch{
swatches := []input.ColorSwatch{
{Color: ui.Hex("#7D2034"), Name: "Wine"}, {Color: ui.Hex("#A12635"), Name: "Crimson"}, {Color: ui.Hex("#AD9164"), Name: "Old gold"},
{Color: ui.Hex("#805714"), Name: "Bronze"}, {Color: ui.Hex("#326247"), Name: "Ivy"}, {Color: ui.Hex("#355E80"), Name: "Slate blue"},
{Color: ui.Hex("#4B3A5A"), Name: "Plum"}, {Color: ui.Hex("#211C1D"), Name: "Ink"}, {Color: ui.Hex("#6A5B55"), Name: "Taupe"},
@@ -65,7 +67,7 @@ func colorPaletteDemo(c *ui.Context, log func(string)) {
ui.Checkbox(c, &s.custom, "Custom entry")
ui.Checkbox(c, &s.disabled, "Disabled")
})
if mujica.ColorPalette(c, &s.color, swatches, mujica.ColorPaletteOptions{Columns: 6, Custom: s.custom, Disabled: s.disabled, Label: "Tag color"}).Changed() {
if input.ColorPalette(c, &s.color, swatches, input.ColorPaletteOptions{Columns: 6, Custom: s.custom, Disabled: s.disabled, Label: "Tag color"}).Changed() {
log(fmt.Sprintf("palette: %v", s.color))
}
})
@@ -73,14 +75,14 @@ func colorPaletteDemo(c *ui.Context, log func(string)) {
func signaturePadDemo(c *ui.Context, log func(string)) {
type state struct {
strokes [][]mujica.SignaturePoint
strokes [][]input.SignaturePoint
disabled bool
}
s := ui.Local(c.Root(), "493", func() state { return state{} })
ui.Column(c).Gap(16).Width(480).Children(func() {
ui.Checkbox(c, &s.disabled, "Disabled")
if mujica.SignaturePad(c, &s.strokes, mujica.SignaturePadOptions{Disabled: s.disabled}).Changed() {
log(fmt.Sprintf("signature: %d strokes, %d bytes of path", len(s.strokes), len(mujica.SignaturePath(s.strokes))))
if input.SignaturePad(c, &s.strokes, input.SignaturePadOptions{Disabled: s.disabled}).Changed() {
log(fmt.Sprintf("signature: %d strokes, %d bytes of path", len(s.strokes), len(input.SignaturePath(s.strokes))))
}
})
}
@@ -99,7 +101,7 @@ func bannerDemo(c *ui.Context, log func(string)) {
ui.Checkbox(c, &s.closable, "Closable")
})
})
opts := mujica.BannerOptions{Severity: feedbackSeverities[s.sev]}
opts := input.BannerOptions{Severity: feedbackSeverities[s.sev]}
if s.action {
opts.Action = "Details"
}
@@ -107,14 +109,14 @@ func bannerDemo(c *ui.Context, log func(string)) {
if s.closable {
open = &s.op
}
r := mujica.Banner(c, open, "Scheduled maintenance tonight from 22:00 to 23:00.", opts)
r := input.Banner(c, open, "Scheduled maintenance tonight from 22:00 to 23:00.", opts)
if r.Action {
log("banner: action")
}
if r.Closed {
log("banner: closed")
}
if !s.op && mujica.Button(c, "Show again", mujica.ButtonOptions{Variant: mujica.Secondary}).Clicked() {
if !s.op && input.Button(c, "Show again", input.ButtonOptions{Variant: input.Secondary}).Clicked() {
s.op = true
log("banner: reopened")
}
@@ -139,7 +141,7 @@ func undoToastDemo(c *ui.Context, log func(string)) {
for i, n := range s.notes {
ui.Row(c).Key(n).Gap(12).AlignItems(ui.Center).Children(func() {
ui.Text(c, n).Grow(1)
if mujica.Button(c, "Delete", mujica.ButtonOptions{Variant: mujica.Ghost}).Clicked() {
if input.Button(c, "Delete", input.ButtonOptions{Variant: input.Ghost}).Clicked() {
s.removed, s.open = n, true
s.notes = append(s.notes[:i:i], s.notes[i+1:]...)
log("undo toast: deleted " + n)
@@ -151,7 +153,7 @@ func undoToastDemo(c *ui.Context, log func(string)) {
return
}
removed := s.removed
r := mujica.UndoToast(c, &s.open, "Deleted “"+removed+"”", mujica.UndoToastOptions{
r := feedback.UndoToast(c, &s.open, "Deleted “"+removed+"”", feedback.UndoToastOptions{
Duration: time.Duration(s.seconds * float64(time.Second)),
OnUndo: func() { s.notes = append(s.notes, removed) },
})
@@ -168,7 +170,7 @@ func masonryDemo(c *ui.Context, log func(string)) {
count, width float64
}
s := ui.Local(c.Root(), "496", func() state { return state{count: 40, width: 180} })
k := mujica.Tokens(c)
k := core.Tokens(c)
ui.Column(c).Gap(16).FillWidth().Children(func() {
ui.Row(c).Gap(12).AlignItems(ui.Center).Children(func() {
ui.Text(c, fmt.Sprintf("Items %.0f", s.count))
@@ -178,7 +180,7 @@ func masonryDemo(c *ui.Context, log func(string)) {
ui.Text(c, fmt.Sprintf("Column %.0f", s.width))
ui.Slider(c, &s.width, 120, 320).Width(160)
})
mujica.Masonry(c, int(s.count), mujica.MasonryOptions{Key: strconv.Itoa, ColumnWidth: float32(s.width)}, func(i int) {
input.Masonry(c, int(s.count), input.MasonryOptions{Key: strconv.Itoa, ColumnWidth: float32(s.width)}, func(i int) {
card := ui.Column(c).Height(float32(60+i*37%90)).Padding(10).Background(k.Surface).Border(1, k.Border).Radius(4).Cursor(ui.CursorPointer)
card.Children(func() { ui.Text(c, "Card "+strconv.Itoa(i+1)) })
if card.Clicked() {
@@ -194,7 +196,7 @@ func resizablePanelGroupDemo(c *ui.Context, log func(string)) {
vertical, disabled bool
}
s := ui.Local(c.Root(), "497", func() state { return state{} })
k := mujica.Tokens(c)
k := core.Tokens(c)
pane := func(name string) func() {
return func() {
ui.Column(c).Fill().Padding(12).Background(k.Surface).Children(func() { ui.Text(c, name) })
@@ -207,11 +209,11 @@ func resizablePanelGroupDemo(c *ui.Context, log func(string)) {
}
ui.Checkbox(c, &s.disabled, "Disabled")
})
r := mujica.ResizablePanelGroup(c, &s.sizes, []mujica.ResizablePanel{
r := input.ResizablePanelGroup(c, &s.sizes, []input.ResizablePanel{
{Label: "Files", Collapsible: true, Build: pane("Files")},
{Label: "Editor", Min: 160, Build: pane("Editor")},
{Label: "Outline", Collapsible: true, Build: pane("Outline")},
}, mujica.ResizablePanelGroupOptions{Vertical: s.vertical, Disabled: s.disabled})
}, input.ResizablePanelGroupOptions{Vertical: s.vertical, Disabled: s.disabled})
r.Element.FillWidth().Height(320)
if r.Committed {
log(fmt.Sprintf("panels: save %.2f", s.sizes))
@@ -221,19 +223,19 @@ func resizablePanelGroupDemo(c *ui.Context, log func(string)) {
func dockPanelDemo(c *ui.Context, log func(string)) {
type state struct {
layout mujica.DockLayout
layout input.DockLayout
disabled bool
}
s := ui.Local(c.Root(), "498", func() state {
return state{layout: mujica.DockLayout{Zones: [4][]string{{"editor"}, {"files"}, {"outline"}, {"terminal", "problems"}}}}
return state{layout: input.DockLayout{Zones: [4][]string{{"editor"}, {"files"}, {"outline"}, {"terminal", "problems"}}}}
})
var items []mujica.DockItem
var items []input.DockItem
for _, id := range []string{"files", "editor", "outline", "terminal", "problems"} {
items = append(items, mujica.DockItem{ID: id, Title: strings.ToUpper(id[:1]) + id[1:], Build: func() { ui.Text(c, "Contents of "+id) }})
items = append(items, input.DockItem{ID: id, Title: strings.ToUpper(id[:1]) + id[1:], Build: func() { ui.Text(c, "Contents of "+id) }})
}
ui.Column(c).Gap(16).FillWidth().Children(func() {
ui.Checkbox(c, &s.disabled, "Disabled")
r := mujica.DockPanel(c, &s.layout, items, mujica.DockPanelOptions{Disabled: s.disabled})
r := input.DockPanel(c, &s.layout, items, input.DockPanelOptions{Disabled: s.disabled})
r.Element.FillWidth().Height(440)
if r.Changed() {
log(fmt.Sprintf("dock: save %v", s.layout.Zones))
@@ -248,7 +250,7 @@ func qrCodeDemo(c *ui.Context, log func(string)) {
size float64
}
s := ui.Local(c.Root(), "499", func() state { return state{text: "https://example.com/mujica", level: 1, size: 200} })
levels := []mujica.QRLevel{mujica.QRLow, mujica.QRMedium, mujica.QRQuartile, mujica.QRHigh}
levels := []input.QRLevel{input.QRLow, input.QRMedium, input.QRQuartile, input.QRHigh}
ui.Column(c).Gap(16).FillWidth().Children(func() {
feedbackSection(c, "Parameters", func() {
if ui.TextInput(c, &s.text).Width(360).Changed() {
@@ -262,31 +264,31 @@ func qrCodeDemo(c *ui.Context, log func(string)) {
ui.Slider(c, &s.size, 100, 320).Width(200)
})
})
mujica.QRCode(c, s.text, mujica.QRCodeOptions{Level: levels[s.level], Size: float32(s.size)})
if m, err := mujica.EncodeQR([]byte(s.text), levels[s.level]); err == nil {
input.QRCode(c, s.text, input.QRCodeOptions{Level: levels[s.level], Size: float32(s.size)})
if m, err := input.EncodeQR([]byte(s.text), levels[s.level]); err == nil {
ui.Text(c, fmt.Sprintf("Version %d, mask %d, %d × %d modules", m.Version, m.Mask, m.Size(), m.Size()))
}
})
}
func activityFeedDemo(c *ui.Context, log func(string)) {
type state struct{ events []mujica.Activity }
type state struct{ events []feedback.Activity }
s := ui.Local(c.Root(), "500", func() state {
now := time.Now()
var ev []mujica.Activity
var ev []feedback.Activity
actors := []string{"Sakiko", "Mutsumi", "Uika", "Umiri", "Nyamu"}
verbs := []string{"commented on", "edited", "approved", "closed", "opened"}
for i := range 400 {
ev = append(ev, mujica.Activity{ID: strconv.Itoa(i), Actor: actors[i%5], Action: verbs[i*3%5], Object: fmt.Sprintf("Score #%d", 400-i), Time: now.Add(-time.Duration(i) * 97 * time.Minute)})
ev = append(ev, feedback.Activity{ID: strconv.Itoa(i), Actor: actors[i%5], Action: verbs[i*3%5], Object: fmt.Sprintf("Score #%d", 400-i), Time: now.Add(-time.Duration(i) * 97 * time.Minute)})
}
return state{events: ev}
})
ui.Column(c).Gap(16).FillWidth().Children(func() {
if mujica.Button(c, "Add event", mujica.ButtonOptions{Variant: mujica.Secondary}).AlignSelf(ui.Start).Clicked() {
e := mujica.Activity{ID: fmt.Sprint("n", len(s.events)), Actor: "You", Action: "added", Object: "a new note", Time: time.Now()}
s.events = append([]mujica.Activity{e}, s.events...)
if input.Button(c, "Add event", input.ButtonOptions{Variant: input.Secondary}).AlignSelf(ui.Start).Clicked() {
e := feedback.Activity{ID: fmt.Sprint("n", len(s.events)), Actor: "You", Action: "added", Object: "a new note", Time: time.Now()}
s.events = append([]feedback.Activity{e}, s.events...)
log("activity: added " + e.ID)
}
mujica.ActivityFeed(c, s.events, mujica.ActivityFeedOptions{}).FillWidth().Height(420)
feedback.ActivityFeed(c, s.events, feedback.ActivityFeedOptions{}).FillWidth().Height(420)
})
}
+20 -20
View File
@@ -5,8 +5,8 @@ var feedbackPages = []page{
ID: "091", Name: "Alert", ZhName: "提示条 警告 通知横幅", Category: "Feedback",
Demo: alertDemo,
Code: `open := true
r := mujica.Alert(c, &open, "The archive could not be read.", mujica.AlertOptions{
Severity: mujica.SeverityError,
r := feedback.Alert(c, &open, "The archive could not be read.", feedback.AlertOptions{
Severity: core.SeverityError,
Title: "Import failed",
Action: "Retry",
})
@@ -18,11 +18,11 @@ if r.Action {
{
ID: "092", Name: "Toast", ZhName: "轻提示 消息队列 吐司", Category: "Feedback",
Demo: toastDemo,
Code: `if mujica.Button(c, "Delete note", mujica.ButtonOptions{Variant: mujica.Danger}).Clicked() {
mujica.ShowToast(c, mujica.ToastMessage{ID: "note-1", Message: "Note deleted", Action: "Undo"})
Code: `if input.Button(c, "Delete note", input.ButtonOptions{Variant: input.Danger}).Clicked() {
feedback.ShowToast(c, feedback.ToastMessage{ID: "note-1", Message: "Note deleted", Action: "Undo"})
}
// Last in the view:
if r := mujica.Toaster(c); r.Action == "note-1" {
if r := feedback.Toaster(c); r.Action == "note-1" {
// restore the note
}`,
Keys: "Toasts never take the focus. Tab reaches their buttons after the page; Escape closes the focused toast. Hover or focus pauses its timer.",
@@ -30,9 +30,9 @@ if r := mujica.Toaster(c); r.Action == "note-1" {
{
ID: "093", Name: "Progress", ZhName: "进度条 环形进度 计量条", Category: "Feedback",
Demo: progressDemo,
Code: `mujica.Progress(c, 0.62, mujica.ProgressOptions{Label: "Uploading", ShowValue: true})
mujica.Progress(c, 0, mujica.ProgressOptions{Shape: mujica.ProgressRing, Indeterminate: true, Label: "Connecting"})
mujica.Meter(c, 93, mujica.MeterOptions{
Code: `feedback.Progress(c, 0.62, feedback.ProgressOptions{Label: "Uploading", ShowValue: true})
feedback.Progress(c, 0, feedback.ProgressOptions{Shape: feedback.ProgressRing, Indeterminate: true, Label: "Connecting"})
feedback.Meter(c, 93, feedback.MeterOptions{
Max: 100, Label: "Disk", ShowValue: true,
Levels: &ui.MeterLevels{Warning: 70, Critical: 90},
})`,
@@ -41,16 +41,16 @@ mujica.Meter(c, 93, mujica.MeterOptions{
{
ID: "094", Name: "Spinner", ZhName: "加载标记 加载中 转圈", Category: "Feedback",
Demo: spinnerDemo,
Code: `mujica.Spinner(c, mujica.SpinnerOptions{Size: 20, Label: "Syncing", ShowLabel: true})`,
Code: `core.Spinner(c, core.SpinnerOptions{Size: 20, Label: "Syncing", ShowLabel: true})`,
Keys: "Not focusable; read as an indeterminate progress indicator named by Label.",
},
{
ID: "095", Name: "Skeleton", ZhName: "骨架屏 占位", Category: "Feedback",
Demo: skeletonDemo,
Code: `ui.Row(c).Gap(12).Children(func() {
mujica.Skeleton(c, mujica.SkeletonOptions{Shape: mujica.SkeletonCircle})
feedback.Skeleton(c, feedback.SkeletonOptions{Shape: feedback.SkeletonCircle})
ui.Column(c).Grow(1).Children(func() {
mujica.Skeleton(c, mujica.SkeletonOptions{Lines: 2})
feedback.Skeleton(c, feedback.SkeletonOptions{Lines: 2})
})
})`,
Keys: "Decorative; not focusable and hidden from assistive technology.",
@@ -58,8 +58,8 @@ mujica.Meter(c, 93, mujica.MeterOptions{
{
ID: "096", Name: "EmptyState", ZhName: "空状态 无数据 无搜索结果", Category: "Feedback",
Demo: emptyStateDemo,
Code: `r := mujica.EmptyState(c, mujica.EmptyStateOptions{
Kind: mujica.EmptyNoResults,
Code: `r := feedback.EmptyState(c, feedback.EmptyStateOptions{
Kind: feedback.EmptyNoResults,
Query: query,
Action: "Clear search",
})
@@ -71,8 +71,8 @@ if r.Action {
{
ID: "097", Name: "Result", ZhName: "结果页 成功 失败 无权限", Category: "Feedback",
Demo: resultDemo,
Code: `r := mujica.Result(c, mujica.ResultOptions{
Kind: mujica.ResultForbidden,
Code: `r := feedback.Result(c, feedback.ResultOptions{
Kind: feedback.ResultForbidden,
Description: "You need the Editor role to change billing.",
Actions: []string{"Request access", "Go back"},
})
@@ -85,14 +85,14 @@ case 1: // navigate back
{
ID: "098", Name: "StatusIndicator", ZhName: "状态标记 在线 离线", Category: "Feedback",
Demo: statusDemo,
Code: `mujica.StatusIndicator(c, mujica.StatusSuccess, mujica.StatusIndicatorOptions{Label: "API online"})
mujica.StatusIndicator(c, mujica.StatusOffline, mujica.StatusIndicatorOptions{})`,
Code: `feedback.StatusIndicator(c, feedback.StatusSuccess, feedback.StatusIndicatorOptions{Label: "API online"})
feedback.StatusIndicator(c, feedback.StatusOffline, feedback.StatusIndicatorOptions{})`,
Keys: "Not focusable; read as a status with its word.",
},
{
ID: "099", Name: "NotificationCenter", ZhName: "通知中心 消息列表", Category: "Feedback",
Demo: notificationDemo,
Code: `r := mujica.NotificationCenter(c, &items, mujica.NotificationCenterOptions{})
Code: `r := feedback.NotificationCenter(c, &items, feedback.NotificationCenterOptions{})
if r.Action == "a" {
// open the build log
}`,
@@ -101,9 +101,9 @@ if r.Action == "a" {
{
ID: "100", Name: "LoadingOverlay", ZhName: "加载遮罩 区域加载", Category: "Feedback",
Demo: loadingOverlayDemo,
Code: `r := mujica.LoadingOverlay(c, loading, mujica.LoadingOverlayOptions{Cancel: "Cancel"}, func() {
Code: `r := feedback.LoadingOverlay(c, loading, feedback.LoadingOverlayOptions{Cancel: "Cancel"}, func() {
ui.Text(c, "Quarterly report")
mujica.Button(c, "Export", mujica.ButtonOptions{})
input.Button(c, "Export", input.ButtonOptions{})
})
if r.Canceled {
loading = false
+48 -46
View File
@@ -6,19 +6,21 @@ import (
"github.com/egoist/mygo/ui"
mujica "mujicaui"
"mujicaui/theme"
"github.com/ZacharyZhang-NY/MujicaUI/core"
"github.com/ZacharyZhang-NY/MujicaUI/feedback"
"github.com/ZacharyZhang-NY/MujicaUI/input"
"github.com/ZacharyZhang-NY/MujicaUI/theme"
)
// feedbackSection titles a part of a demo.
func feedbackSection(c *ui.Context, title string, fn func()) {
ui.Column(c).Gap(8).FillWidth().Children(func() {
ui.Text(c, title).FontSize(theme.CaptionSize).TextColor(mujica.Tokens(c).TextMuted)
ui.Text(c, title).FontSize(theme.CaptionSize).TextColor(core.Tokens(c).TextMuted)
fn()
})
}
var feedbackSeverities = []mujica.Severity{mujica.SeverityInfo, mujica.SeveritySuccess, mujica.SeverityWarning, mujica.SeverityError}
var feedbackSeverities = []core.Severity{core.SeverityInfo, core.SeveritySuccess, core.SeverityWarning, core.SeverityError}
func alertDemo(c *ui.Context, log func(string)) {
type state struct {
@@ -38,7 +40,7 @@ func alertDemo(c *ui.Context, log func(string)) {
})
})
feedbackSection(c, "Live", func() {
opts := mujica.AlertOptions{Severity: feedbackSeverities[s.sev]}
opts := feedback.AlertOptions{Severity: feedbackSeverities[s.sev]}
if s.title {
opts.Title = "Import failed"
}
@@ -49,21 +51,21 @@ func alertDemo(c *ui.Context, log func(string)) {
if s.closable {
open = &s.open
}
r := mujica.Alert(c, open, "The archive could not be read.", opts)
r := feedback.Alert(c, open, "The archive could not be read.", opts)
if r.Action {
log("alert: action Retry")
}
if r.Closed {
log("alert: closed")
}
if s.closable && !s.open && mujica.Button(c, "Show again", mujica.ButtonOptions{Variant: mujica.Secondary}).Clicked() {
if s.closable && !s.open && input.Button(c, "Show again", input.ButtonOptions{Variant: input.Secondary}).Clicked() {
s.open = true
log("alert: reopened")
}
})
feedbackSection(c, "Severities", func() {
for _, sev := range feedbackSeverities {
mujica.Alert(c, nil, "Severity "+sev.String()+" uses its own icon, bar and name.", mujica.AlertOptions{Severity: sev}).Element.Key(sev)
feedback.Alert(c, nil, "Severity "+sev.String()+" uses its own icon, bar and name.", feedback.AlertOptions{Severity: sev}).Element.Key(sev)
}
})
})
@@ -89,28 +91,28 @@ func toastDemo(c *ui.Context, log func(string)) {
})
feedbackSection(c, "Actions", func() {
ui.Row(c).Gap(12).Wrap().Children(func() {
if mujica.Button(c, "Show toast", mujica.ButtonOptions{}).Clicked() {
if input.Button(c, "Show toast", input.ButtonOptions{}).Clicked() {
s.n++
m := mujica.ToastMessage{ID: fmt.Sprint(s.n), Severity: feedbackSeverities[s.sev], Message: fmt.Sprintf("Message %d", s.n)}
m := feedback.ToastMessage{ID: fmt.Sprint(s.n), Severity: feedbackSeverities[s.sev], Message: fmt.Sprintf("Message %d", s.n)}
if s.action {
m.Action = "Undo"
}
if m.Severity != mujica.SeverityError {
if m.Severity != core.SeverityError {
m.Duration = time.Duration(s.seconds * float64(time.Second))
}
mujica.ShowToast(c, m)
feedback.ShowToast(c, m)
log("toast: show " + m.ID)
}
if mujica.Button(c, "Show five at once", mujica.ButtonOptions{Variant: mujica.Secondary}).Clicked() {
if input.Button(c, "Show five at once", input.ButtonOptions{Variant: input.Secondary}).Clicked() {
for range 5 {
s.n++
mujica.ShowToast(c, mujica.ToastMessage{ID: fmt.Sprint(s.n), Message: fmt.Sprintf("Queued %d", s.n)})
feedback.ShowToast(c, feedback.ToastMessage{ID: fmt.Sprint(s.n), Message: fmt.Sprintf("Queued %d", s.n)})
}
log("toast: five queued, three visible")
}
if mujica.Button(c, "Repeat same ID", mujica.ButtonOptions{Variant: mujica.Secondary}).Clicked() {
if input.Button(c, "Repeat same ID", input.ButtonOptions{Variant: input.Secondary}).Clicked() {
s.n++
mujica.ShowToast(c, mujica.ToastMessage{ID: "sync", Message: fmt.Sprintf("Synced %d times", s.n)})
feedback.ShowToast(c, feedback.ToastMessage{ID: "sync", Message: fmt.Sprintf("Synced %d times", s.n)})
log("toast: dedupe id sync")
}
})
@@ -118,7 +120,7 @@ func toastDemo(c *ui.Context, log func(string)) {
ui.TextInput(c, &s.note).Width(320)
})
})
if r := mujica.Toaster(c); r.Action != "" {
if r := feedback.Toaster(c); r.Action != "" {
log("toast: action " + r.Action)
} else if r.Closed != "" {
log("toast: closed " + r.Closed)
@@ -149,8 +151,8 @@ func progressDemo(c *ui.Context, log func(string)) {
})
})
feedbackSection(c, "Progress", func() {
mujica.Progress(c, s.value, mujica.ProgressOptions{
Shape: mujica.ProgressShape(s.ring), Indeterminate: s.indet, Label: "Uploading", ShowValue: s.showValue,
feedback.Progress(c, s.value, feedback.ProgressOptions{
Shape: feedback.ProgressShape(s.ring), Indeterminate: s.indet, Label: "Uploading", ShowValue: s.showValue,
})
})
feedbackSection(c, "Meter with thresholds 70 and 90", func() {
@@ -161,8 +163,8 @@ func progressDemo(c *ui.Context, log func(string)) {
}
})
v := float64(int(s.meter))
mujica.Meter(c, v, mujica.MeterOptions{Max: 100, Label: "Disk", ShowValue: true, Levels: &ui.MeterLevels{Warning: 70, Critical: 90}})
mujica.Meter(c, 100-v, mujica.MeterOptions{Max: 100, Label: "Battery (low is bad)", ShowValue: true, Levels: &ui.MeterLevels{Warning: 30, Critical: 10}})
feedback.Meter(c, v, feedback.MeterOptions{Max: 100, Label: "Disk", ShowValue: true, Levels: &ui.MeterLevels{Warning: 70, Critical: 90}})
feedback.Meter(c, 100-v, feedback.MeterOptions{Max: 100, Label: "Battery (low is bad)", ShowValue: true, Levels: &ui.MeterLevels{Warning: 30, Critical: 10}})
})
})
}
@@ -188,13 +190,13 @@ func spinnerDemo(c *ui.Context, log func(string)) {
})
feedbackSection(c, "Live", func() {
if s.active {
mujica.Spinner(c, mujica.SpinnerOptions{Size: float32(s.size), Label: "Syncing", ShowLabel: s.showLabel})
core.Spinner(c, core.SpinnerOptions{Size: float32(s.size), Label: "Syncing", ShowLabel: s.showLabel})
} else {
ui.Text(c, "Done")
}
})
feedbackSection(c, "In a button", func() {
mujica.Button(c, "Saving", mujica.ButtonOptions{Loading: true})
input.Button(c, "Saving", input.ButtonOptions{Loading: true})
})
})
}
@@ -216,15 +218,15 @@ func skeletonDemo(c *ui.Context, log func(string)) {
}
})
feedbackSection(c, "Card (same size loading and loaded)", func() {
ui.Row(c).Gap(12).Padding(16).Width(420).Background(mujica.Tokens(c).Surface).Radius(theme.CardRadius).Children(func() {
ui.Row(c).Gap(12).Padding(16).Width(420).Background(core.Tokens(c).Surface).Radius(theme.CardRadius).Children(func() {
if s.loading {
mujica.Skeleton(c, mujica.SkeletonOptions{Shape: mujica.SkeletonCircle})
feedback.Skeleton(c, feedback.SkeletonOptions{Shape: feedback.SkeletonCircle})
ui.Column(c).Grow(1).Children(func() {
mujica.Skeleton(c, mujica.SkeletonOptions{Lines: int(s.lines)})
feedback.Skeleton(c, feedback.SkeletonOptions{Lines: int(s.lines)})
})
return
}
ui.Box(c).Size(40, 40).Radius(20).Background(mujica.Tokens(c).Selection)
ui.Box(c).Size(40, 40).Radius(20).Background(core.Tokens(c).Selection)
ui.Column(c).Grow(1).Children(func() {
for i := range int(s.lines) {
ui.Text(c, fmt.Sprintf("Loaded line %d", i+1)).LineHeight(theme.LineHeight)
@@ -233,7 +235,7 @@ func skeletonDemo(c *ui.Context, log func(string)) {
})
})
feedbackSection(c, "Rect", func() {
mujica.Skeleton(c, mujica.SkeletonOptions{Shape: mujica.SkeletonRect, Width: 420, Height: 96})
feedback.Skeleton(c, feedback.SkeletonOptions{Shape: feedback.SkeletonRect, Width: 420, Height: 96})
})
})
}
@@ -257,14 +259,14 @@ func emptyStateDemo(c *ui.Context, log func(string)) {
})
})
feedbackSection(c, "Live", func() {
opts := mujica.EmptyStateOptions{Kind: mujica.EmptyKind(s.kind), NoIcon: !s.icon}
opts := feedback.EmptyStateOptions{Kind: feedback.EmptyKind(s.kind), NoIcon: !s.icon}
if s.kind == 1 {
opts.Query = s.query
}
if s.action {
opts.Action = []string{"New project", "Clear search"}[s.kind]
}
if mujica.EmptyState(c, opts).Action {
if feedback.EmptyState(c, opts).Action {
log("empty state: action " + opts.Action)
s.query = ""
}
@@ -281,7 +283,7 @@ func resultDemo(c *ui.Context, log func(string)) {
feedbackSection(c, "Parameters", func() {
ui.Segmented(c, &s.kind, "Success", "Failure", "Forbidden")
})
r := mujica.Result(c, mujica.ResultOptions{Kind: mujica.ResultKind(s.kind), Description: descs[s.kind], Actions: actions[s.kind]})
r := feedback.Result(c, feedback.ResultOptions{Kind: feedback.ResultKind(s.kind), Description: descs[s.kind], Actions: actions[s.kind]})
if r.Action >= 0 {
log("result: action " + actions[s.kind][r.Action])
}
@@ -297,21 +299,21 @@ func statusDemo(c *ui.Context, log func(string)) {
ui.Column(c).Gap(16).FillWidth().Children(func() {
feedbackSection(c, "Parameters", func() {
if ui.Segmented(c, &s.status, "Success", "Warning", "Error", "Offline", "Busy").Changed() {
log("status: " + mujica.Status(s.status).String())
log("status: " + feedback.Status(s.status).String())
}
ui.Checkbox(c, &s.custom, "Custom label")
})
feedbackSection(c, "Live", func() {
opts := mujica.StatusIndicatorOptions{}
opts := feedback.StatusIndicatorOptions{}
if s.custom {
opts.Label = "Database replica"
}
mujica.StatusIndicator(c, mujica.Status(s.status), opts)
feedback.StatusIndicator(c, feedback.Status(s.status), opts)
})
feedbackSection(c, "All states (shape and word, not color alone)", func() {
ui.Row(c).Gap(20).Wrap().Children(func() {
for st := mujica.StatusSuccess; st <= mujica.StatusBusy; st++ {
mujica.StatusIndicator(c, st, mujica.StatusIndicatorOptions{}).Key(st)
for st := feedback.StatusSuccess; st <= feedback.StatusBusy; st++ {
feedback.StatusIndicator(c, st, feedback.StatusIndicatorOptions{}).Key(st)
}
})
})
@@ -320,11 +322,11 @@ func statusDemo(c *ui.Context, log func(string)) {
func notificationDemo(c *ui.Context, log func(string)) {
type state struct {
items []mujica.Notification
items []feedback.Notification
n int
}
s := ui.Local(c.Root(), "099", func() state {
return state{items: []mujica.Notification{
return state{items: []feedback.Notification{
{ID: "a", Title: "Build failed", Body: "main · 3 tests failing", Time: "2 min ago", Action: "View log"},
{ID: "b", Title: "Invite accepted", Body: "Mira joined the workspace", Time: "1 h ago"},
{ID: "c", Title: "Backup complete", Time: "Yesterday", Read: true},
@@ -332,15 +334,15 @@ func notificationDemo(c *ui.Context, log func(string)) {
})
ui.Column(c).Gap(16).FillWidth().Children(func() {
feedbackSection(c, "Caller-owned list", func() {
if mujica.Button(c, "Add notification", mujica.ButtonOptions{Variant: mujica.Secondary}).Clicked() {
if input.Button(c, "Add notification", input.ButtonOptions{Variant: input.Secondary}).Clicked() {
s.n++
id := fmt.Sprintf("new-%d", s.n)
s.items = append([]mujica.Notification{{ID: id, Title: fmt.Sprintf("Comment %d on the design doc", s.n), Time: "now"}}, s.items...)
s.items = append([]feedback.Notification{{ID: id, Title: fmt.Sprintf("Comment %d on the design doc", s.n), Time: "now"}}, s.items...)
log("notifications: added " + id)
}
})
ui.Column(c).Width(520).Height(420).Children(func() {
r := mujica.NotificationCenter(c, &s.items, mujica.NotificationCenterOptions{})
r := feedback.NotificationCenter(c, &s.items, feedback.NotificationCenterOptions{})
for _, ev := range [][2]string{{"action", r.Action}, {"toggled", r.Toggled}, {"opened", r.Opened}, {"removed", r.Removed}} {
if ev[1] != "" {
log("notifications: " + ev[0] + " " + ev[1])
@@ -372,15 +374,15 @@ func loadingOverlayDemo(c *ui.Context, log func(string)) {
})
})
feedbackSection(c, "Covered region", func() {
opts := mujica.LoadingOverlayOptions{Label: "Generating report…"}
opts := feedback.LoadingOverlayOptions{Label: "Generating report…"}
if s.cancel {
opts.Cancel = "Cancel"
}
r := mujica.LoadingOverlay(c, s.loading, opts, func() {
ui.Column(c).Gap(8).Padding(16).FillWidth().AlignItems(ui.Start).Background(mujica.Tokens(c).Surface).Radius(theme.CardRadius).Children(func() {
r := feedback.LoadingOverlay(c, s.loading, opts, func() {
ui.Column(c).Gap(8).Padding(16).FillWidth().AlignItems(ui.Start).Background(core.Tokens(c).Surface).Radius(theme.CardRadius).Children(func() {
ui.Text(c, "Quarterly report").Font(theme.SerifFont).FontSize(theme.H3Size)
ui.Text(c, fmt.Sprintf("Exported %d times.", s.saves))
if mujica.Button(c, "Export", mujica.ButtonOptions{}).Clicked() {
if input.Button(c, "Export", input.ButtonOptions{}).Clicked() {
s.saves++
log("overlay: export clicked")
}
@@ -393,7 +395,7 @@ func loadingOverlayDemo(c *ui.Context, log func(string)) {
}
})
feedbackSection(c, "Outside the region (always usable)", func() {
if mujica.Button(c, "Outside button", mujica.ButtonOptions{Variant: mujica.Secondary}).AlignSelf(ui.Start).Clicked() {
if input.Button(c, "Outside button", input.ButtonOptions{Variant: input.Secondary}).AlignSelf(ui.Start).Clicked() {
log("overlay: outside clicked")
}
})
+68 -64
View File
@@ -7,7 +7,11 @@ import (
"time"
"github.com/egoist/mygo/ui"
mujica "mujicaui"
"github.com/ZacharyZhang-NY/MujicaUI/data"
"github.com/ZacharyZhang-NY/MujicaUI/display"
"github.com/ZacharyZhang-NY/MujicaUI/files"
"github.com/ZacharyZhang-NY/MujicaUI/input"
)
var filesFolders = map[string][]string{
@@ -16,24 +20,24 @@ var filesFolders = map[string][]string{
"/court/decrees": {"42.pdf", "crest.png"},
}
func filesEntries(dir string) []mujica.FileEntry {
var entries []mujica.FileEntry
func filesEntries(dir string) []files.FileEntry {
var entries []files.FileEntry
for _, n := range filesFolders[dir] {
name := strings.TrimSuffix(n, "/")
entries = append(entries, mujica.FileEntry{Path: path.Join(dir, name), Name: name, Dir: name != n, Size: 2048})
entries = append(entries, files.FileEntry{Path: path.Join(dir, name), Name: name, Dir: name != n, Size: 2048})
}
return entries
}
func fileExplorerDemo(c *ui.Context, log func(string)) {
type state struct {
explorer mujica.FileExplorerState
explorer files.FileExplorerState
disabled, empty bool
}
s := ui.Local(c.Root(), "381", func() state { return state{} })
mediaCheck(c, log, &s.disabled, "Disabled")
mediaCheck(c, log, &s.empty, "Empty folder")
r := mujica.FileExplorer(c, &s.explorer, mujica.FileExplorerOptions{
r := files.FileExplorer(c, &s.explorer, files.FileExplorerOptions{
Roots: []string{"/court"}, Disabled: s.disabled,
Folders: func(dir string) []string {
var dirs []string
@@ -44,7 +48,7 @@ func fileExplorerDemo(c *ui.Context, log func(string)) {
}
return dirs
},
Entries: func(dir string) []mujica.FileEntry {
Entries: func(dir string) []files.FileEntry {
if s.empty {
return nil
}
@@ -65,8 +69,8 @@ func fileExplorerDemo(c *ui.Context, log func(string)) {
func fileGridDemo(c *ui.Context, log func(string)) {
type state struct {
grid mujica.FileGridState
entries []mujica.FileEntry
grid files.FileGridState
entries []files.FileEntry
disabled, thumbs, empty bool
}
s := ui.Local(c.Root(), "382", func() state {
@@ -79,11 +83,11 @@ func fileGridDemo(c *ui.Context, log func(string)) {
if s.empty {
entries = nil
}
r := mujica.FileGrid(c, entries, &s.grid, mujica.FileGridOptions{Disabled: s.disabled, Thumbnail: func(e mujica.FileEntry) bool {
if !s.thumbs || mujica.FileKindOf(e.Name) != mujica.FileImage {
r := files.FileGrid(c, entries, &s.grid, files.FileGridOptions{Disabled: s.disabled, Thumbnail: func(e files.FileEntry) bool {
if !s.thumbs || files.FileKindOf(e.Name) != files.FileImage {
return false
}
mujica.Image(c, mediaBitmaps[0], mujica.ImageOptions{Alt: e.Name}).Element.Fill()
display.Image(c, mediaBitmaps[0], display.ImageOptions{Alt: e.Name}).Element.Fill()
return true
}})
r.Element.Height(320).FillWidth()
@@ -102,20 +106,20 @@ func fileIconDemo(c *ui.Context, log func(string)) {
size float64
}
s := ui.Local(c.Root(), "383", func() state { return state{name: "seal.go", size: 32} })
if mujica.TextInput(c, &s.name, mujica.TextInputOptions{Label: "File name"}).Changed() {
if input.TextInput(c, &s.name, input.TextInputOptions{Label: "File name"}).Changed() {
log("File name changed")
}
mediaCheck(c, log, &s.folder, "Folder")
if s.folder {
mediaCheck(c, log, &s.open, "Open folder")
}
if mujica.NumberInput(c, &s.size, mujica.NumberInputOptions{Min: 20, Max: 64, Step: 4, Label: "Icon size"}).Changed() {
if input.NumberInput(c, &s.size, input.NumberInputOptions{Min: 20, Max: 64, Step: 4, Label: "Icon size"}).Changed() {
log(fmt.Sprintf("Size: %.0f", s.size))
}
mujica.FileIcon(c, s.name, mujica.FileIconOptions{Folder: s.folder, Open: s.folder && s.open, Size: float32(s.size)})
files.FileIcon(c, s.name, files.FileIconOptions{Folder: s.folder, Open: s.folder && s.open, Size: float32(s.size)})
ui.Row(c).Gap(12).Children(func() {
for _, name := range []string{"seal.go", "notes.md", "crest.png", "deed.pdf", "ledger.zip", "hymn.mp3", "rite.mov", "blob"} {
mujica.FileIcon(c, name, mujica.FileIconOptions{Size: 32})
files.FileIcon(c, name, files.FileIconOptions{Size: 32})
}
})
}
@@ -123,24 +127,24 @@ func fileIconDemo(c *ui.Context, log func(string)) {
func filePreviewDemo(c *ui.Context, log func(string)) {
type state struct {
kind int
status mujica.DataStatus
status data.DataStatus
closed bool
}
s := ui.Local(c.Root(), "384", func() state { return state{} })
if mujica.Select(c, &s.kind, []mujica.SelectOption[int]{{Value: 0, Label: "Code"}, {Value: 1, Label: "Text"}, {Value: 2, Label: "Image"}, {Value: 3, Label: "Unsupported"}}, mujica.SelectOptions{Label: "Preview type"}).Changed() {
if input.Select(c, &s.kind, []input.SelectOption[int]{{Value: 0, Label: "Code"}, {Value: 1, Label: "Text"}, {Value: 2, Label: "Image"}, {Value: 3, Label: "Unsupported"}}, input.SelectOptions{Label: "Preview type"}).Changed() {
log("Preview type changed")
s.closed = false
}
filesStatus(c, log, &s.status)
if s.closed {
if mujica.Button(c, "Show preview", mujica.ButtonOptions{}).Clicked() {
if input.Button(c, "Show preview", input.ButtonOptions{}).Clicked() {
s.closed = false
log("Preview reopened")
}
return
}
f := []mujica.PreviewFile{{Name: "seal.go", Text: "package court\n\nfunc Seal() {}"}, {Name: "notes.md", Text: "The seal of the court."}, {Name: "crest.png", Image: mediaBitmaps[0]}, {Name: "deed.pdf", Size: 4096}}[s.kind]
r := mujica.FilePreview(c, f, mujica.FilePreviewOptions{Status: s.status, Error: "Reading failed"})
f := []files.PreviewFile{{Name: "seal.go", Text: "package court\n\nfunc Seal() {}"}, {Name: "notes.md", Text: "The seal of the court."}, {Name: "crest.png", Image: mediaBitmaps[0]}, {Name: "deed.pdf", Size: 4096}}[s.kind]
r := files.FilePreview(c, f, files.FilePreviewOptions{Status: s.status, Error: "Reading failed"})
r.Element.Height(280).FillWidth()
if r.Closed {
s.closed = true
@@ -150,13 +154,13 @@ func filePreviewDemo(c *ui.Context, log func(string)) {
log("Open: " + f.Name)
}
if r.Retried {
s.status = mujica.DataReady
s.status = data.DataReady
log("Read retried")
}
}
func filesStatus(c *ui.Context, log func(string), value *mujica.DataStatus) {
if mujica.Select(c, value, []mujica.SelectOption[mujica.DataStatus]{{Value: mujica.DataReady, Label: "Ready"}, {Value: mujica.DataLoading, Label: "Loading"}, {Value: mujica.DataFailed, Label: "Failed"}}, mujica.SelectOptions{Label: "Load status"}).Changed() {
func filesStatus(c *ui.Context, log func(string), value *data.DataStatus) {
if input.Select(c, value, []input.SelectOption[data.DataStatus]{{Value: data.DataReady, Label: "Ready"}, {Value: data.DataLoading, Label: "Loading"}, {Value: data.DataFailed, Label: "Failed"}}, input.SelectOptions{Label: "Load status"}).Changed() {
log(fmt.Sprintf("Status: %d", *value))
}
}
@@ -168,14 +172,14 @@ func pathBarDemo(c *ui.Context, log func(string)) {
}
s := ui.Local(c.Root(), "385", func() state { return state{path: "/court/chronicles/royal/archives/seals"} })
mediaCheck(c, log, &s.disabled, "Disabled")
if mujica.TextInput(c, &s.path, mujica.TextInputOptions{Label: "Path"}).Changed() {
if input.TextInput(c, &s.path, input.TextInputOptions{Label: "Path"}).Changed() {
log("Path changed")
}
if strings.Trim(s.path, "/") == "" {
ui.Text(c, "Enter a path")
return
}
if r := mujica.PathBar(c, s.path, mujica.PathBarOptions{Disabled: s.disabled}); r.Navigated != "" {
if r := files.PathBar(c, s.path, files.PathBarOptions{Disabled: s.disabled}); r.Navigated != "" {
s.path = r.Navigated
log("Navigate: " + r.Navigated)
}
@@ -189,11 +193,11 @@ func renameInlineDemo(c *ui.Context, log func(string)) {
s := ui.Local(c.Root(), "386", func() state { return state{name: "seal.txt"} })
mediaCheck(c, log, &s.disabled, "Disabled")
mediaCheck(c, log, &s.folder, "Folder")
if mujica.Button(c, "Rename", mujica.ButtonOptions{Disabled: s.disabled}).Clicked() {
if input.Button(c, "Rename", input.ButtonOptions{Disabled: s.disabled}).Clicked() {
s.editing = true
log("Rename started")
}
r := mujica.RenameInline(c, &s.name, &s.editing, mujica.RenameInlineOptions{Disabled: s.disabled, Folder: s.folder, Validate: func(name string) string {
r := files.RenameInline(c, &s.name, &s.editing, files.RenameInlineOptions{Disabled: s.disabled, Folder: s.folder, Validate: func(name string) string {
if strings.ContainsAny(name, "/\\") {
return "Name contains a path separator"
}
@@ -209,23 +213,23 @@ func renameInlineDemo(c *ui.Context, log func(string)) {
func fileOperationProgressDemo(c *ui.Context, log func(string)) {
type state struct {
op mujica.FileOperation
op files.FileOperation
disabled, conflict bool
canceled bool
}
s := ui.Local(c.Root(), "387", func() state {
return state{op: mujica.FileOperation{Kind: mujica.FileCopy, Total: 10, Current: "seal.go"}}
return state{op: files.FileOperation{Kind: files.FileCopy, Total: 10, Current: "seal.go"}}
})
mediaCheck(c, log, &s.disabled, "Disabled")
mediaCheck(c, log, &s.conflict, "Conflict")
if mujica.Select(c, &s.op.Kind, []mujica.SelectOption[mujica.FileOperationKind]{{Value: mujica.FileCopy, Label: "Copy"}, {Value: mujica.FileMove, Label: "Move"}, {Value: mujica.FileDelete, Label: "Delete"}}, mujica.SelectOptions{Label: "Operation"}).Changed() {
if input.Select(c, &s.op.Kind, []input.SelectOption[files.FileOperationKind]{{Value: files.FileCopy, Label: "Copy"}, {Value: files.FileMove, Label: "Move"}, {Value: files.FileDelete, Label: "Delete"}}, input.SelectOptions{Label: "Operation"}).Changed() {
log("Operation changed")
}
if mujica.Button(c, "Advance", mujica.ButtonOptions{Disabled: s.op.Done == s.op.Total}).Clicked() {
if input.Button(c, "Advance", input.ButtonOptions{Disabled: s.op.Done == s.op.Total}).Clicked() {
s.op.Done++
log(fmt.Sprintf("Done: %d", s.op.Done))
}
if mujica.Button(c, "Reset", mujica.ButtonOptions{}).Clicked() {
if input.Button(c, "Reset", input.ButtonOptions{}).Clicked() {
s.op.Done = 0
s.canceled = false
log("Operation reset")
@@ -234,51 +238,51 @@ func fileOperationProgressDemo(c *ui.Context, log func(string)) {
if s.conflict {
op.Conflict = "seal.go"
}
r := mujica.FileOperationProgress(c, op, mujica.FileOperationOptions{Disabled: s.disabled || s.canceled})
r := files.FileOperationProgress(c, op, files.FileOperationOptions{Disabled: s.disabled || s.canceled})
if r.Canceled {
s.canceled = true
log("Operation canceled")
}
if r.Resolved != mujica.ConflictNone {
if r.Resolved != files.ConflictNone {
s.conflict = false
log(fmt.Sprintf("Conflict: %d; apply all: %v", r.Resolved, r.ApplyAll))
}
}
func filesTransfers() []mujica.QueuedTransfer {
return []mujica.QueuedTransfer{
{ID: "a", Name: "ledger.zip", Upload: true, Status: mujica.TransferActive, Done: 30, Total: 120},
{ID: "b", Name: "crest.png", Status: mujica.TransferPaused, Done: 1, Total: 4},
{ID: "c", Name: "deed.pdf", Upload: true, Status: mujica.TransferFailed, Total: 2, Error: "Network lost"},
func filesTransfers() []files.QueuedTransfer {
return []files.QueuedTransfer{
{ID: "a", Name: "ledger.zip", Upload: true, Status: files.TransferActive, Done: 30, Total: 120},
{ID: "b", Name: "crest.png", Status: files.TransferPaused, Done: 1, Total: 4},
{ID: "c", Name: "deed.pdf", Upload: true, Status: files.TransferFailed, Total: 2, Error: "Network lost"},
}
}
func transferQueueDemo(c *ui.Context, log func(string)) {
type state struct {
items []mujica.QueuedTransfer
queue mujica.TransferQueueState
items []files.QueuedTransfer
queue files.TransferQueueState
disabled bool
revision uint64
}
s := ui.Local(c.Root(), "388", func() state { return state{items: filesTransfers()} })
mediaCheck(c, log, &s.disabled, "Disabled")
if mujica.Button(c, "Reset", mujica.ButtonOptions{}).Clicked() {
if input.Button(c, "Reset", input.ButtonOptions{}).Clicked() {
s.items = filesTransfers()
log("Queue reset")
}
r := mujica.TransferQueue(c, s.items, &s.queue, mujica.TransferQueueOptions{Disabled: s.disabled, Revision: s.revision})
r := files.TransferQueue(c, s.items, &s.queue, files.TransferQueueOptions{Disabled: s.disabled, Revision: s.revision})
r.Element.Height(260).FillWidth()
if r.Action == mujica.TransferNone {
if r.Action == files.TransferNone {
return
}
for i := range s.items {
if s.items[i].ID == r.ID {
switch r.Action {
case mujica.TransferPause:
s.items[i].Status = mujica.TransferPaused
case mujica.TransferResume, mujica.TransferRetry:
s.items[i].Status = mujica.TransferActive
case mujica.TransferRemove:
case files.TransferPause:
s.items[i].Status = files.TransferPaused
case files.TransferResume, files.TransferRetry:
s.items[i].Status = files.TransferActive
case files.TransferRemove:
s.items = append(s.items[:i:i], s.items[i+1:]...)
}
break
@@ -291,30 +295,30 @@ func transferQueueDemo(c *ui.Context, log func(string)) {
func storageUsageDemo(c *ui.Context, log func(string)) {
type state struct{ photos float64 }
s := ui.Local(c.Root(), "389", func() state { return state{photos: 40} })
if mujica.NumberInput(c, &s.photos, mujica.NumberInputOptions{Min: 0, Max: 92, Step: 1, Label: "Photos (GB)"}).Changed() {
if input.NumberInput(c, &s.photos, input.NumberInputOptions{Min: 0, Max: 92, Step: 1, Label: "Photos (GB)"}).Changed() {
log(fmt.Sprintf("Photos: %.0f GB", s.photos))
}
mujica.StorageUsage(c, []mujica.StorageSegment{{Label: "Documents", Bytes: 24 << 30}, {Label: "Photos", Bytes: int64(s.photos) * (1 << 30)}, {Label: "Apps", Bytes: 12 << 30}}, mujica.StorageUsageOptions{Capacity: 128 << 30, Label: "Court disk"}).FillWidth()
files.StorageUsage(c, []files.StorageSegment{{Label: "Documents", Bytes: 24 << 30}, {Label: "Photos", Bytes: int64(s.photos) * (1 << 30)}, {Label: "Apps", Bytes: 12 << 30}}, files.StorageUsageOptions{Capacity: 128 << 30, Label: "Court disk"}).FillWidth()
}
func filesRecent() []mujica.RecentFile {
func filesRecent() []files.RecentFile {
now := time.Date(2026, 10, 5, 15, 0, 0, 0, time.UTC)
return []mujica.RecentFile{{Path: "/court/seal.go", Opened: now}, {Path: "/court/notes.md", Opened: now.AddDate(0, 0, -1)}, {Path: "/court/crest.png", Opened: now.AddDate(0, 0, -4)}, {Path: "/court/charter.pdf", Opened: now.AddDate(0, -1, 0)}}
return []files.RecentFile{{Path: "/court/seal.go", Opened: now}, {Path: "/court/notes.md", Opened: now.AddDate(0, 0, -1)}, {Path: "/court/crest.png", Opened: now.AddDate(0, 0, -4)}, {Path: "/court/charter.pdf", Opened: now.AddDate(0, -1, 0)}}
}
func recentFilesDemo(c *ui.Context, log func(string)) {
type state struct {
files []mujica.RecentFile
recent mujica.RecentFilesState
files []files.RecentFile
recent files.RecentFilesState
disabled bool
}
s := ui.Local(c.Root(), "390", func() state { return state{files: filesRecent()} })
mediaCheck(c, log, &s.disabled, "Disabled")
if mujica.Button(c, "Reset", mujica.ButtonOptions{}).Clicked() {
if input.Button(c, "Reset", input.ButtonOptions{}).Clicked() {
s.files = filesRecent()
log("Recent files reset")
}
r := mujica.RecentFiles(c, s.files, &s.recent, mujica.RecentFilesOptions{Disabled: s.disabled, Now: time.Date(2026, 10, 5, 15, 0, 0, 0, time.UTC)})
r := files.RecentFiles(c, s.files, &s.recent, files.RecentFilesOptions{Disabled: s.disabled, Now: time.Date(2026, 10, 5, 15, 0, 0, 0, time.UTC)})
r.Element.Height(320).FillWidth()
if r.Opened != nil {
log("Open: " + r.Opened.Path)
@@ -336,13 +340,13 @@ func recentFilesDemo(c *ui.Context, log func(string)) {
func archiveViewerDemo(c *ui.Context, log func(string)) {
type state struct {
archive mujica.ArchiveViewerState
entries []mujica.ArchiveEntry
status mujica.DataStatus
archive files.ArchiveViewerState
entries []files.ArchiveEntry
status data.DataStatus
empty bool
}
s := ui.Local(c.Root(), "391", func() state {
return state{entries: []mujica.ArchiveEntry{{Path: "court/seal.go", Size: 4000, Packed: 1000}, {Path: "court/decrees/42.pdf", Size: 6000, Packed: 5000}, {Path: "README.md", Size: 1000, Packed: 400}}}
return state{entries: []files.ArchiveEntry{{Path: "court/seal.go", Size: 4000, Packed: 1000}, {Path: "court/decrees/42.pdf", Size: 6000, Packed: 5000}, {Path: "README.md", Size: 1000, Packed: 400}}}
})
filesStatus(c, log, &s.status)
mediaCheck(c, log, &s.empty, "Empty")
@@ -350,13 +354,13 @@ func archiveViewerDemo(c *ui.Context, log func(string)) {
if s.empty {
entries = nil
}
r := mujica.ArchiveViewer(c, entries, &s.archive, mujica.ArchiveViewerOptions{Status: s.status, Error: "Invalid archive"})
r := files.ArchiveViewer(c, entries, &s.archive, files.ArchiveViewerOptions{Status: s.status, Error: "Invalid archive"})
r.Element.Height(280).FillWidth()
if r.Opened != nil {
log("Open archive entry: " + r.Opened.Path)
}
if r.Retried {
s.status = mujica.DataReady
s.status = data.DataReady
log("Archive read retried")
}
}
+55 -55
View File
@@ -1,135 +1,135 @@
package main
const filesFileExplorerCode = `var state mujica.FileExplorerState
opts := mujica.FileExplorerOptions{
const filesFileExplorerCode = `var state files.FileExplorerState
opts := files.FileExplorerOptions{
Roots: []string{"/court"},
Folders: func(string) []string { return nil },
Entries: func(string) []mujica.FileEntry {
return []mujica.FileEntry{{Path: "/court/seal.go", Name: "seal.go", Size: 1024}}
Entries: func(string) []files.FileEntry {
return []files.FileEntry{{Path: "/court/seal.go", Name: "seal.go", Size: 1024}}
},
}
view := func(c *ui.Context) {
mujica.Use(c, mujica.Settings{})
mujica.FileExplorer(c, &state, opts).Element.Height(240)
core.Use(c, core.Settings{})
files.FileExplorer(c, &state, opts).Element.Height(240)
}
ui.Render(view, 600, 300, 1)`
const filesFileGridCode = `entries := []mujica.FileEntry{{Path: "/court/seal.go", Name: "seal.go"}}
var state mujica.FileGridState
const filesFileGridCode = `entries := []files.FileEntry{{Path: "/court/seal.go", Name: "seal.go"}}
var state files.FileGridState
view := func(c *ui.Context) {
mujica.Use(c, mujica.Settings{})
mujica.FileGrid(c, entries, &state, mujica.FileGridOptions{}).Element.Height(240)
core.Use(c, core.Settings{})
files.FileGrid(c, entries, &state, files.FileGridOptions{}).Element.Height(240)
}
ui.Render(view, 400, 300, 1)`
const filesFileIconCode = `view := func(c *ui.Context) {
mujica.Use(c, mujica.Settings{})
mujica.FileIcon(c, "seal.go", mujica.FileIconOptions{Size: 32})
core.Use(c, core.Settings{})
files.FileIcon(c, "seal.go", files.FileIconOptions{Size: 32})
}
ui.Render(view, 100, 80, 1)`
const filesFilePreviewCode = `file := mujica.PreviewFile{Name: "seal.go", Text: "package court\nfunc Seal() {}"}
const filesFilePreviewCode = `file := files.PreviewFile{Name: "seal.go", Text: "package court\nfunc Seal() {}"}
view := func(c *ui.Context) {
mujica.Use(c, mujica.Settings{})
mujica.FilePreview(c, file, mujica.FilePreviewOptions{}).Element.Height(240)
core.Use(c, core.Settings{})
files.FilePreview(c, file, files.FilePreviewOptions{}).Element.Height(240)
}
ui.Render(view, 500, 300, 1)`
const filesPathBarCode = `view := func(c *ui.Context) {
mujica.Use(c, mujica.Settings{})
mujica.PathBar(c, "/court/chronicles/seals", mujica.PathBarOptions{})
core.Use(c, core.Settings{})
files.PathBar(c, "/court/chronicles/seals", files.PathBarOptions{})
}
ui.Render(view, 400, 100, 1)`
const filesRenameInlineCode = `name, editing := "seal.txt", true
view := func(c *ui.Context) {
mujica.Use(c, mujica.Settings{})
mujica.RenameInline(c, &name, &editing, mujica.RenameInlineOptions{})
core.Use(c, core.Settings{})
files.RenameInline(c, &name, &editing, files.RenameInlineOptions{})
}
ui.Render(view, 400, 100, 1)`
const filesFileOperationProgressCode = `op := mujica.FileOperation{Kind: mujica.FileCopy, Done: 2, Total: 10, Current: "seal.go"}
const filesFileOperationProgressCode = `op := files.FileOperation{Kind: files.FileCopy, Done: 2, Total: 10, Current: "seal.go"}
view := func(c *ui.Context) {
mujica.Use(c, mujica.Settings{})
mujica.FileOperationProgress(c, op, mujica.FileOperationOptions{})
core.Use(c, core.Settings{})
files.FileOperationProgress(c, op, files.FileOperationOptions{})
}
ui.Render(view, 500, 240, 1)`
const filesTransferQueueCode = `items := []mujica.QueuedTransfer{{ID: "a", Name: "ledger.zip", Upload: true, Status: mujica.TransferActive, Done: 25, Total: 100}}
var state mujica.TransferQueueState
const filesTransferQueueCode = `items := []files.QueuedTransfer{{ID: "a", Name: "ledger.zip", Upload: true, Status: files.TransferActive, Done: 25, Total: 100}}
var state files.TransferQueueState
view := func(c *ui.Context) {
mujica.Use(c, mujica.Settings{})
mujica.TransferQueue(c, items, &state, mujica.TransferQueueOptions{}).Element.Height(240)
core.Use(c, core.Settings{})
files.TransferQueue(c, items, &state, files.TransferQueueOptions{}).Element.Height(240)
}
ui.Render(view, 500, 300, 1)`
const filesStorageUsageCode = `segments := []mujica.StorageSegment{{Label: "Documents", Bytes: 24 << 30}, {Label: "Photos", Bytes: 40 << 30}}
const filesStorageUsageCode = `segments := []files.StorageSegment{{Label: "Documents", Bytes: 24 << 30}, {Label: "Photos", Bytes: 40 << 30}}
view := func(c *ui.Context) {
mujica.Use(c, mujica.Settings{})
mujica.StorageUsage(c, segments, mujica.StorageUsageOptions{Capacity: 128 << 30, Label: "Court disk"})
core.Use(c, core.Settings{})
files.StorageUsage(c, segments, files.StorageUsageOptions{Capacity: 128 << 30, Label: "Court disk"})
}
ui.Render(view, 500, 240, 1)`
const filesRecentFilesCode = `now := time.Date(2026, 10, 5, 12, 0, 0, 0, time.UTC)
files := []mujica.RecentFile{{Path: "/court/seal.go", Opened: now}}
var state mujica.RecentFilesState
recent := []files.RecentFile{{Path: "/court/seal.go", Opened: now}}
var state files.RecentFilesState
view := func(c *ui.Context) {
mujica.Use(c, mujica.Settings{})
mujica.RecentFiles(c, files, &state, mujica.RecentFilesOptions{Now: now}).Element.Height(240)
core.Use(c, core.Settings{})
files.RecentFiles(c, recent, &state, files.RecentFilesOptions{Now: now}).Element.Height(240)
}
ui.Render(view, 500, 300, 1)`
const filesArchiveViewerCode = `entries := []mujica.ArchiveEntry{{Path: "court/seal.go", Size: 4000, Packed: 1000}}
var state mujica.ArchiveViewerState
const filesArchiveViewerCode = `entries := []files.ArchiveEntry{{Path: "court/seal.go", Size: 4000, Packed: 1000}}
var state files.ArchiveViewerState
view := func(c *ui.Context) {
mujica.Use(c, mujica.Settings{})
mujica.ArchiveViewer(c, entries, &state, mujica.ArchiveViewerOptions{}).Element.Height(240)
core.Use(c, core.Settings{})
files.ArchiveViewer(c, entries, &state, files.ArchiveViewerOptions{}).Element.Height(240)
}
ui.Render(view, 600, 300, 1)`
const filesPresenceAvatarsCode = `people := []mujica.Collaborator{{ID: "ada", Name: "Ada Lovelace"}}
const filesPresenceAvatarsCode = `people := []files.Collaborator{{ID: "ada", Name: "Ada Lovelace"}}
view := func(c *ui.Context) {
mujica.Use(c, mujica.Settings{})
mujica.PresenceAvatars(c, people, mujica.PresenceAvatarsOptions{})
core.Use(c, core.Settings{})
files.PresenceAvatars(c, people, files.PresenceAvatarsOptions{})
}
ui.Render(view, 300, 100, 1)`
const filesReactionsCode = `items := []mujica.Reaction{{Emoji: "\U0001f44d", Count: 3, Mine: true}}
const filesReactionsCode = `items := []files.Reaction{{Emoji: "\U0001f44d", Count: 3, Mine: true}}
view := func(c *ui.Context) {
mujica.Use(c, mujica.Settings{})
mujica.Reactions(c, items, mujica.ReactionsOptions{Choices: []string{"\U0001f44d", "\U0001f389"}})
core.Use(c, core.Settings{})
files.Reactions(c, items, files.ReactionsOptions{Choices: []string{"\U0001f44d", "\U0001f389"}})
}
ui.Render(view, 400, 100, 1)`
const filesShareDialogCode = `open := false
members := []mujica.ShareMember{{ID: "ada", Name: "Ada", Permission: mujica.PermissionEdit, Owner: true}}
members := []files.ShareMember{{ID: "ada", Name: "Ada", Permission: files.PermissionEdit, Owner: true}}
view := func(c *ui.Context) {
mujica.Use(c, mujica.Settings{})
if mujica.Button(c, "Share", mujica.ButtonOptions{}).Clicked() {
core.Use(c, core.Settings{})
if input.Button(c, "Share", input.ButtonOptions{}).Clicked() {
open = true
}
mujica.ShareDialog(c, &open, &members, mujica.ShareDialogOptions{Title: "Ledger", Link: "https://court.org/ledger"})
files.ShareDialog(c, &open, &members, files.ShareDialogOptions{Title: "Ledger", Link: "https://court.org/ledger"})
}
ui.Render(view, 600, 400, 1)`
const filesPermissionSelectCode = `value := mujica.PermissionView
const filesPermissionSelectCode = `value := files.PermissionView
view := func(c *ui.Context) {
mujica.Use(c, mujica.Settings{})
mujica.PermissionSelect(c, &value, mujica.PermissionSelectOptions{})
core.Use(c, core.Settings{})
files.PermissionSelect(c, &value, files.PermissionSelectOptions{})
}
ui.Render(view, 300, 100, 1)`
const filesLiveIndicatorCode = `view := func(c *ui.Context) {
mujica.Use(c, mujica.Settings{})
mujica.LiveIndicator(c, mujica.LiveOffline, mujica.LiveIndicatorOptions{})
core.Use(c, core.Settings{})
files.LiveIndicator(c, files.LiveOffline, files.LiveIndicatorOptions{})
}
ui.Render(view, 500, 100, 1)`
const filesRemoteCursorCode = `carets := []mujica.RemoteCaret{{ID: "ada", Name: "Ada", Offset: 4, Anchor: 4}}
const filesRemoteCursorCode = `carets := []files.RemoteCaret{{ID: "ada", Name: "Ada", Offset: 4, Anchor: 4}}
view := func(c *ui.Context) {
mujica.Use(c, mujica.Settings{})
core.Use(c, core.Settings{})
ui.Box(c).Size(300, 100).Children(func() {
mujica.RemoteCursor(c, carets, mujica.RemoteCursorOptions{
files.RemoteCursor(c, carets, files.RemoteCursorOptions{
Caret: func(offset int) ui.Rect { return ui.Rect{X: float32(offset) * 8, Y: 20, H: 20} },
})
})
+33 -30
View File
@@ -5,11 +5,14 @@ import (
"time"
"github.com/egoist/mygo/ui"
mujica "mujicaui"
"mujicaui/theme"
"github.com/ZacharyZhang-NY/MujicaUI/core"
"github.com/ZacharyZhang-NY/MujicaUI/files"
"github.com/ZacharyZhang-NY/MujicaUI/input"
"github.com/ZacharyZhang-NY/MujicaUI/theme"
)
var filesPeople = []mujica.Collaborator{
var filesPeople = []files.Collaborator{
{ID: "a", Name: "Ada Lovelace"}, {ID: "b", Name: "Blaise Pascal", Away: true},
{ID: "c", Name: "Carl Gauss"}, {ID: "d", Name: "Dora Maar"}, {ID: "e", Name: "Emmy Noether"},
}
@@ -23,14 +26,14 @@ func presenceAvatarsDemo(c *ui.Context, log func(string)) {
s := ui.Local(c.Root(), "392", func() state { return state{max: 3} })
mediaCheck(c, log, &s.disabled, "Disabled")
mediaCheck(c, log, &s.empty, "Empty")
if mujica.NumberInput(c, &s.max, mujica.NumberInputOptions{Min: 1, Max: 5, Step: 1, Label: "Visible avatars"}).Changed() {
if input.NumberInput(c, &s.max, input.NumberInputOptions{Min: 1, Max: 5, Step: 1, Label: "Visible avatars"}).Changed() {
log(fmt.Sprintf("Visible: %.0f", s.max))
}
people := filesPeople
if s.empty {
people = nil
}
r := mujica.PresenceAvatars(c, people, mujica.PresenceAvatarsOptions{Following: s.following, Max: int(s.max), Disabled: s.disabled})
r := files.PresenceAvatars(c, people, files.PresenceAvatarsOptions{Following: s.following, Max: int(s.max), Disabled: s.disabled})
if r.Follow != "" {
s.following = r.Follow
log("Follow: " + r.Follow)
@@ -45,18 +48,18 @@ var filesReactionChoices = []string{"\U0001f44d", "\u2764\ufe0f", "\U0001f389",
func reactionsDemo(c *ui.Context, log func(string)) {
type state struct {
items []mujica.Reaction
items []files.Reaction
disabled bool
}
s := ui.Local(c.Root(), "394", func() state {
return state{items: []mujica.Reaction{{Emoji: filesReactionChoices[0], Count: 3, Mine: true}, {Emoji: filesReactionChoices[2], Count: 1}}}
return state{items: []files.Reaction{{Emoji: filesReactionChoices[0], Count: 3, Mine: true}, {Emoji: filesReactionChoices[2], Count: 1}}}
})
mediaCheck(c, log, &s.disabled, "Disabled")
if mujica.Button(c, "Clear reactions", mujica.ButtonOptions{}).Clicked() {
if input.Button(c, "Clear reactions", input.ButtonOptions{}).Clicked() {
s.items = nil
log("Reactions cleared")
}
r := mujica.Reactions(c, s.items, mujica.ReactionsOptions{Choices: filesReactionChoices, Disabled: s.disabled})
r := files.Reactions(c, s.items, files.ReactionsOptions{Choices: filesReactionChoices, Disabled: s.disabled})
emoji := r.Toggled
if emoji == "" {
emoji = r.Added
@@ -80,32 +83,32 @@ func reactionsDemo(c *ui.Context, log func(string)) {
return
}
}
s.items = append(s.items, mujica.Reaction{Emoji: emoji, Count: 1, Mine: true})
s.items = append(s.items, files.Reaction{Emoji: emoji, Count: 1, Mine: true})
log("Reaction added")
}
func shareDialogDemo(c *ui.Context, log func(string)) {
type state struct {
members []mujica.ShareMember
members []files.ShareMember
open, disabled bool
next int
}
s := ui.Local(c.Root(), "395", func() state {
return state{members: []mujica.ShareMember{
{ID: "owner", Name: "Ada Lovelace", Email: "ada@court.org", Owner: true, Permission: mujica.PermissionEdit},
{ID: "b", Name: "Blaise Pascal", Email: "blaise@court.org", Permission: mujica.PermissionView},
return state{members: []files.ShareMember{
{ID: "owner", Name: "Ada Lovelace", Email: "ada@court.org", Owner: true, Permission: files.PermissionEdit},
{ID: "b", Name: "Blaise Pascal", Email: "blaise@court.org", Permission: files.PermissionView},
}}
})
mediaCheck(c, log, &s.disabled, "Disabled")
if mujica.Button(c, "Share Ledger", mujica.ButtonOptions{Disabled: s.disabled}).Clicked() {
if input.Button(c, "Share Ledger", input.ButtonOptions{Disabled: s.disabled}).Clicked() {
s.open = true
log("Share dialog opened")
}
was := s.open
r := mujica.ShareDialog(c, &s.open, &s.members, mujica.ShareDialogOptions{Title: "Ledger", Link: "https://court.org/ledger", Disabled: s.disabled, OnCopy: func(link string) { c.WriteClipboard(link); log("Copy callback") }})
r := files.ShareDialog(c, &s.open, &s.members, files.ShareDialogOptions{Title: "Ledger", Link: "https://court.org/ledger", Disabled: s.disabled, OnCopy: func(link string) { c.WriteClipboard(link); log("Copy callback") }})
if r.Invited != nil {
s.next++
s.members = append(s.members, mujica.ShareMember{ID: fmt.Sprintf("invite-%d", s.next), Name: r.Invited.Email, Email: r.Invited.Email, Permission: r.Invited.Permission})
s.members = append(s.members, files.ShareMember{ID: fmt.Sprintf("invite-%d", s.next), Name: r.Invited.Email, Email: r.Invited.Email, Permission: r.Invited.Permission})
log("Member invited")
}
if r.Changed != "" {
@@ -124,33 +127,33 @@ func shareDialogDemo(c *ui.Context, log func(string)) {
func permissionSelectDemo(c *ui.Context, log func(string)) {
type state struct {
value mujica.Permission
value files.Permission
disabled bool
}
s := ui.Local(c.Root(), "396", func() state { return state{} })
mediaCheck(c, log, &s.disabled, "Disabled")
if mujica.PermissionSelect(c, &s.value, mujica.PermissionSelectOptions{Disabled: s.disabled}).Changed() {
if files.PermissionSelect(c, &s.value, files.PermissionSelectOptions{Disabled: s.disabled}).Changed() {
log(fmt.Sprintf("Permission: %d", s.value))
}
}
func liveIndicatorDemo(c *ui.Context, log func(string)) {
type state struct {
status mujica.LiveStatus
status files.LiveStatus
showTime bool
}
s := ui.Local(c.Root(), "399", func() state { return state{showTime: true} })
if mujica.Select(c, &s.status, []mujica.SelectOption[mujica.LiveStatus]{{Value: mujica.LiveSynced, Label: "Synced"}, {Value: mujica.LiveSyncing, Label: "Syncing"}, {Value: mujica.LiveReconnecting, Label: "Reconnecting"}, {Value: mujica.LiveOffline, Label: "Offline"}, {Value: mujica.LiveError, Label: "Error"}}, mujica.SelectOptions{Label: "Sync status"}).Changed() {
if input.Select(c, &s.status, []input.SelectOption[files.LiveStatus]{{Value: files.LiveSynced, Label: "Synced"}, {Value: files.LiveSyncing, Label: "Syncing"}, {Value: files.LiveReconnecting, Label: "Reconnecting"}, {Value: files.LiveOffline, Label: "Offline"}, {Value: files.LiveError, Label: "Error"}}, input.SelectOptions{Label: "Sync status"}).Changed() {
log(fmt.Sprintf("Sync status: %d", s.status))
}
mediaCheck(c, log, &s.showTime, "Last sync time")
now := time.Date(2026, 10, 5, 12, 0, 0, 0, time.UTC)
opts := mujica.LiveIndicatorOptions{Error: "Server unavailable", Now: now}
opts := files.LiveIndicatorOptions{Error: "Server unavailable", Now: now}
if s.showTime {
opts.LastSynced = now.Add(-3 * time.Minute)
}
if mujica.LiveIndicator(c, s.status, opts).Changed() {
s.status = mujica.LiveReconnecting
if files.LiveIndicator(c, s.status, opts).Changed() {
s.status = files.LiveReconnecting
log("Sync retried")
}
}
@@ -161,26 +164,26 @@ func remoteCursorDemo(c *ui.Context, log func(string)) {
selection, second bool
}
s := ui.Local(c.Root(), "400", func() state { return state{offset: 48, selection: true, second: true} })
if mujica.NumberInput(c, &s.offset, mujica.NumberInputOptions{Min: 0, Max: 79, Step: 1, Label: "Remote text offset"}).Changed() {
if input.NumberInput(c, &s.offset, input.NumberInputOptions{Min: 0, Max: 79, Step: 1, Label: "Remote text offset"}).Changed() {
log(fmt.Sprintf("Offset: %.0f", s.offset))
}
mediaCheck(c, log, &s.selection, "Selection")
mediaCheck(c, log, &s.second, "Second collaborator")
carets := []mujica.RemoteCaret{{ID: "ada", Name: "Ada", Offset: int(s.offset), Anchor: int(s.offset)}}
carets := []files.RemoteCaret{{ID: "ada", Name: "Ada", Offset: int(s.offset), Anchor: int(s.offset)}}
if s.selection {
carets[0].Anchor = max(0, carets[0].Offset-5)
}
if s.second {
carets = append(carets, mujica.RemoteCaret{ID: "blaise", Name: "Blaise", Offset: 8, Anchor: 8})
carets = append(carets, files.RemoteCaret{ID: "blaise", Name: "Blaise", Offset: 8, Anchor: 8})
}
ui.Box(c).Height(140).FillWidth().Background(mujica.Tokens(c).Surface).Children(func() {
ui.Box(c).Height(140).FillWidth().Background(core.Tokens(c).Surface).Children(func() {
ui.Column(c).Children(func() {
for _, line := range []string{"The seal of the court is kept", "by the chancellor, who answers"} {
ui.Text(c, line).Font(theme.MonoFont).FontSize(mujica.FontSize(c, 13)).Height(20)
ui.Text(c, line).Font(theme.MonoFont).FontSize(core.FontSize(c, 13)).Height(20)
}
})
at := func(o int) (float32, float32) { return float32(o%40) * 8, float32(o/40) * 20 }
mujica.RemoteCursor(c, carets, mujica.RemoteCursorOptions{
files.RemoteCursor(c, carets, files.RemoteCursorOptions{
Caret: func(o int) ui.Rect { x, y := at(o); return ui.Rect{X: x, Y: y, H: 20} },
Selection: func(from, to int) []ui.Rect {
var out []ui.Rect
+7 -6
View File
@@ -6,16 +6,17 @@ import (
"testing"
"github.com/egoist/mygo/ui"
mujica "mujicaui"
"github.com/ZacharyZhang-NY/MujicaUI/core"
)
func filesDemoTester(t *testing.T, id string, mode mujica.Mode) (*ui.Tester, *[]string) {
func filesDemoTester(t *testing.T, id string, mode core.Mode) (*ui.Tester, *[]string) {
t.Helper()
for _, p := range filesPages {
if p.ID == id {
events := &[]string{}
tt := ui.NewTester(func(c *ui.Context) {
mujica.Use(c, mujica.Settings{Mode: mode})
core.Use(c, core.Settings{Mode: mode})
ui.Scroll(c).Fill().Padding(24).Gap(12).Children(func() { p.Demo(c, func(s string) { *events = append(*events, s) }) })
}, 900, 800)
tt.SetPreferences(ui.Preferences{ReduceMotion: true, TextScale: 1})
@@ -59,7 +60,7 @@ func TestFilesGalleryActions(t *testing.T) {
{"400", "Selection", "Selection = false"},
}
for _, tc := range cases {
for _, mode := range []mujica.Mode{mujica.Light, mujica.Dark} {
for _, mode := range []core.Mode{core.Light, core.Dark} {
t.Run(fmt.Sprintf("%s-%s", tc.id, mode), func(t *testing.T) {
tt, events := filesDemoTester(t, tc.id, mode)
if err := tt.Click(tc.action); err != nil {
@@ -88,7 +89,7 @@ func TestFilesGalleryActions(t *testing.T) {
}
func TestFilesGalleryStorageParameter(t *testing.T) {
tt, events := filesDemoTester(t, "389", mujica.Light)
tt, events := filesDemoTester(t, "389", core.Light)
if err := tt.Click("Photos (GB)"); err != nil {
t.Fatal(err)
}
@@ -101,7 +102,7 @@ func TestFilesGalleryStorageParameter(t *testing.T) {
}
func TestFilesGalleryGridRange(t *testing.T) {
tt, events := filesDemoTester(t, "382", mujica.Light)
tt, events := filesDemoTester(t, "382", core.Light)
if err := tt.Click("notes.md"); err != nil {
t.Fatal(err)
}
+60 -60
View File
@@ -8,8 +8,8 @@ import (
"github.com/egoist/mygo"
"github.com/egoist/mygo/ui"
mujica "mujicaui"
"mujicaui/icons"
"github.com/ZacharyZhang-NY/MujicaUI/icons"
"github.com/ZacharyZhang-NY/MujicaUI/input"
)
// formStates shows a component's states side by side.
@@ -30,7 +30,7 @@ func formToggles(c *ui.Context, names []string, values ...*bool) {
}
type formDemo struct {
st mujica.FormState
st input.FormState
name, email string
tags []string
horizontal, slowServer bool
@@ -75,7 +75,7 @@ type pickerDemo struct {
}
type dropDemo struct {
files []mujica.DroppedFile
files []input.DroppedFile
images bool
disabled bool
}
@@ -85,7 +85,7 @@ type transferDemo struct {
disabled bool
}
var transferDemoItems = []mujica.TransferItem{
var transferDemoItems = []input.TransferItem{
{ID: "sakiko", Label: "Sakiko Togawa"}, {ID: "uika", Label: "Uika Misumi"}, {ID: "mutsumi", Label: "Mutsumi Wakaba"},
{ID: "umiri", Label: "Umiri Yahata"}, {ID: "nyamu", Label: "Nyamu Yutenji"}, {ID: "tomori", Label: "Tomori Takamatsu", Disabled: true},
{ID: "anon", Label: "Anon Chihaya"}, {ID: "rana", Label: "Rana Kaname"}, {ID: "soyo", Label: "Soyo Nagasaki"},
@@ -117,8 +117,8 @@ var formPages = []page{
}
}
formToggles(c, []string{"Labels beside fields", "Server rejects the submit"}, &d.horizontal, &d.slowServer)
res := mujica.Form(c, &d.st, mujica.FormOptions{Horizontal: d.horizontal}, func(f *mujica.FormScope) {
mujica.FormItem(c, f, "name", &d.name, mujica.FormItemOptions[string]{Label: "Stage name", Required: true, Initial: "Sakiko",
res := input.Form(c, &d.st, input.FormOptions{Horizontal: d.horizontal}, func(f *input.FormScope) {
input.FormItem(c, f, "name", &d.name, input.FormItemOptions[string]{Label: "Stage name", Required: true, Initial: "Sakiko",
Description: "Checked against the archive; \"Oblivionis\" is taken.",
Async: func(v string, version uint64) {
log(fmt.Sprintf("async check started v%d", version))
@@ -132,23 +132,23 @@ var formPages = []page{
c.Invalidate()
}()
}}, func() *ui.Element {
return mujica.InputGroup(c, &d.name, mujica.InputGroupOptions{Invalid: d.st.Error("name") != ""}).Input
return input.InputGroup(c, &d.name, input.InputGroupOptions{Invalid: d.st.Error("name") != ""}).Input
})
mujica.FormItem(c, f, "email", &d.email, mujica.FormItemOptions[string]{Label: "Email", Required: true,
input.FormItem(c, f, "email", &d.email, input.FormItemOptions[string]{Label: "Email", Required: true,
Validate: func(v string) string {
if !strings.Contains(v, "@") {
return "Enter an email address."
}
return ""
}}, func() *ui.Element {
g := mujica.InputGroup(c, &d.email, mujica.InputGroupOptions{Invalid: d.st.Error("email") != ""})
g := input.InputGroup(c, &d.email, input.InputGroupOptions{Invalid: d.st.Error("email") != ""})
if g.Submitted() {
d.st.Submit()
}
return g.Input
})
mujica.FormItem(c, f, "tags", &d.tags, mujica.FormItemOptions[[]string]{Label: "Roles", Required: true, Initial: []string{"keyboard"}}, func() *ui.Element {
return mujica.TagsInput(c, &d.tags, mujica.TagsInputOptions{Max: 4, Invalid: d.st.Error("tags") != ""}).Input
input.FormItem(c, f, "tags", &d.tags, input.FormItemOptions[[]string]{Label: "Roles", Required: true, Initial: []string{"keyboard"}}, func() *ui.Element {
return input.TagsInput(c, &d.tags, input.TagsInputOptions{Max: 4, Invalid: d.st.Error("tags") != ""}).Input
})
})
if res.Submitted() {
@@ -176,9 +176,9 @@ var formPages = []page{
if d.withError {
e = "This name is already in the guest list."
}
mujica.FormField(c, "Guest name", mujica.FormFieldOptions{Required: d.required, Horizontal: d.horizontal,
input.FormField(c, "Guest name", input.FormFieldOptions{Required: d.required, Horizontal: d.horizontal,
Description: "As written on the invitation.", Error: e}, func() *ui.Element {
g := mujica.InputGroup(c, &d.value, mujica.InputGroupOptions{Invalid: d.withError})
g := input.InputGroup(c, &d.value, input.InputGroupOptions{Invalid: d.withError})
if g.Changed() {
log(fmt.Sprintf("value %q", d.value))
}
@@ -190,9 +190,9 @@ var formPages = []page{
Demo: func(c *ui.Context, log func(string)) {
d := ui.Local(c.Root(), "043", func() groupDemo { return groupDemo{value: "mujica"} })
formToggles(c, []string{"Disabled", "Read-only", "Invalid"}, &d.disabled, &d.readOnly, &d.invalid)
opts := mujica.InputGroupOptions{Prefix: "https://", Suffix: ".org", Label: "Site", Disabled: d.disabled, ReadOnly: d.readOnly, Invalid: d.invalid,
Buttons: []mujica.InputGroupButton{{Label: "Clear", Icon: icons.Must("x"), IconOnly: true}, {Label: "Copy", Icon: icons.Must("copy")}}}
g := mujica.InputGroup(c, &d.value, opts)
opts := input.InputGroupOptions{Prefix: "https://", Suffix: ".org", Label: "Site", Disabled: d.disabled, ReadOnly: d.readOnly, Invalid: d.invalid,
Buttons: []input.InputGroupButton{{Label: "Clear", Icon: icons.Must("x"), IconOnly: true}, {Label: "Copy", Icon: icons.Must("copy")}}}
g := input.InputGroup(c, &d.value, opts)
switch g.Clicked {
case 0:
d.value = ""
@@ -208,9 +208,9 @@ var formPages = []page{
log("submitted")
}
formStates(c, func() {
for _, o := range []mujica.InputGroupOptions{{Label: "Disabled", Disabled: true}, {Label: "Read-only", ReadOnly: true}, {Label: "Invalid", Invalid: true}} {
for _, o := range []input.InputGroupOptions{{Label: "Disabled", Disabled: true}, {Label: "Read-only", ReadOnly: true}, {Label: "Invalid", Invalid: true}} {
v := o.Label
mujica.InputGroup(c, &v, o)
input.InputGroup(c, &v, o)
}
})
}},
@@ -226,7 +226,7 @@ var formPages = []page{
if len(d.code) > n {
d.code = d.code[:n]
}
p := mujica.PinInput(c, &d.code, mujica.PinInputOptions{Length: n, Disabled: d.disabled, Invalid: d.invalid})
p := input.PinInput(c, &d.code, input.PinInputOptions{Length: n, Disabled: d.disabled, Invalid: d.invalid})
if p.Changed() {
log(fmt.Sprintf("code %q", d.code))
}
@@ -235,8 +235,8 @@ var formPages = []page{
}
formStates(c, func() {
a, b := "12", "0420"
mujica.PinInput(c, &a, mujica.PinInputOptions{Length: 4, Disabled: true, Label: "Disabled code"})
mujica.PinInput(c, &b, mujica.PinInputOptions{Length: 4, Invalid: true, Label: "Invalid code"})
input.PinInput(c, &a, input.PinInputOptions{Length: 4, Disabled: true, Label: "Disabled code"})
input.PinInput(c, &b, input.PinInputOptions{Length: 4, Invalid: true, Label: "Invalid code"})
})
}},
{ID: "045", Name: "TagsInput", ZhName: "标签输入", Category: "Forms", Code: formCode["TagsInput"],
@@ -244,7 +244,7 @@ var formPages = []page{
Demo: func(c *ui.Context, log func(string)) {
d := ui.Local(c.Root(), "045", func() tagsDemo { return tagsDemo{tags: []string{"rose", "thorn"}} })
formToggles(c, []string{"At most 5", "Disabled"}, &d.limit, &d.disabled)
opts := mujica.TagsInputOptions{Placeholder: "Add a flower", Label: "Flowers", Disabled: d.disabled,
opts := input.TagsInputOptions{Placeholder: "Add a flower", Label: "Flowers", Disabled: d.disabled,
Validate: func(s string) string {
if len([]rune(s)) > 16 {
return "Keep tags under 16 characters."
@@ -254,7 +254,7 @@ var formPages = []page{
if d.limit {
opts.Max = 5
}
if mujica.TagsInput(c, &d.tags, opts).Changed() {
if input.TagsInput(c, &d.tags, opts).Changed() {
log(fmt.Sprintf("tags %q", d.tags))
}
}},
@@ -270,13 +270,13 @@ var formPages = []page{
if !d.half {
d.value = float64(int(d.value + 0.5))
}
if mujica.Rating(c, &d.value, mujica.RatingOptions{Step: step, ReadOnly: d.readOnly, Disabled: d.disabled, ShowValue: true}).Changed() {
if input.Rating(c, &d.value, input.RatingOptions{Step: step, ReadOnly: d.readOnly, Disabled: d.disabled, ShowValue: true}).Changed() {
log(fmt.Sprintf("rating %v", d.value))
}
formStates(c, func() {
a, b := 4.5, 2.0
mujica.Rating(c, &a, mujica.RatingOptions{Step: 0.5, ReadOnly: true, Label: "Read-only rating"})
mujica.Rating(c, &b, mujica.RatingOptions{Disabled: true, Label: "Disabled rating"})
input.Rating(c, &a, input.RatingOptions{Step: 0.5, ReadOnly: true, Label: "Read-only rating"})
input.Rating(c, &b, input.RatingOptions{Disabled: true, Label: "Disabled rating"})
})
}},
{ID: "047", Name: "ColorPicker", ZhName: "颜色选择器", Category: "Forms", Code: formCode["ColorPicker"],
@@ -284,7 +284,7 @@ var formPages = []page{
Demo: func(c *ui.Context, log func(string)) {
d := ui.Local(c.Root(), "047", func() colorDemo { return colorDemo{color: ui.Hex("#7D2034")} })
formToggles(c, []string{"Disabled"}, &d.disabled)
if mujica.ColorPicker(c, &d.color, mujica.ColorPickerOptions{Label: "Banner color", Disabled: d.disabled}).Changed() {
if input.ColorPicker(c, &d.color, input.ColorPickerOptions{Label: "Banner color", Disabled: d.disabled}).Changed() {
log(fmt.Sprintf("color %v", d.color))
}
}},
@@ -293,11 +293,11 @@ var formPages = []page{
Demo: func(c *ui.Context, log func(string)) {
d := ui.Local(c.Root(), "048", func() pickerDemo { return pickerDemo{multiple: true} })
formToggles(c, []string{"Folders", "Multiple", "PDF and MIDI only", "Disabled"}, &d.directory, &d.multiple, &d.filter, &d.disabled)
opts := mujica.FilePickerOptions{Directory: d.directory, Multiple: d.multiple, Disabled: d.disabled, Title: "Choose scores"}
opts := input.FilePickerOptions{Directory: d.directory, Multiple: d.multiple, Disabled: d.disabled, Title: "Choose scores"}
if d.filter {
opts.Filters = []mygo.FileFilter{{Name: "Scores", Extensions: []string{"pdf", "mid"}}}
}
p := mujica.FilePicker(c, &d.paths, opts)
p := input.FilePicker(c, &d.paths, opts)
if p.Changed() {
log(fmt.Sprintf("picked %d: %q", len(d.paths), d.paths))
}
@@ -310,11 +310,11 @@ var formPages = []page{
Demo: func(c *ui.Context, log func(string)) {
d := ui.Local(c.Root(), "049", func() dropDemo { return dropDemo{} })
formToggles(c, []string{"Images only", "Disabled"}, &d.images, &d.disabled)
opts := mujica.FileDropZoneOptions{MaxFiles: 4, MaxSize: 10 << 20, Disabled: d.disabled}
opts := input.FileDropZoneOptions{MaxFiles: 4, MaxSize: 10 << 20, Disabled: d.disabled}
if d.images {
opts.Extensions = []string{"png", "jpg", "jpeg", "gif"}
}
if mujica.FileDropZone(c, &d.files, opts).Changed() {
if input.FileDropZone(c, &d.files, opts).Changed() {
log(fmt.Sprintf("files %d", len(d.files)))
}
}},
@@ -323,7 +323,7 @@ var formPages = []page{
Demo: func(c *ui.Context, log func(string)) {
d := ui.Local(c.Root(), "050", func() transferDemo { return transferDemo{chosen: []string{"sakiko"}} })
formToggles(c, []string{"Disabled"}, &d.disabled)
if mujica.Transfer(c, &d.chosen, transferDemoItems, mujica.TransferOptions{SourceTitle: "Auditions", TargetTitle: "Band", Disabled: d.disabled}).Changed() {
if input.Transfer(c, &d.chosen, transferDemoItems, input.TransferOptions{SourceTitle: "Auditions", TargetTitle: "Band", Disabled: d.disabled}).Changed() {
log(fmt.Sprintf("band %q", d.chosen))
}
}},
@@ -331,13 +331,13 @@ var formPages = []page{
// formCode holds each page's copyable snippet, the same as its doc and Example.
var formCode = map[string]string{
"Form": `var state mujica.FormState
"Form": `var state input.FormState
name := ""
view := func(c *ui.Context) {
mujica.Use(c, mujica.Settings{})
res := mujica.Form(c, &state, mujica.FormOptions{}, func(f *mujica.FormScope) {
mujica.FormItem(c, f, "name", &name, mujica.FormItemOptions[string]{Label: "Name", Required: true}, func() *ui.Element {
return mujica.InputGroup(c, &name, mujica.InputGroupOptions{Invalid: state.Error("name") != ""}).Input
core.Use(c, core.Settings{})
res := input.Form(c, &state, input.FormOptions{}, func(f *input.FormScope) {
input.FormItem(c, f, "name", &name, input.FormItemOptions[string]{Label: "Name", Required: true}, func() *ui.Element {
return input.InputGroup(c, &name, input.InputGroupOptions{Invalid: state.Error("name") != ""}).Input
})
})
if res.Submitted() {
@@ -347,18 +347,18 @@ view := func(c *ui.Context) {
ui.Render(view, 400, 300, 1)`,
"FormField": `email := ""
view := func(c *ui.Context) {
mujica.Use(c, mujica.Settings{})
mujica.FormField(c, "Email", mujica.FormFieldOptions{Required: true, Description: "For receipts."}, func() *ui.Element {
return mujica.InputGroup(c, &email, mujica.InputGroupOptions{}).Input
core.Use(c, core.Settings{})
input.FormField(c, "Email", input.FormFieldOptions{Required: true, Description: "For receipts."}, func() *ui.Element {
return input.InputGroup(c, &email, input.InputGroupOptions{}).Input
})
}
ui.Render(view, 400, 300, 1)`,
"InputGroup": `site := ""
view := func(c *ui.Context) {
mujica.Use(c, mujica.Settings{})
g := mujica.InputGroup(c, &site, mujica.InputGroupOptions{
core.Use(c, core.Settings{})
g := input.InputGroup(c, &site, input.InputGroupOptions{
Prefix: "https://", Label: "Site",
Buttons: []mujica.InputGroupButton{{Label: "Open"}},
Buttons: []input.InputGroupButton{{Label: "Open"}},
})
if g.Clicked == 0 {
c.OpenURL("https://" + site)
@@ -367,52 +367,52 @@ view := func(c *ui.Context) {
ui.Render(view, 400, 300, 1)`,
"PinInput": `code := ""
view := func(c *ui.Context) {
mujica.Use(c, mujica.Settings{})
if mujica.PinInput(c, &code, mujica.PinInputOptions{Length: 6}).Completed() {
core.Use(c, core.Settings{})
if input.PinInput(c, &code, input.PinInputOptions{Length: 6}).Completed() {
fmt.Println("verify", code)
}
}
ui.Render(view, 400, 300, 1)`,
"TagsInput": `tags := []string{"gothic"}
view := func(c *ui.Context) {
mujica.Use(c, mujica.Settings{})
mujica.TagsInput(c, &tags, mujica.TagsInputOptions{Max: 5, Placeholder: "Add a tag", Label: "Tags"})
core.Use(c, core.Settings{})
input.TagsInput(c, &tags, input.TagsInputOptions{Max: 5, Placeholder: "Add a tag", Label: "Tags"})
}
ui.Render(view, 400, 300, 1)`,
"Rating": `stars := 3.5
view := func(c *ui.Context) {
mujica.Use(c, mujica.Settings{})
mujica.Rating(c, &stars, mujica.RatingOptions{Step: 0.5, ShowValue: true})
core.Use(c, core.Settings{})
input.Rating(c, &stars, input.RatingOptions{Step: 0.5, ShowValue: true})
}
ui.Render(view, 400, 300, 1)`,
"ColorPicker": `tint := ui.Hex("#7D2034")
view := func(c *ui.Context) {
mujica.Use(c, mujica.Settings{})
mujica.ColorPicker(c, &tint, mujica.ColorPickerOptions{Label: "Tint"})
core.Use(c, core.Settings{})
input.ColorPicker(c, &tint, input.ColorPickerOptions{Label: "Tint"})
}
ui.Render(view, 400, 300, 1)`,
"FilePicker": `var paths []string
view := func(c *ui.Context) {
mujica.Use(c, mujica.Settings{})
mujica.FilePicker(c, &paths, mujica.FilePickerOptions{
core.Use(c, core.Settings{})
input.FilePicker(c, &paths, input.FilePickerOptions{
Multiple: true,
Filters: []mygo.FileFilter{{Name: "Scores", Extensions: []string{"pdf", "mid"}}},
})
}
ui.Render(view, 400, 300, 1)`,
"FileDropZone": `var files []mujica.DroppedFile
"FileDropZone": `var files []input.DroppedFile
view := func(c *ui.Context) {
mujica.Use(c, mujica.Settings{})
mujica.FileDropZone(c, &files, mujica.FileDropZoneOptions{
core.Use(c, core.Settings{})
input.FileDropZone(c, &files, input.FileDropZoneOptions{
Extensions: []string{"png", "jpg"}, MaxFiles: 3, MaxSize: 5 << 20,
})
}
ui.Render(view, 400, 300, 1)`,
"Transfer": `chosen := []string{"uika"}
items := []mujica.TransferItem{{ID: "uika", Label: "Uika"}, {ID: "mutsumi", Label: "Mutsumi"}, {ID: "nyamu", Label: "Nyamu", Disabled: true}}
items := []input.TransferItem{{ID: "uika", Label: "Uika"}, {ID: "mutsumi", Label: "Mutsumi"}, {ID: "nyamu", Label: "Nyamu", Disabled: true}}
view := func(c *ui.Context) {
mujica.Use(c, mujica.Settings{})
mujica.Transfer(c, &chosen, items, mujica.TransferOptions{})
core.Use(c, core.Settings{})
input.Transfer(c, &chosen, items, input.TransferOptions{})
}
ui.Render(view, 400, 300, 1)`,
}
+69 -69
View File
@@ -4,12 +4,12 @@ var gitPages = []page{
{
ID: "301", Name: "DiffViewer", ZhName: "差异查看器 比较 diff", Category: "Git",
Demo: gitDiffViewerDemo,
Code: `var state mujica.DiffViewerState
Code: `var state git.DiffViewerState
from := "func main() {\n\tprintln(\"hi\")\n}\n"
to := "func main() {\n\tprintln(\"hello\")\n}\n"
view := func(c *ui.Context) {
mujica.Use(c, mujica.Settings{})
mujica.DiffViewer(c, &state, from, to, mujica.DiffViewerOptions{Language: "go", SideBySide: true}).Height(240)
core.Use(c, core.Settings{})
git.DiffViewer(c, &state, from, to, git.DiffViewerOptions{Language: "go", SideBySide: true}).Height(240)
}
ui.Render(view, 400, 300, 1)`,
Keys: "Tab focuses the list; arrows and Page Up/Down scroll. Fold buttons take Tab; Enter or Space shows the hidden lines.",
@@ -18,8 +18,8 @@ ui.Render(view, 400, 300, 1)`,
ID: "302", Name: "InlineDiff", ZhName: "行内差异 词级差异", Category: "Git",
Demo: gitInlineDiffDemo,
Code: `view := func(c *ui.Context) {
mujica.Use(c, mujica.Settings{})
mujica.InlineDiff(c, "The court meets at noon", "The court meets at dusk", mujica.InlineDiffOptions{})
core.Use(c, core.Settings{})
git.InlineDiff(c, "The court meets at noon", "The court meets at dusk", git.InlineDiffOptions{})
}
ui.Render(view, 400, 300, 1)`,
Keys: "Not focusable; each line is read with its full text.",
@@ -27,11 +27,11 @@ ui.Render(view, 400, 300, 1)`,
{
ID: "303", Name: "ThreeWayMerge", ZhName: "三方合并 合并冲突", Category: "Git",
Demo: gitThreeWayMergeDemo,
Code: `var state mujica.ThreeWayMergeState
Code: `var state git.ThreeWayMergeState
base, local, remote := "a\nb\nc", "a\nB1\nc", "a\nB2\nc"
view := func(c *ui.Context) {
mujica.Use(c, mujica.Settings{})
r := mujica.ThreeWayMerge(c, &state, base, local, remote, mujica.ThreeWayMergeOptions{})
core.Use(c, core.Settings{})
r := git.ThreeWayMerge(c, &state, base, local, remote, git.ThreeWayMergeOptions{})
r.Element.Height(280)
if done, total := state.Resolved(); r.Changed() && done == total {
_ = state.Result() // write the merged file
@@ -43,11 +43,11 @@ ui.Render(view, 400, 300, 1)`,
{
ID: "304", Name: "ConflictResolver", ZhName: "冲突解决 冲突标记", Category: "Git",
Demo: gitConflictResolverDemo,
Code: `var state mujica.ConflictResolverState
Code: `var state git.ConflictResolverState
text := "a\n<<<<<<< HEAD\nmine\n=======\ntheirs\n>>>>>>> feature\nb"
view := func(c *ui.Context) {
mujica.Use(c, mujica.Settings{})
if mujica.ConflictResolver(c, &state, text, mujica.ConflictResolverOptions{}).Changed() {
core.Use(c, core.Settings{})
if git.ConflictResolver(c, &state, text, git.ConflictResolverOptions{}).Changed() {
_ = state.Result() // save the file once every block is resolved
}
}
@@ -57,14 +57,14 @@ ui.Render(view, 400, 300, 1)`,
{
ID: "305", Name: "ChangesList", ZhName: "更改列表 暂存 未暂存", Category: "Git",
Demo: gitChangesListDemo,
Code: `var state mujica.ChangesListState
files := []mujica.ChangedFile{
{Path: "main.go", Status: mujica.GitModified, Staged: true},
{Path: "notes.txt", Status: mujica.GitUntracked},
Code: `var state git.ChangesListState
files := []git.ChangedFile{
{Path: "main.go", Status: git.GitModified, Staged: true},
{Path: "notes.txt", Status: git.GitUntracked},
}
view := func(c *ui.Context) {
mujica.Use(c, mujica.Settings{})
r := mujica.ChangesList(c, &state, files, mujica.ChangesListOptions{})
core.Use(c, core.Settings{})
r := git.ChangesList(c, &state, files, git.ChangesListOptions{})
r.Element.Height(240)
if action, path, ok := r.Action(); ok {
_, _ = action, path // run git add or git restore --staged
@@ -76,11 +76,11 @@ ui.Render(view, 400, 300, 1)`,
{
ID: "306", Name: "CommitInput", ZhName: "提交输入 提交信息", Category: "Git",
Demo: gitCommitInputDemo,
Code: `msg := mujica.CommitMessage{}
Code: `msg := git.CommitMessage{}
busy := false
view := func(c *ui.Context) {
mujica.Use(c, mujica.Settings{})
r := mujica.CommitInput(c, &msg, mujica.CommitInputOptions{Busy: busy})
core.Use(c, core.Settings{})
r := git.CommitInput(c, &msg, git.CommitInputOptions{Busy: busy})
if r.Committed() || r.Amended() {
busy = true // run git commit, then clear busy
}
@@ -91,16 +91,16 @@ ui.Render(view, 400, 300, 1)`,
{
ID: "307", Name: "CommitGraph", ZhName: "提交图谱 泳道 分支图", Category: "Git",
Demo: gitCommitGraphDemo,
Code: `var state mujica.CommitGraphState
commits := []mujica.Commit{
Code: `var state git.CommitGraphState
commits := []git.Commit{
{Hash: "c3", Parents: []string{"c2", "b1"}, Subject: "Merge branch", Refs: []string{"main"}},
{Hash: "b1", Parents: []string{"c1"}, Subject: "Feature"},
{Hash: "c2", Parents: []string{"c1"}, Subject: "Fix"},
{Hash: "c1", Subject: "Initial commit"},
}
view := func(c *ui.Context) {
mujica.Use(c, mujica.Settings{})
mujica.CommitGraph(c, &state, commits, mujica.CommitGraphOptions{}).Element.Height(240)
core.Use(c, core.Settings{})
git.CommitGraph(c, &state, commits, git.CommitGraphOptions{}).Element.Height(240)
}
ui.Render(view, 400, 300, 1)`,
Keys: "Up/Down/Home/End choose a commit; typing jumps by subject; Enter opens it.",
@@ -108,11 +108,11 @@ ui.Render(view, 400, 300, 1)`,
{
ID: "308", Name: "CommitList", ZhName: "提交列表 提交历史", Category: "Git",
Demo: gitCommitListDemo,
Code: `var state mujica.ListState[string]
commits := []mujica.Commit{{Hash: "a1b2c3d4", Author: "Sakiko", When: time.Now(), Subject: "Add the commit list"}}
Code: `var state data.ListState[string]
commits := []git.Commit{{Hash: "a1b2c3d4", Author: "Sakiko", When: time.Now(), Subject: "Add the commit list"}}
view := func(c *ui.Context) {
mujica.Use(c, mujica.Settings{})
r := mujica.CommitList(c, &state, commits, mujica.CommitListOptions{})
core.Use(c, core.Settings{})
r := git.CommitList(c, &state, commits, git.CommitListOptions{})
r.Element.Height(240)
if hash, ok := state.Selected(); ok && r.Submitted() {
_ = hash // show the commit
@@ -125,10 +125,10 @@ ui.Render(view, 400, 300, 1)`,
ID: "309", Name: "BranchSelector", ZhName: "分支选择器 切换分支", Category: "Git",
Demo: gitBranchSelectorDemo,
Code: `branch := "main"
branches := []mujica.Branch{{Name: "main", Current: true}, {Name: "origin/main", Remote: true}}
branches := []git.Branch{{Name: "main", Current: true}, {Name: "origin/main", Remote: true}}
view := func(c *ui.Context) {
mujica.Use(c, mujica.Settings{})
r := mujica.BranchSelector(c, &branch, branches, mujica.BranchSelectorOptions{AllowCreate: true})
core.Use(c, core.Settings{})
r := git.BranchSelector(c, &branch, branches, git.BranchSelectorOptions{AllowCreate: true})
if name, ok := r.Created(); ok {
_ = name // run git switch -c name
}
@@ -139,12 +139,12 @@ ui.Render(view, 400, 300, 1)`,
{
ID: "310", Name: "BranchList", ZhName: "分支列表 上游 领先落后", Category: "Git",
Demo: gitBranchListDemo,
Code: `var state mujica.ListState[string]
branches := []mujica.Branch{{Name: "main", Current: true, Upstream: "origin/main", Ahead: 2}}
actions := []mujica.GitAction{{ID: "delete", Label: "Delete", Icon: "trash-2"}}
Code: `var state data.ListState[string]
branches := []git.Branch{{Name: "main", Current: true, Upstream: "origin/main", Ahead: 2}}
actions := []git.GitAction{{ID: "delete", Label: "Delete", Icon: "trash-2"}}
view := func(c *ui.Context) {
mujica.Use(c, mujica.Settings{})
r := mujica.BranchList(c, &state, branches, mujica.BranchListOptions{Actions: actions})
core.Use(c, core.Settings{})
r := git.BranchList(c, &state, branches, git.BranchListOptions{Actions: actions})
r.Element.Height(200)
if action, name, ok := r.Action(); ok {
_, _ = action, name // run the action on the branch
@@ -156,11 +156,11 @@ ui.Render(view, 400, 300, 1)`,
{
ID: "311", Name: "TagList", ZhName: "标签列表 tag", Category: "Git",
Demo: gitTagListDemo,
Code: `var state mujica.ListState[string]
tags := []mujica.GitTag{{Name: "v1.1.0", Target: "a1b2c3d4", Annotation: "Release"}}
Code: `var state data.ListState[string]
tags := []git.GitTag{{Name: "v1.1.0", Target: "a1b2c3d4", Annotation: "Release"}}
view := func(c *ui.Context) {
mujica.Use(c, mujica.Settings{})
r := mujica.TagList(c, &state, tags, mujica.TagListOptions{})
core.Use(c, core.Settings{})
r := git.TagList(c, &state, tags, git.TagListOptions{})
r.Element.Height(200)
if _, hash, ok := r.Action(); ok {
_ = hash // show the tagged commit
@@ -172,11 +172,11 @@ ui.Render(view, 400, 300, 1)`,
{
ID: "312", Name: "StashList", ZhName: "贮藏列表 stash", Category: "Git",
Demo: gitStashListDemo,
Code: `var state mujica.ListState[string]
stashes := []mujica.Stash{{Ref: "stash@{0}", Message: "WIP on main", Branch: "main"}}
Code: `var state data.ListState[string]
stashes := []git.Stash{{Ref: "stash@{0}", Message: "WIP on main", Branch: "main"}}
view := func(c *ui.Context) {
mujica.Use(c, mujica.Settings{})
r := mujica.StashList(c, &state, stashes, mujica.StashListOptions{})
core.Use(c, core.Settings{})
r := git.StashList(c, &state, stashes, git.StashListOptions{})
r.Element.Height(200)
if action, ref, ok := r.Action(); ok {
_, _ = action, ref // run git stash apply, pop or drop
@@ -188,14 +188,14 @@ ui.Render(view, 400, 300, 1)`,
{
ID: "313", Name: "BlameView", ZhName: "逐行追溯 blame 作者", Category: "Git",
Demo: gitBlameViewDemo,
Code: `var state mujica.BlameViewState
lines := []mujica.BlameLine{
Code: `var state git.BlameViewState
lines := []git.BlameLine{
{Hash: "a1b2c3d4", Author: "Sakiko", When: time.Now(), Summary: "Add main", Text: "package main"},
{Hash: "a1b2c3d4", Author: "Sakiko", When: time.Now(), Summary: "Add main", Text: "func main() {}"},
}
view := func(c *ui.Context) {
mujica.Use(c, mujica.Settings{})
mujica.BlameView(c, &state, lines, mujica.BlameViewOptions{Language: "go"}).Height(240)
core.Use(c, core.Settings{})
git.BlameView(c, &state, lines, git.BlameViewOptions{Language: "go"}).Height(240)
}
ui.Render(view, 400, 300, 1)`,
Keys: "Tab focuses the code; arrows scroll; typing jumps to a line starting with the text. Hover the left column for details.",
@@ -203,11 +203,11 @@ ui.Render(view, 400, 300, 1)`,
{
ID: "314", Name: "FileHistory", ZhName: "文件历史 单文件提交", Category: "Git",
Demo: gitFileHistoryDemo,
Code: `var state mujica.ListState[string]
commits := []mujica.Commit{{Hash: "a1b2c3d4", Author: "Sakiko", When: time.Now(), Subject: "Edit main.go"}}
Code: `var state data.ListState[string]
commits := []git.Commit{{Hash: "a1b2c3d4", Author: "Sakiko", When: time.Now(), Subject: "Edit main.go"}}
view := func(c *ui.Context) {
mujica.Use(c, mujica.Settings{})
r := mujica.FileHistory(c, &state, "main.go", commits, mujica.FileHistoryOptions{})
core.Use(c, core.Settings{})
r := git.FileHistory(c, &state, "main.go", commits, git.FileHistoryOptions{})
r.Element.Height(240)
if _, hash, ok := r.Action(); ok {
_ = hash // open the diff of main.go at hash
@@ -219,11 +219,11 @@ ui.Render(view, 400, 300, 1)`,
{
ID: "315", Name: "PullRequestCard", ZhName: "拉取请求卡片 合并请求 PR", Category: "Git",
Demo: gitPullRequestCardDemo,
Code: `pr := mujica.PullRequest{Number: 42, Title: "Add git components", Author: "Sakiko", Head: "feature", Base: "main",
ChecksPassed: 3, Reviewers: []mujica.Reviewer{{Name: "Uika", State: mujica.ReviewApproved}}, Added: 120, Deleted: 8, Files: 6}
Code: `pr := git.PullRequest{Number: 42, Title: "Add git components", Author: "Sakiko", Head: "feature", Base: "main",
ChecksPassed: 3, Reviewers: []git.Reviewer{{Name: "Uika", State: git.ReviewApproved}}, Added: 120, Deleted: 8, Files: 6}
view := func(c *ui.Context) {
mujica.Use(c, mujica.Settings{})
if mujica.PullRequestCard(c, pr, mujica.PullRequestCardOptions{}).Changed() {
core.Use(c, core.Settings{})
if git.PullRequestCard(c, pr, git.PullRequestCardOptions{}).Changed() {
// open the pull request
}
}
@@ -233,15 +233,15 @@ ui.Render(view, 400, 300, 1)`,
{
ID: "316", Name: "ReviewComment", ZhName: "评审评论 代码评审", Category: "Git",
Demo: gitReviewCommentDemo,
Code: `thread := mujica.ReviewThread{File: "main.go", Line: 12, Messages: []mujica.ReviewMessage{
Code: `thread := git.ReviewThread{File: "main.go", Line: 12, Messages: []git.ReviewMessage{
{Author: "Uika", When: time.Now(), Body: "Can this return early?"},
}}
draft := ""
view := func(c *ui.Context) {
mujica.Use(c, mujica.Settings{})
r := mujica.ReviewComment(c, thread, &draft, mujica.ReviewCommentOptions{})
core.Use(c, core.Settings{})
r := git.ReviewComment(c, thread, &draft, git.ReviewCommentOptions{})
if text, ok := r.Replied(); ok {
thread.Messages = append(thread.Messages, mujica.ReviewMessage{Author: "Sakiko", When: time.Now(), Body: text})
thread.Messages = append(thread.Messages, git.ReviewMessage{Author: "Sakiko", When: time.Now(), Body: text})
}
if r.Resolved() || r.Reopened() {
thread.Resolved = !thread.Resolved
@@ -254,8 +254,8 @@ ui.Render(view, 400, 300, 1)`,
ID: "317", Name: "DiffStat", ZhName: "差异统计 增删行数", Category: "Git",
Demo: gitDiffStatDemo,
Code: `view := func(c *ui.Context) {
mujica.Use(c, mujica.Settings{})
mujica.DiffStat(c, 120, 8, mujica.DiffStatOptions{})
core.Use(c, core.Settings{})
git.DiffStat(c, 120, 8, git.DiffStatOptions{})
}
ui.Render(view, 400, 300, 1)`,
Keys: "Not focusable; read as additions and deletions.",
@@ -264,8 +264,8 @@ ui.Render(view, 400, 300, 1)`,
ID: "318", Name: "GitStatusBadge", ZhName: "Git 状态标记 文件状态", Category: "Git",
Demo: gitStatusBadgeDemo,
Code: `view := func(c *ui.Context) {
mujica.Use(c, mujica.Settings{})
mujica.GitStatusBadge(c, mujica.GitModified, mujica.GitStatusBadgeOptions{ShowLabel: true})
core.Use(c, core.Settings{})
git.GitStatusBadge(c, git.GitModified, git.GitStatusBadgeOptions{ShowLabel: true})
}
ui.Render(view, 400, 300, 1)`,
Keys: "Not focusable; each badge is read by its status name.",
@@ -274,10 +274,10 @@ ui.Render(view, 400, 300, 1)`,
ID: "319", Name: "CommandBlock", ZhName: "命令块 终端输出 退出码", Category: "Git",
Demo: gitCommandBlockDemo,
Code: `open := true
run := mujica.CommandRun{Command: "go test ./...", Output: "\x1b[32mok\x1b[0m example 0.4s", Duration: 400 * time.Millisecond}
run := agent.CommandRun{Command: "go test ./...", Output: "\x1b[32mok\x1b[0m example 0.4s", Duration: 400 * time.Millisecond}
view := func(c *ui.Context) {
mujica.Use(c, mujica.Settings{})
r := mujica.CommandBlock(c, run, &open, mujica.CommandBlockOptions{})
core.Use(c, core.Settings{})
r := git.CommandBlock(c, run, &open, git.CommandBlockOptions{})
if what, ok := r.Copied(); ok {
_ = what // "command" or "output" is on the clipboard
}
@@ -289,8 +289,8 @@ ui.Render(view, 400, 300, 1)`,
ID: "320", Name: "AnsiText", ZhName: "ANSI 文本 终端颜色", Category: "Git",
Demo: gitAnsiTextDemo,
Code: `view := func(c *ui.Context) {
mujica.Use(c, mujica.Settings{})
mujica.AnsiText(c, "\x1b[1;32mPASS\x1b[0m 12 tests\n\x1b[31mFAIL\x1b[0m 1 test", mujica.AnsiTextOptions{})
core.Use(c, core.Settings{})
git.AnsiText(c, "\x1b[1;32mPASS\x1b[0m 12 tests\n\x1b[31mFAIL\x1b[0m 1 test", git.AnsiTextOptions{})
}
ui.Render(view, 400, 300, 1)`,
Keys: "Not focusable; shows the text with its styles.",
+20 -17
View File
@@ -6,7 +6,10 @@ import (
"github.com/egoist/mygo/ui"
mujica "mujicaui"
"github.com/ZacharyZhang-NY/MujicaUI/agent"
"github.com/ZacharyZhang-NY/MujicaUI/core"
"github.com/ZacharyZhang-NY/MujicaUI/git"
"github.com/ZacharyZhang-NY/MujicaUI/navigation"
)
func gitPullRequestCardDemo(c *ui.Context, log func(string)) {
@@ -16,18 +19,18 @@ func gitPullRequestCardDemo(c *ui.Context, log func(string)) {
}
s := ui.Local(c.Root(), "315", func() state { return state{} })
navigationDemo(c, func() {
mujica.SegmentedControl(c, &s.st, []mujica.Segment{{Label: "Open"}, {Label: "Draft"}, {Label: "Merged"}, {Label: "Closed"}}, mujica.SegmentedControlOptions{Label: "State"})
navigation.SegmentedControl(c, &s.st, []navigation.Segment{{Label: "Open"}, {Label: "Draft"}, {Label: "Merged"}, {Label: "Closed"}}, navigation.SegmentedControlOptions{Label: "State"})
navigationCheck(c, &s.failed, "Failing check", log)
navigationCheck(c, &s.disabled, "Disabled", log)
}, func() {
pr := mujica.PullRequest{Number: 318, Title: "Git components 301–320", Author: "Sakiko", State: mujica.PullRequestState(s.st),
pr := git.PullRequest{Number: 318, Title: "Git components 301–320", Author: "Sakiko", State: git.PullRequestState(s.st),
Head: "feature/git", Base: "main", ChecksPassed: 12, ChecksPending: 1,
Reviewers: []mujica.Reviewer{{Name: "Uika", State: mujica.ReviewApproved}, {Name: "Mutsumi", State: mujica.ReviewChangesRequested}, {Name: "Umiri"}},
Reviewers: []git.Reviewer{{Name: "Uika", State: git.ReviewApproved}, {Name: "Mutsumi", State: git.ReviewChangesRequested}, {Name: "Umiri"}},
Added: 2412, Deleted: 310, Files: 46}
if s.failed {
pr.ChecksFailed = 1
}
if mujica.PullRequestCard(c, pr, mujica.PullRequestCardOptions{Disabled: s.disabled}).Changed() {
if git.PullRequestCard(c, pr, git.PullRequestCardOptions{Disabled: s.disabled}).Changed() {
log(fmt.Sprintf("Open #%d", pr.Number))
}
})
@@ -35,12 +38,12 @@ func gitPullRequestCardDemo(c *ui.Context, log func(string)) {
func gitReviewCommentDemo(c *ui.Context, log func(string)) {
type state struct {
thread mujica.ReviewThread
thread git.ReviewThread
draft string
disabled bool
}
s := ui.Local(c.Root(), "316", func() state {
return state{thread: mujica.ReviewThread{File: "commit_graph.go", Line: 88, Messages: []mujica.ReviewMessage{
return state{thread: git.ReviewThread{File: "commit_graph.go", Line: 88, Messages: []git.ReviewMessage{
{Author: "Uika", When: gitDemoTime, Body: "Should merge lanes reuse the parent's color?"},
{Author: "Sakiko", When: gitDemoTime.Add(time.Hour), Body: "Yes, the first parent keeps it."},
}}}
@@ -48,10 +51,10 @@ func gitReviewCommentDemo(c *ui.Context, log func(string)) {
navigationDemo(c, func() {
navigationCheck(c, &s.disabled, "Disabled", log)
}, func() {
r := mujica.ReviewComment(c, s.thread, &s.draft, mujica.ReviewCommentOptions{Disabled: s.disabled})
r := git.ReviewComment(c, s.thread, &s.draft, git.ReviewCommentOptions{Disabled: s.disabled})
if text, ok := r.Replied(); ok {
log("Reply: " + text)
s.thread.Messages = append(s.thread.Messages, mujica.ReviewMessage{Author: "You", When: c.Now(), Body: text})
s.thread.Messages = append(s.thread.Messages, git.ReviewMessage{Author: "You", When: c.Now(), Body: text})
}
if r.Resolved() {
log("Resolved")
@@ -72,7 +75,7 @@ func gitDiffStatDemo(c *ui.Context, log func(string)) {
navigationSlider(c, &s.deleted, "Deleted", 0, 500, log)
navigationSlider(c, &s.blocks, "Blocks", 1, 10, log)
}, func() {
mujica.DiffStat(c, int(s.added), int(s.deleted), mujica.DiffStatOptions{Blocks: int(s.blocks)})
git.DiffStat(c, int(s.added), int(s.deleted), git.DiffStatOptions{Blocks: int(s.blocks)})
})
}
@@ -83,8 +86,8 @@ func gitStatusBadgeDemo(c *ui.Context, log func(string)) {
navigationCheck(c, &s.labels, "Show labels", log)
}, func() {
ui.Row(c).Gap(16).Wrap().Children(func() {
for st := mujica.GitModified; st <= mujica.GitCopied; st++ {
mujica.GitStatusBadge(c, st, mujica.GitStatusBadgeOptions{ShowLabel: s.labels})
for st := git.GitModified; st <= git.GitCopied; st++ {
git.GitStatusBadge(c, st, git.GitStatusBadgeOptions{ShowLabel: s.labels})
}
})
})
@@ -99,14 +102,14 @@ func gitCommandBlockDemo(c *ui.Context, log func(string)) {
navigationCheck(c, &s.failed, "Failed", log)
navigationCheck(c, &s.running, "Running", log)
}, func() {
run := mujica.CommandRun{Command: "go test -run Git ./...", Duration: 3214 * time.Millisecond,
run := agent.CommandRun{Command: "go test -run Git ./...", Duration: 3214 * time.Millisecond,
Output: "\x1b[32mok\x1b[0m \tmujicaui\t3.214s\n\x1b[33mwarn\x1b[0m slow test: TestCommitGraphVirtualized"}
if s.failed {
run.ExitCode = 1
run.Output = "\x1b[31m--- FAIL\x1b[0m: TestDiffViewer (0.02s)\n\x1b[1mFAIL\x1b[0m\tmujicaui\t0.401s"
}
run.Running = s.running
r := mujica.CommandBlock(c, run, &s.open, mujica.CommandBlockOptions{})
r := git.CommandBlock(c, run, &s.open, git.CommandBlockOptions{})
if r.Toggled() {
log(fmt.Sprintf("Toggled: open=%v", s.open))
}
@@ -122,13 +125,13 @@ func gitAnsiTextDemo(c *ui.Context, log func(string)) {
navigationDemo(c, func() {
navigationCheck(c, &s.wrap, "Wrap", log)
}, func() {
ui.Column(c).Padding(12).Background(mujica.Tokens(c).Surface).Radius(4).Children(func() {
mujica.AnsiText(c, "\x1b[1mBuilding\x1b[0m mujica \x1b[2m(v1.1)\x1b[0m\n"+
ui.Column(c).Padding(12).Background(core.Tokens(c).Surface).Radius(4).Children(func() {
git.AnsiText(c, "\x1b[1mBuilding\x1b[0m mujica \x1b[2m(v1.1)\x1b[0m\n"+
"\x1b[32m✓\x1b[0m 300 passed \x1b[31m✗\x1b[0m 2 failed \x1b[33m!\x1b[0m 1 skipped\n"+
"\x1b[30m0\x1b[31m1\x1b[32m2\x1b[33m3\x1b[34m4\x1b[35m5\x1b[36m6\x1b[37m7\x1b[90m8\x1b[91m9\x1b[92mA\x1b[93mB\x1b[94mC\x1b[95mD\x1b[96mE\x1b[97mF\x1b[0m\n"+
"\x1b[38;5;208m256-color\x1b[0m \x1b[38;2;125;32;52mtrue color\x1b[0m \x1b[4munderline\x1b[0m \x1b[3mitalic\x1b[0m \x1b[9mstruck\x1b[0m \x1b[7m inverse \x1b[0m\n"+
"A long line that only breaks when wrapping is on, so the terminal layout of columns stays intact as it was printed.\n"+
"Unknown escapes stay as text: \x1b[2K(cleared line)", mujica.AnsiTextOptions{Wrap: s.wrap})
"Unknown escapes stay as text: \x1b[2K(cleared line)", git.AnsiTextOptions{Wrap: s.wrap})
})
})
}
+62 -59
View File
@@ -7,7 +7,10 @@ import (
"github.com/egoist/mygo/ui"
mujica "mujicaui"
"github.com/ZacharyZhang-NY/MujicaUI/core"
"github.com/ZacharyZhang-NY/MujicaUI/data"
"github.com/ZacharyZhang-NY/MujicaUI/git"
"github.com/ZacharyZhang-NY/MujicaUI/input"
)
var gitDemoTime = time.Date(2026, 10, 5, 9, 30, 0, 0, time.UTC)
@@ -15,11 +18,11 @@ var gitDemoTime = time.Date(2026, 10, 5, 9, 30, 0, 0, time.UTC)
const gitDemoOld = "package court\n\nimport \"fmt\"\n\n// Greet welcomes a guest.\nfunc Greet(name string) {\n\tfmt.Println(\"Welcome,\", name)\n}\n\nfunc Leave() {}\n\nfunc Dance() {}\n\nfunc Bow() {}\n"
const gitDemoNew = "package court\n\nimport \"fmt\"\n\n// Greet welcomes a guest by title.\nfunc Greet(title, name string) {\n\tfmt.Println(\"Welcome,\", title, name)\n}\n\nfunc Leave() {}\n\nfunc Dance() {}\n\nfunc Bow() {}\n\nfunc Curtsy() {}\n"
func gitDemoCommits(n int) []mujica.Commit {
out := make([]mujica.Commit, n)
func gitDemoCommits(n int) []git.Commit {
out := make([]git.Commit, n)
authors := []string{"Sakiko", "Mutsumi", "Uika", "Umiri", "Nyamu"}
for i := range out {
out[i] = mujica.Commit{Hash: fmt.Sprintf("%016x%024x", uint64(n-i)*0x9E3779B97F4A7C15, n-i), Author: authors[i%5],
out[i] = git.Commit{Hash: fmt.Sprintf("%016x%024x", uint64(n-i)*0x9E3779B97F4A7C15, n-i), Author: authors[i%5],
When: gitDemoTime.Add(-time.Duration(i) * 37 * time.Minute), Subject: fmt.Sprintf("Chapter %d of the chronicle", n-i)}
if i+1 < n {
out[i].Parents = []string{fmt.Sprintf("%016x%024x", uint64(n-i-1)*0x9E3779B97F4A7C15, n-i-1)}
@@ -31,8 +34,8 @@ func gitDemoCommits(n int) []mujica.Commit {
return out
}
func gitDemoGraph() []mujica.Commit {
cs := []mujica.Commit{
func gitDemoGraph() []git.Commit {
cs := []git.Commit{
{Hash: "e7", Parents: []string{"e5", "f3"}, Subject: "Merge branch 'feature/lanes'", Refs: []string{"main"}},
{Hash: "f3", Parents: []string{"f2"}, Subject: "Color lanes by branch"},
{Hash: "e5", Parents: []string{"e4", "h1"}, Subject: "Merge hotfix"},
@@ -51,8 +54,8 @@ func gitDemoGraph() []mujica.Commit {
return cs
}
func gitDemoBranches() []mujica.Branch {
return []mujica.Branch{
func gitDemoBranches() []git.Branch {
return []git.Branch{
{Name: "main", Current: true, Upstream: "origin/main", Ahead: 2, Behind: 1},
{Name: "feature/lanes", Upstream: "origin/feature/lanes", Ahead: 5},
{Name: "fix/blame"},
@@ -64,7 +67,7 @@ func gitDemoBranches() []mujica.Branch {
func gitDiffViewerDemo(c *ui.Context, log func(string)) {
type state struct {
viewer mujica.DiffViewerState
viewer git.DiffViewerState
split, plain bool
context float64
}
@@ -78,7 +81,7 @@ func gitDiffViewerDemo(c *ui.Context, log func(string)) {
if s.plain {
lang = ""
}
mujica.DiffViewer(c, &s.viewer, gitDemoOld, gitDemoNew, mujica.DiffViewerOptions{SideBySide: s.split, Context: int(s.context), Language: lang}).Height(380)
git.DiffViewer(c, &s.viewer, gitDemoOld, gitDemoNew, git.DiffViewerOptions{SideBySide: s.split, Context: int(s.context), Language: lang}).Height(380)
})
}
@@ -93,19 +96,19 @@ func gitInlineDiffDemo(c *ui.Context, log func(string)) {
navigationDemo(c, func() {
navigationCheck(c, &s.mono, "Monospace", log)
}, func() {
if mujica.TextInput(c, &s.from, mujica.TextInputOptions{Label: "Old line"}).Changed() {
if input.TextInput(c, &s.from, input.TextInputOptions{Label: "Old line"}).Changed() {
log("Old: " + s.from)
}
if mujica.TextInput(c, &s.to, mujica.TextInputOptions{Label: "New line"}).Changed() {
if input.TextInput(c, &s.to, input.TextInputOptions{Label: "New line"}).Changed() {
log("New: " + s.to)
}
mujica.InlineDiff(c, s.from, s.to, mujica.InlineDiffOptions{Mono: s.mono})
git.InlineDiff(c, s.from, s.to, git.InlineDiffOptions{Mono: s.mono})
})
}
func gitThreeWayMergeDemo(c *ui.Context, log func(string)) {
type state struct {
merge mujica.ThreeWayMergeState
merge git.ThreeWayMergeState
disabled bool
}
s := ui.Local(c.Root(), "303", func() state { return state{} })
@@ -115,19 +118,19 @@ func gitThreeWayMergeDemo(c *ui.Context, log func(string)) {
navigationDemo(c, func() {
navigationCheck(c, &s.disabled, "Disabled", log)
}, func() {
r := mujica.ThreeWayMerge(c, &s.merge, base, ours, theirs, mujica.ThreeWayMergeOptions{Disabled: s.disabled})
r := git.ThreeWayMerge(c, &s.merge, base, ours, theirs, git.ThreeWayMergeOptions{Disabled: s.disabled})
r.Element.Height(360)
if r.Changed() {
done, total := s.merge.Resolved()
log(fmt.Sprintf("Changed: %d of %d resolved", done, total))
}
ui.Text(c, s.merge.Result()).Font("monospace").TextColor(mujica.Tokens(c).TextMuted)
ui.Text(c, s.merge.Result()).Font("monospace").TextColor(core.Tokens(c).TextMuted)
})
}
func gitConflictResolverDemo(c *ui.Context, log func(string)) {
type state struct {
res mujica.ConflictResolverState
res git.ConflictResolverState
disabled bool
}
s := ui.Local(c.Root(), "304", func() state { return state{} })
@@ -135,7 +138,7 @@ func gitConflictResolverDemo(c *ui.Context, log func(string)) {
navigationDemo(c, func() {
navigationCheck(c, &s.disabled, "Disabled", log)
}, func() {
if mujica.ConflictResolver(c, &s.res, text, mujica.ConflictResolverOptions{Disabled: s.disabled}).Changed() {
if git.ConflictResolver(c, &s.res, text, git.ConflictResolverOptions{Disabled: s.disabled}).Changed() {
done, total := s.res.Resolved()
log(fmt.Sprintf("Changed: %d of %d resolved", done, total))
}
@@ -144,23 +147,23 @@ func gitConflictResolverDemo(c *ui.Context, log func(string)) {
func gitChangesListDemo(c *ui.Context, log func(string)) {
type state struct {
list mujica.ChangesListState
files []mujica.ChangedFile
list git.ChangesListState
files []git.ChangedFile
disabled bool
}
s := ui.Local(c.Root(), "305", func() state {
return state{files: []mujica.ChangedFile{
{Path: "commit_graph.go", Status: mujica.GitAdded, Staged: true},
{Path: "internal/git.go", Status: mujica.GitModified},
{Path: "docs/old.md", Status: mujica.GitDeleted},
{Path: "notes.txt", Status: mujica.GitUntracked},
{Path: "icons/svg/tag.svg", Status: mujica.GitRenamed},
return state{files: []git.ChangedFile{
{Path: "commit_graph.go", Status: git.GitAdded, Staged: true},
{Path: "internal/git.go", Status: git.GitModified},
{Path: "docs/old.md", Status: git.GitDeleted},
{Path: "notes.txt", Status: git.GitUntracked},
{Path: "icons/svg/tag.svg", Status: git.GitRenamed},
}}
})
navigationDemo(c, func() {
navigationCheck(c, &s.disabled, "Disabled", log)
}, func() {
r := mujica.ChangesList(c, &s.list, s.files, mujica.ChangesListOptions{Disabled: s.disabled})
r := git.ChangesList(c, &s.list, s.files, git.ChangesListOptions{Disabled: s.disabled})
r.Element.Height(320)
if a, path, ok := r.Action(); ok {
log("Action: " + a + " " + path)
@@ -173,8 +176,8 @@ func gitChangesListDemo(c *ui.Context, log func(string)) {
}
// gitStage applies a stage action to a copy of files.
func gitStage(files []mujica.ChangedFile, action, path string) []mujica.ChangedFile {
out := append([]mujica.ChangedFile(nil), files...)
func gitStage(files []git.ChangedFile, action, path string) []git.ChangedFile {
out := append([]git.ChangedFile(nil), files...)
for i := range out {
switch {
case action == "stage_all":
@@ -190,14 +193,14 @@ func gitStage(files []mujica.ChangedFile, action, path string) []mujica.ChangedF
func gitCommitInputDemo(c *ui.Context, log func(string)) {
type state struct {
msg mujica.CommitMessage
msg git.CommitMessage
busy, disabled bool
until time.Time
}
s := ui.Local(c.Root(), "306", func() state { return state{} })
if s.busy && c.Now().After(s.until) {
s.busy = false
s.msg = mujica.CommitMessage{}
s.msg = git.CommitMessage{}
log("Committed")
}
if s.busy {
@@ -206,7 +209,7 @@ func gitCommitInputDemo(c *ui.Context, log func(string)) {
navigationDemo(c, func() {
navigationCheck(c, &s.disabled, "Disabled", log)
}, func() {
r := mujica.CommitInput(c, &s.msg, mujica.CommitInputOptions{Busy: s.busy, Disabled: s.disabled})
r := git.CommitInput(c, &s.msg, git.CommitInputOptions{Busy: s.busy, Disabled: s.disabled})
if r.Committed() || r.Amended() {
log(fmt.Sprintf("Submit: amend=%v title=%q", r.Amended(), s.msg.Title))
s.busy, s.until = true, c.Now().Add(1200*time.Millisecond)
@@ -216,7 +219,7 @@ func gitCommitInputDemo(c *ui.Context, log func(string)) {
func gitCommitGraphDemo(c *ui.Context, log func(string)) {
type state struct {
graph mujica.CommitGraphState
graph git.CommitGraphState
big bool
}
s := ui.Local(c.Root(), "307", func() state { return state{} })
@@ -228,7 +231,7 @@ func gitCommitGraphDemo(c *ui.Context, log func(string)) {
if s.big {
commits = big
}
r := mujica.CommitGraph(c, &s.graph, commits, mujica.CommitGraphOptions{})
r := git.CommitGraph(c, &s.graph, commits, git.CommitGraphOptions{})
r.Element.Height(380)
if hash, ok := s.graph.List.Selected(); ok && (r.Changed() || r.Submitted()) {
log(fmt.Sprintf("Selected %s, open=%v", hash[:min(7, len(hash))], r.Submitted()))
@@ -238,7 +241,7 @@ func gitCommitGraphDemo(c *ui.Context, log func(string)) {
func gitCommitListDemo(c *ui.Context, log func(string)) {
type state struct {
list mujica.ListState[string]
list data.ListState[string]
loading bool
}
s := ui.Local(c.Root(), "308", func() state { return state{} })
@@ -246,11 +249,11 @@ func gitCommitListDemo(c *ui.Context, log func(string)) {
navigationDemo(c, func() {
navigationCheck(c, &s.loading, "Loading more", log)
}, func() {
st := mujica.DataReady
st := data.DataReady
if s.loading {
st = mujica.DataLoadingMore
st = data.DataLoadingMore
}
r := mujica.CommitList(c, &s.list, commits, mujica.CommitListOptions{Status: st})
r := git.CommitList(c, &s.list, commits, git.CommitListOptions{Status: st})
r.Element.Height(360)
if hash, ok := s.list.Selected(); ok && (r.Changed() || r.Submitted()) {
log(fmt.Sprintf("Selected %s, open=%v", hash[:7], r.Submitted()))
@@ -262,29 +265,29 @@ func gitBranchSelectorDemo(c *ui.Context, log func(string)) {
type state struct {
branch string
create, disabled bool
branches []mujica.Branch
branches []git.Branch
}
s := ui.Local(c.Root(), "309", func() state { return state{branch: "main", create: true, branches: gitDemoBranches()} })
navigationDemo(c, func() {
navigationCheck(c, &s.create, "Allow create", log)
navigationCheck(c, &s.disabled, "Disabled", log)
}, func() {
r := mujica.BranchSelector(c, &s.branch, s.branches, mujica.BranchSelectorOptions{AllowCreate: s.create, Disabled: s.disabled})
r := git.BranchSelector(c, &s.branch, s.branches, git.BranchSelectorOptions{AllowCreate: s.create, Disabled: s.disabled})
if r.Changed() {
log("Changed: " + s.branch)
}
if name, ok := r.Created(); ok {
log("Create: " + name)
s.branches = append(append([]mujica.Branch(nil), s.branches...), mujica.Branch{Name: name})
s.branches = append(append([]git.Branch(nil), s.branches...), git.Branch{Name: name})
s.branch = name
}
})
}
func gitBranchListDemo(c *ui.Context, log func(string)) {
s := ui.Local(c.Root(), "310", func() mujica.ListState[string] { return mujica.ListState[string]{} })
actions := []mujica.GitAction{{ID: "checkout", Label: "Check out", Icon: "git-branch"}, {ID: "delete", Label: "Delete", Icon: "trash-2"}}
r := mujica.BranchList(c, s, gitDemoBranches(), mujica.BranchListOptions{Actions: actions})
s := ui.Local(c.Root(), "310", func() data.ListState[string] { return data.ListState[string]{} })
actions := []git.GitAction{{ID: "checkout", Label: "Check out", Icon: "git-branch"}, {ID: "delete", Label: "Delete", Icon: "trash-2"}}
r := git.BranchList(c, s, gitDemoBranches(), git.BranchListOptions{Actions: actions})
r.Element.Height(300)
if a, name, ok := r.Action(); ok {
log("Action: " + a + " " + name)
@@ -295,13 +298,13 @@ func gitBranchListDemo(c *ui.Context, log func(string)) {
}
func gitTagListDemo(c *ui.Context, log func(string)) {
s := ui.Local(c.Root(), "311", func() mujica.ListState[string] { return mujica.ListState[string]{} })
tags := []mujica.GitTag{
s := ui.Local(c.Root(), "311", func() data.ListState[string] { return data.ListState[string]{} })
tags := []git.GitTag{
{Name: "v1.1.0", Target: "9e3779b97f4a7c15", Annotation: "Five hundred components"},
{Name: "v1.0.0", Target: "3c6ef372fe94f82a", Annotation: "The first hundred"},
{Name: "nightly", Target: "daa66d2c7ddf743f"},
}
r := mujica.TagList(c, s, tags, mujica.TagListOptions{})
r := git.TagList(c, s, tags, git.TagListOptions{})
r.Element.Height(220)
if _, hash, ok := r.Action(); ok {
log("Jump to " + hash[:7])
@@ -313,22 +316,22 @@ func gitTagListDemo(c *ui.Context, log func(string)) {
func gitStashListDemo(c *ui.Context, log func(string)) {
type state struct {
list mujica.ListState[string]
stashes []mujica.Stash
list data.ListState[string]
stashes []git.Stash
}
s := ui.Local(c.Root(), "312", func() state {
return state{stashes: []mujica.Stash{
return state{stashes: []git.Stash{
{Ref: "stash@{0}", Message: "WIP on main: lane colors", Branch: "main"},
{Ref: "stash@{1}", Message: "Try serif headings", Branch: "feature/type"},
{Ref: "stash@{2}", Message: "Blame hover experiment", Branch: "fix/blame"},
}}
})
r := mujica.StashList(c, &s.list, s.stashes, mujica.StashListOptions{})
r := git.StashList(c, &s.list, s.stashes, git.StashListOptions{})
r.Element.Height(240)
if a, ref, ok := r.Action(); ok {
log("Action: " + a + " " + ref)
if a != "apply" {
var keep []mujica.Stash
var keep []git.Stash
for _, st := range s.stashes {
if st.Ref != ref {
keep = append(keep, st)
@@ -340,23 +343,23 @@ func gitStashListDemo(c *ui.Context, log func(string)) {
}
func gitBlameViewDemo(c *ui.Context, log func(string)) {
s := ui.Local(c.Root(), "313", func() mujica.BlameViewState { return mujica.BlameViewState{} })
var lines []mujica.BlameLine
s := ui.Local(c.Root(), "313", func() git.BlameViewState { return git.BlameViewState{} })
var lines []git.BlameLine
for i, l := range strings.Split(strings.TrimSuffix(gitDemoNew, "\n"), "\n") {
who := []mujica.BlameLine{
who := []git.BlameLine{
{Hash: "9e3779b97f4a7c15", Author: "Sakiko", When: gitDemoTime, Summary: "Create the court package"},
{Hash: "3c6ef372fe94f82a", Author: "Uika", When: gitDemoTime.Add(-48 * time.Hour), Summary: "Greet guests by title"},
}[min(i/4%2, 1)]
who.Text = l
lines = append(lines, who)
}
mujica.BlameView(c, s, lines, mujica.BlameViewOptions{Language: "go"}).Height(380)
ui.Text(c, "Hover the left column for commit details.").TextColor(mujica.Tokens(c).TextMuted)
git.BlameView(c, s, lines, git.BlameViewOptions{Language: "go"}).Height(380)
ui.Text(c, "Hover the left column for commit details.").TextColor(core.Tokens(c).TextMuted)
}
func gitFileHistoryDemo(c *ui.Context, log func(string)) {
s := ui.Local(c.Root(), "314", func() mujica.ListState[string] { return mujica.ListState[string]{} })
r := mujica.FileHistory(c, s, "court/greet.go", gitDemoCommits(12), mujica.FileHistoryOptions{})
s := ui.Local(c.Root(), "314", func() data.ListState[string] { return data.ListState[string]{} })
r := git.FileHistory(c, s, "court/greet.go", gitDemoCommits(12), git.FileHistoryOptions{})
r.Element.Height(360)
if _, hash, ok := r.Action(); ok {
log("Diff at " + hash[:7])
+61 -60
View File
@@ -7,22 +7,23 @@ import (
"github.com/egoist/mygo/ui"
mujica "mujicaui"
"mujicaui/icons"
"github.com/ZacharyZhang-NY/MujicaUI/core"
"github.com/ZacharyZhang-NY/MujicaUI/icons"
"github.com/ZacharyZhang-NY/MujicaUI/input"
)
const inputCategory = "Inputs"
// inputVariants lists the button variants for variant pickers.
var inputVariants = []mujica.RadioItem[mujica.ButtonVariant]{
{Value: mujica.Primary, Label: "Primary"}, {Value: mujica.Secondary, Label: "Secondary"},
{Value: mujica.Outline, Label: "Outline"}, {Value: mujica.Ghost, Label: "Ghost"}, {Value: mujica.Danger, Label: "Danger"},
var inputVariants = []input.RadioItem[input.ButtonVariant]{
{Value: input.Primary, Label: "Primary"}, {Value: input.Secondary, Label: "Secondary"},
{Value: input.Outline, Label: "Outline"}, {Value: input.Ghost, Label: "Ghost"}, {Value: input.Danger, Label: "Danger"},
}
// inputSection titles one part of a demo.
func inputSection(c *ui.Context, title string, fn func()) {
ui.Column(c).Gap(8).Children(func() {
ui.Text(c, title).FontWeight(600).TextColor(mujica.Tokens(c).TextMuted)
ui.Text(c, title).FontWeight(600).TextColor(core.Tokens(c).TextMuted)
fn()
})
}
@@ -31,7 +32,7 @@ func inputSection(c *ui.Context, title string, fn func()) {
func inputFlags(c *ui.Context, log func(string), flags map[string]*bool, order ...string) {
ui.Row(c).Gap(16).Wrap().Children(func() {
for _, name := range order {
if mujica.Checkbox(c, flags[name], name, mujica.CheckboxOptions{}).Changed() {
if input.Checkbox(c, flags[name], name, input.CheckboxOptions{}).Changed() {
log(fmt.Sprintf("option %s = %v", name, *flags[name]))
}
}
@@ -39,7 +40,7 @@ func inputFlags(c *ui.Context, log func(string), flags map[string]*bool, order .
}
type inputButtonDemo struct {
variant mujica.ButtonVariant
variant input.ButtonVariant
icon, loading, disabled bool
}
@@ -81,22 +82,22 @@ type inputRadioDemo struct {
var inputPages = []page{
{ID: "021", Name: "Button", ZhName: "按钮", Category: inputCategory, Keys: "Tab 聚焦;Enter 或 Space 触发。",
Code: `if mujica.Button(c, "Save", mujica.ButtonOptions{Icon: icons.Must("check")}).Clicked() {
Code: `if input.Button(c, "Save", input.ButtonOptions{Icon: icons.Must("check")}).Clicked() {
fmt.Println("saved")
}`,
Demo: func(c *ui.Context, log func(string)) {
s := ui.Local(c.Root(), "021", func() inputButtonDemo { return inputButtonDemo{} })
inputSection(c, "Parameters", func() {
mujica.RadioGroup(c, &s.variant, inputVariants, mujica.RadioGroupOptions{Horizontal: true, Label: "Variant"})
input.RadioGroup(c, &s.variant, inputVariants, input.RadioGroupOptions{Horizontal: true, Label: "Variant"})
inputFlags(c, log, map[string]*bool{"Icon": &s.icon, "Loading": &s.loading, "Disabled": &s.disabled}, "Icon", "Loading", "Disabled")
})
inputSection(c, "Live", func() {
opts := mujica.ButtonOptions{Variant: s.variant, Loading: s.loading, Disabled: s.disabled}
opts := input.ButtonOptions{Variant: s.variant, Loading: s.loading, Disabled: s.disabled}
if s.icon {
opts.Icon = icons.Must("check")
}
ui.Row(c).Children(func() {
if mujica.Button(c, "Save changes", opts).Clicked() {
if input.Button(c, "Save changes", opts).Clicked() {
log("Button clicked")
}
})
@@ -104,30 +105,30 @@ var inputPages = []page{
inputSection(c, "Variants", func() {
ui.Row(c).Gap(12).Wrap().Children(func() {
for _, v := range inputVariants {
if mujica.Button(c, v.Label, mujica.ButtonOptions{Variant: v.Value}).Clicked() {
if input.Button(c, v.Label, input.ButtonOptions{Variant: v.Value}).Clicked() {
log(v.Label + " clicked")
}
}
mujica.Button(c, "Loading", mujica.ButtonOptions{Loading: true})
mujica.Button(c, "Disabled", mujica.ButtonOptions{Disabled: true})
input.Button(c, "Loading", input.ButtonOptions{Loading: true})
input.Button(c, "Disabled", input.ButtonOptions{Disabled: true})
})
})
}},
{ID: "022", Name: "IconButton", ZhName: "图标按钮", Category: inputCategory, Keys: "Tab 聚焦;Enter 或 Space 触发;悬停显示提示。",
Code: `if mujica.IconButton(c, icons.Must("settings"), "Settings", mujica.IconButtonOptions{Variant: mujica.Ghost}).Clicked() {
Code: `if input.IconButton(c, icons.Must("settings"), "Settings", input.IconButtonOptions{Variant: input.Ghost}).Clicked() {
fmt.Println("open settings")
}`,
Demo: func(c *ui.Context, log func(string)) {
s := ui.Local(c.Root(), "022", func() inputButtonDemo { return inputButtonDemo{variant: mujica.Ghost} })
s := ui.Local(c.Root(), "022", func() inputButtonDemo { return inputButtonDemo{variant: input.Ghost} })
inputSection(c, "Parameters", func() {
mujica.RadioGroup(c, &s.variant, inputVariants, mujica.RadioGroupOptions{Horizontal: true, Label: "Variant"})
input.RadioGroup(c, &s.variant, inputVariants, input.RadioGroupOptions{Horizontal: true, Label: "Variant"})
inputFlags(c, log, map[string]*bool{"Loading": &s.loading, "Disabled": &s.disabled}, "Loading", "Disabled")
})
inputSection(c, "Live", func() {
ui.Row(c).Gap(8).Children(func() {
for _, n := range []string{"bold", "italic", "underline", "copy", "trash-2"} {
opts := mujica.IconButtonOptions{Variant: s.variant, Loading: s.loading, Disabled: s.disabled}
if mujica.IconButton(c, icons.Must(n), n, opts).Clicked() {
opts := input.IconButtonOptions{Variant: s.variant, Loading: s.loading, Disabled: s.disabled}
if input.IconButton(c, icons.Must(n), n, opts).Clicked() {
log("IconButton " + n + " clicked")
}
}
@@ -135,8 +136,8 @@ var inputPages = []page{
})
}},
{ID: "023", Name: "ButtonGroup", ZhName: "按钮组", Category: inputCategory, Keys: "Tab 进入组;方向键在组内移动并跳过禁用项;Enter 或 Space 触发。",
Code: `items := []mujica.ButtonGroupItem{{Label: "Day"}, {Label: "Week"}, {Label: "Month"}}
if i := mujica.ButtonGroup(c, items, mujica.ButtonGroupOptions{Label: "Range"}).ClickedItem(); i >= 0 {
Code: `items := []input.ButtonGroupItem{{Label: "Day"}, {Label: "Week"}, {Label: "Month"}}
if i := input.ButtonGroup(c, items, input.ButtonGroupOptions{Label: "Range"}).ClickedItem(); i >= 0 {
fmt.Println("chose", items[i].Label)
}`,
Demo: func(c *ui.Context, log func(string)) {
@@ -145,15 +146,15 @@ if i := mujica.ButtonGroup(c, items, mujica.ButtonGroupOptions{Label: "Range"}).
inputFlags(c, log, map[string]*bool{"Vertical": &s.vertical, "Disable group": &s.disabled}, "Vertical", "Disable group")
})
inputSection(c, "Live", func() {
items := []mujica.ButtonGroupItem{{Label: "Day"}, {Label: "Week", Disabled: true}, {Label: "Month"}, {Icon: icons.Must("calendar"), Name: "Pick a date"}}
r := mujica.ButtonGroup(c, items, mujica.ButtonGroupOptions{Vertical: s.vertical, Disabled: s.disabled, Label: "Range"})
items := []input.ButtonGroupItem{{Label: "Day"}, {Label: "Week", Disabled: true}, {Label: "Month"}, {Icon: icons.Must("calendar"), Name: "Pick a date"}}
r := input.ButtonGroup(c, items, input.ButtonGroupOptions{Vertical: s.vertical, Disabled: s.disabled, Label: "Range"})
if i := r.ClickedItem(); i >= 0 {
log(fmt.Sprintf("ButtonGroup item %d clicked", i))
}
})
}},
{ID: "024", Name: "TextInput", ZhName: "文本输入框", Category: inputCategory, Keys: "Tab 聚焦;Enter 提交;Escape 清空(可清空时);Cmd/Ctrl+Z 撤销、+C/X/V 剪贴板;支持输入法组合。",
Code: `r := mujica.TextInput(c, &name, mujica.TextInputOptions{Label: "Name", Placeholder: "Your name", Clearable: true})
Code: `r := input.TextInput(c, &name, input.TextInputOptions{Label: "Name", Placeholder: "Your name", Clearable: true})
if r.Submitted() {
fmt.Println("hello", name)
}`,
@@ -161,15 +162,15 @@ if r.Submitted() {
s := ui.Local(c.Root(), "024", func() inputTextDemo { return inputTextDemo{clearable: true, sl: true} })
inputSection(c, "Parameters", func() {
inputFlags(c, log, map[string]*bool{"Clearable": &s.clearable, "Slots": &s.sl, "ReadOnly": &s.readOnly, "Disabled": &s.disabled}, "Clearable", "Slots", "ReadOnly", "Disabled")
mujica.TextInput(c, &s.err, mujica.TextInputOptions{Label: "Error message", Placeholder: "Error message (empty for none)"})
input.TextInput(c, &s.err, input.TextInputOptions{Label: "Error message", Placeholder: "Error message (empty for none)"})
})
inputSection(c, "Live", func() {
opts := mujica.TextInputOptions{Label: "Name", Placeholder: "Your name", Clearable: s.clearable, ReadOnly: s.readOnly, Disabled: s.disabled, Error: s.err}
opts := input.TextInputOptions{Label: "Name", Placeholder: "Your name", Clearable: s.clearable, ReadOnly: s.readOnly, Disabled: s.disabled, Error: s.err}
if s.sl {
opts.Leading = func() { ui.Icon(c, icons.Must("user")).Size(16, 16).TextColor(mujica.Tokens(c).TextMuted) }
opts.Trailing = func() { ui.Text(c, fmt.Sprintf("%d", len([]rune(s.value)))).TextColor(mujica.Tokens(c).TextMuted) }
opts.Leading = func() { ui.Icon(c, icons.Must("user")).Size(16, 16).TextColor(core.Tokens(c).TextMuted) }
opts.Trailing = func() { ui.Text(c, fmt.Sprintf("%d", len([]rune(s.value)))).TextColor(core.Tokens(c).TextMuted) }
}
r := mujica.TextInput(c, &s.value, opts)
r := input.TextInput(c, &s.value, opts)
if r.Changed() {
log(fmt.Sprintf("TextInput changed: %q", s.value))
}
@@ -180,14 +181,14 @@ if r.Submitted() {
inputSection(c, "States", func() {
ui.Row(c).Gap(12).Children(func() {
e, ro, off := "1789-13-01", "Read only", "Disabled"
mujica.TextInput(c, &e, mujica.TextInputOptions{Label: "Error state", Error: "No such date"}).Element.Grow(1)
mujica.TextInput(c, &ro, mujica.TextInputOptions{Label: "Read-only state", ReadOnly: true}).Element.Grow(1)
mujica.TextInput(c, &off, mujica.TextInputOptions{Label: "Disabled state", Disabled: true}).Element.Grow(1)
input.TextInput(c, &e, input.TextInputOptions{Label: "Error state", Error: "No such date"}).Element.Grow(1)
input.TextInput(c, &ro, input.TextInputOptions{Label: "Read-only state", ReadOnly: true}).Element.Grow(1)
input.TextInput(c, &off, input.TextInputOptions{Label: "Disabled state", Disabled: true}).Element.Grow(1)
})
})
}},
{ID: "025", Name: "TextArea", ZhName: "多行文本框", Category: inputCategory, Keys: "Enter 换行;Cmd/Ctrl+Enter 提交(可配置);滚轮与方向键滚动;撤销、剪贴板与输入法同 TextInput。",
Code: `r := mujica.TextArea(c, &notes, mujica.TextAreaOptions{Label: "Notes", MaxHeight: 160, MaxLength: 200})
Code: `r := input.TextArea(c, &notes, input.TextAreaOptions{Label: "Notes", MaxHeight: 160, MaxLength: 200})
if r.Submitted() {
fmt.Println("send", notes)
}`,
@@ -195,12 +196,12 @@ if r.Submitted() {
s := ui.Local(c.Root(), "025", func() inputAreaDemo { return inputAreaDemo{max: 120} })
inputSection(c, "Parameters", func() {
inputFlags(c, log, map[string]*bool{"ReadOnly": &s.readOnly, "Disabled": &s.disabled}, "ReadOnly", "Disabled")
if mujica.NumberInput(c, &s.max, mujica.NumberInputOptions{Min: 0, Max: 1000, Step: 10, Label: "MaxLength"}).Changed() {
if input.NumberInput(c, &s.max, input.NumberInputOptions{Min: 0, Max: 1000, Step: 10, Label: "MaxLength"}).Changed() {
log(fmt.Sprintf("option MaxLength = %v", s.max))
}
})
inputSection(c, "Live", func() {
r := mujica.TextArea(c, &s.value, mujica.TextAreaOptions{Label: "Notes", Placeholder: "Write a dedication…", MinHeight: 80, MaxHeight: 160,
r := input.TextArea(c, &s.value, input.TextAreaOptions{Label: "Notes", Placeholder: "Write a dedication…", MinHeight: 80, MaxHeight: 160,
MaxLength: int(s.max), ReadOnly: s.readOnly, Disabled: s.disabled})
if r.Changed() {
log(fmt.Sprintf("TextArea changed: %d characters", len([]rune(s.value))))
@@ -211,15 +212,15 @@ if r.Submitted() {
})
}},
{ID: "026", Name: "NumberInput", ZhName: "数字输入框", Category: inputCategory, Keys: "Up/Down 按步长增减;步进模式下 Up/Down/Left/Right、Home、End。",
Code: `if mujica.NumberInput(c, &copies, mujica.NumberInputOptions{Min: 1, Max: 99, Label: "Copies"}).Changed() {
Code: `if input.NumberInput(c, &copies, input.NumberInputOptions{Min: 1, Max: 99, Label: "Copies"}).Changed() {
fmt.Println("copies", copies)
}`,
Demo: func(c *ui.Context, log func(string)) {
s := ui.Local(c.Root(), "026", func() inputNumberDemo { return inputNumberDemo{value: 3, step: 1} })
inputSection(c, "Parameters", func() {
inputFlags(c, log, map[string]*bool{"AllowEmpty": &s.empty, "StepperOnly": &s.stepper, "Disabled": &s.disabled, "Error": &s.fail}, "AllowEmpty", "StepperOnly", "Disabled", "Error")
steps := []mujica.RadioItem[float64]{{Value: 1, Label: "Step 1"}, {Value: 0.5, Label: "Step 0.5"}, {Value: 0.01, Label: "Step 0.01"}}
if mujica.RadioGroup(c, &s.step, steps, mujica.RadioGroupOptions{Horizontal: true, Label: "Step"}).Changed() {
steps := []input.RadioItem[float64]{{Value: 1, Label: "Step 1"}, {Value: 0.5, Label: "Step 0.5"}, {Value: 0.01, Label: "Step 0.01"}}
if input.RadioGroup(c, &s.step, steps, input.RadioGroupOptions{Horizontal: true, Label: "Step"}).Changed() {
log(fmt.Sprintf("option Step = %v", s.step))
}
})
@@ -230,12 +231,12 @@ if r.Submitted() {
s.value = 0
}
inputSection(c, "Live", func() {
opts := mujica.NumberInputOptions{Min: 0, Max: 10, Step: s.step, AllowEmpty: s.empty, StepperOnly: s.stepper, Disabled: s.disabled, Label: "Quantity", Placeholder: "Empty"}
opts := input.NumberInputOptions{Min: 0, Max: 10, Step: s.step, AllowEmpty: s.empty, StepperOnly: s.stepper, Disabled: s.disabled, Label: "Quantity", Placeholder: "Empty"}
if s.fail {
opts.Error = "Not in stock"
}
ui.Box(c).Width(240).Children(func() {
if mujica.NumberInput(c, &s.value, opts).Changed() {
if input.NumberInput(c, &s.value, opts).Changed() {
log(fmt.Sprintf("NumberInput changed: %v", s.value))
}
})
@@ -243,14 +244,14 @@ if r.Submitted() {
inputSection(c, "States", func() {
ui.Row(c).Gap(12).AlignItems(ui.Start).Children(func() {
a, b, e := 4.0, 2.0, 7.5
mujica.NumberInput(c, &a, mujica.NumberInputOptions{Min: 1, Max: 8, StepperOnly: true, Label: "Stepper state"})
mujica.NumberInput(c, &b, mujica.NumberInputOptions{Label: "Disabled state", Disabled: true}).Element.Width(160)
mujica.NumberInput(c, &e, mujica.NumberInputOptions{Step: 0.5, Label: "Error state", Error: "Must be whole"}).Element.Width(160)
input.NumberInput(c, &a, input.NumberInputOptions{Min: 1, Max: 8, StepperOnly: true, Label: "Stepper state"})
input.NumberInput(c, &b, input.NumberInputOptions{Label: "Disabled state", Disabled: true}).Element.Width(160)
input.NumberInput(c, &e, input.NumberInputOptions{Step: 0.5, Label: "Error state", Error: "Must be whole"}).Element.Width(160)
})
})
}},
{ID: "027", Name: "PasswordInput", ZhName: "密码输入框", Category: inputCategory, Keys: "Tab 依次聚焦输入框与显隐按钮;Space 或 Enter 切换显隐;Enter 在输入框中提交;粘贴可用,遮蔽时不可复制。",
Code: `if mujica.PasswordInput(c, &password, mujica.PasswordInputOptions{Label: "Password"}).Submitted() {
Code: `if input.PasswordInput(c, &password, input.PasswordInputOptions{Label: "Password"}).Submitted() {
fmt.Println("signing in")
}`,
Demo: func(c *ui.Context, log func(string)) {
@@ -259,11 +260,11 @@ if r.Submitted() {
inputFlags(c, log, map[string]*bool{"Disabled": &s.disabled, "Error": &s.fail}, "Disabled", "Error")
})
inputSection(c, "Live", func() {
opts := mujica.PasswordInputOptions{Label: "Password", Placeholder: "Password", Disabled: s.disabled}
opts := input.PasswordInputOptions{Label: "Password", Placeholder: "Password", Disabled: s.disabled}
if s.fail {
opts.Error = "At least 8 characters"
}
r := mujica.PasswordInput(c, &s.value, opts)
r := input.PasswordInput(c, &s.value, opts)
if r.Changed() {
// The contents never reach the log.
log(fmt.Sprintf("PasswordInput changed: %d characters", len([]rune(s.value))))
@@ -278,7 +279,7 @@ if r.Submitted() {
if query != "" {
matches = strings.Count(text, query)
}
r := mujica.SearchInput(c, &query, mujica.SearchInputOptions{Find: true, Matches: matches, Current: &current})
r := input.SearchInput(c, &query, input.SearchInputOptions{Find: true, Matches: matches, Current: &current})
if r.Moved() {
fmt.Println("show match", current)
}`,
@@ -289,7 +290,7 @@ if r.Moved() {
inputFlags(c, log, map[string]*bool{"Disabled": &s.disabled}, "Disabled")
})
inputSection(c, "Search mode", func() {
r := mujica.SearchInput(c, &s.query, mujica.SearchInputOptions{Disabled: s.disabled})
r := input.SearchInput(c, &s.query, input.SearchInputOptions{Disabled: s.disabled})
if r.Changed() {
log(fmt.Sprintf("SearchInput changed: %q", s.query))
}
@@ -302,45 +303,45 @@ if r.Moved() {
if s.find != "" {
n = strings.Count(text, s.find)
}
r := mujica.SearchInput(c, &s.find, mujica.SearchInputOptions{Find: true, Matches: n, Current: &s.current, Disabled: s.disabled})
r := input.SearchInput(c, &s.find, input.SearchInputOptions{Find: true, Matches: n, Current: &s.current, Disabled: s.disabled})
if r.Changed() {
log(fmt.Sprintf("find query: %q, %d matches", s.find, n))
}
if r.Moved() {
log(fmt.Sprintf("find moved to match %d", s.current+1))
}
ui.Text(c, text).TextColor(mujica.Tokens(c).TextMuted)
ui.Text(c, text).TextColor(core.Tokens(c).TextMuted)
})
}},
{ID: "029", Name: "Checkbox", ZhName: "复选框", Category: inputCategory, Keys: "Tab 聚焦;Space 切换;点击标签同样切换;全选框在组内勾选全部或清空。",
Code: `mujica.CheckboxGroup(c, "Notifications", []mujica.CheckboxItem{
Code: `input.CheckboxGroup(c, "Notifications", []input.CheckboxItem{
{Label: "Mail", Value: &mail},
{Label: "Calendar", Value: &calendar},
}, mujica.CheckboxGroupOptions{})`,
}, input.CheckboxGroupOptions{})`,
Demo: func(c *ui.Context, log func(string)) {
s := ui.Local(c.Root(), "029", func() inputCheckDemo { return inputCheckDemo{items: [4]bool{true, false, false, true}} })
inputSection(c, "Parameters", func() {
inputFlags(c, log, map[string]*bool{"Indeterminate": &s.mixed, "Disabled": &s.disabled}, "Indeterminate", "Disabled")
})
inputSection(c, "Live", func() {
if mujica.Checkbox(c, &s.single, "Accept the terms", mujica.CheckboxOptions{Indeterminate: s.mixed, Disabled: s.disabled}).Changed() {
if input.Checkbox(c, &s.single, "Accept the terms", input.CheckboxOptions{Indeterminate: s.mixed, Disabled: s.disabled}).Changed() {
s.mixed = false
log(fmt.Sprintf("Checkbox changed: %v", s.single))
}
})
inputSection(c, "Group with select all", func() {
items := []mujica.CheckboxItem{
items := []input.CheckboxItem{
{Label: "Mail", Value: &s.items[0]}, {Label: "Calendar", Value: &s.items[1]},
{Label: "Messages", Value: &s.items[2]}, {Label: "Security alerts (required)", Value: &s.items[3], Disabled: true},
}
if mujica.CheckboxGroup(c, "Notifications", items, mujica.CheckboxGroupOptions{Disabled: s.disabled}).Changed() {
if input.CheckboxGroup(c, "Notifications", items, input.CheckboxGroupOptions{Disabled: s.disabled}).Changed() {
log(fmt.Sprintf("CheckboxGroup changed: %v", s.items))
}
})
}},
{ID: "030", Name: "RadioGroup", ZhName: "单选组", Category: inputCategory, Keys: "Tab 进入组并落在已选项;方向键选择并跳过禁用项;Home、End 选择首末项。",
Code: `items := []mujica.RadioItem[string]{{Value: "s", Label: "Small"}, {Value: "m", Label: "Medium"}, {Value: "l", Label: "Large"}}
if mujica.RadioGroup(c, &size, items, mujica.RadioGroupOptions{Label: "Size"}).Changed() {
Code: `items := []input.RadioItem[string]{{Value: "s", Label: "Small"}, {Value: "m", Label: "Medium"}, {Value: "l", Label: "Large"}}
if input.RadioGroup(c, &size, items, input.RadioGroupOptions{Label: "Size"}).Changed() {
fmt.Println("size", size)
}`,
Demo: func(c *ui.Context, log func(string)) {
@@ -349,8 +350,8 @@ if mujica.RadioGroup(c, &size, items, mujica.RadioGroupOptions{Label: "Size"}).C
inputFlags(c, log, map[string]*bool{"Horizontal": &s.horizontal, "Disabled": &s.disabledAll}, "Horizontal", "Disabled")
})
inputSection(c, "Live", func() {
items := []mujica.RadioItem[string]{{Value: "s", Label: "Small"}, {Value: "m", Label: "Medium"}, {Value: "l", Label: "Large"}, {Value: "xl", Label: "Sold out", Disabled: true}}
if mujica.RadioGroup(c, &s.value, items, mujica.RadioGroupOptions{Horizontal: s.horizontal, Disabled: s.disabledAll, Label: "Size"}).Changed() {
items := []input.RadioItem[string]{{Value: "s", Label: "Small"}, {Value: "m", Label: "Medium"}, {Value: "l", Label: "Large"}, {Value: "xl", Label: "Sold out", Disabled: true}}
if input.RadioGroup(c, &s.value, items, input.RadioGroupOptions{Horizontal: s.horizontal, Disabled: s.disabledAll, Label: "Size"}).Changed() {
log("RadioGroup chose " + s.value)
}
})
+68 -66
View File
@@ -7,8 +7,10 @@ import (
"github.com/egoist/mygo/ui"
mujica "mujicaui"
"mujicaui/icons"
"github.com/ZacharyZhang-NY/MujicaUI/core"
"github.com/ZacharyZhang-NY/MujicaUI/icons"
"github.com/ZacharyZhang-NY/MujicaUI/input"
"github.com/ZacharyZhang-NY/MujicaUI/layout"
)
var layoutPages = []page{
@@ -67,16 +69,16 @@ func layoutSlider(c *ui.Context, v *float64, label string, lo, hi float64, log f
func layoutControls(c *ui.Context, fn func()) { ui.Row(c).Wrap().Gap(12).Children(fn) }
func layoutFrame(c *ui.Context, h float32, fn func()) *ui.Element {
return ui.Column(c).Height(h).Border(1, mujica.Tokens(c).Border).Radius(4).Clip().Children(fn)
return ui.Column(c).Height(h).Border(1, core.Tokens(c).Border).Radius(4).Clip().Children(fn)
}
var layoutSpaces = []string{"Default", "None", "XS", "S", "M", "L", "XL", "XXL"}
func layoutSpace(s string) mujica.Space { return mujica.Space(slices.Index(layoutSpaces, s)) }
func layoutSpace(s string) layout.Space { return layout.Space(slices.Index(layoutSpaces, s)) }
const layoutAppShellCode = `var collapsed bool
mujica.AppShell(c, &collapsed, mujica.AppShellOptions{
TitleBar: func() { mujica.TitleBar(c, "Court Records", mujica.TitleBarOptions{}) },
layout.AppShell(c, &collapsed, layout.AppShellOptions{
TitleBar: func() { layout.TitleBar(c, "Court Records", layout.TitleBarOptions{}) },
Sidebar: func() { ui.Text(c, "Chronicles") },
Content: func() { ui.Text(c, "The Grand Hall") },
})`
@@ -94,7 +96,7 @@ func layoutAppShellDemo(c *ui.Context, log func(string)) {
layoutCheck(c, &s.collapsed, "Collapsed", log)
layoutSlider(c, &s.width, "Sidebar width", 160, 320, log)
})
opts := mujica.AppShellOptions{
opts := layout.AppShellOptions{
SidebarWidth: float32(s.width),
ContentScroll: &s.scroll,
Sidebar: func() {
@@ -103,7 +105,7 @@ func layoutAppShellDemo(c *ui.Context, log func(string)) {
}
},
Content: func() {
mujica.Container(c, mujica.ContainerOptions{}, func() {
layout.Container(c, layout.ContainerOptions{}, func() {
for i := range 30 {
ui.Text(c, fmt.Sprintf("Entry %d of the court record", i+1)).Padding(4, 0)
}
@@ -111,24 +113,24 @@ func layoutAppShellDemo(c *ui.Context, log func(string)) {
},
}
if s.title {
opts.TitleBar = func() { mujica.TitleBar(c, "Court Records", mujica.TitleBarOptions{}) }
opts.TitleBar = func() { layout.TitleBar(c, "Court Records", layout.TitleBarOptions{}) }
}
if s.status {
opts.StatusBar = func() {
mujica.StatusBar(c, mujica.StatusBarOptions{Left: []mujica.StatusItem{{ID: "pos", Text: fmt.Sprintf("Scrolled %.0f", s.scroll.Y)}}})
layout.StatusBar(c, layout.StatusBarOptions{Left: []layout.StatusItem{{ID: "pos", Text: fmt.Sprintf("Scrolled %.0f", s.scroll.Y)}}})
}
}
ui.Column(c).Margin(16, 0, 0, 0).Children(func() {
layoutFrame(c, 360, func() {
if mujica.AppShell(c, &s.collapsed, opts).Toggled {
if layout.AppShell(c, &s.collapsed, opts).Toggled {
log(fmt.Sprintf("sidebar collapsed: %v", s.collapsed))
}
})
})
}
const layoutTitleBarCode = `mujica.TitleBar(c, "Court Records", mujica.TitleBarOptions{
Trailing: func() { mujica.Button(c, "Share", mujica.ButtonOptions{Variant: mujica.Ghost}) },
const layoutTitleBarCode = `layout.TitleBar(c, "Court Records", layout.TitleBarOptions{
Trailing: func() { input.Button(c, "Share", input.ButtonOptions{Variant: input.Ghost}) },
})`
func layoutTitleBarDemo(c *ui.Context, log func(string)) {
@@ -142,32 +144,32 @@ func layoutTitleBarDemo(c *ui.Context, log func(string)) {
layoutCheck(c, &s.trailing, "Trailing tool", log)
layoutChoice(c, &s.title, "Title", []string{"Court Records", "宫廷档案", "A very long title of the royal chronicle of the court"}, log)
})
var opts mujica.TitleBarOptions
var opts layout.TitleBarOptions
if s.leading {
opts.Leading = func() {
if mujica.Button(c, "", mujica.ButtonOptions{Variant: mujica.Ghost, Icon: icons.Must("panel-left"), Label: "Sidebar"}).Clicked() {
if input.Button(c, "", input.ButtonOptions{Variant: input.Ghost, Icon: icons.Must("panel-left"), Label: "Sidebar"}).Clicked() {
log("leading tool clicked")
}
}
}
if s.trailing {
opts.Trailing = func() {
if mujica.Button(c, "Share", mujica.ButtonOptions{Variant: mujica.Ghost}).Clicked() {
if input.Button(c, "Share", input.ButtonOptions{Variant: input.Ghost}).Clicked() {
log("Share clicked")
}
}
}
ui.Column(c).Margin(16, 0, 0, 0).Width(520).Children(func() {
layoutFrame(c, 90, func() { mujica.TitleBar(c, s.title, opts) })
layoutFrame(c, 90, func() { layout.TitleBar(c, s.title, opts) })
})
bar := c.TitleBar()
ui.Text(c, fmt.Sprintf("Window controls reserve: left %.0f, right %.0f, height %.0f DIP", bar.Left, bar.Right, bar.Height)).
TextColor(mujica.Tokens(c).TextMuted).Margin(8, 0, 0, 0)
TextColor(core.Tokens(c).TextMuted).Margin(8, 0, 0, 0)
}
const layoutStatusBarCode = `bar := mujica.StatusBar(c, mujica.StatusBarOptions{
Left: []mujica.StatusItem{{ID: "sync", Text: "Syncing", ShowProgress: true, Progress: 0.4}},
Right: []mujica.StatusItem{{ID: "bell", Label: "Notifications", Icon: icons.Must("bell"), Action: true}},
const layoutStatusBarCode = `bar := layout.StatusBar(c, layout.StatusBarOptions{
Left: []layout.StatusItem{{ID: "sync", Text: "Syncing", ShowProgress: true, Progress: 0.4}},
Right: []layout.StatusItem{{ID: "bell", Label: "Notifications", Icon: icons.Must("bell"), Action: true}},
})
if bar.Clicked == "bell" {
opened++
@@ -186,30 +188,30 @@ func layoutStatusBarDemo(c *ui.Context, log func(string)) {
layoutSlider(c, &s.progress, "Percent", 0, 100, log)
layoutChoice(c, &s.tone, "Tone", []string{"Neutral", "Success", "Warning", "Danger", "Info"}, log)
})
tones := map[string]mujica.StatusItemTone{"Neutral": mujica.StatusItemNeutral, "Success": mujica.StatusItemSuccess,
"Warning": mujica.StatusItemWarning, "Danger": mujica.StatusItemDanger, "Info": mujica.StatusItemInfo}
opts := mujica.StatusBarOptions{
Left: []mujica.StatusItem{
tones := map[string]layout.StatusItemTone{"Neutral": layout.StatusItemNeutral, "Success": layout.StatusItemSuccess,
"Warning": layout.StatusItemWarning, "Danger": layout.StatusItemDanger, "Info": layout.StatusItemInfo}
opts := layout.StatusBarOptions{
Left: []layout.StatusItem{
{ID: "branch", Text: "main", Icon: icons.Must("folder")},
{ID: "sync", Text: "Syncing", ShowProgress: s.showProgress, Progress: float32(s.progress / 100)},
{ID: "lint", Text: "2 warnings", Tone: tones[s.tone], Secondary: true, Action: true},
},
Right: []mujica.StatusItem{
Right: []layout.StatusItem{
{ID: "enc", Text: "UTF-8", Secondary: true},
{ID: "pos", Text: "Ln 12, Col 4", Secondary: true},
{ID: "bell", Label: "Notifications", Icon: icons.Must("bell"), Action: true},
},
}
ui.Column(c).Margin(16, 0, 0, 0).Width(float32(s.width)).Children(func() {
if id := mujica.StatusBar(c, opts).Clicked; id != "" {
if id := layout.StatusBar(c, opts).Clicked; id != "" {
log("clicked " + id)
}
})
ui.Text(c, "Narrow the width: secondary items fold into a count, last first.").
TextColor(mujica.Tokens(c).TextMuted).Margin(8, 0, 0, 0)
TextColor(core.Tokens(c).TextMuted).Margin(8, 0, 0, 0)
}
const layoutContainerCode = `mujica.Container(c, mujica.ContainerOptions{MaxWidth: 320, Center: true}, func() {
const layoutContainerCode = `layout.Container(c, layout.ContainerOptions{MaxWidth: 320, Center: true}, func() {
ui.Text(c, "A centered column of prose.")
})`
@@ -228,10 +230,10 @@ func layoutContainerDemo(c *ui.Context, log func(string)) {
layoutCheck(c, &s.clip, "Clip", log)
layoutCheck(c, &s.wide, "Overflowing child", log)
})
k := mujica.Tokens(c)
k := core.Tokens(c)
ui.Column(c).Margin(16, 0, 0, 0).Children(func() {
layoutFrame(c, 200, func() {
mujica.Container(c, mujica.ContainerOptions{MaxWidth: float32(s.maxWidth), Padding: layoutSpace(s.padding),
layout.Container(c, layout.ContainerOptions{MaxWidth: float32(s.maxWidth), Padding: layoutSpace(s.padding),
Center: s.center, Fill: s.fill, Clip: s.clip}, func() {
ui.Text(c, "Of the Court").FontSize(18)
ui.Text(c, "Prose keeps a readable measure inside a container.").TextColor(k.TextMuted)
@@ -243,12 +245,12 @@ func layoutContainerDemo(c *ui.Context, log func(string)) {
})
}
const layoutStackCode = `mujica.Stack(c, mujica.StackOptions{Horizontal: true, Gap: mujica.SpaceM}, func() {
mujica.Button(c, "Save", mujica.ButtonOptions{})
const layoutStackCode = `layout.Stack(c, layout.StackOptions{Horizontal: true, Gap: layout.SpaceM}, func() {
input.Button(c, "Save", input.ButtonOptions{})
if showDraft {
mujica.Button(c, "Draft", mujica.ButtonOptions{Variant: mujica.Secondary})
input.Button(c, "Draft", input.ButtonOptions{Variant: input.Secondary})
}
mujica.Button(c, "Cancel", mujica.ButtonOptions{Variant: mujica.Ghost})
input.Button(c, "Cancel", input.ButtonOptions{Variant: input.Ghost})
})`
func layoutStackDemo(c *ui.Context, log func(string)) {
@@ -272,13 +274,13 @@ func layoutStackDemo(c *ui.Context, log func(string)) {
layoutChoice(c, &s.align, "Align", aligns, log)
layoutChoice(c, &s.justify, "Justify", []string{"Start", "Center", "End", "SpaceBetween"}, log)
})
align := mujica.StackAlign(slices.Index(aligns, s.align))
align := layout.StackAlign(slices.Index(aligns, s.align))
ui.Column(c).Margin(16, 0, 0, 0).Width(420).Children(func() {
layoutFrame(c, 200, func() {
mujica.Stack(c, mujica.StackOptions{Horizontal: s.horizontal, Wrap: s.wrap, Gap: layoutSpace(s.gap),
layout.Stack(c, layout.StackOptions{Horizontal: s.horizontal, Wrap: s.wrap, Gap: layoutSpace(s.gap),
Align: align, Justify: justifies[s.justify]}, func() {
for _, n := range []string{"Save", "Draft", "Publish", "Archive", "Cancel"} {
if (!s.hide || n != "Draft") && mujica.Button(c, n, mujica.ButtonOptions{Variant: mujica.Secondary}).Clicked() {
if (!s.hide || n != "Draft") && input.Button(c, n, input.ButtonOptions{Variant: input.Secondary}).Clicked() {
log(n + " clicked")
}
}
@@ -287,8 +289,8 @@ func layoutStackDemo(c *ui.Context, log func(string)) {
})
}
const layoutGridCode = `mujica.Grid(c, mujica.GridOptions{Columns: []ui.Track{ui.Fixed(96), ui.Fr(1)}}, func() {
mujica.GridCell(c, mujica.GridCellOptions{ColumnSpan: -1}, func() { ui.Text(c, "Header") })
const layoutGridCode = `layout.Grid(c, layout.GridOptions{Columns: []ui.Track{ui.Fixed(96), ui.Fr(1)}}, func() {
layout.GridCell(c, layout.GridCellOptions{ColumnSpan: -1}, func() { ui.Text(c, "Header") })
ui.Text(c, "Name")
ui.Text(c, "Ada")
})`
@@ -309,22 +311,22 @@ func layoutGridDemo(c *ui.Context, log func(string)) {
layoutCheck(c, &s.header, "Header spans all", log)
layoutCheck(c, &s.tall, "First cell spans 2 rows", log)
})
k := mujica.Tokens(c)
k := core.Tokens(c)
cell := func(text string) {
ui.Box(c).Center().Padding(8).Background(k.Surface).Border(1, k.Border).Radius(4).
Children(func() { ui.Text(c, text).TextColor(k.TextMuted) })
}
ui.Column(c).Margin(16, 0, 0, 0).Width(520).Children(func() {
mujica.Grid(c, mujica.GridOptions{Columns: []ui.Track{tracks[s.first], ui.Fr(1), ui.Fr(1)},
layout.Grid(c, layout.GridOptions{Columns: []ui.Track{tracks[s.first], ui.Fr(1), ui.Fr(1)},
Gap: layoutSpace(s.gap), RowGap: layoutSpace(s.rowGap)}, func() {
if s.header {
mujica.GridCell(c, mujica.GridCellOptions{ColumnSpan: -1}, func() { cell("Header, ColumnSpan -1") })
layout.GridCell(c, layout.GridCellOptions{ColumnSpan: -1}, func() { cell("Header, ColumnSpan -1") })
}
span := 1
if s.tall {
span = 2
}
mujica.GridCell(c, mujica.GridCellOptions{RowSpan: span}, func() { cell(s.first) })
layout.GridCell(c, layout.GridCellOptions{RowSpan: span}, func() { cell(s.first) })
for i := range 4 {
cell(fmt.Sprintf("Cell %d", i+1))
}
@@ -333,7 +335,7 @@ func layoutGridDemo(c *ui.Context, log func(string)) {
}
const layoutScrollAreaCode = `var offset ui.ScrollState
mujica.ScrollArea(c, &offset, mujica.ScrollAreaOptions{Label: "Log"}, func() {
layout.ScrollArea(c, &offset, layout.ScrollAreaOptions{Label: "Log"}, func() {
for range 50 {
ui.Text(c, "A line of the log")
}
@@ -346,25 +348,25 @@ func layoutScrollAreaDemo(c *ui.Context, log func(string)) {
lastX, lastY float32
}
s := ui.Local(c.Root(), "007", func() state { return state{direction: "Vertical"} })
dirs := map[string]mujica.ScrollDirection{"Vertical": mujica.ScrollVertical, "Horizontal": mujica.ScrollHorizontal, "Both": mujica.ScrollBoth}
dirs := map[string]layout.ScrollDirection{"Vertical": layout.ScrollVertical, "Horizontal": layout.ScrollHorizontal, "Both": layout.ScrollBoth}
layoutControls(c, func() {
layoutChoice(c, &s.direction, "Direction", []string{"Vertical", "Horizontal", "Both"}, log)
if mujica.Button(c, "Top", mujica.ButtonOptions{Variant: mujica.Secondary}).Clicked() {
if input.Button(c, "Top", input.ButtonOptions{Variant: input.Secondary}).Clicked() {
s.offset.X, s.offset.Y = 0, 0
log("scroll to start")
}
if mujica.Button(c, "Middle", mujica.ButtonOptions{Variant: mujica.Secondary}).Clicked() {
if input.Button(c, "Middle", input.ButtonOptions{Variant: input.Secondary}).Clicked() {
s.offset.X, s.offset.Y = s.offset.MaxX/2, s.offset.MaxY/2
log("scroll to middle")
}
if mujica.Button(c, "End", mujica.ButtonOptions{Variant: mujica.Secondary}).Clicked() {
if input.Button(c, "End", input.ButtonOptions{Variant: input.Secondary}).Clicked() {
s.offset.X, s.offset.Y = math.MaxFloat32, math.MaxFloat32
log("scroll to end")
}
})
k := mujica.Tokens(c)
k := core.Tokens(c)
ui.Column(c).Margin(16, 0, 0, 0).Width(420).Children(func() {
mujica.ScrollArea(c, &s.offset, mujica.ScrollAreaOptions{Direction: dirs[s.direction], Label: "Ledger"}, func() {
layout.ScrollArea(c, &s.offset, layout.ScrollAreaOptions{Direction: dirs[s.direction], Label: "Ledger"}, func() {
for i := range 40 {
ui.Text(c, fmt.Sprintf("%02d Ledger line of the treasury, folio %d, carried over from the last quarter", i+1, i+7)).
SingleLine().Padding(4, 12)
@@ -379,30 +381,30 @@ func layoutScrollAreaDemo(c *ui.Context, log func(string)) {
TextColor(k.TextMuted).Margin(8, 0, 0, 0)
}
const layoutSplitPaneCode = `split := mujica.SplitPaneState{Size: 160}
mujica.SplitPane(c, &split, mujica.SplitPaneOptions{MinFirst: 120, Collapsible: mujica.SplitPaneFirst},
const layoutSplitPaneCode = `split := layout.SplitPaneState{Size: 160}
layout.SplitPane(c, &split, layout.SplitPaneOptions{MinFirst: 120, Collapsible: layout.SplitPaneFirst},
func() { ui.Text(c, "Files") },
func() { ui.Text(c, "Editor") },
).Fill()`
func layoutSplitPaneDemo(c *ui.Context, log func(string)) {
type state struct {
split mujica.SplitPaneState
split layout.SplitPaneState
vertical bool
collapsible string
minFirst float64
}
s := ui.Local(c.Root(), "008", func() state {
return state{split: mujica.SplitPaneState{Size: 180}, collapsible: "First", minFirst: 120}
return state{split: layout.SplitPaneState{Size: 180}, collapsible: "First", minFirst: 120}
})
sides := map[string]mujica.SplitPaneSide{"Neither": mujica.SplitPaneNeither, "First": mujica.SplitPaneFirst, "Second": mujica.SplitPaneSecond}
sides := map[string]layout.SplitPaneSide{"Neither": layout.SplitPaneNeither, "First": layout.SplitPaneFirst, "Second": layout.SplitPaneSecond}
layoutControls(c, func() {
layoutCheck(c, &s.vertical, "Vertical", log)
layoutChoice(c, &s.collapsible, "Collapsible", []string{"Neither", "First", "Second"}, log)
layoutSlider(c, &s.minFirst, "Min first", 40, 200, log)
})
s.split.Collapsed = s.split.Collapsed && s.collapsible != "Neither"
k := mujica.Tokens(c)
k := core.Tokens(c)
pane := func(title, body string, bg ui.Color) func() {
return func() {
ui.Column(c).Fill().Padding(12).Gap(6).Background(bg).Children(func() {
@@ -413,7 +415,7 @@ func layoutSplitPaneDemo(c *ui.Context, log func(string)) {
}
ui.Column(c).Margin(16, 0, 0, 0).Width(560).Children(func() {
layoutFrame(c, 280, func() {
r := mujica.SplitPane(c, &s.split, mujica.SplitPaneOptions{Vertical: s.vertical, MinFirst: float32(s.minFirst),
r := layout.SplitPane(c, &s.split, layout.SplitPaneOptions{Vertical: s.vertical, MinFirst: float32(s.minFirst),
Collapsible: sides[s.collapsible]},
pane("Files", "Drag the divider, or focus it and use the arrows.", k.Surface),
pane("Editor", "Enter on the divider folds the collapsible pane.", k.Background))
@@ -429,7 +431,7 @@ func layoutSplitPaneDemo(c *ui.Context, log func(string)) {
}
const layoutAspectRatioCode = `ui.Box(c).Size(320, 240).Children(func() {
mujica.AspectRatio(c, mujica.AspectRatioOptions{Ratio: 16.0 / 9}, func() {
layout.AspectRatio(c, layout.AspectRatioOptions{Ratio: 16.0 / 9}, func() {
ui.Text(c, "16 : 9")
})
})`
@@ -446,9 +448,9 @@ func layoutAspectRatioDemo(c *ui.Context, log func(string)) {
layoutSlider(c, &s.width, "Parent width", 120, 560, log)
layoutSlider(c, &s.height, "Parent height", 80, 320, log)
})
k := mujica.Tokens(c)
k := core.Tokens(c)
ui.Box(c).Margin(16, 0, 0, 0).Size(float32(s.width), float32(s.height)).Border(1, k.Border).Children(func() {
mujica.AspectRatio(c, mujica.AspectRatioOptions{Ratio: ratios[s.ratio]}, func() {
layout.AspectRatio(c, layout.AspectRatioOptions{Ratio: ratios[s.ratio]}, func() {
ui.Box(c).Fill().Center().Background(k.Surface).Border(1, k.Ornament).Children(func() {
ui.Text(c, s.ratio).TextColor(k.TextMuted)
})
@@ -457,8 +459,8 @@ func layoutAspectRatioDemo(c *ui.Context, log func(string)) {
}
const layoutDividerCode = `ui.Column(c).Padding(16).Gap(12).Children(func() {
mujica.Divider(c, mujica.DividerOptions{Ornament: true})
mujica.Divider(c, mujica.DividerOptions{Label: "Chapter II"})
layout.Divider(c, layout.DividerOptions{Ornament: true})
layout.Divider(c, layout.DividerOptions{Label: "Chapter II"})
})`
func layoutDividerDemo(c *ui.Context, log func(string)) {
@@ -478,19 +480,19 @@ func layoutDividerDemo(c *ui.Context, log func(string)) {
if label == "None" || s.vertical {
label = ""
}
opts := mujica.DividerOptions{Vertical: s.vertical, Ornament: s.ornament, Label: label}
opts := layout.DividerOptions{Vertical: s.vertical, Ornament: s.ornament, Label: label}
ui.Column(c).Margin(16, 0, 0, 0).Width(480).Children(func() {
if s.vertical {
ui.Row(c).Height(80).Gap(16).Children(func() {
ui.Text(c, "Overture")
mujica.Divider(c, opts)
layout.Divider(c, opts)
ui.Text(c, "Aria")
})
return
}
ui.Column(c).Gap(16).Children(func() {
ui.Text(c, "Overture")
mujica.Divider(c, opts)
layout.Divider(c, opts)
ui.Text(c, "Aria")
})
})
+2 -2
View File
@@ -5,7 +5,7 @@ import (
"github.com/egoist/mygo/ui"
mujica "mujicaui"
"github.com/ZacharyZhang-NY/MujicaUI/core"
)
func layoutPageTester(t *testing.T, id string, events *[]string) *ui.Tester {
@@ -13,7 +13,7 @@ func layoutPageTester(t *testing.T, id string, events *[]string) *ui.Tester {
for _, p := range layoutPages {
if p.ID == id {
return ui.NewTester(func(c *ui.Context) {
mujica.Use(c, mujica.Settings{})
core.Use(c, core.Settings{})
ui.Scroll(c).Fill().Children(func() {
p.Demo(c, func(s string) { *events = append(*events, s) })
})
+76 -73
View File
@@ -2,65 +2,68 @@ package main
import (
"fmt"
"github.com/egoist/mygo/ui"
mujica "mujicaui"
"github.com/ZacharyZhang-NY/MujicaUI/data"
"github.com/ZacharyZhang-NY/MujicaUI/input"
)
var marketsPages = []page{
{ID: "181", Name: "OptionChain", ZhName: "期权链", Category: "Markets", Demo: marketsOptionChainDemo, Code: `var state mujica.OptionChainState
rows := []mujica.OptionStrike{{Strike: 100, Call: mujica.OptionQuote{Bid: 3, Ask: 3.2, Last: 3.1}, Put: mujica.OptionQuote{Bid: 2, Ask: 2.2, Last: 2.1}}}
{ID: "181", Name: "OptionChain", ZhName: "期权链", Category: "Markets", Demo: marketsOptionChainDemo, Code: `var state finance.OptionChainState
rows := []finance.OptionStrike{{Strike: 100, Call: finance.OptionQuote{Bid: 3, Ask: 3.2, Last: 3.1}, Put: finance.OptionQuote{Bid: 2, Ask: 2.2, Last: 2.1}}}
view := func(c *ui.Context) {
mujica.Use(c, mujica.Settings{})
r := mujica.OptionChain(c, &state, []string{"Oct 17", "Nov 21"}, rows, mujica.OptionChainOptions{Spot: 100.4})
core.Use(c, core.Settings{})
r := finance.OptionChain(c, &state, []string{"Oct 17", "Nov 21"}, rows, finance.OptionChainOptions{Spot: 100.4})
if r.Changed || r.Submitted || r.ExpiryChanged {
fmt.Println(state.Expiry, state.Strike, state.Side)
}
}
ui.Render(view, 960, 440, 1)
`, Keys: "Tab 切换到期日或列表;上下、Home/End 选择行;左右切换认购认沽;Enter 确认。"},
{ID: "182", Name: "GreeksTable", ZhName: "希腊值表", Category: "Markets", Demo: marketsGreeksTableDemo, Code: `rows := []mujica.GreeksRow{{Label: "AAPL 100 C", Greeks: mujica.OptionGreeks{Delta: 0.5, Gamma: 0.03, Theta: -0.1, Vega: 0.2, Rho: 0.05}}}
{ID: "182", Name: "GreeksTable", ZhName: "希腊值表", Category: "Markets", Demo: marketsGreeksTableDemo, Code: `rows := []finance.GreeksRow{{Label: "AAPL 100 C", Greeks: finance.OptionGreeks{Delta: 0.5, Gamma: 0.03, Theta: -0.1, Vega: 0.2, Rho: 0.05}}}
view := func(c *ui.Context) {
mujica.Use(c, mujica.Settings{})
mujica.GreeksTable(c, rows, mujica.GreeksTableOptions{Notes: true})
core.Use(c, core.Settings{})
finance.GreeksTable(c, rows, finance.GreeksTableOptions{Notes: true})
}
ui.Render(view, 960, 440, 1)
`, Keys: "展示组件;表头提供说明和悬停提示。"},
{ID: "183", Name: "PayoffDiagram", ZhName: "到期损益图", Category: "Markets", Demo: marketsPayoffDiagramDemo, Code: `legs := []mujica.OptionLeg{{Kind: mujica.LegCall, Strike: 100, Premium: 3, Quantity: 1}}
{ID: "183", Name: "PayoffDiagram", ZhName: "到期损益图", Category: "Markets", Demo: marketsPayoffDiagramDemo, Code: `legs := []finance.OptionLeg{{Kind: finance.LegCall, Strike: 100, Premium: 3, Quantity: 1}}
view := func(c *ui.Context) {
mujica.Use(c, mujica.Settings{})
r := mujica.PayoffDiagram(c, legs, mujica.PayoffDiagramOptions{Frame: mujica.ChartFrame{Label: "Call payoff"}, Spot: 103})
core.Use(c, core.Settings{})
r := finance.PayoffDiagram(c, legs, finance.PayoffDiagramOptions{Frame: chart.ChartFrame{Label: "Call payoff"}, Spot: 103})
if r.Changed() || r.Retried() {
fmt.Println(r.Point)
}
}
ui.Render(view, 960, 440, 1)
`, Keys: "Tab 聚焦;方向键、Home/End 逐点读数;Escape 清除。"},
{ID: "184", Name: "Watchlist", ZhName: "自选列表", Category: "Markets", Demo: marketsWatchlistDemo, Code: `var state mujica.WatchlistState
groups := []mujica.WatchGroup{{Name: "Technology", Items: []mujica.WatchItem{{Symbol: "AAPL", Name: "Apple", Price: 227.5, Change: 2.7, ChangePercent: 1.2}}}, {Name: "Energy", Items: []mujica.WatchItem{{Symbol: "XOM", Price: 117.4}}}}
{ID: "184", Name: "Watchlist", ZhName: "自选列表", Category: "Markets", Demo: marketsWatchlistDemo, Code: `var state finance.WatchlistState
groups := []finance.WatchGroup{{Name: "Technology", Items: []finance.WatchItem{{Symbol: "AAPL", Name: "Apple", Price: 227.5, Change: 2.7, ChangePercent: 1.2}}}, {Name: "Energy", Items: []finance.WatchItem{{Symbol: "XOM", Price: 117.4}}}}
view := func(c *ui.Context) {
mujica.Use(c, mujica.Settings{})
r := mujica.Watchlist(c, &state, groups, mujica.WatchlistOptions{})
core.Use(c, core.Settings{})
r := finance.Watchlist(c, &state, groups, finance.WatchlistOptions{})
if r.Changed || r.Submitted || r.Moved {
fmt.Println(state.Selected, r.Rows, r.To)
}
}
ui.Render(view, 960, 440, 1)
`, Keys: "Tab 切换分组或列菜单;上下、Home/End 选择;Enter 确认;菜单 Space 开关列。"},
{ID: "185", Name: "SymbolSearch", ZhName: "代码搜索", Category: "Markets", Demo: marketsSymbolSearchDemo, Code: `var state mujica.SymbolSearchState
rows := []mujica.MarketSymbol{{Code: "AAPL", Name: "Apple", Market: "NASDAQ", Type: "Stock"}}
{ID: "185", Name: "SymbolSearch", ZhName: "代码搜索", Category: "Markets", Demo: marketsSymbolSearchDemo, Code: `var state finance.SymbolSearchState
rows := []finance.MarketSymbol{{Code: "AAPL", Name: "Apple", Market: "NASDAQ", Type: "Stock"}}
view := func(c *ui.Context) {
mujica.Use(c, mujica.Settings{})
r := mujica.SymbolSearch(c, &state, rows, mujica.SymbolSearchOptions{Markets: []string{"NASDAQ", "NYSE"}, Recent: rows})
core.Use(c, core.Settings{})
r := finance.SymbolSearch(c, &state, rows, finance.SymbolSearchOptions{Markets: []string{"NASDAQ", "NYSE"}, Recent: rows})
if r.Changed || r.Picked || r.Retry {
fmt.Println(state.Query, state.Market, r.Chosen.Code)
}
}
ui.Render(view, 960, 440, 1)
`, Keys: "Tab 聚焦输入与市场;Down 进入结果;Tab 到达结果按钮,Enter 选择。"},
{ID: "186", Name: "SymbolBadge", ZhName: "代码徽标", Category: "Markets", Demo: marketsSymbolBadgeDemo, Code: `symbol := mujica.MarketSymbol{Code: "AAPL", Name: "Apple", Market: "NASDAQ", Type: "Stock"}
{ID: "186", Name: "SymbolBadge", ZhName: "代码徽标", Category: "Markets", Demo: marketsSymbolBadgeDemo, Code: `symbol := finance.MarketSymbol{Code: "AAPL", Name: "Apple", Market: "NASDAQ", Type: "Stock"}
view := func(c *ui.Context) {
mujica.Use(c, mujica.Settings{})
r := mujica.SymbolBadge(c, symbol, mujica.SymbolBadgeOptions{Clickable: true})
core.Use(c, core.Settings{})
r := finance.SymbolBadge(c, symbol, finance.SymbolBadgeOptions{Clickable: true})
if r.Changed() {
fmt.Println(symbol.Code)
}
@@ -68,42 +71,42 @@ view := func(c *ui.Context) {
ui.Render(view, 960, 440, 1)
`, Keys: "Clickable 开启时 Tab 聚焦,Enter/Space 激活。"},
{ID: "187", Name: "MarketHeatmap", ZhName: "市场热力图", Category: "Markets", Demo: marketsMarketHeatmapDemo, Code: `var path []string
root := mujica.HeatNode{Name: "Market", Children: []mujica.HeatNode{{Name: "Technology", Children: []mujica.HeatNode{{Name: "AAPL", Cap: 3000, Change: 1.2}, {Name: "MSFT", Cap: 2800, Change: -0.6}}}, {Name: "Energy", Cap: 450, Change: -2.1}}}
root := chart.HeatNode{Name: "Market", Children: []chart.HeatNode{{Name: "Technology", Children: []chart.HeatNode{{Name: "AAPL", Cap: 3000, Change: 1.2}, {Name: "MSFT", Cap: 2800, Change: -0.6}}}, {Name: "Energy", Cap: 450, Change: -2.1}}}
view := func(c *ui.Context) {
mujica.Use(c, mujica.Settings{})
r := mujica.MarketHeatmap(c, &path, root, mujica.MarketHeatmapOptions{Frame: mujica.ChartFrame{Label: "Market"}})
core.Use(c, core.Settings{})
r := chart.MarketHeatmap(c, &path, root, chart.MarketHeatmapOptions{Frame: chart.ChartFrame{Label: "Market"}})
if r.Changed() || r.ValueChanged() || r.Retried() {
fmt.Println(path, r.Point)
}
}
ui.Render(view, 960, 440, 1)
`, Keys: "Tab 聚焦图;方向键逐块读数;Enter 下钻;Backspace 返回;路径按钮可用 Tab。"},
{ID: "188", Name: "Screener", ZhName: "选股器", Category: "Markets", Demo: marketsScreenerDemo, Code: `var state mujica.ScreenerState
fields := []mujica.ScreenerField{{ID: "pe", Label: "P/E", Decimals: 1}}
rows := []mujica.ScreenerRow{{Symbol: "AAPL", Values: map[string]float64{"pe": 34}}, {Symbol: "XOM", Values: map[string]float64{"pe": 13}}}
{ID: "188", Name: "Screener", ZhName: "选股器", Category: "Markets", Demo: marketsScreenerDemo, Code: `var state finance.ScreenerState
fields := []finance.ScreenerField{{ID: "pe", Label: "P/E", Decimals: 1}}
rows := []finance.ScreenerRow{{Symbol: "AAPL", Values: map[string]float64{"pe": 34}}, {Symbol: "XOM", Values: map[string]float64{"pe": 13}}}
view := func(c *ui.Context) {
mujica.Use(c, mujica.Settings{})
r := mujica.Screener(c, &state, fields, rows, mujica.ScreenerOptions{})
core.Use(c, core.Settings{})
r := finance.Screener(c, &state, fields, rows, finance.ScreenerOptions{})
if r.Changed || r.Save || r.Table.Changed() || r.Table.Submitted() || r.Table.SortChanged() || r.Table.Retried() {
fmt.Println(state.Conditions)
}
}
ui.Render(view, 960, 440, 1)
`, Keys: "Tab 到达字段、比较、数值和保存;Select 方向键选择;NumberInput 方向键步进;表格方向键选择。"},
{ID: "189", Name: "MarketOverview", ZhName: "市场概览", Category: "Markets", Demo: marketsMarketOverviewDemo, Code: `indices := []mujica.IndexQuote{{Name: "S&P 500", Price: 5751.07, Change: 51.13, ChangePercent: 0.9, Series: []float64{5700, 5730, 5720, 5751}}}
{ID: "189", Name: "MarketOverview", ZhName: "市场概览", Category: "Markets", Demo: marketsMarketOverviewDemo, Code: `indices := []finance.IndexQuote{{Name: "S&P 500", Price: 5751.07, Change: 51.13, ChangePercent: 0.9, Series: []float64{5700, 5730, 5720, 5751}}}
view := func(c *ui.Context) {
mujica.Use(c, mujica.Settings{})
r := mujica.MarketOverview(c, indices, mujica.MarketOverviewOptions{Breadth: mujica.MarketBreadth{Advancing: 2100, Declining: 1400, Unchanged: 300}})
core.Use(c, core.Settings{})
r := finance.MarketOverview(c, indices, finance.MarketOverviewOptions{Breadth: finance.MarketBreadth{Advancing: 2100, Declining: 1400, Unchanged: 300}})
if r.Chosen >= 0 {
fmt.Println(indices[r.Chosen].Name)
}
}
ui.Render(view, 960, 440, 1)
`, Keys: "Tab 到达指数卡片;Enter/Space 选择。"},
{ID: "190", Name: "IndexCard", ZhName: "指数卡片", Category: "Markets", Demo: marketsIndexCardDemo, Code: `quote := mujica.IndexQuote{Name: "S&P 500", Price: 5751.07, Change: 51.13, ChangePercent: 0.9, Series: []float64{5700, 5730, 5720, 5751}}
{ID: "190", Name: "IndexCard", ZhName: "指数卡片", Category: "Markets", Demo: marketsIndexCardDemo, Code: `quote := finance.IndexQuote{Name: "S&P 500", Price: 5751.07, Change: 51.13, ChangePercent: 0.9, Series: []float64{5700, 5730, 5720, 5751}}
view := func(c *ui.Context) {
mujica.Use(c, mujica.Settings{})
r := mujica.IndexCard(c, quote, mujica.IndexCardOptions{Clickable: true})
core.Use(c, core.Settings{})
r := finance.IndexCard(c, quote, finance.IndexCardOptions{Clickable: true})
if r.Changed() {
fmt.Println(quote.Name)
}
@@ -112,46 +115,46 @@ ui.Render(view, 960, 440, 1)
`, Keys: "Tab 聚焦卡片或迷你图;Enter/Space 激活卡片,图表方向键读数。"},
{ID: "191", Name: "MarketStatus", ZhName: "市场状态", Category: "Markets", Demo: marketsMarketStatusDemo, Code: `now := time.Date(2026, 10, 5, 14, 30, 0, 0, time.UTC)
view := func(c *ui.Context) {
mujica.Use(c, mujica.Settings{})
mujica.MarketStatus(c, mujica.MarketOpen, mujica.MarketStatusOptions{Market: "NYSE", Now: now, Next: now.Add(5 * time.Hour), NextPhase: mujica.MarketAfterHours})
core.Use(c, core.Settings{})
finance.MarketStatus(c, finance.MarketOpen, finance.MarketStatusOptions{Market: "NYSE", Now: now, Next: now.Add(5 * time.Hour), NextPhase: finance.MarketAfterHours})
}
ui.Render(view, 960, 440, 1)
`, Keys: "展示组件。"},
{ID: "192", Name: "TradingSessionClock", ZhName: "交易时段时钟", Category: "Markets", Demo: marketsTradingSessionClockDemo, Code: `now := time.Date(2026, 10, 5, 14, 30, 0, 0, time.UTC)
markets := []mujica.SessionMarket{{Name: "NYSE", Location: time.FixedZone("EDT", -4*3600), Sessions: []mujica.TradingSession{{Phase: mujica.MarketOpen, Start: 570, End: 960}}}}
markets := []finance.SessionMarket{{Name: "NYSE", Location: time.FixedZone("EDT", -4*3600), Sessions: []finance.TradingSession{{Phase: finance.MarketOpen, Start: 570, End: 960}}}}
view := func(c *ui.Context) {
mujica.Use(c, mujica.Settings{})
mujica.TradingSessionClock(c, markets, mujica.TradingSessionClockOptions{Now: now})
core.Use(c, core.Settings{})
finance.TradingSessionClock(c, markets, finance.TradingSessionClockOptions{Now: now})
}
ui.Render(view, 960, 440, 1)
`, Keys: "展示组件。"},
{ID: "193", Name: "EconomicCalendar", ZhName: "经济日历", Category: "Markets", Demo: marketsEconomicCalendarDemo, Code: `var state mujica.EconomicCalendarState
rows := []mujica.EconomicEvent{{ID: "jobs", Country: "US", Title: "Payrolls", Time: time.Date(2026, 10, 5, 14, 30, 0, 0, time.UTC), Importance: 3, Forecast: "150K", Previous: "142K", Actual: "254K"}}
{ID: "193", Name: "EconomicCalendar", ZhName: "经济日历", Category: "Markets", Demo: marketsEconomicCalendarDemo, Code: `var state finance.EconomicCalendarState
rows := []finance.EconomicEvent{{ID: "jobs", Country: "US", Title: "Payrolls", Time: time.Date(2026, 10, 5, 14, 30, 0, 0, time.UTC), Importance: 3, Forecast: "150K", Previous: "142K", Actual: "254K"}}
view := func(c *ui.Context) {
mujica.Use(c, mujica.Settings{})
r := mujica.EconomicCalendar(c, &state, rows, mujica.EconomicCalendarOptions{})
core.Use(c, core.Settings{})
r := finance.EconomicCalendar(c, &state, rows, finance.EconomicCalendarOptions{})
if r.Changed() || r.Submitted() || r.SortChanged() || r.Retried() {
fmt.Println(state.Table.Selected())
}
}
ui.Render(view, 960, 440, 1)
`, Keys: "Tab 到达国家过滤、重要性和表格;方向键选择;Enter 提交行。"},
{ID: "194", Name: "EarningsCalendar", ZhName: "财报日历", Category: "Markets", Demo: marketsEarningsCalendarDemo, Code: `var state mujica.DataTableState[string]
rows := []mujica.EarningsEvent{{ID: "a", Symbol: "AAPL", Name: "Apple", Date: mujica.Date{Year: 2026, Month: 10, Day: 30}, Timing: mujica.EarningsAfterClose, EPSEstimate: 1.6, EPSActual: 1.64}}
{ID: "194", Name: "EarningsCalendar", ZhName: "财报日历", Category: "Markets", Demo: marketsEarningsCalendarDemo, Code: `var state data.DataTableState[string]
rows := []finance.EarningsEvent{{ID: "a", Symbol: "AAPL", Name: "Apple", Date: core.Date{Year: 2026, Month: 10, Day: 30}, Timing: finance.EarningsAfterClose, EPSEstimate: 1.6, EPSActual: 1.64}}
view := func(c *ui.Context) {
mujica.Use(c, mujica.Settings{})
r := mujica.EarningsCalendar(c, &state, rows, mujica.EarningsCalendarOptions{})
core.Use(c, core.Settings{})
r := finance.EarningsCalendar(c, &state, rows, finance.EarningsCalendarOptions{})
if r.Changed() || r.Submitted() || r.SortChanged() || r.Retried() {
fmt.Println(state.Selected())
}
}
ui.Render(view, 960, 440, 1)
`, Keys: "Tab 聚焦表格;方向键及 Home/End 选择;Enter 提交;表头支持键盘排序。"},
{ID: "195", Name: "NewsFeed", ZhName: "新闻流", Category: "Markets", Demo: marketsNewsFeedDemo, Code: `var state mujica.NewsFeedState
rows := []mujica.NewsItem{{ID: "a", Title: "Apple unveils new chips", Source: "Wire", Time: time.Date(2026, 10, 5, 14, 30, 0, 0, time.UTC), Symbols: []string{"AAPL"}}}
{ID: "195", Name: "NewsFeed", ZhName: "新闻流", Category: "Markets", Demo: marketsNewsFeedDemo, Code: `var state finance.NewsFeedState
rows := []finance.NewsItem{{ID: "a", Title: "Apple unveils new chips", Source: "Wire", Time: time.Date(2026, 10, 5, 14, 30, 0, 0, time.UTC), Symbols: []string{"AAPL"}}}
view := func(c *ui.Context) {
mujica.Use(c, mujica.Settings{})
r := mujica.NewsFeed(c, &state, rows, mujica.NewsFeedOptions{})
core.Use(c, core.Settings{})
r := finance.NewsFeed(c, &state, rows, finance.NewsFeedOptions{})
if r.Opened != "" || r.Symbol != "" || r.Retry {
fmt.Println(r.Opened, r.Symbol)
}
@@ -160,26 +163,26 @@ ui.Render(view, 960, 440, 1)
`, Keys: "Tab 聚焦列表或相关代码;上下选择;Enter 打开并标记已读。"},
{ID: "196", Name: "SentimentGauge", ZhName: "情绪仪表", Category: "Markets", Demo: marketsSentimentGaugeDemo, Code: `value := 72.0
view := func(c *ui.Context) {
mujica.Use(c, mujica.Settings{})
mujica.SentimentGauge(c, value, mujica.SentimentGaugeOptions{Label: "Fear & Greed"})
core.Use(c, core.Settings{})
finance.SentimentGauge(c, value, finance.SentimentGaugeOptions{Label: "Fear & Greed"})
}
ui.Render(view, 960, 440, 1)
`, Keys: "展示组件,数值与文字同时呈现。"},
{ID: "197", Name: "PortfolioSummary", ZhName: "资产总览", Category: "Markets", Demo: marketsPortfolioSummaryDemo, Code: `account := ""
accounts := []mujica.PortfolioAccount{{ID: "a", Name: "Brokerage", Total: 120000, DayPnL: 1250, TotalReturn: 20000, Cost: 100000}, {ID: "b", Name: "Retirement", Total: 80000, DayPnL: -300, TotalReturn: 5000, Cost: 75000}}
accounts := []finance.PortfolioAccount{{ID: "a", Name: "Brokerage", Total: 120000, DayPnL: 1250, TotalReturn: 20000, Cost: 100000}, {ID: "b", Name: "Retirement", Total: 80000, DayPnL: -300, TotalReturn: 5000, Cost: 75000}}
view := func(c *ui.Context) {
mujica.Use(c, mujica.Settings{})
r := mujica.PortfolioSummary(c, &account, accounts, mujica.PortfolioSummaryOptions{})
core.Use(c, core.Settings{})
r := finance.PortfolioSummary(c, &account, accounts, finance.PortfolioSummaryOptions{})
if r.Changed() {
fmt.Println(account)
}
}
ui.Render(view, 960, 440, 1)
`, Keys: "Tab 聚焦账户组;方向键切换账户。"},
{ID: "198", Name: "AssetAllocationChart", ZhName: "资产配置图", Category: "Markets", Demo: marketsAssetAllocationChartDemo, Code: `items := []mujica.AllocationItem{{Name: "Stocks", Value: 60, Target: 50}, {Name: "Bonds", Value: 30, Target: 40}, {Name: "Cash", Value: 10, Target: 10}}
{ID: "198", Name: "AssetAllocationChart", ZhName: "资产配置图", Category: "Markets", Demo: marketsAssetAllocationChartDemo, Code: `items := []finance.AllocationItem{{Name: "Stocks", Value: 60, Target: 50}, {Name: "Bonds", Value: 30, Target: 40}, {Name: "Cash", Value: 10, Target: 10}}
view := func(c *ui.Context) {
mujica.Use(c, mujica.Settings{})
r := mujica.AssetAllocationChart(c, items, mujica.AssetAllocationChartOptions{Frame: mujica.ChartFrame{Label: "Allocation"}})
core.Use(c, core.Settings{})
r := finance.AssetAllocationChart(c, items, finance.AssetAllocationChartOptions{Frame: chart.ChartFrame{Label: "Allocation"}})
if r.Changed() || r.Retried() {
fmt.Println(r.Point)
}
@@ -190,8 +193,8 @@ ui.Render(view, 960, 440, 1)
benchmark := []float64{100, 105, 108, 112}
times := []time.Time{time.Date(2026, 1, 1, 0, 0, 0, 0, time.UTC), time.Date(2026, 2, 1, 0, 0, 0, 0, time.UTC), time.Date(2026, 3, 1, 0, 0, 0, 0, time.UTC), time.Date(2026, 4, 1, 0, 0, 0, 0, time.UTC)}
view := func(c *ui.Context) {
mujica.Use(c, mujica.Settings{})
r := mujica.PerformanceChart(c, values, mujica.PerformanceChartOptions{Frame: mujica.ChartFrame{Label: "Performance"}, Times: times, Benchmark: benchmark})
core.Use(c, core.Settings{})
r := finance.PerformanceChart(c, values, finance.PerformanceChartOptions{Frame: chart.ChartFrame{Label: "Performance"}, Times: times, Benchmark: benchmark})
if r.Changed() || r.Retried() {
fmt.Println(r.Point)
}
@@ -201,12 +204,12 @@ view := func(c *ui.Context) {
}
ui.Render(view, 960, 440, 1)
`, Keys: "Tab 聚焦图和图例;方向键读数;Space 切换序列。"},
{ID: "200", Name: "CurrencyConverter", ZhName: "货币换算", Category: "Markets", Demo: marketsCurrencyConverterDemo, Code: `state := mujica.CurrencyConverterState{Amount: 100, From: "USD", To: "JPY"}
currencies := []mujica.Currency{{Code: "USD", Name: "Dollar", Decimals: 2}, {Code: "JPY", Name: "Yen", Decimals: 0}}
{ID: "200", Name: "CurrencyConverter", ZhName: "货币换算", Category: "Markets", Demo: marketsCurrencyConverterDemo, Code: `state := finance.CurrencyConverterState{Amount: 100, From: "USD", To: "JPY"}
currencies := []finance.Currency{{Code: "USD", Name: "Dollar", Decimals: 2}, {Code: "JPY", Name: "Yen", Decimals: 0}}
rate := 149.37
view := func(c *ui.Context) {
mujica.Use(c, mujica.Settings{})
r := mujica.CurrencyConverter(c, &state, currencies, mujica.CurrencyConverterOptions{Rate: rate})
core.Use(c, core.Settings{})
r := finance.CurrencyConverter(c, &state, currencies, finance.CurrencyConverterOptions{Rate: rate})
if r.PairChanged {
rate = 0
fmt.Println(state.From, state.To)
@@ -216,18 +219,18 @@ ui.Render(view, 960, 440, 1)
`, Keys: "Tab 到达金额、币种和互换;方向键步进、选择;Enter/Space 互换。"},
}
func marketsDataState(c *ui.Context, id string, log func(string)) (mujica.DataStatus, bool, string) {
func marketsDataState(c *ui.Context, id string, log func(string)) (data.DataStatus, bool, string) {
state := ui.Local(c.Root(), id+".status", func() int { return 0 })
if mujica.RadioGroup(c, state, []mujica.RadioItem[int]{{Value: 0, Label: "Ready"}, {Value: 1, Label: "Loading"}, {Value: 2, Label: "Empty"}, {Value: 3, Label: "Error"}}, mujica.RadioGroupOptions{Horizontal: true}).Changed() {
if input.RadioGroup(c, state, []input.RadioItem[int]{{Value: 0, Label: "Ready"}, {Value: 1, Label: "Loading"}, {Value: 2, Label: "Empty"}, {Value: 3, Label: "Error"}}, input.RadioGroupOptions{Horizontal: true}).Changed() {
log(fmt.Sprint("status ", *state))
}
switch *state {
case 1:
return mujica.DataLoading, false, ""
return data.DataLoading, false, ""
case 2:
return mujica.DataReady, true, ""
return data.DataReady, true, ""
case 3:
return mujica.DataFailed, false, "Sample failed"
return data.DataFailed, false, "Sample failed"
}
return mujica.DataReady, false, ""
return data.DataReady, false, ""
}
+44 -39
View File
@@ -2,32 +2,37 @@ package main
import (
"fmt"
"github.com/egoist/mygo/ui"
mujica "mujicaui"
"slices"
"strings"
"github.com/egoist/mygo/ui"
"github.com/ZacharyZhang-NY/MujicaUI/chart"
"github.com/ZacharyZhang-NY/MujicaUI/data"
"github.com/ZacharyZhang-NY/MujicaUI/finance"
"github.com/ZacharyZhang-NY/MujicaUI/input"
)
func marketsOptionChainDemo(c *ui.Context, log func(string)) {
disabled := ui.Local(c.Root(), "181.disabled", func() bool { return false })
if mujica.Checkbox(c, disabled, "Disabled", mujica.CheckboxOptions{}).Changed() {
if input.Checkbox(c, disabled, "Disabled", input.CheckboxOptions{}).Changed() {
log(fmt.Sprint("disabled ", *disabled))
}
state := ui.Local(c.Root(), "181.state", func() mujica.OptionChainState { return mujica.OptionChainState{} })
rows := *ui.Local(c.Root(), "markets.OptionChain.rows.pages_markets_1", func() []mujica.OptionStrike {
return []mujica.OptionStrike{{Strike: 100, Call: mujica.OptionQuote{Bid: 3, Ask: 3.2, Last: 3.1}, Put: mujica.OptionQuote{Bid: 2, Ask: 2.2, Last: 2.1}}}
state := ui.Local(c.Root(), "181.state", func() finance.OptionChainState { return finance.OptionChainState{} })
rows := *ui.Local(c.Root(), "markets.OptionChain.rows.pages_markets_1", func() []finance.OptionStrike {
return []finance.OptionStrike{{Strike: 100, Call: finance.OptionQuote{Bid: 3, Ask: 3.2, Last: 3.1}, Put: finance.OptionQuote{Bid: 2, Ask: 2.2, Last: 2.1}}}
})
precision := ui.Local(c.Root(), "markets.OptionChain.precision", func() float64 { return 2 })
if mujica.Slider(c, precision, mujica.SliderOptions{Min: 1, Max: 8, Step: 1, Label: "Decimals", ShowValue: true}).Changed() {
if input.Slider(c, precision, input.SliderOptions{Min: 1, Max: 8, Step: 1, Label: "Decimals", ShowValue: true}).Changed() {
log(fmt.Sprint("decimals ", *precision))
}
spot := ui.Local(c.Root(), "181.spot", func() float64 { return 100.4 })
if mujica.Slider(c, spot, mujica.SliderOptions{Min: 95, Max: 105, Step: 0.1, Label: "Spot", ShowValue: true}).Changed() {
if input.Slider(c, spot, input.SliderOptions{Min: 95, Max: 105, Step: 0.1, Label: "Spot", ShowValue: true}).Changed() {
log(fmt.Sprint("spot ", *spot))
}
ui.Column(c).Disabled(*disabled).MinHeight(0).Height(400).Children(func() {
r := mujica.OptionChain(c, state, []string{"Oct 17", "Nov 21"}, rows, mujica.OptionChainOptions{Decimals: int(*precision), Spot: *spot})
r := finance.OptionChain(c, state, []string{"Oct 17", "Nov 21"}, rows, finance.OptionChainOptions{Decimals: int(*precision), Spot: *spot})
if r.Changed || r.Submitted || r.ExpiryChanged {
log(fmt.Sprint(state.Expiry, state.Strike, state.Side))
}
@@ -36,52 +41,52 @@ func marketsOptionChainDemo(c *ui.Context, log func(string)) {
func marketsGreeksTableDemo(c *ui.Context, log func(string)) {
disabled := ui.Local(c.Root(), "182.disabled", func() bool { return false })
if mujica.Checkbox(c, disabled, "Disabled", mujica.CheckboxOptions{}).Changed() {
if input.Checkbox(c, disabled, "Disabled", input.CheckboxOptions{}).Changed() {
log(fmt.Sprint("disabled ", *disabled))
}
rows := *ui.Local(c.Root(), "markets.GreeksTable.rows.pages_markets_1", func() []mujica.GreeksRow {
return []mujica.GreeksRow{{Label: "AAPL 100 C", Greeks: mujica.OptionGreeks{Delta: 0.5, Gamma: 0.03, Theta: -0.1, Vega: 0.2, Rho: 0.05}}}
rows := *ui.Local(c.Root(), "markets.GreeksTable.rows.pages_markets_1", func() []finance.GreeksRow {
return []finance.GreeksRow{{Label: "AAPL 100 C", Greeks: finance.OptionGreeks{Delta: 0.5, Gamma: 0.03, Theta: -0.1, Vega: 0.2, Rho: 0.05}}}
})
precision := ui.Local(c.Root(), "markets.GreeksTable.precision", func() float64 { return 2 })
if mujica.Slider(c, precision, mujica.SliderOptions{Min: 1, Max: 8, Step: 1, Label: "Decimals", ShowValue: true}).Changed() {
if input.Slider(c, precision, input.SliderOptions{Min: 1, Max: 8, Step: 1, Label: "Decimals", ShowValue: true}).Changed() {
log(fmt.Sprint("decimals ", *precision))
}
notes := ui.Local(c.Root(), "182.notes", func() bool { return true })
if mujica.Checkbox(c, notes, "Notes", mujica.CheckboxOptions{}).Changed() {
if input.Checkbox(c, notes, "Notes", input.CheckboxOptions{}).Changed() {
log(fmt.Sprint("notes ", *notes))
}
ui.Column(c).Disabled(*disabled).MinHeight(0).Height(400).Children(func() {
mujica.GreeksTable(c, rows, mujica.GreeksTableOptions{Decimals: int(*precision), Notes: *notes})
finance.GreeksTable(c, rows, finance.GreeksTableOptions{Decimals: int(*precision), Notes: *notes})
})
}
func marketsPayoffDiagramDemo(c *ui.Context, log func(string)) {
disabled := ui.Local(c.Root(), "183.disabled", func() bool { return false })
if mujica.Checkbox(c, disabled, "Disabled", mujica.CheckboxOptions{}).Changed() {
if input.Checkbox(c, disabled, "Disabled", input.CheckboxOptions{}).Changed() {
log(fmt.Sprint("disabled ", *disabled))
}
legs := *ui.Local(c.Root(), "markets.PayoffDiagram.legs.pages_markets_1", func() []mujica.OptionLeg {
return []mujica.OptionLeg{{Kind: mujica.LegCall, Strike: 100, Premium: 3, Quantity: 1}}
legs := *ui.Local(c.Root(), "markets.PayoffDiagram.legs.pages_markets_1", func() []finance.OptionLeg {
return []finance.OptionLeg{{Kind: finance.LegCall, Strike: 100, Premium: 3, Quantity: 1}}
})
status := ui.Local(c.Root(), "183.status", func() int { return 0 })
if mujica.RadioGroup(c, status, []mujica.RadioItem[int]{{Value: 0, Label: "Ready"}, {Value: 1, Label: "Loading"}, {Value: 2, Label: "Empty"}, {Value: 3, Label: "Error"}}, mujica.RadioGroupOptions{Horizontal: true}).Changed() {
if input.RadioGroup(c, status, []input.RadioItem[int]{{Value: 0, Label: "Ready"}, {Value: 1, Label: "Loading"}, {Value: 2, Label: "Empty"}, {Value: 3, Label: "Error"}}, input.RadioGroupOptions{Horizontal: true}).Changed() {
log(fmt.Sprint("status ", *status))
}
frame := mujica.ChartFrame{Label: "PayoffDiagram"}
frame := chart.ChartFrame{Label: "PayoffDiagram"}
if *status == 1 {
frame.Status = mujica.DataLoading
frame.Status = data.DataLoading
}
if *status == 3 {
frame.Status = mujica.DataFailed
frame.Status = data.DataFailed
frame.Error = "Sample failed"
}
if *status == 2 {
legs = nil
}
ui.Column(c).Disabled(*disabled).MinHeight(0).Height(400).Children(func() {
r := mujica.PayoffDiagram(c, legs, mujica.PayoffDiagramOptions{Frame: frame, Spot: 103})
r := finance.PayoffDiagram(c, legs, finance.PayoffDiagramOptions{Frame: frame, Spot: 103})
if r.Changed() || r.Retried() {
log(fmt.Sprint(r.Point))
}
@@ -90,33 +95,33 @@ func marketsPayoffDiagramDemo(c *ui.Context, log func(string)) {
func marketsWatchlistDemo(c *ui.Context, log func(string)) {
disabled := ui.Local(c.Root(), "184.disabled", func() bool { return false })
if mujica.Checkbox(c, disabled, "Disabled", mujica.CheckboxOptions{}).Changed() {
if input.Checkbox(c, disabled, "Disabled", input.CheckboxOptions{}).Changed() {
log(fmt.Sprint("disabled ", *disabled))
}
state := ui.Local(c.Root(), "184.state", func() mujica.WatchlistState { return mujica.WatchlistState{} })
groups := *ui.Local(c.Root(), "markets.Watchlist.groups.pages_markets_1", func() []mujica.WatchGroup {
return []mujica.WatchGroup{{Name: "Technology", Items: []mujica.WatchItem{{Symbol: "AAPL", Name: "Apple", Price: 227.5, Change: 2.7, ChangePercent: 1.2}, {Symbol: "MSFT", Name: "Microsoft", Price: 418.2, Change: -2.5, ChangePercent: -0.6}}}, {Name: "Energy", Items: []mujica.WatchItem{{Symbol: "XOM", Price: 117.4}}}}
state := ui.Local(c.Root(), "184.state", func() finance.WatchlistState { return finance.WatchlistState{} })
groups := *ui.Local(c.Root(), "markets.Watchlist.groups.pages_markets_1", func() []finance.WatchGroup {
return []finance.WatchGroup{{Name: "Technology", Items: []finance.WatchItem{{Symbol: "AAPL", Name: "Apple", Price: 227.5, Change: 2.7, ChangePercent: 1.2}, {Symbol: "MSFT", Name: "Microsoft", Price: 418.2, Change: -2.5, ChangePercent: -0.6}}}, {Name: "Energy", Items: []finance.WatchItem{{Symbol: "XOM", Price: 117.4}}}}
})
precision := ui.Local(c.Root(), "markets.Watchlist.precision", func() float64 { return 2 })
if mujica.Slider(c, precision, mujica.SliderOptions{Min: 1, Max: 8, Step: 1, Label: "Decimals", ShowValue: true}).Changed() {
if input.Slider(c, precision, input.SliderOptions{Min: 1, Max: 8, Step: 1, Label: "Decimals", ShowValue: true}).Changed() {
log(fmt.Sprint("decimals ", *precision))
}
if mujica.Button(c, "Refresh quotes", mujica.ButtonOptions{Variant: mujica.Secondary}).Clicked() {
if input.Button(c, "Refresh quotes", input.ButtonOptions{Variant: input.Secondary}).Clicked() {
groups[0].Items = slices.Clone(groups[0].Items)
groups[0].Items[0].Price += 0.1
log("quotes refreshed")
}
beforeGroup, beforeColumns := state.Group, fmt.Sprint(state.Hidden)
ui.Column(c).Disabled(*disabled).MinHeight(0).Height(400).Children(func() {
r := mujica.Watchlist(c, state, groups, mujica.WatchlistOptions{Decimals: int(*precision)})
r := finance.Watchlist(c, state, groups, finance.WatchlistOptions{Decimals: int(*precision)})
if r.Changed || r.Submitted || r.Moved {
log(fmt.Sprint(state.Selected, r.Rows, r.To))
}
if r.Moved {
items := groups[state.Group].Items
var moving []mujica.WatchItem
var rest []mujica.WatchItem
var moving []finance.WatchItem
var rest []finance.WatchItem
at := r.To
for j, item := range items {
if slices.Contains(r.Rows, j) {
@@ -138,26 +143,26 @@ func marketsWatchlistDemo(c *ui.Context, log func(string)) {
func marketsSymbolSearchDemo(c *ui.Context, log func(string)) {
disabled := ui.Local(c.Root(), "185.disabled", func() bool { return false })
if mujica.Checkbox(c, disabled, "Disabled", mujica.CheckboxOptions{}).Changed() {
if input.Checkbox(c, disabled, "Disabled", input.CheckboxOptions{}).Changed() {
log(fmt.Sprint("disabled ", *disabled))
}
state := ui.Local(c.Root(), "185.state", func() mujica.SymbolSearchState { return mujica.SymbolSearchState{} })
rows := *ui.Local(c.Root(), "markets.SymbolSearch.rows.pages_markets_1", func() []mujica.MarketSymbol {
return []mujica.MarketSymbol{{Code: "AAPL", Name: "Apple", Market: "NASDAQ", Type: "Stock"}}
state := ui.Local(c.Root(), "185.state", func() finance.SymbolSearchState { return finance.SymbolSearchState{} })
rows := *ui.Local(c.Root(), "markets.SymbolSearch.rows.pages_markets_1", func() []finance.MarketSymbol {
return []finance.MarketSymbol{{Code: "AAPL", Name: "Apple", Market: "NASDAQ", Type: "Stock"}}
})
status, empty, err := marketsDataState(c, "185", log)
if empty || status != mujica.DataReady {
if empty || status != data.DataReady {
rows = nil
}
results := []mujica.MarketSymbol{}
results := []finance.MarketSymbol{}
for _, row := range rows {
if (state.Market == "" || state.Market == row.Market) && strings.Contains(strings.ToUpper(row.Code+" "+row.Name), strings.ToUpper(state.Query)) {
results = append(results, row)
}
}
ui.Column(c).Disabled(*disabled).MinHeight(0).Height(400).Children(func() {
r := mujica.SymbolSearch(c, state, results, mujica.SymbolSearchOptions{Loading: status == mujica.DataLoading, Error: err, Markets: []string{"NASDAQ", "NYSE"}, Recent: rows})
r := finance.SymbolSearch(c, state, results, finance.SymbolSearchOptions{Loading: status == data.DataLoading, Error: err, Markets: []string{"NASDAQ", "NYSE"}, Recent: rows})
if r.Changed || r.Picked || r.Retry {
log(fmt.Sprint(state.Query, state.Market, r.Chosen.Code))
}
+38 -33
View File
@@ -2,26 +2,31 @@ package main
import (
"fmt"
"github.com/egoist/mygo/ui"
mujica "mujicaui"
"github.com/ZacharyZhang-NY/MujicaUI/chart"
"github.com/ZacharyZhang-NY/MujicaUI/data"
"github.com/ZacharyZhang-NY/MujicaUI/finance"
"github.com/ZacharyZhang-NY/MujicaUI/input"
)
func marketsSymbolBadgeDemo(c *ui.Context, log func(string)) {
disabled := ui.Local(c.Root(), "186.disabled", func() bool { return false })
if mujica.Checkbox(c, disabled, "Disabled", mujica.CheckboxOptions{}).Changed() {
if input.Checkbox(c, disabled, "Disabled", input.CheckboxOptions{}).Changed() {
log(fmt.Sprint("disabled ", *disabled))
}
symbol := *ui.Local(c.Root(), "markets.SymbolBadge.symbol.pages_markets_2", func() mujica.MarketSymbol {
return mujica.MarketSymbol{Code: "AAPL", Name: "Apple", Market: "NASDAQ", Type: "Stock"}
symbol := *ui.Local(c.Root(), "markets.SymbolBadge.symbol.pages_markets_2", func() finance.MarketSymbol {
return finance.MarketSymbol{Code: "AAPL", Name: "Apple", Market: "NASDAQ", Type: "Stock"}
})
kind := ui.Local(c.Root(), "186.kind", func() string { return "Stock" })
if mujica.RadioGroup(c, kind, []mujica.RadioItem[string]{{Value: "Stock", Label: "Stock"}, {Value: "ETF", Label: "ETF"}}, mujica.RadioGroupOptions{Horizontal: true}).Changed() {
if input.RadioGroup(c, kind, []input.RadioItem[string]{{Value: "Stock", Label: "Stock"}, {Value: "ETF", Label: "ETF"}}, input.RadioGroupOptions{Horizontal: true}).Changed() {
log(*kind)
}
symbol.Type = *kind
ui.Column(c).Disabled(*disabled).MinHeight(0).Height(400).Children(func() {
r := mujica.SymbolBadge(c, symbol, mujica.SymbolBadgeOptions{Clickable: true})
r := finance.SymbolBadge(c, symbol, finance.SymbolBadgeOptions{Clickable: true})
if r.Changed() {
log(fmt.Sprint(symbol.Code))
}
@@ -30,24 +35,24 @@ func marketsSymbolBadgeDemo(c *ui.Context, log func(string)) {
func marketsMarketHeatmapDemo(c *ui.Context, log func(string)) {
disabled := ui.Local(c.Root(), "187.disabled", func() bool { return false })
if mujica.Checkbox(c, disabled, "Disabled", mujica.CheckboxOptions{}).Changed() {
if input.Checkbox(c, disabled, "Disabled", input.CheckboxOptions{}).Changed() {
log(fmt.Sprint("disabled ", *disabled))
}
path := ui.Local(c.Root(), "187.path", func() []string { return []string{} })
root := *ui.Local(c.Root(), "markets.MarketHeatmap.root.pages_markets_2", func() mujica.HeatNode {
return mujica.HeatNode{Name: "Market", Children: []mujica.HeatNode{{Name: "Technology", Children: []mujica.HeatNode{{Name: "AAPL", Cap: 3000, Change: 1.2}, {Name: "MSFT", Cap: 2800, Change: -0.6}}}, {Name: "Energy", Cap: 450, Change: -2.1}}}
root := *ui.Local(c.Root(), "markets.MarketHeatmap.root.pages_markets_2", func() chart.HeatNode {
return chart.HeatNode{Name: "Market", Children: []chart.HeatNode{{Name: "Technology", Children: []chart.HeatNode{{Name: "AAPL", Cap: 3000, Change: 1.2}, {Name: "MSFT", Cap: 2800, Change: -0.6}}}, {Name: "Energy", Cap: 450, Change: -2.1}}}
})
status := ui.Local(c.Root(), "187.status", func() int { return 0 })
if mujica.RadioGroup(c, status, []mujica.RadioItem[int]{{Value: 0, Label: "Ready"}, {Value: 1, Label: "Loading"}, {Value: 2, Label: "Empty"}, {Value: 3, Label: "Error"}}, mujica.RadioGroupOptions{Horizontal: true}).Changed() {
if input.RadioGroup(c, status, []input.RadioItem[int]{{Value: 0, Label: "Ready"}, {Value: 1, Label: "Loading"}, {Value: 2, Label: "Empty"}, {Value: 3, Label: "Error"}}, input.RadioGroupOptions{Horizontal: true}).Changed() {
log(fmt.Sprint("status ", *status))
}
frame := mujica.ChartFrame{Label: "MarketHeatmap"}
frame := chart.ChartFrame{Label: "MarketHeatmap"}
if *status == 1 {
frame.Status = mujica.DataLoading
frame.Status = data.DataLoading
}
if *status == 3 {
frame.Status = mujica.DataFailed
frame.Status = data.DataFailed
frame.Error = "Sample failed"
}
if *status == 2 {
@@ -58,11 +63,11 @@ func marketsMarketHeatmapDemo(c *ui.Context, log func(string)) {
}
}
scale := ui.Local(c.Root(), "187.scale", func() float64 { return 3 })
if mujica.Slider(c, scale, mujica.SliderOptions{Min: 1, Max: 10, Step: 1, Label: "Color scale", ShowValue: true}).Changed() {
if input.Slider(c, scale, input.SliderOptions{Min: 1, Max: 10, Step: 1, Label: "Color scale", ShowValue: true}).Changed() {
log(fmt.Sprint(*scale))
}
ui.Column(c).Disabled(*disabled).MinHeight(0).Height(400).Children(func() {
r := mujica.MarketHeatmap(c, path, root, mujica.MarketHeatmapOptions{Frame: frame, Scale: *scale})
r := chart.MarketHeatmap(c, path, root, chart.MarketHeatmapOptions{Frame: frame, Scale: *scale})
if r.Changed() || r.ValueChanged() || r.Retried() {
log(fmt.Sprint(path, r.Point))
}
@@ -71,21 +76,21 @@ func marketsMarketHeatmapDemo(c *ui.Context, log func(string)) {
func marketsScreenerDemo(c *ui.Context, log func(string)) {
disabled := ui.Local(c.Root(), "188.disabled", func() bool { return false })
if mujica.Checkbox(c, disabled, "Disabled", mujica.CheckboxOptions{}).Changed() {
if input.Checkbox(c, disabled, "Disabled", input.CheckboxOptions{}).Changed() {
log(fmt.Sprint("disabled ", *disabled))
}
state := ui.Local(c.Root(), "188.state", func() mujica.ScreenerState { return mujica.ScreenerState{} })
fields := *ui.Local(c.Root(), "markets.Screener.fields.pages_markets_2", func() []mujica.ScreenerField { return []mujica.ScreenerField{{ID: "pe", Label: "P/E", Decimals: 1}} })
rows := *ui.Local(c.Root(), "markets.Screener.rows.pages_markets_2", func() []mujica.ScreenerRow {
return []mujica.ScreenerRow{{Symbol: "AAPL", Values: map[string]float64{"pe": 34}}, {Symbol: "XOM", Values: map[string]float64{"pe": 13}}}
state := ui.Local(c.Root(), "188.state", func() finance.ScreenerState { return finance.ScreenerState{} })
fields := *ui.Local(c.Root(), "markets.Screener.fields.pages_markets_2", func() []finance.ScreenerField { return []finance.ScreenerField{{ID: "pe", Label: "P/E", Decimals: 1}} })
rows := *ui.Local(c.Root(), "markets.Screener.rows.pages_markets_2", func() []finance.ScreenerRow {
return []finance.ScreenerRow{{Symbol: "AAPL", Values: map[string]float64{"pe": 34}}, {Symbol: "XOM", Values: map[string]float64{"pe": 13}}}
})
status, empty, err := marketsDataState(c, "188", log)
if empty || status != mujica.DataReady {
if empty || status != data.DataReady {
rows = nil
}
ui.Column(c).Disabled(*disabled).MinHeight(0).Height(400).Children(func() {
r := mujica.Screener(c, state, fields, rows, mujica.ScreenerOptions{Status: status, Error: err})
r := finance.Screener(c, state, fields, rows, finance.ScreenerOptions{Status: status, Error: err})
if r.Changed || r.Save || r.Table.Changed() || r.Table.Submitted() || r.Table.SortChanged() || r.Table.Retried() {
log(fmt.Sprint(state.Conditions))
}
@@ -94,19 +99,19 @@ func marketsScreenerDemo(c *ui.Context, log func(string)) {
func marketsMarketOverviewDemo(c *ui.Context, log func(string)) {
disabled := ui.Local(c.Root(), "189.disabled", func() bool { return false })
if mujica.Checkbox(c, disabled, "Disabled", mujica.CheckboxOptions{}).Changed() {
if input.Checkbox(c, disabled, "Disabled", input.CheckboxOptions{}).Changed() {
log(fmt.Sprint("disabled ", *disabled))
}
indices := *ui.Local(c.Root(), "markets.MarketOverview.indices.pages_markets_2", func() []mujica.IndexQuote {
return []mujica.IndexQuote{{Name: "S&P 500", Price: 5751.07, Change: 51.13, ChangePercent: 0.9, Series: []float64{5700, 5730, 5720, 5751}}}
indices := *ui.Local(c.Root(), "markets.MarketOverview.indices.pages_markets_2", func() []finance.IndexQuote {
return []finance.IndexQuote{{Name: "S&P 500", Price: 5751.07, Change: 51.13, ChangePercent: 0.9, Series: []float64{5700, 5730, 5720, 5751}}}
})
advancing := ui.Local(c.Root(), "189.advancing", func() float64 { return 2100 })
if mujica.Slider(c, advancing, mujica.SliderOptions{Min: 0, Max: 4000, Step: 100, Label: "Advancing", ShowValue: true}).Changed() {
if input.Slider(c, advancing, input.SliderOptions{Min: 0, Max: 4000, Step: 100, Label: "Advancing", ShowValue: true}).Changed() {
log(fmt.Sprint(*advancing))
}
ui.Column(c).Disabled(*disabled).MinHeight(0).Height(400).Children(func() {
r := mujica.MarketOverview(c, indices, mujica.MarketOverviewOptions{Breadth: mujica.MarketBreadth{Advancing: int(*advancing), Declining: 1400, Unchanged: 300}})
r := finance.MarketOverview(c, indices, finance.MarketOverviewOptions{Breadth: finance.MarketBreadth{Advancing: int(*advancing), Declining: 1400, Unchanged: 300}})
if r.Chosen >= 0 {
log(fmt.Sprint(indices[r.Chosen].Name))
}
@@ -115,24 +120,24 @@ func marketsMarketOverviewDemo(c *ui.Context, log func(string)) {
func marketsIndexCardDemo(c *ui.Context, log func(string)) {
disabled := ui.Local(c.Root(), "190.disabled", func() bool { return false })
if mujica.Checkbox(c, disabled, "Disabled", mujica.CheckboxOptions{}).Changed() {
if input.Checkbox(c, disabled, "Disabled", input.CheckboxOptions{}).Changed() {
log(fmt.Sprint("disabled ", *disabled))
}
quote := *ui.Local(c.Root(), "markets.IndexCard.quote.pages_markets_2", func() mujica.IndexQuote {
return mujica.IndexQuote{Name: "S&P 500", Price: 5751.07, Change: 51.13, ChangePercent: 0.9, Series: []float64{5700, 5730, 5720, 5751}}
quote := *ui.Local(c.Root(), "markets.IndexCard.quote.pages_markets_2", func() finance.IndexQuote {
return finance.IndexQuote{Name: "S&P 500", Price: 5751.07, Change: 51.13, ChangePercent: 0.9, Series: []float64{5700, 5730, 5720, 5751}}
})
precision := ui.Local(c.Root(), "markets.IndexCard.precision", func() float64 { return 2 })
if mujica.Slider(c, precision, mujica.SliderOptions{Min: 1, Max: 8, Step: 1, Label: "Decimals", ShowValue: true}).Changed() {
if input.Slider(c, precision, input.SliderOptions{Min: 1, Max: 8, Step: 1, Label: "Decimals", ShowValue: true}).Changed() {
log(fmt.Sprint("decimals ", *precision))
}
price := ui.Local(c.Root(), "190.price", func() float64 { return 5751.07 })
if mujica.NumberInput(c, price, mujica.NumberInputOptions{Min: 1, Max: 10000, Step: 0.01, Precision: 2, Label: "Price"}).Changed() {
if input.NumberInput(c, price, input.NumberInputOptions{Min: 1, Max: 10000, Step: 0.01, Precision: 2, Label: "Price"}).Changed() {
log(fmt.Sprint(*price))
}
quote.Price = *price
ui.Column(c).Disabled(*disabled).MinHeight(0).Height(400).Children(func() {
r := mujica.IndexCard(c, quote, mujica.IndexCardOptions{Decimals: int(*precision), Clickable: true})
r := finance.IndexCard(c, quote, finance.IndexCardOptions{Decimals: int(*precision), Clickable: true})
if r.Changed() {
log(fmt.Sprint(quote.Name))
}
+35 -30
View File
@@ -2,69 +2,74 @@ package main
import (
"fmt"
"github.com/egoist/mygo/ui"
mujica "mujicaui"
"time"
"github.com/egoist/mygo/ui"
"github.com/ZacharyZhang-NY/MujicaUI/core"
"github.com/ZacharyZhang-NY/MujicaUI/data"
"github.com/ZacharyZhang-NY/MujicaUI/finance"
"github.com/ZacharyZhang-NY/MujicaUI/input"
)
func marketsMarketStatusDemo(c *ui.Context, log func(string)) {
disabled := ui.Local(c.Root(), "191.disabled", func() bool { return false })
if mujica.Checkbox(c, disabled, "Disabled", mujica.CheckboxOptions{}).Changed() {
if input.Checkbox(c, disabled, "Disabled", input.CheckboxOptions{}).Changed() {
log(fmt.Sprint("disabled ", *disabled))
}
now := time.Date(2026, 10, 5, 14, 30, 0, 0, time.UTC)
phase := ui.Local(c.Root(), "191.phase", func() mujica.MarketPhase { return mujica.MarketOpen })
if mujica.RadioGroup(c, phase, []mujica.RadioItem[mujica.MarketPhase]{{Value: mujica.MarketClosed, Label: "Closed"}, {Value: mujica.MarketPreMarket, Label: "Pre-market"}, {Value: mujica.MarketOpen, Label: "Open"}, {Value: mujica.MarketAfterHours, Label: "After hours"}, {Value: mujica.MarketHoliday, Label: "Holiday"}}, mujica.RadioGroupOptions{Horizontal: true}).Changed() {
phase := ui.Local(c.Root(), "191.phase", func() finance.MarketPhase { return finance.MarketOpen })
if input.RadioGroup(c, phase, []input.RadioItem[finance.MarketPhase]{{Value: finance.MarketClosed, Label: "Closed"}, {Value: finance.MarketPreMarket, Label: "Pre-market"}, {Value: finance.MarketOpen, Label: "Open"}, {Value: finance.MarketAfterHours, Label: "After hours"}, {Value: finance.MarketHoliday, Label: "Holiday"}}, input.RadioGroupOptions{Horizontal: true}).Changed() {
log(fmt.Sprint(*phase))
}
ui.Column(c).Disabled(*disabled).MinHeight(0).Height(400).Children(func() {
mujica.MarketStatus(c, *phase, mujica.MarketStatusOptions{Market: "NYSE", Now: now, Next: now.Add(5 * time.Hour), NextPhase: mujica.MarketAfterHours})
finance.MarketStatus(c, *phase, finance.MarketStatusOptions{Market: "NYSE", Now: now, Next: now.Add(5 * time.Hour), NextPhase: finance.MarketAfterHours})
})
}
func marketsTradingSessionClockDemo(c *ui.Context, log func(string)) {
disabled := ui.Local(c.Root(), "192.disabled", func() bool { return false })
if mujica.Checkbox(c, disabled, "Disabled", mujica.CheckboxOptions{}).Changed() {
if input.Checkbox(c, disabled, "Disabled", input.CheckboxOptions{}).Changed() {
log(fmt.Sprint("disabled ", *disabled))
}
now := time.Date(2026, 10, 5, 14, 30, 0, 0, time.UTC)
markets := *ui.Local(c.Root(), "markets.TradingSessionClock.markets.pages_markets_3", func() []mujica.SessionMarket {
return []mujica.SessionMarket{{Name: "NYSE", Location: time.FixedZone("EDT", -4*3600), Sessions: []mujica.TradingSession{{Phase: mujica.MarketOpen, Start: 570, End: 960}}}}
markets := *ui.Local(c.Root(), "markets.TradingSessionClock.markets.pages_markets_3", func() []finance.SessionMarket {
return []finance.SessionMarket{{Name: "NYSE", Location: time.FixedZone("EDT", -4*3600), Sessions: []finance.TradingSession{{Phase: finance.MarketOpen, Start: 570, End: 960}}}}
})
holiday := ui.Local(c.Root(), "192.holiday", func() bool { return false })
if mujica.Checkbox(c, holiday, "Holiday", mujica.CheckboxOptions{}).Changed() {
if input.Checkbox(c, holiday, "Holiday", input.CheckboxOptions{}).Changed() {
log(fmt.Sprint(*holiday))
}
markets[0].Holiday = *holiday
minute := ui.Local(c.Root(), "192.minute", func() float64 { return 870 })
if mujica.Slider(c, minute, mujica.SliderOptions{Min: 0, Max: 1439, Step: 1, Label: "UTC minute", ShowValue: true}).Changed() {
if input.Slider(c, minute, input.SliderOptions{Min: 0, Max: 1439, Step: 1, Label: "UTC minute", ShowValue: true}).Changed() {
log(fmt.Sprint(*minute))
}
now = now.Truncate(24 * time.Hour).Add(time.Duration(*minute) * time.Minute)
ui.Column(c).Disabled(*disabled).MinHeight(0).Height(400).Children(func() {
mujica.TradingSessionClock(c, markets, mujica.TradingSessionClockOptions{Now: now})
finance.TradingSessionClock(c, markets, finance.TradingSessionClockOptions{Now: now})
})
}
func marketsEconomicCalendarDemo(c *ui.Context, log func(string)) {
disabled := ui.Local(c.Root(), "193.disabled", func() bool { return false })
if mujica.Checkbox(c, disabled, "Disabled", mujica.CheckboxOptions{}).Changed() {
if input.Checkbox(c, disabled, "Disabled", input.CheckboxOptions{}).Changed() {
log(fmt.Sprint("disabled ", *disabled))
}
state := ui.Local(c.Root(), "193.state", func() mujica.EconomicCalendarState { return mujica.EconomicCalendarState{} })
rows := *ui.Local(c.Root(), "markets.EconomicCalendar.rows.pages_markets_3", func() []mujica.EconomicEvent {
return []mujica.EconomicEvent{{ID: "jobs", Country: "US", Title: "Payrolls", Time: time.Date(2026, 10, 5, 14, 30, 0, 0, time.UTC), Importance: 3, Forecast: "150K", Previous: "142K", Actual: "254K"}}
state := ui.Local(c.Root(), "193.state", func() finance.EconomicCalendarState { return finance.EconomicCalendarState{} })
rows := *ui.Local(c.Root(), "markets.EconomicCalendar.rows.pages_markets_3", func() []finance.EconomicEvent {
return []finance.EconomicEvent{{ID: "jobs", Country: "US", Title: "Payrolls", Time: time.Date(2026, 10, 5, 14, 30, 0, 0, time.UTC), Importance: 3, Forecast: "150K", Previous: "142K", Actual: "254K"}}
})
status, empty, err := marketsDataState(c, "193", log)
if empty || status != mujica.DataReady {
if empty || status != data.DataReady {
rows = nil
}
beforeFilter := fmt.Sprint(state.Countries, state.MinImportance)
ui.Column(c).Disabled(*disabled).MinHeight(0).Height(400).Children(func() {
r := mujica.EconomicCalendar(c, state, rows, mujica.EconomicCalendarOptions{Status: status, Error: err})
r := finance.EconomicCalendar(c, state, rows, finance.EconomicCalendarOptions{Status: status, Error: err})
if r.Changed() || r.Submitted() || r.SortChanged() || r.Retried() {
log(fmt.Sprint(state.Table.Selected()))
}
@@ -76,20 +81,20 @@ func marketsEconomicCalendarDemo(c *ui.Context, log func(string)) {
func marketsEarningsCalendarDemo(c *ui.Context, log func(string)) {
disabled := ui.Local(c.Root(), "194.disabled", func() bool { return false })
if mujica.Checkbox(c, disabled, "Disabled", mujica.CheckboxOptions{}).Changed() {
if input.Checkbox(c, disabled, "Disabled", input.CheckboxOptions{}).Changed() {
log(fmt.Sprint("disabled ", *disabled))
}
state := ui.Local(c.Root(), "194.state", func() mujica.DataTableState[string] { return mujica.DataTableState[string]{} })
rows := *ui.Local(c.Root(), "markets.EarningsCalendar.rows.pages_markets_3", func() []mujica.EarningsEvent {
return []mujica.EarningsEvent{{ID: "a", Symbol: "AAPL", Name: "Apple", Date: mujica.Date{Year: 2026, Month: 10, Day: 30}, Timing: mujica.EarningsAfterClose, EPSEstimate: 1.6, EPSActual: 1.64}}
state := ui.Local(c.Root(), "194.state", func() data.DataTableState[string] { return data.DataTableState[string]{} })
rows := *ui.Local(c.Root(), "markets.EarningsCalendar.rows.pages_markets_3", func() []finance.EarningsEvent {
return []finance.EarningsEvent{{ID: "a", Symbol: "AAPL", Name: "Apple", Date: core.Date{Year: 2026, Month: 10, Day: 30}, Timing: finance.EarningsAfterClose, EPSEstimate: 1.6, EPSActual: 1.64}}
})
status, empty, err := marketsDataState(c, "194", log)
if empty || status != mujica.DataReady {
if empty || status != data.DataReady {
rows = nil
}
ui.Column(c).Disabled(*disabled).MinHeight(0).Height(400).Children(func() {
r := mujica.EarningsCalendar(c, state, rows, mujica.EarningsCalendarOptions{Status: status, Error: err})
r := finance.EarningsCalendar(c, state, rows, finance.EarningsCalendarOptions{Status: status, Error: err})
if r.Changed() || r.Submitted() || r.SortChanged() || r.Retried() {
log(fmt.Sprint(state.Selected()))
}
@@ -98,20 +103,20 @@ func marketsEarningsCalendarDemo(c *ui.Context, log func(string)) {
func marketsNewsFeedDemo(c *ui.Context, log func(string)) {
disabled := ui.Local(c.Root(), "195.disabled", func() bool { return false })
if mujica.Checkbox(c, disabled, "Disabled", mujica.CheckboxOptions{}).Changed() {
if input.Checkbox(c, disabled, "Disabled", input.CheckboxOptions{}).Changed() {
log(fmt.Sprint("disabled ", *disabled))
}
state := ui.Local(c.Root(), "195.state", func() mujica.NewsFeedState { return mujica.NewsFeedState{} })
rows := *ui.Local(c.Root(), "markets.NewsFeed.rows.pages_markets_3", func() []mujica.NewsItem {
return []mujica.NewsItem{{ID: "a", Title: "Apple unveils new chips", Source: "Wire", Time: time.Date(2026, 10, 5, 14, 30, 0, 0, time.UTC), Symbols: []string{"AAPL"}}}
state := ui.Local(c.Root(), "195.state", func() finance.NewsFeedState { return finance.NewsFeedState{} })
rows := *ui.Local(c.Root(), "markets.NewsFeed.rows.pages_markets_3", func() []finance.NewsItem {
return []finance.NewsItem{{ID: "a", Title: "Apple unveils new chips", Source: "Wire", Time: time.Date(2026, 10, 5, 14, 30, 0, 0, time.UTC), Symbols: []string{"AAPL"}}}
})
status, empty, err := marketsDataState(c, "195", log)
if empty || status != mujica.DataReady {
if empty || status != data.DataReady {
rows = nil
}
ui.Column(c).Disabled(*disabled).MinHeight(0).Height(400).Children(func() {
r := mujica.NewsFeed(c, state, rows, mujica.NewsFeedOptions{Status: status, Error: err})
r := finance.NewsFeed(c, state, rows, finance.NewsFeedOptions{Status: status, Error: err})
if r.Opened != "" || r.Symbol != "" || r.Retry || r.ReadChanged {
log(fmt.Sprint(r.Opened, r.Symbol))
}
+38 -33
View File
@@ -2,46 +2,51 @@ package main
import (
"fmt"
"github.com/egoist/mygo/ui"
mujica "mujicaui"
"time"
"github.com/egoist/mygo/ui"
"github.com/ZacharyZhang-NY/MujicaUI/chart"
"github.com/ZacharyZhang-NY/MujicaUI/data"
"github.com/ZacharyZhang-NY/MujicaUI/finance"
"github.com/ZacharyZhang-NY/MujicaUI/input"
)
func marketsSentimentGaugeDemo(c *ui.Context, log func(string)) {
disabled := ui.Local(c.Root(), "196.disabled", func() bool { return false })
if mujica.Checkbox(c, disabled, "Disabled", mujica.CheckboxOptions{}).Changed() {
if input.Checkbox(c, disabled, "Disabled", input.CheckboxOptions{}).Changed() {
log(fmt.Sprint("disabled ", *disabled))
}
value := 72.0
level := ui.Local(c.Root(), "196.value", func() float64 { return 72 })
if mujica.Slider(c, level, mujica.SliderOptions{Min: 0, Max: 100, Step: 1, Label: "Sentiment", ShowValue: true}).Changed() {
if input.Slider(c, level, input.SliderOptions{Min: 0, Max: 100, Step: 1, Label: "Sentiment", ShowValue: true}).Changed() {
log(fmt.Sprint(*level))
}
value = *level
status, empty, err := marketsDataState(c, "196", log)
ui.Column(c).Disabled(*disabled).MinHeight(0).Height(400).Children(func() {
mujica.SentimentGauge(c, value, mujica.SentimentGaugeOptions{Label: "Fear & Greed", Status: status, Empty: empty, Error: err, Retry: func() { log("retry requested") }})
finance.SentimentGauge(c, value, finance.SentimentGaugeOptions{Label: "Fear & Greed", Status: status, Empty: empty, Error: err, Retry: func() { log("retry requested") }})
})
}
func marketsPortfolioSummaryDemo(c *ui.Context, log func(string)) {
disabled := ui.Local(c.Root(), "197.disabled", func() bool { return false })
if mujica.Checkbox(c, disabled, "Disabled", mujica.CheckboxOptions{}).Changed() {
if input.Checkbox(c, disabled, "Disabled", input.CheckboxOptions{}).Changed() {
log(fmt.Sprint("disabled ", *disabled))
}
account := ui.Local(c.Root(), "197.account", func() string { return "" })
accounts := *ui.Local(c.Root(), "markets.PortfolioSummary.accounts.pages_markets_4", func() []mujica.PortfolioAccount {
return []mujica.PortfolioAccount{{ID: "a", Name: "Brokerage", Total: 120000, DayPnL: 1250, TotalReturn: 20000, Cost: 100000}, {ID: "b", Name: "Retirement", Total: 80000, DayPnL: -300, TotalReturn: 5000, Cost: 75000}}
accounts := *ui.Local(c.Root(), "markets.PortfolioSummary.accounts.pages_markets_4", func() []finance.PortfolioAccount {
return []finance.PortfolioAccount{{ID: "a", Name: "Brokerage", Total: 120000, DayPnL: 1250, TotalReturn: 20000, Cost: 100000}, {ID: "b", Name: "Retirement", Total: 80000, DayPnL: -300, TotalReturn: 5000, Cost: 75000}}
})
assets := ui.Local(c.Root(), "197.assets", func() float64 { return 120000 })
if mujica.NumberInput(c, assets, mujica.NumberInputOptions{Min: 0, Max: 1000000, Step: 100, Precision: 2, Label: "Brokerage assets"}).Changed() {
if input.NumberInput(c, assets, input.NumberInputOptions{Min: 0, Max: 1000000, Step: 100, Precision: 2, Label: "Brokerage assets"}).Changed() {
log(fmt.Sprint(*assets))
}
accounts[0].Total = *assets
ui.Column(c).Disabled(*disabled).MinHeight(0).Height(400).Children(func() {
r := mujica.PortfolioSummary(c, account, accounts, mujica.PortfolioSummaryOptions{})
r := finance.PortfolioSummary(c, account, accounts, finance.PortfolioSummaryOptions{})
if r.Changed() {
log(fmt.Sprint(account))
}
@@ -50,34 +55,34 @@ func marketsPortfolioSummaryDemo(c *ui.Context, log func(string)) {
func marketsAssetAllocationChartDemo(c *ui.Context, log func(string)) {
disabled := ui.Local(c.Root(), "198.disabled", func() bool { return false })
if mujica.Checkbox(c, disabled, "Disabled", mujica.CheckboxOptions{}).Changed() {
if input.Checkbox(c, disabled, "Disabled", input.CheckboxOptions{}).Changed() {
log(fmt.Sprint("disabled ", *disabled))
}
items := *ui.Local(c.Root(), "markets.AssetAllocationChart.items.pages_markets_4", func() []mujica.AllocationItem {
return []mujica.AllocationItem{{Name: "Stocks", Value: 60, Target: 50}, {Name: "Bonds", Value: 30, Target: 40}, {Name: "Cash", Value: 10, Target: 10}}
items := *ui.Local(c.Root(), "markets.AssetAllocationChart.items.pages_markets_4", func() []finance.AllocationItem {
return []finance.AllocationItem{{Name: "Stocks", Value: 60, Target: 50}, {Name: "Bonds", Value: 30, Target: 40}, {Name: "Cash", Value: 10, Target: 10}}
})
status := ui.Local(c.Root(), "198.status", func() int { return 0 })
if mujica.RadioGroup(c, status, []mujica.RadioItem[int]{{Value: 0, Label: "Ready"}, {Value: 1, Label: "Loading"}, {Value: 2, Label: "Empty"}, {Value: 3, Label: "Error"}}, mujica.RadioGroupOptions{Horizontal: true}).Changed() {
if input.RadioGroup(c, status, []input.RadioItem[int]{{Value: 0, Label: "Ready"}, {Value: 1, Label: "Loading"}, {Value: 2, Label: "Empty"}, {Value: 3, Label: "Error"}}, input.RadioGroupOptions{Horizontal: true}).Changed() {
log(fmt.Sprint("status ", *status))
}
frame := mujica.ChartFrame{Label: "AssetAllocationChart"}
frame := chart.ChartFrame{Label: "AssetAllocationChart"}
if *status == 1 {
frame.Status = mujica.DataLoading
frame.Status = data.DataLoading
}
if *status == 3 {
frame.Status = mujica.DataFailed
frame.Status = data.DataFailed
frame.Error = "Sample failed"
}
if *status == 2 {
items = nil
}
tolerance := ui.Local(c.Root(), "198.tolerance", func() float64 { return 5 })
if mujica.Slider(c, tolerance, mujica.SliderOptions{Min: 1, Max: 15, Step: 1, Label: "Drift tolerance", ShowValue: true}).Changed() {
if input.Slider(c, tolerance, input.SliderOptions{Min: 1, Max: 15, Step: 1, Label: "Drift tolerance", ShowValue: true}).Changed() {
log(fmt.Sprint(*tolerance))
}
ui.Column(c).Disabled(*disabled).MinHeight(0).Height(400).Children(func() {
r := mujica.AssetAllocationChart(c, items, mujica.AssetAllocationChartOptions{Frame: frame, Tolerance: *tolerance})
r := finance.AssetAllocationChart(c, items, finance.AssetAllocationChartOptions{Frame: frame, Tolerance: *tolerance})
if r.Changed() || r.Retried() {
log(fmt.Sprint(r.Point))
}
@@ -86,7 +91,7 @@ func marketsAssetAllocationChartDemo(c *ui.Context, log func(string)) {
func marketsPerformanceChartDemo(c *ui.Context, log func(string)) {
disabled := ui.Local(c.Root(), "199.disabled", func() bool { return false })
if mujica.Checkbox(c, disabled, "Disabled", mujica.CheckboxOptions{}).Changed() {
if input.Checkbox(c, disabled, "Disabled", input.CheckboxOptions{}).Changed() {
log(fmt.Sprint("disabled ", *disabled))
}
values := *ui.Local(c.Root(), "markets.PerformanceChart.values.pages_markets_4", func() []float64 { return []float64{100, 110, 95, 120} })
@@ -96,15 +101,15 @@ func marketsPerformanceChartDemo(c *ui.Context, log func(string)) {
})
status := ui.Local(c.Root(), "199.status", func() int { return 0 })
if mujica.RadioGroup(c, status, []mujica.RadioItem[int]{{Value: 0, Label: "Ready"}, {Value: 1, Label: "Loading"}, {Value: 2, Label: "Empty"}, {Value: 3, Label: "Error"}}, mujica.RadioGroupOptions{Horizontal: true}).Changed() {
if input.RadioGroup(c, status, []input.RadioItem[int]{{Value: 0, Label: "Ready"}, {Value: 1, Label: "Loading"}, {Value: 2, Label: "Empty"}, {Value: 3, Label: "Error"}}, input.RadioGroupOptions{Horizontal: true}).Changed() {
log(fmt.Sprint("status ", *status))
}
frame := mujica.ChartFrame{Label: "PerformanceChart"}
frame := chart.ChartFrame{Label: "PerformanceChart"}
if *status == 1 {
frame.Status = mujica.DataLoading
frame.Status = data.DataLoading
}
if *status == 3 {
frame.Status = mujica.DataFailed
frame.Status = data.DataFailed
frame.Error = "Sample failed"
}
if *status == 2 {
@@ -113,7 +118,7 @@ func marketsPerformanceChartDemo(c *ui.Context, log func(string)) {
times = nil
}
ui.Column(c).Disabled(*disabled).MinHeight(0).Height(400).Children(func() {
r := mujica.PerformanceChart(c, values, mujica.PerformanceChartOptions{Frame: frame, Times: times, Benchmark: benchmark})
r := finance.PerformanceChart(c, values, finance.PerformanceChartOptions{Frame: frame, Times: times, Benchmark: benchmark})
if r.Changed() || r.Retried() {
log(fmt.Sprint(r.Point))
}
@@ -125,21 +130,21 @@ func marketsPerformanceChartDemo(c *ui.Context, log func(string)) {
func marketsCurrencyConverterDemo(c *ui.Context, log func(string)) {
disabled := ui.Local(c.Root(), "200.disabled", func() bool { return false })
if mujica.Checkbox(c, disabled, "Disabled", mujica.CheckboxOptions{}).Changed() {
if input.Checkbox(c, disabled, "Disabled", input.CheckboxOptions{}).Changed() {
log(fmt.Sprint("disabled ", *disabled))
}
state := ui.Local(c.Root(), "200.state", func() mujica.CurrencyConverterState {
return mujica.CurrencyConverterState{Amount: 100, From: "USD", To: "JPY"}
state := ui.Local(c.Root(), "200.state", func() finance.CurrencyConverterState {
return finance.CurrencyConverterState{Amount: 100, From: "USD", To: "JPY"}
})
currencies := *ui.Local(c.Root(), "markets.CurrencyConverter.currencies.pages_markets_4", func() []mujica.Currency {
return []mujica.Currency{{Code: "USD", Name: "Dollar", Decimals: 2}, {Code: "JPY", Name: "Yen", Decimals: 0}}
currencies := *ui.Local(c.Root(), "markets.CurrencyConverter.currencies.pages_markets_4", func() []finance.Currency {
return []finance.Currency{{Code: "USD", Name: "Dollar", Decimals: 2}, {Code: "JPY", Name: "Yen", Decimals: 0}}
})
rate := ui.Local(c.Root(), "200.rate", func() float64 { return 149.37 })
if mujica.NumberInput(c, rate, mujica.NumberInputOptions{Min: 0, Max: 1000, Step: 0.0001, Precision: 4, Label: "Caller rate"}).Changed() {
if input.NumberInput(c, rate, input.NumberInputOptions{Min: 0, Max: 1000, Step: 0.0001, Precision: 4, Label: "Caller rate"}).Changed() {
log(fmt.Sprint("rate ", *rate))
}
if mujica.Button(c, "Load pair rate", mujica.ButtonOptions{Variant: mujica.Secondary}).Clicked() {
if input.Button(c, "Load pair rate", input.ButtonOptions{Variant: input.Secondary}).Clicked() {
*rate = 149.37
if state.From == "JPY" {
*rate = 1 / 149.37
@@ -148,7 +153,7 @@ func marketsCurrencyConverterDemo(c *ui.Context, log func(string)) {
}
beforeAmount := state.Amount
ui.Column(c).Disabled(*disabled).MinHeight(0).Height(400).Children(func() {
r := mujica.CurrencyConverter(c, state, currencies, mujica.CurrencyConverterOptions{Rate: *rate, RateFrom: state.From, RateTo: state.To})
r := finance.CurrencyConverter(c, state, currencies, finance.CurrencyConverterOptions{Rate: *rate, RateFrom: state.From, RateTo: state.To})
if r.PairChanged {
*rate = 0
log(fmt.Sprint(state.From, state.To))
+5 -3
View File
@@ -1,11 +1,13 @@
package main
import (
"github.com/egoist/mygo/ui"
mujica "mujicaui"
"slices"
"strings"
"testing"
"github.com/egoist/mygo/ui"
"github.com/ZacharyZhang-NY/MujicaUI/core"
)
func marketsDemo(t *testing.T, id string) (*ui.Tester, *[]string) {
@@ -16,7 +18,7 @@ func marketsDemo(t *testing.T, id string) (*ui.Tester, *[]string) {
}
events := &[]string{}
tt := ui.NewTester(func(c *ui.Context) {
mujica.Use(c, mujica.Settings{})
core.Use(c, core.Settings{})
ui.Scroll(c).Fill().Padding(24).Children(func() { marketsPages[i].Demo(c, func(s string) { *events = append(*events, s) }) })
}, 1000, 800)
tt.SetPreferences(ui.Preferences{ReduceMotion: true, TextScale: 1})
+5 -4
View File
@@ -9,8 +9,9 @@ import (
"github.com/egoist/mygo/ui"
mujica "mujicaui"
"mujicaui/theme"
"github.com/ZacharyZhang-NY/MujicaUI/core"
"github.com/ZacharyZhang-NY/MujicaUI/input"
"github.com/ZacharyZhang-NY/MujicaUI/theme"
)
const mediaCategory = "Media"
@@ -61,7 +62,7 @@ var mediaPages = []page{
// mediaSection titles a part of a demo.
func mediaSection(c *ui.Context, title string, fn func()) {
ui.Column(c).Gap(8).FillWidth().Children(func() {
ui.Text(c, title).FontSize(mujica.FontSize(c, theme.CaptionSize)).TextColor(mujica.Tokens(c).TextMuted)
ui.Text(c, title).FontSize(core.FontSize(c, theme.CaptionSize)).TextColor(core.Tokens(c).TextMuted)
fn()
})
}
@@ -69,7 +70,7 @@ func mediaSection(c *ui.Context, title string, fn func()) {
// mediaCheck is a parameter checkbox that logs its changes.
func mediaCheck(c *ui.Context, log func(string), v *bool, label string) {
before := *v
mujica.Checkbox(c, v, label, mujica.CheckboxOptions{})
input.Checkbox(c, v, label, input.CheckboxOptions{})
if *v != before {
log(fmt.Sprintf("%s = %v", label, *v))
}
+20 -18
View File
@@ -7,7 +7,9 @@ import (
"github.com/egoist/mygo/ui"
mujica "mujicaui"
"github.com/ZacharyZhang-NY/MujicaUI/data"
"github.com/ZacharyZhang-NY/MujicaUI/media"
"github.com/ZacharyZhang-NY/MujicaUI/navigation"
)
// mediaClockState simulates the caller's playback: position advances while playing.
@@ -55,12 +57,12 @@ func videoPlayerDemo(c *ui.Context, log func(string)) {
if s.noFrame {
frame = nil
}
st := mujica.VideoState{Frame: frame, Position: s.clock.pos, Duration: s.clock.dur, Playing: s.clock.playing,
Rate: s.clock.rate, Buffered: []mujica.MediaRange{{Start: 0, End: s.clock.pos + 40*time.Second}},
st := media.VideoState{Frame: frame, Position: s.clock.pos, Duration: s.clock.dur, Playing: s.clock.playing,
Rate: s.clock.rate, Buffered: []media.MediaRange{{Start: 0, End: s.clock.pos + 40*time.Second}},
Subtitle: cues[int(s.clock.pos/(10*time.Second))%3], SubtitlesOn: s.subs, Fullscreen: s.full, PiP: s.pip}
mediaSection(c, "Live", func() {
ui.Column(c).MaxWidth(640).Children(func() {
mujica.VideoPlayer(c, st, mujica.VideoPlayerOptions{
media.VideoPlayer(c, st, media.VideoPlayerOptions{
OnPlay: func() { s.clock.playing = true; log("play") },
OnPause: func() { s.clock.playing = false; log("pause") },
OnSeek: func(d time.Duration) { s.clock.pos = d; log("seek " + mediaClockLabel(d)) },
@@ -86,8 +88,8 @@ func videoScrubberDemo(c *ui.Context, log func(string)) {
mediaCheck(c, log, &s.disabled, "Disabled")
})
})
opts := mujica.VideoScrubberOptions{Duration: 3 * time.Minute, Disabled: s.disabled,
Buffered: []mujica.MediaRange{{Start: 0, End: 90 * time.Second}, {Start: 120 * time.Second, End: 140 * time.Second}}}
opts := media.VideoScrubberOptions{Duration: 3 * time.Minute, Disabled: s.disabled,
Buffered: []media.MediaRange{{Start: 0, End: 90 * time.Second}, {Start: 120 * time.Second, End: 140 * time.Second}}}
if s.preview {
opts.Preview = func(at time.Duration) {
ui.Image(c, mediaBitmaps[int(at/(time.Minute))%3]).Fit(ui.Cover).Fill()
@@ -95,7 +97,7 @@ func videoScrubberDemo(c *ui.Context, log func(string)) {
}
mediaSection(c, "Live", func() {
ui.Column(c).MaxWidth(560).Padding(110, 0, 0, 0).Children(func() {
if mujica.VideoScrubber(c, &s.pos, opts).Changed() {
if media.VideoScrubber(c, &s.pos, opts).Changed() {
log("seek " + mediaClockLabel(s.pos))
}
})
@@ -112,8 +114,8 @@ func audioPlayerDemo(c *ui.Context, log func(string)) {
mediaSection(c, "Parameters", func() { mediaCheck(c, log, &s.disabled, "Disabled") })
mediaSection(c, "Live", func() {
ui.Column(c).MaxWidth(560).Children(func() {
mujica.AudioPlayer(c, mujica.AudioState{Title: "Ave Mujica — Overture", Position: s.clock.pos, Duration: s.clock.dur, Playing: s.clock.playing},
mujica.AudioPlayerOptions{
media.AudioPlayer(c, media.AudioState{Title: "Ave Mujica — Overture", Position: s.clock.pos, Duration: s.clock.dur, Playing: s.clock.playing},
media.AudioPlayerOptions{
OnPlay: func() { s.clock.playing = true; log("play") },
OnPause: func() { s.clock.playing = false; log("pause") },
OnSeek: func(d time.Duration) { s.clock.pos = d; log("seek " + mediaClockLabel(d)) },
@@ -136,7 +138,7 @@ var mediaSamples = func() []float32 {
func audioWaveformDemo(c *ui.Context, log func(string)) {
type state struct {
pos time.Duration
sel mujica.MediaRange
sel media.MediaRange
status int
selecting bool
}
@@ -144,28 +146,28 @@ func audioWaveformDemo(c *ui.Context, log func(string)) {
mediaSection(c, "Parameters", func() {
ui.Row(c).Gap(16).AlignItems(ui.Center).Children(func() {
before := s.status
mujica.SegmentedControl(c, &s.status, []mujica.Segment{{Label: "Ready"}, {Label: "Loading"}, {Label: "Failed"}, {Label: "Empty"}}, mujica.SegmentedControlOptions{Label: "Status"})
navigation.SegmentedControl(c, &s.status, []navigation.Segment{{Label: "Ready"}, {Label: "Loading"}, {Label: "Failed"}, {Label: "Empty"}}, navigation.SegmentedControlOptions{Label: "Status"})
if s.status != before {
log(fmt.Sprintf("status %d", s.status))
}
mediaCheck(c, log, &s.selecting, "Selectable")
})
})
opts := mujica.AudioWaveformOptions{Duration: time.Minute, Position: s.pos, OnSeek: func(d time.Duration) { s.pos = d; log("seek " + mediaClockLabel(d)) }}
opts := media.AudioWaveformOptions{Duration: time.Minute, Position: s.pos, OnSeek: func(d time.Duration) { s.pos = d; log("seek " + mediaClockLabel(d)) }}
if s.selecting {
opts.Selection = &s.sel
}
data := mediaSamples
samples := mediaSamples
switch s.status {
case 1:
opts.Status, data = mujica.DataLoading, nil
opts.Status, samples = data.DataLoading, nil
case 2:
opts.Status, opts.Error, data = mujica.DataFailed, "The recording could not be decoded.", nil
opts.Status, opts.Error, samples = data.DataFailed, "The recording could not be decoded.", nil
case 3:
data = nil
samples = nil
}
mediaSection(c, "Live", func() {
if mujica.AudioWaveform(c, data, opts).Changed() {
if media.AudioWaveform(c, samples, opts).Changed() {
log(fmt.Sprintf("selection %s – %s", mediaClockLabel(s.sel.Start), mediaClockLabel(s.sel.End)))
}
})
@@ -186,6 +188,6 @@ func audioSpectrumDemo(c *ui.Context, log func(string)) {
c.AnimationFrame()
}
mediaSection(c, "Live", func() {
mujica.AudioSpectrum(c, bands, mujica.AudioSpectrumOptions{BandLabel: func(i int) string { return freqs[i] }})
media.AudioSpectrum(c, bands, media.AudioSpectrumOptions{BandLabel: func(i int) string { return freqs[i] }})
})
}
+65 -65
View File
@@ -2,20 +2,20 @@ package main
// Media page snippets, each its compiled Example's body.
const (
imageCropperCode = `crop := mujica.ImageCrop{} // empty: starts fitted to the image
imageCropperCode = `crop := media.ImageCrop{} // empty: starts fitted to the image
view := func(c *ui.Context) {
mujica.Use(c, mujica.Settings{})
if mujica.ImageCropper(c, examplePhoto, &crop, mujica.ImageCropperOptions{Aspect: 1}).Changed() {
core.Use(c, core.Settings{})
if media.ImageCropper(c, examplePhoto, &crop, media.ImageCropperOptions{Aspect: 1}).Changed() {
_ = crop.Rect // in pixels of the photo turned by crop.Rotation
}
}
ui.Render(view, 400, 300, 1)`
imageAnnotatorCode = `photo := ui.NewBitmap(examplePhoto)
var notes []mujica.Annotation
var notes []media.Annotation
view := func(c *ui.Context) {
mujica.Use(c, mujica.Settings{})
if mujica.ImageAnnotator(c, photo, &notes, mujica.ImageAnnotatorOptions{}).Changed() {
data, _ := mujica.ExportAnnotations(notes)
core.Use(c, core.Settings{})
if media.ImageAnnotator(c, photo, &notes, media.ImageAnnotatorOptions{}).Changed() {
data, _ := media.ExportAnnotations(notes)
_ = data // save the JSON
}
}
@@ -23,26 +23,26 @@ ui.Render(view, 400, 300, 1)`
imageThumbnailCode = `photo := ui.NewBitmap(examplePhoto)
selected := false
view := func(c *ui.Context) {
mujica.Use(c, mujica.Settings{})
if mujica.ImageThumbnail(c, photo, mujica.ImageThumbnailOptions{Alt: "Grand hall", Aspect: 4.0 / 3, Selected: selected}).Changed() {
core.Use(c, core.Settings{})
if media.ImageThumbnail(c, photo, media.ImageThumbnailOptions{Alt: "Grand hall", Aspect: 4.0 / 3, Selected: selected}).Changed() {
selected = !selected
}
}
ui.Render(view, 400, 300, 1)`
lightboxCode = `images := []mujica.ViewerImage{{Source: ui.NewBitmap(examplePhoto), Alt: "The grand hall"}}
lightboxCode = `images := []data.ViewerImage{{Source: ui.NewBitmap(examplePhoto), Alt: "The grand hall"}}
open, index := false, 0
view := func(c *ui.Context) {
mujica.Use(c, mujica.Settings{})
if mujica.Button(c, "View photos", mujica.ButtonOptions{}).Clicked() {
core.Use(c, core.Settings{})
if input.Button(c, "View photos", input.ButtonOptions{}).Clicked() {
open = true
}
mujica.Lightbox(c, &open, &index, images)
media.Lightbox(c, &open, &index, images)
}
ui.Render(view, 400, 300, 1)`
videoPlayerCode = `st := mujica.VideoState{Duration: 4 * time.Minute, Rate: 1}
videoPlayerCode = `st := media.VideoState{Duration: 4 * time.Minute, Rate: 1}
view := func(c *ui.Context) {
mujica.Use(c, mujica.Settings{})
mujica.VideoPlayer(c, st, mujica.VideoPlayerOptions{
core.Use(c, core.Settings{})
media.VideoPlayer(c, st, media.VideoPlayerOptions{
OnPlay: func() { st.Playing = true }, // start your decoder
OnPause: func() { st.Playing = false }, // pause it
OnSeek: func(d time.Duration) { st.Position = d },
@@ -52,26 +52,26 @@ view := func(c *ui.Context) {
ui.Render(view, 400, 300, 1)`
videoScrubberCode = `pos := 30 * time.Second
view := func(c *ui.Context) {
mujica.Use(c, mujica.Settings{})
mujica.VideoScrubber(c, &pos, mujica.VideoScrubberOptions{
core.Use(c, core.Settings{})
media.VideoScrubber(c, &pos, media.VideoScrubberOptions{
Duration: 2 * time.Minute,
Buffered: []mujica.MediaRange{{Start: 0, End: time.Minute}},
Buffered: []media.MediaRange{{Start: 0, End: time.Minute}},
})
}
ui.Render(view, 400, 300, 1)`
videoStripCode = `frames := []mujica.StripFrame{{At: 0}, {At: 10 * time.Second}, {At: 20 * time.Second}}
videoStripCode = `frames := []media.StripFrame{{At: 0}, {At: 10 * time.Second}, {At: 20 * time.Second}}
current := 0
view := func(c *ui.Context) {
mujica.Use(c, mujica.Settings{})
if mujica.VideoThumbnailStrip(c, &current, frames, mujica.VideoThumbnailStripOptions{}).Changed() {
core.Use(c, core.Settings{})
if media.VideoThumbnailStrip(c, &current, frames, media.VideoThumbnailStripOptions{}).Changed() {
_ = frames[current].At // seek the player there
}
}
ui.Render(view, 400, 300, 1)`
audioPlayerCode = `st := mujica.AudioState{Title: "Overture", Duration: 3 * time.Minute}
audioPlayerCode = `st := media.AudioState{Title: "Overture", Duration: 3 * time.Minute}
view := func(c *ui.Context) {
mujica.Use(c, mujica.Settings{})
mujica.AudioPlayer(c, st, mujica.AudioPlayerOptions{
core.Use(c, core.Settings{})
media.AudioPlayer(c, st, media.AudioPlayerOptions{
OnPlay: func() { st.Playing = true },
OnPause: func() { st.Playing = false },
OnSeek: func(d time.Duration) { st.Position = d },
@@ -79,10 +79,10 @@ view := func(c *ui.Context) {
}
ui.Render(view, 400, 300, 1)`
audioWaveformCode = `samples := make([]float32, 48000) // from your decoder, -1 to 1
pos, sel := time.Duration(0), mujica.MediaRange{}
pos, sel := time.Duration(0), media.MediaRange{}
view := func(c *ui.Context) {
mujica.Use(c, mujica.Settings{})
mujica.AudioWaveform(c, samples, mujica.AudioWaveformOptions{
core.Use(c, core.Settings{})
media.AudioWaveform(c, samples, media.AudioWaveformOptions{
Duration: time.Second, Position: pos, Selection: &sel,
OnSeek: func(d time.Duration) { pos = d },
})
@@ -90,89 +90,89 @@ view := func(c *ui.Context) {
ui.Render(view, 400, 300, 1)`
audioSpectrumCode = `bands := []float32{0.2, 0.6, 0.9, 0.4, 0.1} // set by your analyser, then c.Invalidate()
view := func(c *ui.Context) {
mujica.Use(c, mujica.Settings{})
mujica.AudioSpectrum(c, bands, mujica.AudioSpectrumOptions{})
core.Use(c, core.Settings{})
media.AudioSpectrum(c, bands, media.AudioSpectrumOptions{})
}
ui.Render(view, 400, 300, 1)`
volumeCode = `volume, muted := 0.8, false
view := func(c *ui.Context) {
mujica.Use(c, mujica.Settings{})
mujica.VolumeControl(c, &volume, &muted, mujica.VolumeControlOptions{})
core.Use(c, core.Settings{})
media.VolumeControl(c, &volume, &muted, media.VolumeControlOptions{})
}
ui.Render(view, 400, 300, 1)`
speedCode = `rate := 1.0
view := func(c *ui.Context) {
mujica.Use(c, mujica.Settings{})
mujica.PlaybackSpeedControl(c, &rate, mujica.PlaybackSpeedControlOptions{})
core.Use(c, core.Settings{})
media.PlaybackSpeedControl(c, &rate, media.PlaybackSpeedControlOptions{})
}
ui.Render(view, 400, 300, 1)`
mediaControlsCode = `st := mujica.MediaControlsState{}
mediaControlsCode = `st := media.MediaControlsState{}
view := func(c *ui.Context) {
mujica.Use(c, mujica.Settings{})
mujica.MediaControls(c, st, mujica.MediaControlsOptions{
core.Use(c, core.Settings{})
media.MediaControls(c, st, media.MediaControlsOptions{
OnPrevious: func() {}, OnNext: func() {},
OnPlay: func() { st.Playing = true }, OnPause: func() { st.Playing = false },
OnLoop: func(m mujica.LoopMode) { st.Loop = m }, OnShuffle: func(on bool) { st.Shuffle = on },
OnLoop: func(m media.LoopMode) { st.Loop = m }, OnShuffle: func(on bool) { st.Shuffle = on },
})
}
ui.Render(view, 400, 300, 1)`
playlistCode = `items := []mujica.PlaylistItem{{ID: "a", Title: "Overture"}, {ID: "b", Title: "Masquerade"}}
playlistCode = `items := []media.PlaylistItem{{ID: "a", Title: "Overture"}, {ID: "b", Title: "Masquerade"}}
current := "a"
view := func(c *ui.Context) {
mujica.Use(c, mujica.Settings{})
if r := mujica.Playlist(c, &items, mujica.PlaylistOptions{Current: current}); r.Played != "" {
core.Use(c, core.Settings{})
if r := media.Playlist(c, &items, media.PlaylistOptions{Current: current}); r.Played != "" {
current = r.Played
}
}
ui.Render(view, 400, 300, 1)`
subtitleCode = `cues := []mujica.SubtitleCue{{Start: 0, End: 2 * time.Second, Text: "The curtain rises."}}
subtitleCode = `cues := []media.SubtitleCue{{Start: 0, End: 2 * time.Second, Text: "The curtain rises."}}
view := func(c *ui.Context) {
mujica.Use(c, mujica.Settings{})
mujica.SubtitleEditor(c, &cues, mujica.SubtitleEditorOptions{})
if len(mujica.CheckSubtitles(cues)) > 0 {
core.Use(c, core.Settings{})
media.SubtitleEditor(c, &cues, media.SubtitleEditorOptions{})
if len(media.CheckSubtitles(cues)) > 0 {
_ = cues // block saving until the issues are fixed
}
}
ui.Render(view, 400, 300, 1)`
recorderCode = `st := mujica.RecorderState{}
recorderCode = `st := media.RecorderState{}
view := func(c *ui.Context) {
mujica.Use(c, mujica.Settings{})
mujica.ScreenRecorderControls(c, st, mujica.ScreenRecorderControlsOptions{
OnStart: func() { st.Status = mujica.RecorderRecording },
OnPause: func() { st.Status = mujica.RecorderPaused },
OnResume: func() { st.Status = mujica.RecorderRecording },
OnStop: func() { st = mujica.RecorderState{} },
core.Use(c, core.Settings{})
media.ScreenRecorderControls(c, st, media.ScreenRecorderControlsOptions{
OnStart: func() { st.Status = media.RecorderRecording },
OnPause: func() { st.Status = media.RecorderPaused },
OnResume: func() { st.Status = media.RecorderRecording },
OnStop: func() { st = media.RecorderState{} },
})
}
ui.Render(view, 400, 300, 1)`
cameraCode = `frame := mujica.CameraFrame{Image: examplePhoto, Seq: 1} // a new Seq for each captured frame
cameraCode = `frame := media.CameraFrame{Image: examplePhoto, Seq: 1} // a new Seq for each captured frame
mirror := true
view := func(c *ui.Context) {
mujica.Use(c, mujica.Settings{})
mujica.CameraPreview(c, frame, &mirror, mujica.CameraPreviewOptions{})
core.Use(c, core.Settings{})
media.CameraPreview(c, frame, &mirror, media.CameraPreviewOptions{})
}
ui.Render(view, 400, 300, 1)`
micCode = `level := float32(0.4) // the input's peak, 0 to 1 of full scale
view := func(c *ui.Context) {
mujica.Use(c, mujica.Settings{})
mujica.MicLevelMeter(c, level, mujica.MicLevelMeterOptions{})
core.Use(c, core.Settings{})
media.MicLevelMeter(c, level, media.MicLevelMeterOptions{})
}
ui.Render(view, 400, 300, 1)`
deviceCode = `devices := mujica.MediaDevices{
Microphones: []mujica.MediaDevice{{ID: "mic-1", Name: "Built-in Microphone"}},
Speakers: []mujica.MediaDevice{{ID: "out-1", Name: "Headphones"}},
deviceCode = `devices := media.MediaDevices{
Microphones: []media.MediaDevice{{ID: "mic-1", Name: "Built-in Microphone"}},
Speakers: []media.MediaDevice{{ID: "out-1", Name: "Headphones"}},
}
sel := mujica.DeviceSelection{Microphone: "mic-1"}
sel := media.DeviceSelection{Microphone: "mic-1"}
view := func(c *ui.Context) {
mujica.Use(c, mujica.Settings{})
mujica.DeviceSelector(c, &sel, devices, mujica.DeviceSelectorOptions{})
core.Use(c, core.Settings{})
media.DeviceSelector(c, &sel, devices, media.DeviceSelectorOptions{})
}
ui.Render(view, 400, 300, 1)`
compareCode = `before, after := ui.NewBitmap(examplePhoto), ui.NewBitmap(examplePhoto)
split := 0.5
view := func(c *ui.Context) {
mujica.Use(c, mujica.Settings{})
mujica.ImageCompare(c, before, after, &split, mujica.ImageCompareOptions{})
core.Use(c, core.Settings{})
media.ImageCompare(c, before, after, &split, media.ImageCompareOptions{})
}
ui.Render(view, 400, 300, 1)`
)
+36 -35
View File
@@ -7,7 +7,8 @@ import (
"github.com/egoist/mygo/ui"
mujica "mujicaui"
"github.com/ZacharyZhang-NY/MujicaUI/input"
"github.com/ZacharyZhang-NY/MujicaUI/media"
)
func volumeDemo(c *ui.Context, log func(string)) {
@@ -18,7 +19,7 @@ func volumeDemo(c *ui.Context, log func(string)) {
s := ui.Local(c.Root(), "351", func() state { return state{volume: 0.7} })
mediaSection(c, "Parameters", func() { mediaCheck(c, log, &s.disabled, "Disabled") })
mediaSection(c, "Live", func() {
if mujica.VolumeControl(c, &s.volume, &s.muted, mujica.VolumeControlOptions{Disabled: s.disabled}).Changed() {
if media.VolumeControl(c, &s.volume, &s.muted, media.VolumeControlOptions{Disabled: s.disabled}).Changed() {
log(fmt.Sprintf("volume %.0f%%, muted %v", s.volume*100, s.muted))
}
})
@@ -32,7 +33,7 @@ func speedDemo(c *ui.Context, log func(string)) {
s := ui.Local(c.Root(), "352", func() state { return state{rate: 1} })
mediaSection(c, "Parameters", func() { mediaCheck(c, log, &s.disabled, "Disabled") })
mediaSection(c, "Live", func() {
if mujica.PlaybackSpeedControl(c, &s.rate, mujica.PlaybackSpeedControlOptions{Disabled: s.disabled}).Changed() {
if media.PlaybackSpeedControl(c, &s.rate, media.PlaybackSpeedControlOptions{Disabled: s.disabled}).Changed() {
log(fmt.Sprintf("rate %v×", s.rate))
}
})
@@ -40,7 +41,7 @@ func speedDemo(c *ui.Context, log func(string)) {
func mediaControlsDemo(c *ui.Context, log func(string)) {
type state struct {
st mujica.MediaControlsState
st media.MediaControlsState
track int
disabled bool
}
@@ -50,12 +51,12 @@ func mediaControlsDemo(c *ui.Context, log func(string)) {
mediaSection(c, "Parameters", func() { mediaCheck(c, log, &s.disabled, "Disabled") })
mediaSection(c, "Live", func() {
ui.Text(c, fmt.Sprintf("Now: %s (%d of %d)", tracks[s.track], s.track+1, len(tracks)))
mujica.MediaControls(c, s.st, mujica.MediaControlsOptions{
media.MediaControls(c, s.st, media.MediaControlsOptions{
OnPrevious: func() { s.track--; log("previous") },
OnNext: func() { s.track++; log("next") },
OnPlay: func() { s.st.Playing = true; log("play") },
OnPause: func() { s.st.Playing = false; log("pause") },
OnLoop: func(m mujica.LoopMode) { s.st.Loop = m; log(fmt.Sprintf("loop mode %d", m)) },
OnLoop: func(m media.LoopMode) { s.st.Loop = m; log(fmt.Sprintf("loop mode %d", m)) },
OnShuffle: func(on bool) { s.st.Shuffle = on; log(fmt.Sprintf("shuffle %v", on)) },
Disabled: s.disabled,
})
@@ -64,31 +65,31 @@ func mediaControlsDemo(c *ui.Context, log func(string)) {
func playlistDemo(c *ui.Context, log func(string)) {
type state struct {
items []mujica.PlaylistItem
items []media.PlaylistItem
current string
disabled bool
}
s := ui.Local(c.Root(), "354", func() state {
titles := []string{"Ave Mujica", "Imprisoned XII", "KiLLKiSS", "Symbol I: △", "Choir 'S' Choir", "Georgette Me, Georgette You"}
items := make([]mujica.PlaylistItem, len(titles))
items := make([]media.PlaylistItem, len(titles))
for i, t := range titles {
items[i] = mujica.PlaylistItem{ID: fmt.Sprint("t", i), Title: t, Subtitle: fmt.Sprintf("Act %d", i+1), Duration: time.Duration(200+i*17) * time.Second}
items[i] = media.PlaylistItem{ID: fmt.Sprint("t", i), Title: t, Subtitle: fmt.Sprintf("Act %d", i+1), Duration: time.Duration(200+i*17) * time.Second}
}
return state{items: items, current: "t1"}
})
mediaSection(c, "Parameters", func() {
ui.Row(c).Gap(16).AlignItems(ui.Center).Children(func() {
mediaCheck(c, log, &s.disabled, "Disabled")
if mujica.Button(c, "Add track", mujica.ButtonOptions{Variant: mujica.Secondary}).Clicked() {
if input.Button(c, "Add track", input.ButtonOptions{Variant: input.Secondary}).Clicked() {
id := fmt.Sprint("t", len(s.items)+int(time.Now().UnixNano()%1000000))
s.items = append(s.items, mujica.PlaylistItem{ID: id, Title: "Encore " + id, Duration: 3 * time.Minute})
s.items = append(s.items, media.PlaylistItem{ID: id, Title: "Encore " + id, Duration: 3 * time.Minute})
log("added " + id)
}
})
})
mediaSection(c, "Live", func() {
ui.Column(c).MaxWidth(520).Children(func() {
r := mujica.Playlist(c, &s.items, mujica.PlaylistOptions{Current: s.current, Disabled: s.disabled})
r := media.Playlist(c, &s.items, media.PlaylistOptions{Current: s.current, Disabled: s.disabled})
switch {
case r.Played != "":
s.current = r.Played
@@ -104,11 +105,11 @@ func playlistDemo(c *ui.Context, log func(string)) {
func subtitleDemo(c *ui.Context, log func(string)) {
type state struct {
cues []mujica.SubtitleCue
cues []media.SubtitleCue
disabled bool
}
s := ui.Local(c.Root(), "355", func() state {
return state{cues: []mujica.SubtitleCue{
return state{cues: []media.SubtitleCue{
{Start: 0, End: 2500 * time.Millisecond, Text: "The curtain rises."},
{Start: 2 * time.Second, End: 5 * time.Second, Text: "Welcome to the masquerade."},
{Start: 6 * time.Second, End: 9 * time.Second, Text: "Ave Mujica."},
@@ -117,8 +118,8 @@ func subtitleDemo(c *ui.Context, log func(string)) {
mediaSection(c, "Parameters", func() { mediaCheck(c, log, &s.disabled, "Disabled") })
mediaSection(c, "Live", func() {
ui.Column(c).MaxWidth(720).Children(func() {
if mujica.SubtitleEditor(c, &s.cues, mujica.SubtitleEditorOptions{Height: 260, Disabled: s.disabled}).Changed() {
log(fmt.Sprintf("%d cues, %d issues", len(s.cues), len(mujica.CheckSubtitles(s.cues))))
if media.SubtitleEditor(c, &s.cues, media.SubtitleEditorOptions{Height: 260, Disabled: s.disabled}).Changed() {
log(fmt.Sprintf("%d cues, %d issues", len(s.cues), len(media.CheckSubtitles(s.cues))))
}
})
})
@@ -126,27 +127,27 @@ func subtitleDemo(c *ui.Context, log func(string)) {
func recorderDemo(c *ui.Context, log func(string)) {
type state struct {
st mujica.RecorderState
st media.RecorderState
since time.Time
disabled bool
accumulated time.Duration
}
s := ui.Local(c.Root(), "356", func() state { return state{} })
if s.st.Status == mujica.RecorderRecording {
if s.st.Status == media.RecorderRecording {
s.st.Elapsed = s.accumulated + c.Now().Sub(s.since)
// The clock changes each second; ask for that frame only.
c.After(time.Second - s.st.Elapsed%time.Second)
}
mediaSection(c, "Parameters", func() { mediaCheck(c, log, &s.disabled, "Disabled") })
mediaSection(c, "Live", func() {
mujica.ScreenRecorderControls(c, s.st, mujica.ScreenRecorderControlsOptions{
media.ScreenRecorderControls(c, s.st, media.ScreenRecorderControlsOptions{
OnStart: func() {
s.st, s.since, s.accumulated = mujica.RecorderState{Status: mujica.RecorderRecording}, c.Now(), 0
s.st, s.since, s.accumulated = media.RecorderState{Status: media.RecorderRecording}, c.Now(), 0
log("start")
},
OnPause: func() { s.st.Status, s.accumulated = mujica.RecorderPaused, s.st.Elapsed; log("pause") },
OnResume: func() { s.st.Status, s.since = mujica.RecorderRecording, c.Now(); log("resume") },
OnStop: func() { log("stop at " + mediaClockLabel(s.st.Elapsed)); s.st = mujica.RecorderState{} },
OnPause: func() { s.st.Status, s.accumulated = media.RecorderPaused, s.st.Elapsed; log("pause") },
OnResume: func() { s.st.Status, s.since = media.RecorderRecording, c.Now(); log("resume") },
OnStop: func() { log("stop at " + mediaClockLabel(s.st.Elapsed)); s.st = media.RecorderState{} },
Disabled: s.disabled,
})
})
@@ -163,13 +164,13 @@ func cameraDemo(c *ui.Context, log func(string)) {
mediaCheck(c, log, &s.disabled, "Disabled")
})
})
frame := mujica.CameraFrame{}
frame := media.CameraFrame{}
if s.signal {
frame = mujica.CameraFrame{Image: mediaPhotos[2], Seq: 1}
frame = media.CameraFrame{Image: mediaPhotos[2], Seq: 1}
}
mediaSection(c, "Live", func() {
ui.Column(c).MaxWidth(480).Children(func() {
if mujica.CameraPreview(c, frame, &s.mirror, mujica.CameraPreviewOptions{Disabled: s.disabled}).Changed() {
if media.CameraPreview(c, frame, &s.mirror, media.CameraPreviewOptions{Disabled: s.disabled}).Changed() {
log(fmt.Sprintf("mirror %v", s.mirror))
}
})
@@ -187,20 +188,20 @@ func micDemo(c *ui.Context, log func(string)) {
c.AnimationFrame()
}
mediaSection(c, "Live", func() {
ui.Column(c).MaxWidth(420).Children(func() { mujica.MicLevelMeter(c, level, mujica.MicLevelMeterOptions{}) })
ui.Column(c).MaxWidth(420).Children(func() { media.MicLevelMeter(c, level, media.MicLevelMeterOptions{}) })
})
}
func deviceDemo(c *ui.Context, log func(string)) {
type state struct {
sel mujica.DeviceSelection
sel media.DeviceSelection
noCamera, disabled bool
}
s := ui.Local(c.Root(), "359", func() state { return state{sel: mujica.DeviceSelection{Microphone: "m1", Speaker: "s2"}} })
devices := mujica.MediaDevices{
Microphones: []mujica.MediaDevice{{ID: "m1", Name: "Built-in Microphone"}, {ID: "m2", Name: "Studio Condenser"}},
Cameras: []mujica.MediaDevice{{ID: "c1", Name: "FaceTime HD Camera"}},
Speakers: []mujica.MediaDevice{{ID: "s1", Name: "Built-in Speakers"}, {ID: "s2", Name: "Headphones"}},
s := ui.Local(c.Root(), "359", func() state { return state{sel: media.DeviceSelection{Microphone: "m1", Speaker: "s2"}} })
devices := media.MediaDevices{
Microphones: []media.MediaDevice{{ID: "m1", Name: "Built-in Microphone"}, {ID: "m2", Name: "Studio Condenser"}},
Cameras: []media.MediaDevice{{ID: "c1", Name: "FaceTime HD Camera"}},
Speakers: []media.MediaDevice{{ID: "s1", Name: "Built-in Speakers"}, {ID: "s2", Name: "Headphones"}},
}
mediaSection(c, "Parameters", func() {
ui.Row(c).Gap(16).Children(func() {
@@ -213,11 +214,11 @@ func deviceDemo(c *ui.Context, log func(string)) {
})
})
if s.noCamera {
devices.Cameras = []mujica.MediaDevice{}
devices.Cameras = []media.MediaDevice{}
}
mediaSection(c, "Live", func() {
ui.Column(c).MaxWidth(480).Children(func() {
if mujica.DeviceSelector(c, &s.sel, devices, mujica.DeviceSelectorOptions{Disabled: s.disabled}).Changed() {
if media.DeviceSelector(c, &s.sel, devices, media.DeviceSelectorOptions{Disabled: s.disabled}).Changed() {
log(fmt.Sprintf("microphone %q, camera %q, speaker %q", s.sel.Microphone, s.sel.Camera, s.sel.Speaker))
}
})
+21 -18
View File
@@ -8,12 +8,15 @@ import (
"github.com/egoist/mygo/ui"
mujica "mujicaui"
"github.com/ZacharyZhang-NY/MujicaUI/data"
"github.com/ZacharyZhang-NY/MujicaUI/input"
"github.com/ZacharyZhang-NY/MujicaUI/media"
"github.com/ZacharyZhang-NY/MujicaUI/navigation"
)
func imageCropperDemo(c *ui.Context, log func(string)) {
type state struct {
crop mujica.ImageCrop
crop media.ImageCrop
aspect int
disabled bool
}
@@ -22,7 +25,7 @@ func imageCropperDemo(c *ui.Context, log func(string)) {
mediaSection(c, "Parameters", func() {
ui.Row(c).Gap(16).AlignItems(ui.Center).Children(func() {
before := s.aspect
mujica.SegmentedControl(c, &s.aspect, []mujica.Segment{{Label: "Free"}, {Label: "1:1"}, {Label: "16:9"}, {Label: "4:3"}}, mujica.SegmentedControlOptions{Label: "Aspect"})
navigation.SegmentedControl(c, &s.aspect, []navigation.Segment{{Label: "Free"}, {Label: "1:1"}, {Label: "16:9"}, {Label: "4:3"}}, navigation.SegmentedControlOptions{Label: "Aspect"})
if s.aspect != before {
s.crop.Rect = image.Rectangle{} // refit to the new ratio
log(fmt.Sprintf("aspect = %v", aspects[s.aspect]))
@@ -31,7 +34,7 @@ func imageCropperDemo(c *ui.Context, log func(string)) {
})
})
mediaSection(c, "Live", func() {
if mujica.ImageCropper(c, mediaPhotos[0], &s.crop, mujica.ImageCropperOptions{Aspect: aspects[s.aspect], Disabled: s.disabled}).Changed() {
if media.ImageCropper(c, mediaPhotos[0], &s.crop, media.ImageCropperOptions{Aspect: aspects[s.aspect], Disabled: s.disabled}).Changed() {
log(fmt.Sprintf("crop %v rotated %d°", s.crop.Rect, s.crop.Rotation))
}
})
@@ -39,17 +42,17 @@ func imageCropperDemo(c *ui.Context, log func(string)) {
func imageAnnotatorDemo(c *ui.Context, log func(string)) {
type state struct {
notes []mujica.Annotation
notes []media.Annotation
disabled bool
}
s := ui.Local(c.Root(), "342", func() state {
return state{notes: []mujica.Annotation{{Kind: mujica.AnnotateRect, From: image.Pt(150, 60), To: image.Pt(330, 230)}}}
return state{notes: []media.Annotation{{Kind: media.AnnotateRect, From: image.Pt(150, 60), To: image.Pt(330, 230)}}}
})
mediaSection(c, "Parameters", func() {
ui.Row(c).Gap(16).AlignItems(ui.Center).Children(func() {
mediaCheck(c, log, &s.disabled, "Disabled")
if mujica.Button(c, "Export JSON", mujica.ButtonOptions{Variant: mujica.Secondary}).Clicked() {
data, err := mujica.ExportAnnotations(s.notes)
if input.Button(c, "Export JSON", input.ButtonOptions{Variant: input.Secondary}).Clicked() {
data, err := media.ExportAnnotations(s.notes)
if err != nil {
log("export failed: " + err.Error())
} else {
@@ -60,7 +63,7 @@ func imageAnnotatorDemo(c *ui.Context, log func(string)) {
})
})
mediaSection(c, "Live", func() {
if mujica.ImageAnnotator(c, mediaBitmaps[1], &s.notes, mujica.ImageAnnotatorOptions{Disabled: s.disabled}).Changed() {
if media.ImageAnnotator(c, mediaBitmaps[1], &s.notes, media.ImageAnnotatorOptions{Disabled: s.disabled}).Changed() {
log(fmt.Sprintf("annotations: %d", len(s.notes)))
}
})
@@ -86,9 +89,9 @@ func imageThumbnailDemo(c *ui.Context, log func(string)) {
if s.failed && i == 2 {
err = errors.New("the file could not be read")
}
o := mujica.ImageThumbnailOptions{Alt: fmt.Sprintf("Scene %d", i+1), Aspect: 4.0 / 3, Width: 120,
o := media.ImageThumbnailOptions{Alt: fmt.Sprintf("Scene %d", i+1), Aspect: 4.0 / 3, Width: 120,
Selected: s.selected == i, Loading: s.loading && i == 1, Err: err, Disabled: s.disabled}
if mujica.ImageThumbnail(c, b, o).Changed() {
if media.ImageThumbnail(c, b, o).Changed() {
s.selected = i
log(fmt.Sprintf("selected scene %d", i+1))
}
@@ -103,7 +106,7 @@ func lightboxDemo(c *ui.Context, log func(string)) {
index int
}
s := ui.Local(c.Root(), "344", func() state { return state{} })
images := []mujica.ViewerImage{
images := []data.ViewerImage{
{Source: mediaBitmaps[0], Alt: "The grand hall at dusk"},
{Source: mediaBitmaps[1], Alt: "The ivory gallery"},
{Source: mediaBitmaps[2], Alt: "The blue chapel"},
@@ -111,14 +114,14 @@ func lightboxDemo(c *ui.Context, log func(string)) {
mediaSection(c, "Live", func() {
ui.Row(c).Gap(12).Children(func() {
for i, im := range images {
if mujica.ImageThumbnail(c, im.Source, mujica.ImageThumbnailOptions{Alt: im.Alt, Aspect: 4.0 / 3}).Changed() {
if media.ImageThumbnail(c, im.Source, media.ImageThumbnailOptions{Alt: im.Alt, Aspect: 4.0 / 3}).Changed() {
s.open, s.index = true, i
log(fmt.Sprintf("open at %d", i+1))
}
}
})
was := s.open
if mujica.Lightbox(c, &s.open, &s.index, images).Changed() {
if media.Lightbox(c, &s.open, &s.index, images).Changed() {
log(fmt.Sprintf("image %d", s.index+1))
}
if was && !s.open {
@@ -133,13 +136,13 @@ func videoStripDemo(c *ui.Context, log func(string)) {
disabled bool
}
s := ui.Local(c.Root(), "347", func() state { return state{} })
frames := make([]mujica.StripFrame, 240)
frames := make([]media.StripFrame, 240)
for i := range frames {
frames[i] = mujica.StripFrame{Image: mediaBitmaps[i%3], At: time.Duration(i) * 5 * time.Second}
frames[i] = media.StripFrame{Image: mediaBitmaps[i%3], At: time.Duration(i) * 5 * time.Second}
}
mediaSection(c, "Parameters", func() { mediaCheck(c, log, &s.disabled, "Disabled") })
mediaSection(c, "Live", func() {
if mujica.VideoThumbnailStrip(c, &s.current, frames, mujica.VideoThumbnailStripOptions{Disabled: s.disabled}).Changed() {
if media.VideoThumbnailStrip(c, &s.current, frames, media.VideoThumbnailStripOptions{Disabled: s.disabled}).Changed() {
log("jump to " + mediaClockLabel(frames[s.current].At))
}
})
@@ -154,7 +157,7 @@ func compareDemo(c *ui.Context, log func(string)) {
mediaSection(c, "Parameters", func() { mediaCheck(c, log, &s.disabled, "Disabled") })
mediaSection(c, "Live", func() {
ui.Column(c).MaxWidth(560).Children(func() {
if mujica.ImageCompare(c, mediaBitmaps[0], mediaBitmaps[1], &s.split, mujica.ImageCompareOptions{Disabled: s.disabled}).Changed() {
if media.ImageCompare(c, mediaBitmaps[0], mediaBitmaps[1], &s.split, media.ImageCompareOptions{Disabled: s.disabled}).Changed() {
log(fmt.Sprintf("split %.0f%%", s.split*100))
}
})
+68 -68
View File
@@ -4,85 +4,85 @@ const messagingCategory = "Messaging & Mail"
var messagingPages = []page{
{ID: "401", Name: "ChannelList", ZhName: "频道列表", Category: messagingCategory, Demo: channelListDemo,
Keys: "Tab 进入;上下方向键切换行;Enter / Space 选择或折叠;分组标题上的左、右方向键折叠、展开。", Code: `groups := []mujica.ChannelGroup{{ID: "ch", Title: "Channels", Channels: []mujica.Channel{
Keys: "Tab 进入;上下方向键切换行;Enter / Space 选择或折叠;分组标题上的左、右方向键折叠、展开。", Code: `groups := []messaging.ChannelGroup{{ID: "ch", Title: "Channels", Channels: []messaging.Channel{
{ID: "general", Name: "general", Unread: 3},
{ID: "rehearsal", Name: "rehearsal", Pinned: true},
}}}
selected := "general"
view := func(c *ui.Context) {
mujica.Use(c, mujica.Settings{})
if mujica.ChannelList(c, &selected, groups, mujica.ChannelListOptions{}).Changed() {
core.Use(c, core.Settings{})
if messaging.ChannelList(c, &selected, groups, messaging.ChannelListOptions{}).Changed() {
_ = selected // show that channel
}
}
ui.Render(view, 400, 300, 1)`},
{ID: "402", Name: "ChatMessage", ZhName: "聊天消息", Category: messagingCategory, Demo: chatMessageDemo,
Keys: "可选文字支持选择复制;插槽控件按 Tab 访问。", Code: `prev := mujica.ChatEntry{ID: "1", Author: "Uika", Time: time.Now(), Text: "Rehearsal at nine."}
m := mujica.ChatEntry{ID: "2", Author: "Uika", Time: time.Now(), Text: "Bring the masks.", Edited: true}
Keys: "可选文字支持选择复制;插槽控件按 Tab 访问。", Code: `prev := messaging.ChatEntry{ID: "1", Author: "Uika", Time: time.Now(), Text: "Rehearsal at nine."}
m := messaging.ChatEntry{ID: "2", Author: "Uika", Time: time.Now(), Text: "Bring the masks.", Edited: true}
view := func(c *ui.Context) {
mujica.Use(c, mujica.Settings{})
mujica.ChatMessage(c, prev, mujica.ChatMessageOptions{})
mujica.ChatMessage(c, m, mujica.ChatMessageOptions{
Continued: mujica.ChatContinues(prev, m),
Reactions: func() { mujica.Tag(c, "👍 2", mujica.TagOptions{}) },
core.Use(c, core.Settings{})
messaging.ChatMessage(c, prev, messaging.ChatMessageOptions{})
messaging.ChatMessage(c, m, messaging.ChatMessageOptions{
Continued: messaging.ChatContinues(prev, m),
Reactions: func() { display.Tag(c, "👍 2", display.TagOptions{}) },
})
}
ui.Render(view, 400, 300, 1)`},
{ID: "403", Name: "UnreadDivider", ZhName: "未读分隔线", Category: messagingCategory, Demo: unreadDividerDemo,
Keys: "展示组件保持自然焦点序列。", Code: `view := func(c *ui.Context) {
mujica.Use(c, mujica.Settings{})
mujica.UnreadDivider(c, 3)
core.Use(c, core.Settings{})
messaging.UnreadDivider(c, 3)
}
ui.Render(view, 400, 300, 1)`},
{ID: "404", Name: "ReadReceipt", ZhName: "已读回执", Category: messagingCategory, Demo: readReceiptDemo,
Keys: "展示组件保持自然焦点序列。", Code: `readers := []mujica.AvatarItem{{Name: "Uika"}, {Name: "Mutsumi"}}
Keys: "展示组件保持自然焦点序列。", Code: `readers := []display.AvatarItem{{Name: "Uika"}, {Name: "Mutsumi"}}
view := func(c *ui.Context) {
mujica.Use(c, mujica.Settings{})
mujica.ReadReceipt(c, mujica.ReceiptRead, mujica.ReadReceiptOptions{Readers: readers})
core.Use(c, core.Settings{})
messaging.ReadReceipt(c, messaging.ReceiptRead, messaging.ReadReceiptOptions{Readers: readers})
}
ui.Render(view, 400, 300, 1)`},
{ID: "405", Name: "MemberList", ZhName: "成员列表", Category: messagingCategory, Demo: memberListDemo,
Keys: "Tab 聚焦列表;上下方向键选择;Home / End 首尾;Enter 或双击打开成员。", Code: `var s mujica.MemberListState
members := []mujica.Member{
{ID: "1", Name: "Sakiko", Presence: mujica.PresenceOnline, Role: "Leader"},
{ID: "2", Name: "Umiri", Presence: mujica.PresenceOffline},
Keys: "Tab 聚焦列表;上下方向键选择;Home / End 首尾;Enter 或双击打开成员。", Code: `var s messaging.MemberListState
members := []messaging.Member{
{ID: "1", Name: "Sakiko", Presence: messaging.PresenceOnline, Role: "Leader"},
{ID: "2", Name: "Umiri", Presence: messaging.PresenceOffline},
}
view := func(c *ui.Context) {
mujica.Use(c, mujica.Settings{})
if r := mujica.MemberList(c, &s, members, mujica.MemberListOptions{}); r.Opened != "" {
core.Use(c, core.Settings{})
if r := messaging.MemberList(c, &s, members, messaging.MemberListOptions{}); r.Opened != "" {
_ = r.Opened // show that member's profile
}
}
ui.Render(view, 400, 300, 1)`},
{ID: "406", Name: "UserProfileCard", ZhName: "用户资料卡", Category: messagingCategory, Demo: userProfileCardDemo,
Keys: "Tab 访问操作插槽;Enter / Space 激活按钮。", Code: `profile := mujica.UserProfile{Name: "Sakiko Togawa", Presence: mujica.PresenceBusy, Role: "Keyboard", Status: "In rehearsal"}
Keys: "Tab 访问操作插槽;Enter / Space 激活按钮。", Code: `profile := messaging.UserProfile{Name: "Sakiko Togawa", Presence: messaging.PresenceBusy, Role: "Keyboard", Status: "In rehearsal"}
view := func(c *ui.Context) {
mujica.Use(c, mujica.Settings{})
mujica.UserProfileCard(c, profile, mujica.UserProfileCardOptions{Actions: func() {
mujica.Button(c, "Message", mujica.ButtonOptions{})
core.Use(c, core.Settings{})
messaging.UserProfileCard(c, profile, messaging.UserProfileCardOptions{Actions: func() {
input.Button(c, "Message", input.ButtonOptions{})
}})
}
ui.Render(view, 400, 300, 1)`},
{ID: "407", Name: "OnlineStatus", ZhName: "在线状态", Category: messagingCategory, Demo: onlineStatusDemo,
Keys: "展示组件保持自然焦点序列。", Code: `view := func(c *ui.Context) {
mujica.Use(c, mujica.Settings{})
mujica.OnlineStatus(c, mujica.PresenceAway, mujica.OnlineStatusOptions{})
core.Use(c, core.Settings{})
messaging.OnlineStatus(c, messaging.PresenceAway, messaging.OnlineStatusOptions{})
}
ui.Render(view, 400, 300, 1)`},
{ID: "408", Name: "StatusSetter", ZhName: "状态设置", Category: messagingCategory, Demo: statusSetterDemo,
Keys: "Tab 访问表情、文字、期限和清除按钮;选择器用方向键移动、Enter 确认、Escape 关闭。", Code: `status := mujica.CustomStatus{Emoji: "🎻", Text: "Rehearsing"}
Keys: "Tab 访问表情、文字、期限和清除按钮;选择器用方向键移动、Enter 确认、Escape 关闭。", Code: `status := messaging.CustomStatus{Emoji: "🎻", Text: "Rehearsing"}
view := func(c *ui.Context) {
mujica.Use(c, mujica.Settings{})
if mujica.StatusSetter(c, &status, mujica.StatusSetterOptions{}).Changed() {
core.Use(c, core.Settings{})
if messaging.StatusSetter(c, &status, messaging.StatusSetterOptions{}).Changed() {
_ = status // publish it; clear it once status.Until passes
}
}
ui.Render(view, 400, 300, 1)`},
{ID: "409", Name: "CallControls", ZhName: "通话控制", Category: messagingCategory, Demo: callControlsDemo,
Keys: "Tab 进入工具栏;左右方向键移动;Enter / Space 激活。Cmd/Ctrl+D 静音,Cmd/Ctrl+E 摄像头,Cmd/Ctrl+Shift+S 共享,Cmd/Ctrl+Shift+H 挂断。", Code: `st := mujica.CallState{}
Keys: "Tab 进入工具栏;左右方向键移动;Enter / Space 激活。Cmd/Ctrl+D 静音,Cmd/Ctrl+E 摄像头,Cmd/Ctrl+Shift+S 共享,Cmd/Ctrl+Shift+H 挂断。", Code: `st := messaging.CallState{}
view := func(c *ui.Context) {
mujica.Use(c, mujica.Settings{})
mujica.CallControls(c, st, mujica.CallControlsOptions{
core.Use(c, core.Settings{})
messaging.CallControls(c, st, messaging.CallControlsOptions{
OnMute: func(on bool) { st.Muted = on },
OnCamera: func(off bool) { st.CameraOff = off },
OnShare: func(on bool) { st.Sharing = on },
@@ -91,20 +91,20 @@ view := func(c *ui.Context) {
}
ui.Render(view, 400, 300, 1)`},
{ID: "410", Name: "VideoCallGrid", ZhName: "视频通话宫格", Category: messagingCategory, Demo: videoCallGridDemo,
Keys: "Tab 进入宫格;方向键移动参与者焦点并读取状态。", Code: `people := []mujica.CallParticipant{
Keys: "Tab 进入宫格;方向键移动参与者焦点并读取状态。", Code: `people := []messaging.CallParticipant{
{ID: "1", Name: "Sakiko", Speaking: true}, // Frame: the latest decoded frame
{ID: "2", Name: "Uika", Muted: true},
}
view := func(c *ui.Context) {
mujica.Use(c, mujica.Settings{})
mujica.VideoCallGrid(c, people, mujica.VideoCallGridOptions{})
core.Use(c, core.Settings{})
messaging.VideoCallGrid(c, people, messaging.VideoCallGridOptions{})
}
ui.Render(view, 400, 300, 1)`},
{ID: "411", Name: "IncomingCallDialog", ZhName: "来电对话框", Category: messagingCategory, Demo: incomingCallDialogDemo,
Keys: "默认焦点为拒绝;Tab 循环访问操作;Enter / Space 激活;Escape 拒绝;关闭后恢复焦点。", Code: `ringing := true
view := func(c *ui.Context) {
mujica.Use(c, mujica.Settings{})
mujica.IncomingCallDialog(c, &ringing, mujica.IncomingCall{Caller: "Sakiko", Video: true}, mujica.IncomingCallOptions{
core.Use(c, core.Settings{})
messaging.IncomingCallDialog(c, &ringing, messaging.IncomingCall{Caller: "Sakiko", Video: true}, messaging.IncomingCallOptions{
OnAccept: func(video bool) {}, // join, with the camera on when video
OnDecline: func() {},
})
@@ -114,17 +114,17 @@ ui.Render(view, 400, 300, 1)`},
Keys: "Tab 访问搜索、肤色、分类、表情;方向键在组内移动;Enter / Space 选择。", Code: `var recent []string
tone := 0
view := func(c *ui.Context) {
mujica.Use(c, mujica.Settings{})
if r := mujica.EmojiPicker(c, mujica.EmojiPickerOptions{Recent: &recent, Tone: &tone}); r.Picked != "" {
core.Use(c, core.Settings{})
if r := messaging.EmojiPicker(c, messaging.EmojiPickerOptions{Recent: &recent, Tone: &tone}); r.Picked != "" {
_ = r.Picked // insert it
}
}
ui.Render(view, 400, 300, 1)`},
{ID: "413", Name: "PinnedMessages", ZhName: "置顶消息", Category: messagingCategory, Demo: pinnedMessagesDemo,
Keys: "Tab 访问跳转与取消置顶;方向键移动;Enter / Space 激活。", Code: `pins := []mujica.ChatEntry{{ID: "a", Author: "Sakiko", Text: "Rehearsal moves to nine."}}
Keys: "Tab 访问跳转与取消置顶;方向键移动;Enter / Space 激活。", Code: `pins := []messaging.ChatEntry{{ID: "a", Author: "Sakiko", Text: "Rehearsal moves to nine."}}
view := func(c *ui.Context) {
mujica.Use(c, mujica.Settings{})
r := mujica.PinnedMessages(c, pins, mujica.PinnedMessagesOptions{})
core.Use(c, core.Settings{})
r := messaging.PinnedMessages(c, pins, messaging.PinnedMessagesOptions{})
if r.Unpinned != "" {
pins = pins[:0] // unpin it
}
@@ -132,67 +132,67 @@ view := func(c *ui.Context) {
ui.Render(view, 400, 300, 1)`},
{ID: "414", Name: "ThreadPanel", ZhName: "话题侧栏", Category: messagingCategory, Demo: threadPanelDemo,
Keys: "Tab 访问关闭、回复和发送;Enter 发送回复;Escape 关闭面板。", Code: `open := true
root := mujica.ChatEntry{ID: "r", Author: "Sakiko", Text: "Set list for Friday?"}
var replies []mujica.ChatEntry
root := messaging.ChatEntry{ID: "r", Author: "Sakiko", Text: "Set list for Friday?"}
var replies []messaging.ChatEntry
view := func(c *ui.Context) {
mujica.Use(c, mujica.Settings{})
if r := mujica.ThreadPanel(c, &open, root, replies, mujica.ThreadPanelOptions{}); r.Sent != "" {
replies = append(replies, mujica.ChatEntry{ID: r.Sent, Author: "Me", Text: r.Sent, Own: true})
core.Use(c, core.Settings{})
if r := messaging.ThreadPanel(c, &open, root, replies, messaging.ThreadPanelOptions{}); r.Sent != "" {
replies = append(replies, messaging.ChatEntry{ID: r.Sent, Author: "Me", Text: r.Sent, Own: true})
}
}
ui.Render(view, 400, 300, 1)`},
{ID: "415", Name: "MailList", ZhName: "邮件列表", Category: messagingCategory, Demo: mailListDemo,
Keys: "上下方向键、Home / End 移动选择;Enter 或双击打开;S 请求星标;Cmd/Ctrl+A 全选;Cmd/Ctrl 点击切换单项,Shift 点击选择范围。", Code: `var s mujica.ListState[string]
mails := []mujica.MailItem{{ID: "1", From: "Sakiko", Subject: "Second act", Unread: true, Attachments: 1}}
Keys: "上下方向键、Home / End 移动选择;Enter 或双击打开;S 请求星标;Cmd/Ctrl+A 全选;Cmd/Ctrl 点击切换单项,Shift 点击选择范围。", Code: `var s data.ListState[string]
mails := []messaging.MailItem{{ID: "1", From: "Sakiko", Subject: "Second act", Unread: true, Attachments: 1}}
view := func(c *ui.Context) {
mujica.Use(c, mujica.Settings{})
if r := mujica.MailList(c, &s, mails, mujica.MailListOptions{}); r.Starred != "" {
core.Use(c, core.Settings{})
if r := messaging.MailList(c, &s, mails, messaging.MailListOptions{}); r.Starred != "" {
mails[0].Starred = !mails[0].Starred
}
}
ui.Render(view, 400, 300, 1)`},
{ID: "416", Name: "MailReader", ZhName: "邮件阅读器", Category: messagingCategory, Demo: mailReaderDemo,
Keys: "Tab 访问链接、引用开关和附件;Enter / Space 激活;正文支持选择复制。", Code: `m := mujica.Mail{From: "Sakiko", Subject: "Second act", Body: "The score is attached.\n> Can you send it?",
Attachments: []mujica.MailAttachment{{Name: "score.pdf", Size: 2048}}}
Keys: "Tab 访问链接、引用开关和附件;Enter / Space 激活;正文支持选择复制。", Code: `m := messaging.Mail{From: "Sakiko", Subject: "Second act", Body: "The score is attached.\n> Can you send it?",
Attachments: []messaging.MailAttachment{{Name: "score.pdf", Size: 2048}}}
view := func(c *ui.Context) {
mujica.Use(c, mujica.Settings{})
if r := mujica.MailReader(c, m); r.Link != "" {
core.Use(c, core.Settings{})
if r := messaging.MailReader(c, m); r.Link != "" {
c.OpenURL(r.Link)
}
}
ui.Render(view, 400, 300, 1)`},
{ID: "417", Name: "MailComposer", ZhName: "邮件编辑器", Category: messagingCategory, Demo: mailComposerDemo,
Keys: "Tab 访问字段与操作;Cmd/Ctrl+S 保存草稿;组件内 Cmd/Ctrl+Enter 发送;正文 Enter 换行。", Code: `draft := mujica.MailDraft{To: []string{"uika@example.com"}}
Keys: "Tab 访问字段与操作;Cmd/Ctrl+S 保存草稿;组件内 Cmd/Ctrl+Enter 发送;正文 Enter 换行。", Code: `draft := messaging.MailDraft{To: []string{"uika@example.com"}}
view := func(c *ui.Context) {
mujica.Use(c, mujica.Settings{})
r := mujica.MailComposer(c, &draft, mujica.MailComposerOptions{
OnSaveDraft: func(d mujica.MailDraft) {}, // store d
core.Use(c, core.Settings{})
r := messaging.MailComposer(c, &draft, messaging.MailComposerOptions{
OnSaveDraft: func(d messaging.MailDraft) {}, // store d
})
if r.Sent {
draft = mujica.MailDraft{}
draft = messaging.MailDraft{}
}
}
ui.Render(view, 400, 300, 1)`},
{ID: "418", Name: "RecipientInput", ZhName: "收件人输入", Category: messagingCategory, Demo: recipientInputDemo,
Keys: "Enter 或分隔符提交标签;空输入 Backspace 删除末项;上下方向键移动联系人建议,Enter 选择,Escape 收起建议。", Code: `var to []string
contacts := []mujica.Contact{{Name: "Uika Misumi", Email: "uika@example.com"}}
contacts := []messaging.Contact{{Name: "Uika Misumi", Email: "uika@example.com"}}
view := func(c *ui.Context) {
mujica.Use(c, mujica.Settings{})
mujica.RecipientInput(c, &to, mujica.RecipientInputOptions{Contacts: contacts, Label: "To"})
core.Use(c, core.Settings{})
messaging.RecipientInput(c, &to, messaging.RecipientInputOptions{Contacts: contacts, Label: "To"})
}
ui.Render(view, 400, 300, 1)`},
{ID: "419", Name: "QuotedText", ZhName: "引用文字", Category: messagingCategory, Demo: quotedTextDemo,
Keys: "Tab 聚焦开关;Enter / Space 展开或折叠;展开内容支持选择复制。", Code: `open := false
view := func(c *ui.Context) {
mujica.Use(c, mujica.Settings{})
mujica.QuotedText(c, "> Can you send the score?", &open)
core.Use(c, core.Settings{})
messaging.QuotedText(c, "> Can you send the score?", &open)
}
ui.Render(view, 400, 300, 1)`},
{ID: "420", Name: "SnoozePicker", ZhName: "稍后提醒", Category: messagingCategory, Demo: snoozePickerDemo,
Keys: "Tab 进入;上下方向键选择快捷项;Enter / Space 激活;自定义时间沿用 DateTimePicker 的输入、确认和取消操作。", Code: `var until time.Time
view := func(c *ui.Context) {
mujica.Use(c, mujica.Settings{})
if mujica.SnoozePicker(c, &until, mujica.SnoozePickerOptions{}).Changed() {
core.Use(c, core.Settings{})
if messaging.SnoozePicker(c, &until, messaging.SnoozePickerOptions{}).Changed() {
_ = until // hide the mail until then
}
}
+11 -9
View File
@@ -4,22 +4,24 @@ import (
"fmt"
"github.com/egoist/mygo/ui"
mujica "mujicaui"
"github.com/ZacharyZhang-NY/MujicaUI/input"
"github.com/ZacharyZhang-NY/MujicaUI/messaging"
)
func callControlsDemo(c *ui.Context, log func(string)) {
type state struct {
call mujica.CallState
call messaging.CallState
disabled bool
ended bool
}
s := ui.Local(c.Root(), "409", func() state { return state{} })
mediaCheck(c, log, &s.disabled, "Disabled")
if mujica.Button(c, "Reconnect", mujica.ButtonOptions{Variant: mujica.Secondary}).Clicked() {
if input.Button(c, "Reconnect", input.ButtonOptions{Variant: input.Secondary}).Clicked() {
s.ended = false
log("call joined")
}
mujica.CallControls(c, s.call, mujica.CallControlsOptions{
messaging.CallControls(c, s.call, messaging.CallControlsOptions{
Disabled: s.disabled || s.ended,
OnMute: func(on bool) { s.call.Muted = on; log(fmt.Sprintf("mute %v", on)) },
OnCamera: func(off bool) { s.call.CameraOff = off; log(fmt.Sprintf("camera off %v", off)) },
@@ -39,10 +41,10 @@ func videoCallGridDemo(c *ui.Context, log func(string)) {
mediaCheck(c, log, &s.muted, "Muted")
mediaCheck(c, log, &s.cameraOff, "Camera off")
mediaCheck(c, log, &s.frame, "Caller-provided frame")
if mujica.NumberInput(c, &s.columns, mujica.NumberInputOptions{Min: 0, Max: 3, Step: 1, Label: "Columns (0 = auto)"}).Changed() {
if input.NumberInput(c, &s.columns, input.NumberInputOptions{Min: 0, Max: 3, Step: 1, Label: "Columns (0 = auto)"}).Changed() {
log(fmt.Sprintf("columns %.0f", s.columns))
}
people := []mujica.CallParticipant{
people := []messaging.CallParticipant{
{ID: "1", Name: "Sakiko", Speaking: s.speaking, Muted: s.muted, CameraOff: s.cameraOff},
{ID: "2", Name: "Uika", Muted: true}, {ID: "3", Name: "Mutsumi", CameraOff: true}, {ID: "4", Name: "Umiri"},
}
@@ -50,7 +52,7 @@ func videoCallGridDemo(c *ui.Context, log func(string)) {
people[0].Frame = mediaBitmaps[0]
people[3].Frame = mediaBitmaps[1]
}
mujica.VideoCallGrid(c, people, mujica.VideoCallGridOptions{Columns: int(s.columns)})
messaging.VideoCallGrid(c, people, messaging.VideoCallGridOptions{Columns: int(s.columns)})
}
func incomingCallDialogDemo(c *ui.Context, log func(string)) {
@@ -62,11 +64,11 @@ func incomingCallDialogDemo(c *ui.Context, log func(string)) {
mediaCheck(c, log, &s.video, "Video call")
mediaCheck(c, log, &s.focusAccept, "Focus Accept")
mediaCheck(c, log, &s.disabled, "Disabled")
if mujica.Button(c, "Incoming call", mujica.ButtonOptions{Disabled: s.disabled}).Clicked() {
if input.Button(c, "Incoming call", input.ButtonOptions{Disabled: s.disabled}).Clicked() {
s.open = true
log("incoming call")
}
mujica.IncomingCallDialog(c, &s.open, mujica.IncomingCall{Caller: "Sakiko Togawa", Video: s.video}, mujica.IncomingCallOptions{
messaging.IncomingCallDialog(c, &s.open, messaging.IncomingCall{Caller: "Sakiko Togawa", Video: s.video}, messaging.IncomingCallOptions{
Disabled: s.disabled, FocusAccept: s.focusAccept,
OnAccept: func(video bool) { s.result = fmt.Sprintf("Accepted, video: %v", video); log(s.result) },
OnDecline: func() { s.result = "Declined"; log(s.result) },
+33 -29
View File
@@ -6,7 +6,11 @@ import (
"time"
"github.com/egoist/mygo/ui"
mujica "mujicaui"
"github.com/ZacharyZhang-NY/MujicaUI/chat"
"github.com/ZacharyZhang-NY/MujicaUI/display"
"github.com/ZacharyZhang-NY/MujicaUI/input"
"github.com/ZacharyZhang-NY/MujicaUI/messaging"
)
func chatMessageDemo(c *ui.Context, log func(string)) {
@@ -21,54 +25,54 @@ func chatMessageDemo(c *ui.Context, log func(string)) {
mediaCheck(c, log, &s.reply, "Reply slot")
mediaCheck(c, log, &s.reactions, "Reaction slot")
at := time.Date(2026, 10, 5, 21, 7, 0, 0, time.Local)
mujica.ChatMessage(c, mujica.ChatEntry{Author: "Uika", Time: at, Text: "Rehearsal at nine.", Own: s.own}, mujica.ChatMessageOptions{})
opts := mujica.ChatMessageOptions{Continued: s.continued}
messaging.ChatMessage(c, messaging.ChatEntry{Author: "Uika", Time: at, Text: "Rehearsal at nine.", Own: s.own}, messaging.ChatMessageOptions{})
opts := messaging.ChatMessageOptions{Continued: s.continued}
if s.reply {
opts.Reply = func() {
if mujica.QuoteReply(c, "Sakiko", "When do we start?").Clicked() {
if chat.QuoteReply(c, "Sakiko", "When do we start?").Clicked() {
log("jump to quoted message")
}
}
}
if s.reactions {
opts.Reactions = func() {
if mujica.Toggle(c, &s.reacted, "Like", mujica.ToggleOptions{}).Changed() {
if input.Toggle(c, &s.reacted, "Like", input.ToggleOptions{}).Changed() {
log(fmt.Sprintf("reaction %v", s.reacted))
}
}
}
mujica.ChatMessage(c, mujica.ChatEntry{Author: "Uika", Time: at.Add(time.Minute), Text: "Bring the masks.", Own: s.own, Edited: s.edited}, opts)
messaging.ChatMessage(c, messaging.ChatEntry{Author: "Uika", Time: at.Add(time.Minute), Text: "Bring the masks.", Own: s.own, Edited: s.edited}, opts)
}
func unreadDividerDemo(c *ui.Context, log func(string)) {
count := ui.Local(c.Root(), "403", func() float64 { return 3 })
if mujica.NumberInput(c, count, mujica.NumberInputOptions{Min: 0, Max: 999, Step: 1, Label: "Unread count"}).Changed() {
if input.NumberInput(c, count, input.NumberInputOptions{Min: 0, Max: 999, Step: 1, Label: "Unread count"}).Changed() {
log(fmt.Sprintf("unread %.0f", *count))
}
mujica.UnreadDivider(c, int(*count))
inputSection(c, "Zero count", func() { mujica.UnreadDivider(c, 0) })
messaging.UnreadDivider(c, int(*count))
inputSection(c, "Zero count", func() { messaging.UnreadDivider(c, 0) })
}
func readReceiptDemo(c *ui.Context, log func(string)) {
type state struct {
receipt mujica.ReceiptState
receipt messaging.ReceiptState
readers bool
max float64
}
s := ui.Local(c.Root(), "404", func() state { return state{receipt: mujica.ReceiptRead, readers: true, max: 3} })
items := []mujica.SelectOption[mujica.ReceiptState]{{Value: mujica.ReceiptSent, Label: "Sent"}, {Value: mujica.ReceiptDelivered, Label: "Delivered"}, {Value: mujica.ReceiptRead, Label: "Read"}}
if mujica.Select(c, &s.receipt, items, mujica.SelectOptions{Label: "Receipt"}).Changed() {
s := ui.Local(c.Root(), "404", func() state { return state{receipt: messaging.ReceiptRead, readers: true, max: 3} })
items := []input.SelectOption[messaging.ReceiptState]{{Value: messaging.ReceiptSent, Label: "Sent"}, {Value: messaging.ReceiptDelivered, Label: "Delivered"}, {Value: messaging.ReceiptRead, Label: "Read"}}
if input.Select(c, &s.receipt, items, input.SelectOptions{Label: "Receipt"}).Changed() {
log(fmt.Sprintf("receipt %d", s.receipt))
}
mediaCheck(c, log, &s.readers, "Reader avatars")
if mujica.NumberInput(c, &s.max, mujica.NumberInputOptions{Min: 1, Max: 4, Step: 1, Label: "Max avatars"}).Changed() {
if input.NumberInput(c, &s.max, input.NumberInputOptions{Min: 1, Max: 4, Step: 1, Label: "Max avatars"}).Changed() {
log(fmt.Sprintf("max %.0f", s.max))
}
opts := mujica.ReadReceiptOptions{Max: int(s.max)}
opts := messaging.ReadReceiptOptions{Max: int(s.max)}
if s.readers {
opts.Readers = []mujica.AvatarItem{{Name: "Uika"}, {Name: "Mutsumi"}, {Name: "Umiri"}, {Name: "Nyamu"}}
opts.Readers = []display.AvatarItem{{Name: "Uika"}, {Name: "Mutsumi"}, {Name: "Umiri"}, {Name: "Nyamu"}}
}
mujica.ReadReceipt(c, s.receipt, opts)
messaging.ReadReceipt(c, s.receipt, opts)
}
func emojiPickerDemo(c *ui.Context, log func(string)) {
@@ -80,7 +84,7 @@ func emojiPickerDemo(c *ui.Context, log func(string)) {
}
s := ui.Local(c.Root(), "412", func() state { return state{} })
mediaCheck(c, log, &s.disabled, "Disabled")
if r := mujica.EmojiPicker(c, mujica.EmojiPickerOptions{Recent: &s.recent, Tone: &s.tone, Disabled: s.disabled}); r.Picked != "" {
if r := messaging.EmojiPicker(c, messaging.EmojiPickerOptions{Recent: &s.recent, Tone: &s.tone, Disabled: s.disabled}); r.Picked != "" {
s.picked = r.Picked
log("emoji " + r.Picked)
}
@@ -89,26 +93,26 @@ func emojiPickerDemo(c *ui.Context, log func(string)) {
func pinnedMessagesDemo(c *ui.Context, log func(string)) {
type state struct {
pins []mujica.ChatEntry
pins []messaging.ChatEntry
disabled bool
jump string
}
newPins := func() []mujica.ChatEntry {
return []mujica.ChatEntry{{ID: "a", Author: "Sakiko", Text: "Rehearsal moves to nine."}, {ID: "b", Author: "Uika", Text: "Masks are in the hall."}}
newPins := func() []messaging.ChatEntry {
return []messaging.ChatEntry{{ID: "a", Author: "Sakiko", Text: "Rehearsal moves to nine."}, {ID: "b", Author: "Uika", Text: "Masks are in the hall."}}
}
s := ui.Local(c.Root(), "413", func() state { return state{pins: newPins()} })
mediaCheck(c, log, &s.disabled, "Disabled")
if mujica.Button(c, "Restore pins", mujica.ButtonOptions{Variant: mujica.Secondary}).Clicked() {
if input.Button(c, "Restore pins", input.ButtonOptions{Variant: input.Secondary}).Clicked() {
s.pins = newPins()
log("pins restored")
}
r := mujica.PinnedMessages(c, s.pins, mujica.PinnedMessagesOptions{Disabled: s.disabled, Height: 180})
r := messaging.PinnedMessages(c, s.pins, messaging.PinnedMessagesOptions{Disabled: s.disabled, Height: 180})
if r.Jump != "" {
s.jump = r.Jump
log("jump " + r.Jump)
}
if r.Unpinned != "" {
s.pins = slices.DeleteFunc(slices.Clone(s.pins), func(m mujica.ChatEntry) bool { return m.ID == r.Unpinned })
s.pins = slices.DeleteFunc(slices.Clone(s.pins), func(m messaging.ChatEntry) bool { return m.ID == r.Unpinned })
log("unpin " + r.Unpinned)
}
ui.Text(c, "Jump target: "+s.jump)
@@ -117,21 +121,21 @@ func pinnedMessagesDemo(c *ui.Context, log func(string)) {
func threadPanelDemo(c *ui.Context, log func(string)) {
type state struct {
open, disabled bool
replies []mujica.ChatEntry
replies []messaging.ChatEntry
}
s := ui.Local(c.Root(), "414", func() state {
return state{open: true, replies: []mujica.ChatEntry{{ID: "1", Author: "Uika", Text: "Overture first."}}}
return state{open: true, replies: []messaging.ChatEntry{{ID: "1", Author: "Uika", Text: "Overture first."}}}
})
mediaCheck(c, log, &s.disabled, "Disabled")
if mujica.Button(c, "Open thread", mujica.ButtonOptions{Disabled: s.disabled}).Clicked() {
if input.Button(c, "Open thread", input.ButtonOptions{Disabled: s.disabled}).Clicked() {
s.open = true
log("thread opened")
}
before := s.open
ui.Row(c).Height(420).Children(func() {
r := mujica.ThreadPanel(c, &s.open, mujica.ChatEntry{ID: "root", Author: "Sakiko", Text: "Set list for Friday?"}, s.replies, mujica.ThreadPanelOptions{Disabled: s.disabled})
r := messaging.ThreadPanel(c, &s.open, messaging.ChatEntry{ID: "root", Author: "Sakiko", Text: "Set list for Friday?"}, s.replies, messaging.ThreadPanelOptions{Disabled: s.disabled})
if r.Sent != "" {
s.replies = append(s.replies, mujica.ChatEntry{ID: fmt.Sprint(len(s.replies) + 1), Author: "Me", Text: r.Sent, Own: true})
s.replies = append(s.replies, messaging.ChatEntry{ID: fmt.Sprint(len(s.replies) + 1), Author: "Me", Text: r.Sent, Own: true})
log("reply sent")
}
})
+30 -27
View File
@@ -5,18 +5,21 @@ import (
"time"
"github.com/egoist/mygo/ui"
mujica "mujicaui"
"github.com/ZacharyZhang-NY/MujicaUI/data"
"github.com/ZacharyZhang-NY/MujicaUI/input"
"github.com/ZacharyZhang-NY/MujicaUI/messaging"
)
func mailListDemo(c *ui.Context, log func(string)) {
type state struct {
list mujica.ListState[string]
mails []mujica.MailItem
list data.ListState[string]
mails []messaging.MailItem
disabled, empty bool
opened string
}
s := ui.Local(c.Root(), "415", func() state {
return state{mails: []mujica.MailItem{
return state{mails: []messaging.MailItem{
{ID: "1", From: "Sakiko", Subject: "Second act", Snippet: "The score is attached.", Unread: true, Attachments: 2},
{ID: "2", From: "Uika", Subject: "Costumes", Snippet: "Fittings on Thursday.", Starred: true},
{ID: "3", From: "Umiri", Subject: "Venue", Snippet: "The hall is booked."},
@@ -24,10 +27,10 @@ func mailListDemo(c *ui.Context, log func(string)) {
})
mediaCheck(c, log, &s.disabled, "Disabled")
mediaCheck(c, log, &s.empty, "Empty")
if mujica.Button(c, "Load 100,000 mails", mujica.ButtonOptions{Variant: mujica.Secondary}).Clicked() {
s.mails = make([]mujica.MailItem, 100000)
if input.Button(c, "Load 100,000 mails", input.ButtonOptions{Variant: input.Secondary}).Clicked() {
s.mails = make([]messaging.MailItem, 100000)
for i := range s.mails {
s.mails[i] = mujica.MailItem{ID: fmt.Sprint(i), From: "Sender", Subject: fmt.Sprintf("Mail %d", i), Unread: i%2 == 0}
s.mails[i] = messaging.MailItem{ID: fmt.Sprint(i), From: "Sender", Subject: fmt.Sprintf("Mail %d", i), Unread: i%2 == 0}
}
log("loaded 100,000 mails")
}
@@ -35,7 +38,7 @@ func mailListDemo(c *ui.Context, log func(string)) {
if s.empty {
mails = nil
}
r := mujica.MailList(c, &s.list, mails, mujica.MailListOptions{Disabled: s.disabled, Height: 240})
r := messaging.MailList(c, &s.list, mails, messaging.MailListOptions{Disabled: s.disabled, Height: 240})
if r.Changed() {
log(fmt.Sprintf("selected %d mails", s.list.Selection.Len()))
}
@@ -67,16 +70,16 @@ func mailReaderDemo(c *ui.Context, log func(string)) {
mediaCheck(c, log, &s.markdown, "Safe Markdown")
mediaCheck(c, log, &s.attachments, "Attachments")
mediaCheck(c, log, &s.disabled, "Disabled actions")
m := mujica.Mail{From: "Sakiko Togawa", To: []string{"Uika Misumi"}, Cc: []string{"Umiri Yahata"}, Subject: "Second act", Date: time.Date(2026, 10, 5, 21, 7, 0, 0, time.Local), Body: "The score is attached.\n> Can you send it tonight?"}
m := messaging.Mail{From: "Sakiko Togawa", To: []string{"Uika Misumi"}, Cc: []string{"Umiri Yahata"}, Subject: "Second act", Date: time.Date(2026, 10, 5, 21, 7, 0, 0, time.Local), Body: "The score is attached.\n> Can you send it tonight?"}
if s.markdown {
m.Format = mujica.MailMarkdown
m.Format = messaging.MailMarkdown
m.Body = "**Rehearsal** at nine. [Venue](https://example.com)\n\n<script>alert(1)</script>"
}
if s.attachments {
m.Attachments = []mujica.MailAttachment{{Name: "score.pdf", Size: 2048}}
m.Attachments = []messaging.MailAttachment{{Name: "score.pdf", Size: 2048}}
}
ui.Column(c).Disabled(s.disabled).Children(func() {
r := mujica.MailReader(c, m)
r := messaging.MailReader(c, m)
if r.Link != "" {
log("link " + r.Link)
}
@@ -91,32 +94,32 @@ func mailReaderDemo(c *ui.Context, log func(string)) {
func mailComposerDemo(c *ui.Context, log func(string)) {
type state struct {
draft mujica.MailDraft
draft messaging.MailDraft
disabled bool
saved string
}
s := ui.Local(c.Root(), "417", func() state { return state{draft: mujica.MailDraft{To: []string{"uika@example.com"}}} })
s := ui.Local(c.Root(), "417", func() state { return state{draft: messaging.MailDraft{To: []string{"uika@example.com"}}} })
mediaCheck(c, log, &s.disabled, "Disabled")
r := mujica.MailComposer(c, &s.draft, mujica.MailComposerOptions{
r := messaging.MailComposer(c, &s.draft, messaging.MailComposerOptions{
Disabled: s.disabled, Contacts: messagingContacts,
OnSaveDraft: func(d mujica.MailDraft) { s.saved = d.Subject; log("draft saved") },
OnSaveDraft: func(d messaging.MailDraft) { s.saved = d.Subject; log("draft saved") },
OnAttach: func() {
s.draft.Attachments = append(s.draft.Attachments, mujica.MailAttachment{Name: fmt.Sprintf("score-%d.pdf", len(s.draft.Attachments)+1), Size: 2048})
s.draft.Attachments = append(s.draft.Attachments, messaging.MailAttachment{Name: fmt.Sprintf("score-%d.pdf", len(s.draft.Attachments)+1), Size: 2048})
log("attachment added")
},
})
if r.Sent {
log("mail sent")
s.draft = mujica.MailDraft{}
s.draft = messaging.MailDraft{}
}
if r.Discarded {
log("draft discarded")
s.draft = mujica.MailDraft{}
s.draft = messaging.MailDraft{}
}
ui.Text(c, "Saved subject: "+s.saved)
}
var messagingContacts = []mujica.Contact{{Name: "Uika Misumi", Email: "uika@example.com"}, {Name: "Umiri Yahata", Email: "umiri@example.com"}, {Name: "Sakiko Togawa", Email: "sakiko@example.com"}}
var messagingContacts = []messaging.Contact{{Name: "Uika Misumi", Email: "uika@example.com"}, {Name: "Umiri Yahata", Email: "umiri@example.com"}, {Name: "Sakiko Togawa", Email: "sakiko@example.com"}}
func recipientInputDemo(c *ui.Context, log func(string)) {
type state struct {
@@ -130,10 +133,10 @@ func recipientInputDemo(c *ui.Context, log func(string)) {
if !s.contacts {
contacts = nil
}
if mujica.RecipientInput(c, &s.to, mujica.RecipientInputOptions{Contacts: contacts, Label: "Recipients", Disabled: s.disabled}).Changed() {
if messaging.RecipientInput(c, &s.to, messaging.RecipientInputOptions{Contacts: contacts, Label: "Recipients", Disabled: s.disabled}).Changed() {
log(fmt.Sprintf("recipients %d", len(s.to)))
}
if mujica.Button(c, "Clear recipients", mujica.ButtonOptions{Variant: mujica.Secondary, Disabled: s.disabled}).Clicked() {
if input.Button(c, "Clear recipients", input.ButtonOptions{Variant: input.Secondary, Disabled: s.disabled}).Clicked() {
s.to = nil
log("recipients cleared")
}
@@ -147,11 +150,11 @@ func quotedTextDemo(c *ui.Context, log func(string)) {
}
s := ui.Local(c.Root(), "419", func() state { return state{text: "> Can you send the score?\n> Thanks."} })
mediaCheck(c, log, &s.disabled, "Disabled")
if mujica.TextArea(c, &s.text, mujica.TextAreaOptions{Label: "Quoted source", MinHeight: 80}).Changed() {
if input.TextArea(c, &s.text, input.TextAreaOptions{Label: "Quoted source", MinHeight: 80}).Changed() {
log("quote source edited")
}
ui.Column(c).Disabled(s.disabled).Children(func() {
if mujica.QuotedText(c, s.text, &s.open).Changed() {
if messaging.QuotedText(c, s.text, &s.open).Changed() {
log(fmt.Sprintf("quote expanded %v", s.open))
}
})
@@ -165,13 +168,13 @@ func snoozePickerDemo(c *ui.Context, log func(string)) {
}
s := ui.Local(c.Root(), "420", func() state { return state{morning: 9} })
mediaCheck(c, log, &s.disabled, "Disabled")
if mujica.NumberInput(c, &s.morning, mujica.NumberInputOptions{Min: 1, Max: 23, Step: 1, Label: "Morning hour"}).Changed() {
if input.NumberInput(c, &s.morning, input.NumberInputOptions{Min: 1, Max: 23, Step: 1, Label: "Morning hour"}).Changed() {
log(fmt.Sprintf("morning %.0f", s.morning))
}
if mujica.SnoozePicker(c, &s.until, mujica.SnoozePickerOptions{Disabled: s.disabled, MorningHour: int(s.morning)}).Changed() {
if messaging.SnoozePicker(c, &s.until, messaging.SnoozePickerOptions{Disabled: s.disabled, MorningHour: int(s.morning)}).Changed() {
log("snooze " + s.until.Format(time.RFC3339))
}
if mujica.Button(c, "Clear snooze", mujica.ButtonOptions{Variant: mujica.Secondary, Disabled: s.disabled}).Clicked() {
if input.Button(c, "Clear snooze", input.ButtonOptions{Variant: input.Secondary, Disabled: s.disabled}).Clicked() {
s.until = time.Time{}
log("snooze cleared")
}
+36 -34
View File
@@ -5,7 +5,9 @@ import (
"slices"
"github.com/egoist/mygo/ui"
mujica "mujicaui"
"github.com/ZacharyZhang-NY/MujicaUI/input"
"github.com/ZacharyZhang-NY/MujicaUI/messaging"
)
func channelListDemo(c *ui.Context, log func(string)) {
@@ -13,15 +15,15 @@ func channelListDemo(c *ui.Context, log func(string)) {
selected string
disabled bool
revision int
groups []mujica.ChannelGroup
groups []messaging.ChannelGroup
}
s := ui.Local(c.Root(), "401", func() state {
return state{groups: []mujica.ChannelGroup{
{ID: "channels", Title: "Channels", Channels: []mujica.Channel{
return state{groups: []messaging.ChannelGroup{
{ID: "channels", Title: "Channels", Channels: []messaging.Channel{
{ID: "general", Name: "general", Unread: 3}, {ID: "rehearsal", Name: "rehearsal", Pinned: true},
{ID: "random", Name: "random", Unread: 120, Muted: true},
}},
{ID: "direct", Title: "Direct messages", Channels: []mujica.Channel{{ID: "uika", Name: "Uika", Direct: true, Presence: mujica.PresenceOnline}}},
{ID: "direct", Title: "Direct messages", Channels: []messaging.Channel{{ID: "uika", Name: "Uika", Direct: true, Presence: messaging.PresenceOnline}}},
}}
})
mediaCheck(c, log, &s.disabled, "Disabled")
@@ -31,7 +33,7 @@ func channelListDemo(c *ui.Context, log func(string)) {
if before != s.groups[0].Channels[0] {
s.revision++
}
if mujica.ChannelList(c, &s.selected, s.groups, mujica.ChannelListOptions{Disabled: s.disabled, Revision: s.revision}).Changed() {
if messaging.ChannelList(c, &s.selected, s.groups, messaging.ChannelListOptions{Disabled: s.disabled, Revision: s.revision}).Changed() {
log("channel " + s.selected)
}
ui.Text(c, "Selected: "+s.selected)
@@ -39,25 +41,25 @@ func channelListDemo(c *ui.Context, log func(string)) {
func memberListDemo(c *ui.Context, log func(string)) {
type state struct {
list mujica.MemberListState
list messaging.MemberListState
disabled, empty bool
members []mujica.Member
members []messaging.Member
}
s := ui.Local(c.Root(), "405", func() state {
return state{members: []mujica.Member{
{ID: "1", Name: "Sakiko", Presence: mujica.PresenceOnline, Role: "Leader"},
{ID: "2", Name: "Uika", Presence: mujica.PresenceBusy, Role: "Vocal"},
{ID: "3", Name: "Mutsumi", Presence: mujica.PresenceAway}, {ID: "4", Name: "Umiri", Presence: mujica.PresenceOffline},
return state{members: []messaging.Member{
{ID: "1", Name: "Sakiko", Presence: messaging.PresenceOnline, Role: "Leader"},
{ID: "2", Name: "Uika", Presence: messaging.PresenceBusy, Role: "Vocal"},
{ID: "3", Name: "Mutsumi", Presence: messaging.PresenceAway}, {ID: "4", Name: "Umiri", Presence: messaging.PresenceOffline},
}}
})
mediaCheck(c, log, &s.disabled, "Disabled")
mediaCheck(c, log, &s.empty, "Empty")
if mujica.Button(c, "Toggle Uika presence", mujica.ButtonOptions{Variant: mujica.Secondary}).Clicked() {
if input.Button(c, "Toggle Uika presence", input.ButtonOptions{Variant: input.Secondary}).Clicked() {
s.members = slices.Clone(s.members)
if s.members[1].Presence == mujica.PresenceOffline {
s.members[1].Presence = mujica.PresenceOnline
if s.members[1].Presence == messaging.PresenceOffline {
s.members[1].Presence = messaging.PresenceOnline
} else {
s.members[1].Presence = mujica.PresenceOffline
s.members[1].Presence = messaging.PresenceOffline
}
log("Uika presence changed")
}
@@ -65,7 +67,7 @@ func memberListDemo(c *ui.Context, log func(string)) {
if s.empty {
members = nil
}
r := mujica.MemberList(c, &s.list, members, mujica.MemberListOptions{Disabled: s.disabled, Height: 240})
r := messaging.MemberList(c, &s.list, members, messaging.MemberListOptions{Disabled: s.disabled, Height: 240})
if r.Changed() {
log("member " + s.list.Selected)
}
@@ -76,61 +78,61 @@ func memberListDemo(c *ui.Context, log func(string)) {
func userProfileCardDemo(c *ui.Context, log func(string)) {
type state struct {
presence mujica.PresenceStatus
presence messaging.PresenceStatus
actions, disabled bool
}
s := ui.Local(c.Root(), "406", func() state { return state{actions: true, presence: mujica.PresenceOnline} })
s := ui.Local(c.Root(), "406", func() state { return state{actions: true, presence: messaging.PresenceOnline} })
mediaCheck(c, log, &s.actions, "Actions")
mediaCheck(c, log, &s.disabled, "Disable actions")
messagingPresence(c, log, &s.presence)
opts := mujica.UserProfileCardOptions{}
opts := messaging.UserProfileCardOptions{}
if s.actions {
opts.Actions = func() {
for _, action := range []string{"Message", "Call"} {
if mujica.Button(c, action, mujica.ButtonOptions{Disabled: s.disabled}).Clicked() {
if input.Button(c, action, input.ButtonOptions{Disabled: s.disabled}).Clicked() {
log(action)
}
}
}
}
mujica.UserProfileCard(c, mujica.UserProfile{Name: "Sakiko Togawa", Presence: s.presence, Role: "Keyboard", Status: "In rehearsal", About: "Founder of the masquerade."}, opts)
messaging.UserProfileCard(c, messaging.UserProfile{Name: "Sakiko Togawa", Presence: s.presence, Role: "Keyboard", Status: "In rehearsal", About: "Founder of the masquerade."}, opts)
}
func onlineStatusDemo(c *ui.Context, log func(string)) {
type state struct {
presence mujica.PresenceStatus
presence messaging.PresenceStatus
hidden bool
}
s := ui.Local(c.Root(), "407", func() state { return state{presence: mujica.PresenceOnline} })
s := ui.Local(c.Root(), "407", func() state { return state{presence: messaging.PresenceOnline} })
mediaCheck(c, log, &s.hidden, "Hide text")
messagingPresence(c, log, &s.presence)
mujica.OnlineStatus(c, s.presence, mujica.OnlineStatusOptions{HideText: s.hidden})
messaging.OnlineStatus(c, s.presence, messaging.OnlineStatusOptions{HideText: s.hidden})
inputSection(c, "States", func() {
for p := mujica.PresenceOffline; p <= mujica.PresenceBusy; p++ {
mujica.OnlineStatus(c, p, mujica.OnlineStatusOptions{})
for p := messaging.PresenceOffline; p <= messaging.PresenceBusy; p++ {
messaging.OnlineStatus(c, p, messaging.OnlineStatusOptions{})
}
})
}
func statusSetterDemo(c *ui.Context, log func(string)) {
type state struct {
status mujica.CustomStatus
status messaging.CustomStatus
disabled bool
}
s := ui.Local(c.Root(), "408", func() state { return state{} })
mediaCheck(c, log, &s.disabled, "Disabled")
if mujica.StatusSetter(c, &s.status, mujica.StatusSetterOptions{Disabled: s.disabled}).Changed() {
if messaging.StatusSetter(c, &s.status, messaging.StatusSetterOptions{Disabled: s.disabled}).Changed() {
log("status changed")
}
ui.Text(c, fmt.Sprintf("Text: %s; expires: %v", s.status.Text, s.status.Until))
}
func messagingPresence(c *ui.Context, log func(string), value *mujica.PresenceStatus) {
items := []mujica.SelectOption[mujica.PresenceStatus]{
{Value: mujica.PresenceOnline, Label: "Online"}, {Value: mujica.PresenceAway, Label: "Away"},
{Value: mujica.PresenceBusy, Label: "Busy"}, {Value: mujica.PresenceOffline, Label: "Offline"},
func messagingPresence(c *ui.Context, log func(string), value *messaging.PresenceStatus) {
items := []input.SelectOption[messaging.PresenceStatus]{
{Value: messaging.PresenceOnline, Label: "Online"}, {Value: messaging.PresenceAway, Label: "Away"},
{Value: messaging.PresenceBusy, Label: "Busy"}, {Value: messaging.PresenceOffline, Label: "Offline"},
}
if mujica.Select(c, value, items, mujica.SelectOptions{Label: "Presence"}).Changed() {
if input.Select(c, value, items, input.SelectOptions{Label: "Presence"}).Changed() {
log(fmt.Sprintf("presence %d", *value))
}
}
+6 -5
View File
@@ -10,10 +10,11 @@ import (
"testing"
"github.com/egoist/mygo/ui"
mujica "mujicaui"
"github.com/ZacharyZhang-NY/MujicaUI/core"
)
func messagingDemoTester(t *testing.T, id string, mode mujica.Mode) (*ui.Tester, *[]string) {
func messagingDemoTester(t *testing.T, id string, mode core.Mode) (*ui.Tester, *[]string) {
t.Helper()
i := slices.IndexFunc(messagingPages, func(p page) bool { return p.ID == id })
if i < 0 {
@@ -21,7 +22,7 @@ func messagingDemoTester(t *testing.T, id string, mode mujica.Mode) (*ui.Tester,
}
events := &[]string{}
tt := ui.NewTester(func(c *ui.Context) {
mujica.Use(c, mujica.Settings{Mode: mode})
core.Use(c, core.Settings{Mode: mode})
ui.Scroll(c).Fill().Padding(24).Gap(12).Children(func() {
messagingPages[i].Demo(c, func(s string) { *events = append(*events, s) })
})
@@ -58,7 +59,7 @@ func TestMessagingDemosInteractAndLog(t *testing.T) {
t.Fatalf("pages=%d, cases=%d", len(messagingPages), len(cases))
}
for _, tc := range cases {
for _, mode := range []mujica.Mode{mujica.Light, mujica.Dark} {
for _, mode := range []core.Mode{core.Light, core.Dark} {
t.Run(tc.id+mode.String(), func(t *testing.T) {
tt, events := messagingDemoTester(t, tc.id, mode)
if err := tt.Click(tc.click); err != nil {
@@ -105,7 +106,7 @@ func TestMessagingDemoCaptures(t *testing.T) {
if p.Category != "Messaging & Mail" || p.Code == "" || p.Keys == "" {
t.Fatalf("incomplete page %s", p.ID)
}
for _, mode := range []mujica.Mode{mujica.Light, mujica.Dark} {
for _, mode := range []core.Mode{core.Light, core.Dark} {
tt, _ := messagingDemoTester(t, p.ID, mode)
f, err := os.Create(filepath.Join(dir, fmt.Sprintf("%s-%s.png", p.ID, mode)))
if err != nil {
+3 -1
View File
@@ -1,6 +1,8 @@
package main
import "github.com/egoist/mygo/ui"
import (
"github.com/egoist/mygo/ui"
)
var mktchartPages = []page{
{ID: "141", Name: "CandlestickChart", ZhName: "K 线", Category: "Market Charts", Demo: func(c *ui.Context, log func(string)) { mktDemo(c, log, "141") }, Code: mktCode141, Keys: mktKeys},
+80 -80
View File
@@ -1,211 +1,211 @@
package main
const mktCode141 = ` at := time.Date(2026, 10, 5, 9, 0, 0, 0, time.UTC)
candles := []mujica.Candle{
candles := []chart.Candle{
{Time: at, Open: 100, High: 103, Low: 99, Close: 102, Volume: 1200},
{Time: at.Add(time.Hour), Open: 102, High: 104, Low: 100, Close: 101, Volume: 1600},
{Time: at.Add(2 * time.Hour), Open: 101, High: 105, Low: 100, Close: 104, Volume: 900},
}
var window mujica.MarketView
var window chart.MarketView
view := func(c *ui.Context) {
mujica.Use(c, mujica.Settings{})
mujica.CandlestickChart(c, candles, &window, mujica.CandlestickOptions{Frame: mujica.ChartFrame{Label: "BTC", Height: 200}})
core.Use(c, core.Settings{})
chart.CandlestickChart(c, candles, &window, chart.CandlestickOptions{Frame: chart.ChartFrame{Label: "BTC", Height: 200}})
}
ui.Render(view, 640, 400, 1)`
const mktCode142 = ` at := time.Date(2026, 10, 5, 9, 0, 0, 0, time.UTC)
candles := []mujica.Candle{
candles := []chart.Candle{
{Time: at, Open: 100, High: 103, Low: 99, Close: 102, Volume: 1200},
{Time: at.Add(time.Hour), Open: 102, High: 104, Low: 100, Close: 101, Volume: 1600},
{Time: at.Add(2 * time.Hour), Open: 101, High: 105, Low: 100, Close: 104, Volume: 900},
}
var window mujica.MarketView
var window chart.MarketView
view := func(c *ui.Context) {
mujica.Use(c, mujica.Settings{})
mujica.OHLCChart(c, candles, &window, mujica.CandlestickOptions{Frame: mujica.ChartFrame{Label: "BTC", Height: 200}})
core.Use(c, core.Settings{})
chart.OHLCChart(c, candles, &window, chart.CandlestickOptions{Frame: chart.ChartFrame{Label: "BTC", Height: 200}})
}
ui.Render(view, 640, 400, 1)`
const mktCode143 = ` at := time.Date(2026, 10, 5, 9, 0, 0, 0, time.UTC)
candles := []mujica.Candle{
candles := []chart.Candle{
{Time: at, Open: 100, High: 103, Low: 99, Close: 102, Volume: 1200},
{Time: at.Add(time.Hour), Open: 102, High: 104, Low: 100, Close: 101, Volume: 1600},
{Time: at.Add(2 * time.Hour), Open: 101, High: 105, Low: 100, Close: 104, Volume: 900},
}
var window mujica.MarketView
var window chart.MarketView
raw := false
view := func(c *ui.Context) {
mujica.Use(c, mujica.Settings{})
mujica.HeikinAshiChart(c, candles, &window, &raw, mujica.CandlestickOptions{Frame: mujica.ChartFrame{Label: "BTC", Height: 200}})
core.Use(c, core.Settings{})
chart.HeikinAshiChart(c, candles, &window, &raw, chart.CandlestickOptions{Frame: chart.ChartFrame{Label: "BTC", Height: 200}})
}
ui.Render(view, 640, 400, 1)`
const mktCode144 = ` at := time.Date(2026, 10, 5, 9, 0, 0, 0, time.UTC)
points := []mujica.QuotePoint{{Time: at, Price: 101, Volume: 100}, {Time: at.Add(time.Hour), Price: 103, Volume: 200}}
points := []chart.QuotePoint{{Time: at, Price: 101, Volume: 100}, {Time: at.Add(time.Hour), Price: 103, Volume: 200}}
view := func(c *ui.Context) {
mujica.Use(c, mujica.Settings{})
mujica.LineQuoteChart(c, points, mujica.LineQuoteChartOptions{Frame: mujica.ChartFrame{Label: "BTC", Height: 200}, PrevClose: 100, RegularOpen: at.Add(30 * time.Minute), RegularClose: at.Add(45 * time.Minute)})
core.Use(c, core.Settings{})
chart.LineQuoteChart(c, points, chart.LineQuoteChartOptions{Frame: chart.ChartFrame{Label: "BTC", Height: 200}, PrevClose: 100, RegularOpen: at.Add(30 * time.Minute), RegularClose: at.Add(45 * time.Minute)})
}
ui.Render(view, 640, 400, 1)`
const mktCode145 = ` at := time.Date(2026, 10, 5, 9, 0, 0, 0, time.UTC)
candles := []mujica.Candle{
candles := []chart.Candle{
{Time: at, Open: 100, High: 103, Low: 99, Close: 102, Volume: 1200},
{Time: at.Add(time.Hour), Open: 102, High: 104, Low: 100, Close: 101, Volume: 1600},
{Time: at.Add(2 * time.Hour), Open: 101, High: 105, Low: 100, Close: 104, Volume: 900},
}
var window mujica.MarketView
var window chart.MarketView
view := func(c *ui.Context) {
mujica.Use(c, mujica.Settings{})
mujica.VolumeChart(c, candles, &window, mujica.VolumeChartOptions{Frame: mujica.ChartFrame{Label: "Volume"}})
core.Use(c, core.Settings{})
chart.VolumeChart(c, candles, &window, chart.VolumeChartOptions{Frame: chart.ChartFrame{Label: "Volume"}})
}
ui.Render(view, 640, 400, 1)`
const mktCode146 = ` bids := []mujica.DepthLevel{{Price: 99, Size: 10}, {Price: 98, Size: 20}}
asks := []mujica.DepthLevel{{Price: 101, Size: 15}, {Price: 102, Size: 25}}
const mktCode146 = ` bids := []chart.DepthLevel{{Price: 99, Size: 10}, {Price: 98, Size: 20}}
asks := []chart.DepthLevel{{Price: 101, Size: 15}, {Price: 102, Size: 25}}
view := func(c *ui.Context) {
mujica.Use(c, mujica.Settings{})
mujica.DepthChart(c, bids, asks, mujica.DepthChartOptions{Frame: mujica.ChartFrame{Label: "BTC", Height: 200}})
core.Use(c, core.Settings{})
chart.DepthChart(c, bids, asks, chart.DepthChartOptions{Frame: chart.ChartFrame{Label: "BTC", Height: 200}})
}
ui.Render(view, 640, 400, 1)`
const mktCode147 = ` bins := []mujica.ProfileBin{{Low: 99, High: 100, Volume: 30}, {Low: 100, High: 101, Volume: 80}, {Low: 101, High: 102, Volume: 40}}
const mktCode147 = ` bins := []chart.ProfileBin{{Low: 99, High: 100, Volume: 30}, {Low: 100, High: 101, Volume: 80}, {Low: 101, High: 102, Volume: 40}}
view := func(c *ui.Context) {
mujica.Use(c, mujica.Settings{})
mujica.VolumeProfile(c, bins, mujica.VolumeProfileOptions{Frame: mujica.ChartFrame{Label: "BTC", Height: 200}})
core.Use(c, core.Settings{})
chart.VolumeProfile(c, bins, chart.VolumeProfileOptions{Frame: chart.ChartFrame{Label: "BTC", Height: 200}})
}
ui.Render(view, 640, 400, 1)`
const mktCode148 = ` state := mujica.MultiChartState{Count: 2}
const mktCode148 = ` state := chart.MultiChartState{Count: 2}
view := func(c *ui.Context) {
mujica.Use(c, mujica.Settings{})
mujica.MultiChartLayout(c, &state, mujica.MultiChartLayoutOptions{Height: 200}, func(i int, cell *mujica.ChartCell) {
mujica.TextInput(c, &cell.Symbol, mujica.TextInputOptions{Label: "Symbol"})
core.Use(c, core.Settings{})
chart.MultiChartLayout(c, &state, chart.MultiChartLayoutOptions{Height: 200}, func(i int, cell *chart.ChartCell) {
input.TextInput(c, &cell.Symbol, input.TextInputOptions{Label: "Symbol"})
})
}
ui.Render(view, 640, 400, 1)`
const mktCode149 = ` at := time.Date(2026, 10, 5, 9, 0, 0, 0, time.UTC)
candles := []mujica.Candle{
candles := []chart.Candle{
{Time: at, Open: 100, High: 103, Low: 99, Close: 102, Volume: 1200},
{Time: at.Add(time.Hour), Open: 102, High: 104, Low: 100, Close: 101, Volume: 1600},
{Time: at.Add(2 * time.Hour), Open: 101, High: 105, Low: 100, Close: 104, Volume: 900},
}
var window mujica.MarketView
var sync mujica.ChartSync
var window chart.MarketView
var sync chart.ChartSync
view := func(c *ui.Context) {
mujica.Use(c, mujica.Settings{})
core.Use(c, core.Settings{})
sync.Link(c, "price")
mujica.CandlestickChart(c, candles, &window, mujica.CandlestickOptions{Frame: mujica.ChartFrame{Label: "BTC", Height: 200}, Sync: &sync, SyncID: "price"})
chart.CandlestickChart(c, candles, &window, chart.CandlestickOptions{Frame: chart.ChartFrame{Label: "BTC", Height: 200}, Sync: &sync, SyncID: "price"})
}
ui.Render(view, 640, 400, 1)`
const mktCode150 = ` value := mujica.MarketRange{Preset: mujica.Range1M}
const mktCode150 = ` value := chart.MarketRange{Preset: chart.Range1M}
view := func(c *ui.Context) {
mujica.Use(c, mujica.Settings{})
mujica.TimeRangeSelector(c, &value, mujica.TimeRangeSelectorOptions{})
core.Use(c, core.Settings{})
chart.TimeRangeSelector(c, &value, chart.TimeRangeSelectorOptions{})
}
ui.Render(view, 640, 400, 1)`
const mktCode151 = ` value := mujica.Interval("1h")
favorites := []mujica.Interval{"1m", "1h", "1D"}
const mktCode151 = ` value := chart.Interval("1h")
favorites := []chart.Interval{"1m", "1h", "1D"}
view := func(c *ui.Context) {
mujica.Use(c, mujica.Settings{})
mujica.IntervalSelector(c, &value, &favorites, mujica.IntervalSelectorOptions{})
core.Use(c, core.Settings{})
chart.IntervalSelector(c, &value, &favorites, chart.IntervalSelectorOptions{})
}
ui.Render(view, 640, 400, 1)`
const mktCode152 = ` value := mujica.ChartCandles
const mktCode152 = ` value := chart.ChartCandles
view := func(c *ui.Context) {
mujica.Use(c, mujica.Settings{})
mujica.ChartTypeSwitcher(c, &value, mujica.ChartTypeSwitcherOptions{})
core.Use(c, core.Settings{})
chart.ChartTypeSwitcher(c, &value, chart.ChartTypeSwitcherOptions{})
}
ui.Render(view, 640, 400, 1)`
const mktCode153 = ` at := time.Date(2026, 10, 5, 9, 0, 0, 0, time.UTC)
candles := []mujica.Candle{
candles := []chart.Candle{
{Time: at, Open: 100, High: 103, Low: 99, Close: 102, Volume: 1200},
{Time: at.Add(time.Hour), Open: 102, High: 104, Low: 100, Close: 101, Volume: 1600},
{Time: at.Add(2 * time.Hour), Open: 101, High: 105, Low: 100, Close: 104, Volume: 900},
}
var window mujica.MarketView
overlays := []mujica.IndicatorOverlay{{Kind: mujica.IndicatorMA, Params: []float64{2}}}
var window chart.MarketView
overlays := []chart.IndicatorOverlay{{Kind: chart.IndicatorMA, Params: []float64{2}}}
view := func(c *ui.Context) {
mujica.Use(c, mujica.Settings{})
mujica.CandlestickChart(c, candles, &window, mujica.CandlestickOptions{Frame: mujica.ChartFrame{Label: "BTC", Height: 200}, Overlays: overlays})
core.Use(c, core.Settings{})
chart.CandlestickChart(c, candles, &window, chart.CandlestickOptions{Frame: chart.ChartFrame{Label: "BTC", Height: 200}, Overlays: overlays})
}
ui.Render(view, 640, 400, 1)`
const mktCode154 = ` at := time.Date(2026, 10, 5, 9, 0, 0, 0, time.UTC)
candles := []mujica.Candle{
candles := []chart.Candle{
{Time: at, Open: 100, High: 103, Low: 99, Close: 102, Volume: 1200},
{Time: at.Add(time.Hour), Open: 102, High: 104, Low: 100, Close: 101, Volume: 1600},
{Time: at.Add(2 * time.Hour), Open: 101, High: 105, Low: 100, Close: 104, Volume: 900},
}
var window mujica.MarketView
panes := mujica.IndicatorPanes{Panes: []mujica.IndicatorPaneSpec{{Kind: mujica.IndicatorRSI, Params: []float64{2}}}}
var window chart.MarketView
panes := chart.IndicatorPanes{Panes: []chart.IndicatorPaneSpec{{Kind: chart.IndicatorRSI, Params: []float64{2}}}}
view := func(c *ui.Context) {
mujica.Use(c, mujica.Settings{})
mujica.IndicatorPane(c, candles, &window, &panes, mujica.IndicatorPaneOptions{Height: 200, Label: "BTC"})
core.Use(c, core.Settings{})
chart.IndicatorPane(c, candles, &window, &panes, chart.IndicatorPaneOptions{Height: 200, Label: "BTC"})
}
ui.Render(view, 640, 400, 1)`
const mktCode155 = ` var overlays []mujica.IndicatorOverlay
var panes mujica.IndicatorPanes
const mktCode155 = ` var overlays []chart.IndicatorOverlay
var panes chart.IndicatorPanes
view := func(c *ui.Context) {
mujica.Use(c, mujica.Settings{})
mujica.IndicatorSelector(c, &overlays, &panes, mujica.IndicatorSelectorOptions{})
core.Use(c, core.Settings{})
chart.IndicatorSelector(c, &overlays, &panes, chart.IndicatorSelectorOptions{})
}
ui.Render(view, 640, 400, 1)`
const mktCode156 = ` at := time.Date(2026, 10, 5, 9, 0, 0, 0, time.UTC)
candles := []mujica.Candle{
candles := []chart.Candle{
{Time: at, Open: 100, High: 103, Low: 99, Close: 102, Volume: 1200},
{Time: at.Add(time.Hour), Open: 102, High: 104, Low: 100, Close: 101, Volume: 1600},
{Time: at.Add(2 * time.Hour), Open: 101, High: 105, Low: 100, Close: 104, Volume: 900},
}
var window mujica.MarketView
drawings := mujica.DrawingTools{Tool: mujica.ToolTrendLine}
var window chart.MarketView
drawings := chart.DrawingTools{Tool: chart.ToolTrendLine}
view := func(c *ui.Context) {
mujica.Use(c, mujica.Settings{})
mujica.CandlestickChart(c, candles, &window, mujica.CandlestickOptions{Frame: mujica.ChartFrame{Label: "BTC", Height: 200}, Drawings: &drawings})
core.Use(c, core.Settings{})
chart.CandlestickChart(c, candles, &window, chart.CandlestickOptions{Frame: chart.ChartFrame{Label: "BTC", Height: 200}, Drawings: &drawings})
}
ui.Render(view, 640, 400, 1)`
const mktCode157 = ` var drawings mujica.DrawingTools
const mktCode157 = ` var drawings chart.DrawingTools
view := func(c *ui.Context) {
mujica.Use(c, mujica.Settings{})
mujica.DrawingToolbar(c, &drawings, mujica.DrawingToolbarOptions{Horizontal: true})
core.Use(c, core.Settings{})
chart.DrawingToolbar(c, &drawings, chart.DrawingToolbarOptions{Horizontal: true})
}
ui.Render(view, 640, 400, 1)`
const mktCode158 = ` at := time.Date(2026, 10, 5, 9, 0, 0, 0, time.UTC)
candles := []mujica.Candle{
candles := []chart.Candle{
{Time: at, Open: 100, High: 103, Low: 99, Close: 102, Volume: 1200},
{Time: at.Add(time.Hour), Open: 102, High: 104, Low: 100, Close: 101, Volume: 1600},
{Time: at.Add(2 * time.Hour), Open: 101, High: 105, Low: 100, Close: 104, Volume: 900},
}
var window mujica.MarketView
symbols := []mujica.CompareSymbol{{Name: "ETH", Closes: []float64{20, 21, 23}}}
var window chart.MarketView
symbols := []chart.CompareSymbol{{Name: "ETH", Closes: []float64{20, 21, 23}}}
view := func(c *ui.Context) {
mujica.Use(c, mujica.Settings{})
mujica.CompareLegend(c, &symbols)
mujica.CandlestickChart(c, candles, &window, mujica.CandlestickOptions{Frame: mujica.ChartFrame{Label: "BTC", Height: 200}, Compare: &symbols})
core.Use(c, core.Settings{})
chart.CompareLegend(c, &symbols)
chart.CandlestickChart(c, candles, &window, chart.CandlestickOptions{Frame: chart.ChartFrame{Label: "BTC", Height: 200}, Compare: &symbols})
}
ui.Render(view, 640, 400, 1)`
const mktCode159 = ` at := time.Date(2026, 10, 5, 9, 0, 0, 0, time.UTC)
candles := []mujica.Candle{
candles := []chart.Candle{
{Time: at, Open: 100, High: 103, Low: 99, Close: 102, Volume: 1200},
{Time: at.Add(time.Hour), Open: 102, High: 104, Low: 100, Close: 101, Volume: 1600},
{Time: at.Add(2 * time.Hour), Open: 101, High: 105, Low: 100, Close: 104, Volume: 900},
}
var window mujica.MarketView
alerts := []mujica.PriceAlertLine{{ID: "take-profit", Price: 105}}
var window chart.MarketView
alerts := []chart.PriceAlertLine{{ID: "take-profit", Price: 105}}
triggered := ""
view := func(c *ui.Context) {
mujica.Use(c, mujica.Settings{})
mujica.CandlestickChart(c, candles, &window, mujica.CandlestickOptions{Frame: mujica.ChartFrame{Label: "BTC", Height: 200}, Alerts: &alerts, OnAlert: func(alert mujica.PriceAlertLine, last mujica.Candle) {
core.Use(c, core.Settings{})
chart.CandlestickChart(c, candles, &window, chart.CandlestickOptions{Frame: chart.ChartFrame{Label: "BTC", Height: 200}, Alerts: &alerts, OnAlert: func(alert chart.PriceAlertLine, last chart.Candle) {
triggered = alert.ID
}})
ui.Text(c, triggered)
@@ -213,7 +213,7 @@ const mktCode159 = ` at := time.Date(2026, 10, 5, 9, 0, 0, 0, time.UTC)
ui.Render(view, 640, 400, 1)`
const mktCode160 = ` view := func(c *ui.Context) {
mujica.Use(c, mujica.Settings{})
mujica.CandleCountdown(c, "1h", mujica.CandleCountdownOptions{Location: time.UTC})
core.Use(c, core.Settings{})
chart.CandleCountdown(c, "1h", chart.CandleCountdownOptions{Location: time.UTC})
}
ui.Render(view, 640, 400, 1)`
+49 -47
View File
@@ -5,7 +5,9 @@ import (
"time"
"github.com/egoist/mygo/ui"
mujica "mujicaui"
"github.com/ZacharyZhang-NY/MujicaUI/chart"
"github.com/ZacharyZhang-NY/MujicaUI/input"
)
func mktDemo(c *ui.Context, log func(string), id string) {
@@ -21,65 +23,65 @@ func mktDemo(c *ui.Context, log func(string), id string) {
bins = nil
}
ui.Column(c).Disabled(s.disabled).Gap(12).FillWidth().Children(func() {
opts := mujica.CandlestickOptions{Frame: frame, Type: s.chartType}
chart := func() { mktLog(log, id, mujica.CandlestickChart(c, candles, &s.window, opts)) }
opts := chart.CandlestickOptions{Frame: frame, Type: s.chartType}
draw := func() { mktLog(log, id, chart.CandlestickChart(c, candles, &s.window, opts)) }
switch id {
case "141":
if mujica.ChartTypeSwitcher(c, &s.chartType, mujica.ChartTypeSwitcherOptions{}).Changed() {
if chart.ChartTypeSwitcher(c, &s.chartType, chart.ChartTypeSwitcherOptions{}).Changed() {
log(fmt.Sprintf("Chart type: %d", s.chartType))
}
opts.Type = s.chartType
chart()
draw()
case "142":
mktLog(log, id, mujica.OHLCChart(c, candles, &s.window, opts))
mktLog(log, id, chart.OHLCChart(c, candles, &s.window, opts))
case "143":
before := s.raw
mktLog(log, id, mujica.HeikinAshiChart(c, candles, &s.window, &s.raw, opts))
mktLog(log, id, chart.HeikinAshiChart(c, candles, &s.window, &s.raw, opts))
if before != s.raw {
log(fmt.Sprintf("Raw: %v", s.raw))
}
case "144":
if mujica.NumberInput(c, &s.previous, mujica.NumberInputOptions{Min: 80, Max: 120, Step: 0.5, Label: "Previous close"}).Changed() {
if input.NumberInput(c, &s.previous, input.NumberInputOptions{Min: 80, Max: 120, Step: 0.5, Label: "Previous close"}).Changed() {
log(fmt.Sprintf("Previous close: %.2f", s.previous))
}
if mujica.Checkbox(c, &s.extended, "Extended sessions", mujica.CheckboxOptions{}).Changed() {
if input.Checkbox(c, &s.extended, "Extended sessions", input.CheckboxOptions{}).Changed() {
log(fmt.Sprintf("Extended sessions: %v", s.extended))
}
o := mujica.LineQuoteChartOptions{Frame: frame, PrevClose: s.previous}
o := chart.LineQuoteChartOptions{Frame: frame, PrevClose: s.previous}
if s.extended {
o.RegularOpen = s.points[0].Time.Add(90 * time.Minute)
o.RegularClose = s.points[0].Time.Add(8 * time.Hour)
}
mktLog(log, id, mujica.LineQuoteChart(c, points, o))
mktLog(log, id, chart.LineQuoteChart(c, points, o))
case "145":
opts.Frame.Height = 150
chart()
draw()
frame.Label = "Volume"
frame.Height = 100
mktLog(log, id, mujica.VolumeChart(c, candles, &s.window, mujica.VolumeChartOptions{Frame: frame}))
mktLog(log, id, chart.VolumeChart(c, candles, &s.window, chart.VolumeChartOptions{Frame: frame}))
case "146":
mktLog(log, id, mujica.DepthChart(c, bids, asks, mujica.DepthChartOptions{Frame: frame}))
mktLog(log, id, chart.DepthChart(c, bids, asks, chart.DepthChartOptions{Frame: frame}))
case "147":
if mujica.NumberInput(c, &s.fraction, mujica.NumberInputOptions{Min: 0.1, Max: 1, Step: 0.1, Label: "Value area fraction"}).Changed() {
if input.NumberInput(c, &s.fraction, input.NumberInputOptions{Min: 0.1, Max: 1, Step: 0.1, Label: "Value area fraction"}).Changed() {
log(fmt.Sprintf("Value area: %.1f", s.fraction))
}
mktLog(log, id, mujica.VolumeProfile(c, bins, mujica.VolumeProfileOptions{Frame: frame, ValueArea: s.fraction}))
mktLog(log, id, chart.VolumeProfile(c, bins, chart.VolumeProfileOptions{Frame: frame, ValueArea: s.fraction}))
case "148":
if mujica.MultiChartLayout(c, &s.layout, mujica.MultiChartLayoutOptions{Height: 420}, func(i int, cell *mujica.ChartCell) {
if mujica.TextInput(c, &cell.Symbol, mujica.TextInputOptions{Label: fmt.Sprintf("Symbol %d", i+1)}).Changed() {
if chart.MultiChartLayout(c, &s.layout, chart.MultiChartLayoutOptions{Height: 420}, func(i int, cell *chart.ChartCell) {
if input.TextInput(c, &cell.Symbol, input.TextInputOptions{Label: fmt.Sprintf("Symbol %d", i+1)}).Changed() {
log(fmt.Sprintf("Cell %d: %s", i+1, cell.Symbol))
}
items := make([]mujica.SelectOption[mujica.Interval], len(mujica.Intervals))
for j, iv := range mujica.Intervals {
items[j] = mujica.SelectOption[mujica.Interval]{Value: iv, Label: string(iv)}
items := make([]input.SelectOption[chart.Interval], len(chart.Intervals))
for j, iv := range chart.Intervals {
items[j] = input.SelectOption[chart.Interval]{Value: iv, Label: string(iv)}
}
if mujica.Select(c, &cell.Interval, items, mujica.SelectOptions{Label: fmt.Sprintf("Interval %d", i+1)}).Changed() {
if input.Select(c, &cell.Interval, items, input.SelectOptions{Label: fmt.Sprintf("Interval %d", i+1)}).Changed() {
log(fmt.Sprintf("Cell %d: %s", i+1, cell.Interval))
}
f := frame
f.Label = cell.Symbol
f.Height = 70
mktLog(log, fmt.Sprint(i), mujica.CandlestickChart(c, candles, &cell.View, mujica.CandlestickOptions{Frame: f}))
mktLog(log, fmt.Sprint(i), chart.CandlestickChart(c, candles, &cell.View, chart.CandlestickOptions{Frame: f}))
}).Changed() {
log(fmt.Sprintf("Layout: %d, active %d", s.layout.Count, s.layout.Active))
}
@@ -98,41 +100,41 @@ func mktDemo(c *ui.Context, log func(string), id string) {
o.Frame.Label = chartID
o.Sync = &s.sync
o.SyncID = chartID
mktLog(log, chartID, mujica.CandlestickChart(c, candles, v, o))
mktLog(log, chartID, chart.CandlestickChart(c, candles, v, o))
})
}
case "150":
if mujica.TimeRangeSelector(c, &s.rangeValue, mujica.TimeRangeSelectorOptions{}).Changed() {
if chart.TimeRangeSelector(c, &s.rangeValue, chart.TimeRangeSelectorOptions{}).Changed() {
log(fmt.Sprintf("Range: %d", s.rangeValue.Preset))
}
from, to := s.rangeValue.Bounds(c.Now(), time.UTC)
ui.Text(c, from.Format("2006-01-02")+" — "+to.Format("2006-01-02"))
case "151":
before := fmt.Sprint(s.favorites)
if mujica.IntervalSelector(c, &s.interval, &s.favorites, mujica.IntervalSelectorOptions{}).Changed() {
if chart.IntervalSelector(c, &s.interval, &s.favorites, chart.IntervalSelectorOptions{}).Changed() {
log("Interval: " + string(s.interval))
}
if before != fmt.Sprint(s.favorites) {
log("Favorites: " + fmt.Sprint(s.favorites))
}
mujica.CandleCountdown(c, s.interval, mujica.CandleCountdownOptions{})
chart.CandleCountdown(c, s.interval, chart.CandleCountdownOptions{})
case "152":
if mujica.ChartTypeSwitcher(c, &s.chartType, mujica.ChartTypeSwitcherOptions{}).Changed() {
if chart.ChartTypeSwitcher(c, &s.chartType, chart.ChartTypeSwitcherOptions{}).Changed() {
log(fmt.Sprintf("Chart type: %d", s.chartType))
}
opts.Type = s.chartType
chart()
draw()
case "153":
if mujica.IndicatorSelector(c, &s.overlays, &s.panes, mujica.IndicatorSelectorOptions{}).Changed() {
if chart.IndicatorSelector(c, &s.overlays, &s.panes, chart.IndicatorSelectorOptions{}).Changed() {
log("Overlays changed")
}
opts.Overlays = s.overlays
chart()
draw()
case "154":
if mujica.IndicatorSelector(c, &s.overlays, &s.panes, mujica.IndicatorSelectorOptions{}).Changed() {
if chart.IndicatorSelector(c, &s.overlays, &s.panes, chart.IndicatorSelectorOptions{}).Changed() {
log("Indicators changed")
}
r := mujica.IndicatorPane(c, candles, &s.window, &s.panes, mujica.IndicatorPaneOptions{Label: "BTC", Height: 320, Status: frame.Status, Error: frame.Error})
r := chart.IndicatorPane(c, candles, &s.window, &s.panes, chart.IndicatorPaneOptions{Label: "BTC", Height: 320, Status: frame.Status, Error: frame.Error})
if r.PointChanged {
log(fmt.Sprintf("Pane %d: point %d", r.Pane, r.Point))
}
@@ -143,43 +145,43 @@ func mktDemo(c *ui.Context, log func(string), id string) {
log("Panes resized")
}
case "155":
if mujica.IndicatorSelector(c, &s.overlays, &s.panes, mujica.IndicatorSelectorOptions{}).Changed() {
if chart.IndicatorSelector(c, &s.overlays, &s.panes, chart.IndicatorSelectorOptions{}).Changed() {
log("Indicators changed")
}
opts.Overlays = s.overlays
chart()
draw()
case "156", "157":
if mujica.TextInput(c, &s.drawings.Text, mujica.TextInputOptions{Label: "Annotation text"}).Changed() {
if input.TextInput(c, &s.drawings.Text, input.TextInputOptions{Label: "Annotation text"}).Changed() {
log("Annotation text changed")
}
if mujica.DrawingToolbar(c, &s.drawings, mujica.DrawingToolbarOptions{Horizontal: true}).Changed() {
if chart.DrawingToolbar(c, &s.drawings, chart.DrawingToolbarOptions{Horizontal: true}).Changed() {
log(fmt.Sprintf("Tool: %d, locked: %v", s.drawings.Tool, s.drawings.Locked))
}
before := fmt.Sprint(s.drawings.Items)
opts.Drawings = &s.drawings
chart()
draw()
if before != fmt.Sprint(s.drawings.Items) {
log(fmt.Sprintf("Drawings: %d", len(s.drawings.Items)))
}
case "158":
if mujica.CompareLegend(c, &s.compare).Changed() {
if chart.CompareLegend(c, &s.compare).Changed() {
log("Compared symbol removed")
}
if mujica.Button(c, "Restore ETH", mujica.ButtonOptions{Variant: mujica.Secondary}).Clicked() {
if input.Button(c, "Restore ETH", input.ButtonOptions{Variant: input.Secondary}).Clicked() {
s.compare = mktInitial().compare
log("Restored ETH")
}
if len(candles) > 0 {
opts.Compare = &s.compare
}
chart()
draw()
case "159":
before := s.alerts[0].Price
opts.Alerts = &s.alerts
opts.OnAlert = func(a mujica.PriceAlertLine, last mujica.Candle) {
opts.OnAlert = func(a chart.PriceAlertLine, last chart.Candle) {
log(fmt.Sprintf("Alert %s crossed %.2f", a.ID, last.Close))
}
if mujica.Button(c, "Append crossing candle", mujica.ButtonOptions{Variant: mujica.Secondary}).Clicked() {
if input.Button(c, "Append crossing candle", input.ButtonOptions{Variant: input.Secondary}).Clicked() {
last := s.candles[len(s.candles)-1]
next := last
next.Time = last.Time.Add(time.Hour)
@@ -194,19 +196,19 @@ func mktDemo(c *ui.Context, log func(string), id string) {
candles = s.candles
log("Appended candle")
}
chart()
draw()
if before != s.alerts[0].Price {
log(fmt.Sprintf("Alert price: %.2f", s.alerts[0].Price))
}
case "160":
if mujica.IntervalSelector(c, &s.interval, &s.favorites, mujica.IntervalSelectorOptions{}).Changed() {
if chart.IntervalSelector(c, &s.interval, &s.favorites, chart.IntervalSelectorOptions{}).Changed() {
log("Interval: " + string(s.interval))
}
if mujica.Checkbox(c, &s.hidden, "Hide countdown", mujica.CheckboxOptions{}).Changed() {
if input.Checkbox(c, &s.hidden, "Hide countdown", input.CheckboxOptions{}).Changed() {
log(fmt.Sprintf("Hidden: %v", s.hidden))
}
if !s.hidden {
mujica.CandleCountdown(c, s.interval, mujica.CandleCountdownOptions{})
chart.CandleCountdown(c, s.interval, chart.CandleCountdownOptions{})
}
}
})
+44 -40
View File
@@ -6,89 +6,93 @@ import (
"time"
"github.com/egoist/mygo/ui"
mujica "mujicaui"
"github.com/ZacharyZhang-NY/MujicaUI/chart"
"github.com/ZacharyZhang-NY/MujicaUI/core"
"github.com/ZacharyZhang-NY/MujicaUI/data"
"github.com/ZacharyZhang-NY/MujicaUI/input"
)
type mktDemoState struct {
candles []mujica.Candle
points []mujica.QuotePoint
bids, asks []mujica.DepthLevel
bins []mujica.ProfileBin
window mujica.MarketView
second mujica.MarketView
sync mujica.ChartSync
layout mujica.MultiChartState
interval mujica.Interval
favorites []mujica.Interval
rangeValue mujica.MarketRange
chartType mujica.ChartType
overlays []mujica.IndicatorOverlay
panes mujica.IndicatorPanes
drawings mujica.DrawingTools
compare []mujica.CompareSymbol
alerts []mujica.PriceAlertLine
candles []chart.Candle
points []chart.QuotePoint
bids, asks []chart.DepthLevel
bins []chart.ProfileBin
window chart.MarketView
second chart.MarketView
sync chart.ChartSync
layout chart.MultiChartState
interval chart.Interval
favorites []chart.Interval
rangeValue chart.MarketRange
chartType chart.ChartType
overlays []chart.IndicatorOverlay
panes chart.IndicatorPanes
drawings chart.DrawingTools
compare []chart.CompareSymbol
alerts []chart.PriceAlertLine
status string
disabled, raw, redUp, hidden, extended bool
previous, fraction float64
}
func mktInitial() mktDemoState {
s := mktDemoState{status: "Ready", interval: "1h", favorites: []mujica.Interval{"1m", "1h", "1D"}, previous: 100, fraction: 0.7, extended: true}
s := mktDemoState{status: "Ready", interval: "1h", favorites: []chart.Interval{"1m", "1h", "1D"}, previous: 100, fraction: 0.7, extended: true}
start := time.Date(2026, 10, 5, 8, 0, 0, 0, time.UTC)
price := 100.0
compare := make([]float64, 180)
for i := range 180 {
closeAt := price + math.Sin(float64(i)*0.7)*1.5 + math.Cos(float64(i)*0.13)*0.8
s.candles = append(s.candles, mujica.Candle{Time: start.Add(time.Duration(i) * time.Hour), Open: price, High: max(price, closeAt) + 0.8, Low: min(price, closeAt) - 0.7, Close: closeAt, Volume: 1000 + float64(i%17)*130})
s.points = append(s.points, mujica.QuotePoint{Time: start.Add(time.Duration(i) * 5 * time.Minute), Price: 100 + 2*math.Sin(float64(i)/10), Volume: 100 + float64(i%7)*20})
s.candles = append(s.candles, chart.Candle{Time: start.Add(time.Duration(i) * time.Hour), Open: price, High: max(price, closeAt) + 0.8, Low: min(price, closeAt) - 0.7, Close: closeAt, Volume: 1000 + float64(i%17)*130})
s.points = append(s.points, chart.QuotePoint{Time: start.Add(time.Duration(i) * 5 * time.Minute), Price: 100 + 2*math.Sin(float64(i)/10), Volume: 100 + float64(i%7)*20})
compare[i] = 20 + float64(i)*0.08
price = closeAt
}
for i := range 40 {
s.bids = append(s.bids, mujica.DepthLevel{Price: 99.9 - float64(i)*0.1, Size: float64(10 + i*3)})
s.asks = append(s.asks, mujica.DepthLevel{Price: 100.1 + float64(i)*0.1, Size: float64(8 + i*4)})
s.bids = append(s.bids, chart.DepthLevel{Price: 99.9 - float64(i)*0.1, Size: float64(10 + i*3)})
s.asks = append(s.asks, chart.DepthLevel{Price: 100.1 + float64(i)*0.1, Size: float64(8 + i*4)})
}
s.bins = mujica.VolumeProfileOf(s.candles, 24)
s.compare = []mujica.CompareSymbol{{Name: "ETH", Closes: compare}}
s.alerts = []mujica.PriceAlertLine{{ID: "target", Price: 104, Label: "Target"}}
s.overlays = []mujica.IndicatorOverlay{{Kind: mujica.IndicatorMA, Params: []float64{10}}}
s.panes = mujica.IndicatorPanes{Panes: []mujica.IndicatorPaneSpec{{Kind: mujica.IndicatorMACD}, {Kind: mujica.IndicatorRSI}}}
s.bins = chart.VolumeProfileOf(s.candles, 24)
s.compare = []chart.CompareSymbol{{Name: "ETH", Closes: compare}}
s.alerts = []chart.PriceAlertLine{{ID: "target", Price: 104, Label: "Target"}}
s.overlays = []chart.IndicatorOverlay{{Kind: chart.IndicatorMA, Params: []float64{10}}}
s.panes = chart.IndicatorPanes{Panes: []chart.IndicatorPaneSpec{{Kind: chart.IndicatorMACD}, {Kind: chart.IndicatorRSI}}}
s.layout.Count = 2
for i := range s.layout.Cells {
s.layout.Cells[i] = mujica.ChartCell{Symbol: fmt.Sprintf("Symbol %d", i+1), Interval: "1h"}
s.layout.Cells[i] = chart.ChartCell{Symbol: fmt.Sprintf("Symbol %d", i+1), Interval: "1h"}
}
return s
}
func mktControls(c *ui.Context, s *mktDemoState, log func(string)) mujica.ChartFrame {
func mktControls(c *ui.Context, s *mktDemoState, log func(string)) chart.ChartFrame {
ui.Row(c).Gap(12).Wrap().Children(func() {
if mujica.Select(c, &s.status, []mujica.SelectOption[string]{{Value: "Ready", Label: "Ready"}, {Value: "Loading", Label: "Loading"}, {Value: "Failed", Label: "Failed"}, {Value: "Empty", Label: "Empty"}}, mujica.SelectOptions{Label: "Data state"}).Changed() {
if input.Select(c, &s.status, []input.SelectOption[string]{{Value: "Ready", Label: "Ready"}, {Value: "Loading", Label: "Loading"}, {Value: "Failed", Label: "Failed"}, {Value: "Empty", Label: "Empty"}}, input.SelectOptions{Label: "Data state"}).Changed() {
log("Data state: " + s.status)
}
if mujica.Checkbox(c, &s.disabled, "Disabled", mujica.CheckboxOptions{}).Changed() {
if input.Checkbox(c, &s.disabled, "Disabled", input.CheckboxOptions{}).Changed() {
log(fmt.Sprintf("Disabled: %v", s.disabled))
}
if mujica.Checkbox(c, &s.redUp, "Red up", mujica.CheckboxOptions{}).Changed() {
if input.Checkbox(c, &s.redUp, "Red up", input.CheckboxOptions{}).Changed() {
log(fmt.Sprintf("Red up: %v", s.redUp))
}
})
trend := mujica.GreenUp
trend := core.GreenUp
if s.redUp {
trend = mujica.RedUp
trend = core.RedUp
}
mujica.UseTrend(c, trend)
f := mujica.ChartFrame{Label: "BTC", Height: 220}
core.UseTrend(c, trend)
f := chart.ChartFrame{Label: "BTC", Height: 220}
switch s.status {
case "Loading":
f.Status = mujica.DataLoading
f.Status = data.DataLoading
case "Failed":
f.Status = mujica.DataFailed
f.Status = data.DataFailed
f.Error = "Archive unavailable"
}
return f
}
func mktLog(log func(string), name string, r mujica.ChartResult) {
func mktLog(log func(string), name string, r chart.ChartResult) {
if r.Changed() {
log(fmt.Sprintf("%s: point %d", name, r.Point))
}
Loaded 100 of 1822 files, more files were not shown because too many files have changed in this diff. Show more