From a61ac6168a0dfb9aed20fcbcf6a46bd908a22081 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=9B=B7=E7=94=B5=E8=8A=BD=E8=A1=A3?= Date: Mon, 5 Oct 2026 12:16:34 -0400 Subject: [PATCH 1/4] CI: format, cross-platform vet, staticcheck, tests, race, examples --- .github/workflows/ci.yml | 59 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 59 insertions(+) create mode 100644 .github/workflows/ci.yml diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..93a881a --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,59 @@ +name: CI + +on: + push: + branches: [main] + pull_request: + +permissions: + contents: read + +concurrency: + group: ci-${{ github.ref }} + cancel-in-progress: ${{ github.event_name == 'pull_request' }} + +jobs: + test: + name: ${{ matrix.name }} + runs-on: ${{ matrix.os }} + timeout-minutes: 30 + strategy: + fail-fast: false + matrix: + include: + - { name: macOS, os: macos-latest, skip: "" } + # Snapshot goldens exist only for darwin; see docs/compat.md. + - { name: Linux, os: ubuntu-latest, skip: "-skip Snapshot" } + - { name: Windows, os: windows-latest, skip: "-skip Snapshot" } + steps: + - uses: actions/checkout@v7 + - uses: actions/setup-go@v6 + with: + go-version-file: go.mod + - name: Format + if: runner.os == 'Linux' + run: test -z "$(gofmt -l .)" || { gofmt -l .; exit 1; } + - name: Build and vet every platform + if: runner.os == 'Linux' + run: | + for os in darwin linux windows; do + GOOS=$os go build ./... + GOOS=$os go vet ./... + done + - name: Staticcheck + if: runner.os == 'Linux' + run: go run honnef.co/go/tools/cmd/staticcheck@v0.8.1 ./... + - name: Test + run: go test ${{ matrix.skip }} ./... + - name: Race + if: runner.os != 'Windows' + run: go test -race ${{ matrix.skip }} ./... + - name: Examples compile + run: go build ./examples/... + - name: Upload snapshot diffs + if: failure() + uses: actions/upload-artifact@v5 + with: + name: snapshot-diffs-${{ matrix.name }} + path: testdata/snapshots/**/*.got.png + if-no-files-found: ignore From 1b025b5fadae06dc4a8a74100bc2b48c35b01186 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=9B=B7=E7=94=B5=E8=8A=BD=E8=A1=A3?= Date: Mon, 5 Oct 2026 12:17:01 -0400 Subject: [PATCH 2/4] Inputs: components 021-030 --- button_group.go | 124 ++++++ button_group_test.go | 84 +++++ button_test.go | 57 ++- checkbox.go | 134 +++++++ checkbox_test.go | 104 +++++ docs/components/021-button.md | 43 +++ docs/components/022-icon-button.md | 42 +++ docs/components/023-button-group.md | 53 +++ docs/components/024-text-input.md | 49 +++ docs/components/025-text-area.md | 49 +++ docs/components/026-number-input.md | 49 +++ docs/components/027-password-input.md | 42 +++ docs/components/028-search-input.md | 51 +++ docs/components/029-checkbox.md | 51 +++ docs/components/030-radio-group.md | 52 +++ example_input_test.go | 130 +++++++ examples/gallery/pages_input.go | 357 +++++++++++++++++- icon_button.go | 33 ++ icon_button_test.go | 78 ++++ messages_input.go | 22 ++ number_input.go | 227 +++++++++++ number_input_test.go | 141 +++++++ password_input.go | 75 ++++ password_input_test.go | 101 +++++ radio_group.go | 90 +++++ radio_group_test.go | 99 +++++ search_input.go | 140 +++++++ search_input_test.go | 129 +++++++ .../darwin/button-comfortable-dark.png | Bin 0 -> 11237 bytes .../darwin/button-comfortable-light.png | Bin 0 -> 10937 bytes .../snapshots/darwin/button-compact-dark.png | Bin 0 -> 11338 bytes .../snapshots/darwin/button-compact-light.png | Bin 0 -> 11075 bytes testdata/snapshots/darwin/button-dark.png | Bin 10309 -> 11165 bytes testdata/snapshots/darwin/button-light.png | Bin 9932 -> 10867 bytes .../snapshots/darwin/button_group-dark.png | Bin 0 -> 5061 bytes .../snapshots/darwin/button_group-light.png | Bin 0 -> 4956 bytes .../darwin/checkbox-comfortable-dark.png | Bin 0 -> 12926 bytes .../darwin/checkbox-comfortable-light.png | Bin 0 -> 12125 bytes .../darwin/checkbox-compact-dark.png | Bin 0 -> 12557 bytes .../darwin/checkbox-compact-light.png | Bin 0 -> 11910 bytes testdata/snapshots/darwin/checkbox-dark.png | Bin 0 -> 12385 bytes testdata/snapshots/darwin/checkbox-light.png | Bin 0 -> 11756 bytes .../snapshots/darwin/icon_button-dark.png | Bin 0 -> 3561 bytes .../snapshots/darwin/icon_button-light.png | Bin 0 -> 3551 bytes .../snapshots/darwin/number_input-dark.png | Bin 0 -> 7139 bytes .../snapshots/darwin/number_input-light.png | Bin 0 -> 6878 bytes .../snapshots/darwin/password_input-dark.png | Bin 0 -> 7652 bytes .../snapshots/darwin/password_input-light.png | Bin 0 -> 7542 bytes .../snapshots/darwin/radio_group-dark.png | Bin 0 -> 10665 bytes .../snapshots/darwin/radio_group-light.png | Bin 0 -> 10032 bytes .../snapshots/darwin/search_input-dark.png | Bin 0 -> 7926 bytes .../snapshots/darwin/search_input-light.png | Bin 0 -> 7847 bytes testdata/snapshots/darwin/text_area-dark.png | Bin 0 -> 16547 bytes testdata/snapshots/darwin/text_area-light.png | Bin 0 -> 15926 bytes .../darwin/text_input-comfortable-dark.png | Bin 0 -> 13287 bytes .../darwin/text_input-comfortable-light.png | Bin 0 -> 12823 bytes .../darwin/text_input-compact-dark.png | Bin 0 -> 13079 bytes .../darwin/text_input-compact-light.png | Bin 0 -> 12710 bytes testdata/snapshots/darwin/text_input-dark.png | Bin 0 -> 12964 bytes .../snapshots/darwin/text_input-light.png | Bin 0 -> 12498 bytes text_area.go | 88 +++++ text_area_test.go | 94 +++++ text_input.go | 172 +++++++++ text_input_test.go | 141 +++++++ 64 files changed, 3090 insertions(+), 11 deletions(-) create mode 100644 button_group.go create mode 100644 button_group_test.go create mode 100644 checkbox.go create mode 100644 checkbox_test.go create mode 100644 docs/components/021-button.md create mode 100644 docs/components/022-icon-button.md create mode 100644 docs/components/023-button-group.md create mode 100644 docs/components/024-text-input.md create mode 100644 docs/components/025-text-area.md create mode 100644 docs/components/026-number-input.md create mode 100644 docs/components/027-password-input.md create mode 100644 docs/components/028-search-input.md create mode 100644 docs/components/029-checkbox.md create mode 100644 docs/components/030-radio-group.md create mode 100644 example_input_test.go create mode 100644 icon_button.go create mode 100644 icon_button_test.go create mode 100644 messages_input.go create mode 100644 number_input.go create mode 100644 number_input_test.go create mode 100644 password_input.go create mode 100644 password_input_test.go create mode 100644 radio_group.go create mode 100644 radio_group_test.go create mode 100644 search_input.go create mode 100644 search_input_test.go create mode 100644 testdata/snapshots/darwin/button-comfortable-dark.png create mode 100644 testdata/snapshots/darwin/button-comfortable-light.png create mode 100644 testdata/snapshots/darwin/button-compact-dark.png create mode 100644 testdata/snapshots/darwin/button-compact-light.png create mode 100644 testdata/snapshots/darwin/button_group-dark.png create mode 100644 testdata/snapshots/darwin/button_group-light.png create mode 100644 testdata/snapshots/darwin/checkbox-comfortable-dark.png create mode 100644 testdata/snapshots/darwin/checkbox-comfortable-light.png create mode 100644 testdata/snapshots/darwin/checkbox-compact-dark.png create mode 100644 testdata/snapshots/darwin/checkbox-compact-light.png create mode 100644 testdata/snapshots/darwin/checkbox-dark.png create mode 100644 testdata/snapshots/darwin/checkbox-light.png create mode 100644 testdata/snapshots/darwin/icon_button-dark.png create mode 100644 testdata/snapshots/darwin/icon_button-light.png create mode 100644 testdata/snapshots/darwin/number_input-dark.png create mode 100644 testdata/snapshots/darwin/number_input-light.png create mode 100644 testdata/snapshots/darwin/password_input-dark.png create mode 100644 testdata/snapshots/darwin/password_input-light.png create mode 100644 testdata/snapshots/darwin/radio_group-dark.png create mode 100644 testdata/snapshots/darwin/radio_group-light.png create mode 100644 testdata/snapshots/darwin/search_input-dark.png create mode 100644 testdata/snapshots/darwin/search_input-light.png create mode 100644 testdata/snapshots/darwin/text_area-dark.png create mode 100644 testdata/snapshots/darwin/text_area-light.png create mode 100644 testdata/snapshots/darwin/text_input-comfortable-dark.png create mode 100644 testdata/snapshots/darwin/text_input-comfortable-light.png create mode 100644 testdata/snapshots/darwin/text_input-compact-dark.png create mode 100644 testdata/snapshots/darwin/text_input-compact-light.png create mode 100644 testdata/snapshots/darwin/text_input-dark.png create mode 100644 testdata/snapshots/darwin/text_input-light.png create mode 100644 text_area.go create mode 100644 text_area_test.go create mode 100644 text_input.go create mode 100644 text_input_test.go diff --git a/button_group.go b/button_group.go new file mode 100644 index 0000000..b3de134 --- /dev/null +++ b/button_group.go @@ -0,0 +1,124 @@ +package mujica + +import ( + "log/slog" + + "github.com/egoist/mygo/ui" + + "mujicaui/internal" + "mujicaui/theme" +) + +// ButtonGroupItem is one button of a ButtonGroup. Give Label, Icon or both. +type ButtonGroupItem struct { + Label string + Icon *ui.SVG + // Name names an icon-only item for assistive technology. + Name string + Disabled bool +} + +// ButtonGroupOptions configure a ButtonGroup. +type ButtonGroupOptions struct { + Vertical bool + // Label names the group for assistive technology. + Label string + Disabled bool +} + +// ButtonGroupResult is the group element and the item clicked this frame. +type ButtonGroupResult struct { + *ui.Element + clicked int +} + +// ClickedItem returns the index of the item clicked this frame, or -1. +func (r ButtonGroupResult) ClickedItem() int { return r.clicked } + +// ButtonGroup joins buttons in one bordered strip; arrows move between them. +func ButtonGroup(c *ui.Context, items []ButtonGroupItem, opts ButtonGroupOptions) ButtonGroupResult { + if len(items) == 0 { + panic("mujica: ButtonGroup needs items") + } + k := Tokens(c) + d := Density(c) + orient, last := ui.Horizontal, len(items)-1 + g := ui.Row(c).AlignItems(ui.Stretch).Shrink(0) + if opts.Vertical { + orient = ui.Vertical + g.Column() + } + g.FocusGroup(orient).Role(ui.RoleToolbar).Label(opts.Label).Disabled(opts.Disabled) + r := ButtonGroupResult{Element: g, clicked: -1} + g.Children(func() { + for i, it := range items { + if it.Label == "" && it.Icon == nil { + panic("mujica: ButtonGroupItem needs a Label or an Icon") + } + b := ui.ButtonBase(c).Key(i).MinHeight(d.ControlHeight()).MinWidth(d.ControlHeight()). + Padding(0, d.Unit()*3).Gap(d.Unit() * 2).Disabled(it.Disabled) + internal.Ring(b, k.Focus, theme.ControlRadius) + if it.Name != "" { + b.Label(it.Name) + } + bg := k.Surface + if b.Pressed() { + bg = k.Selection + } else if b.Hovered() { + bg = k.SurfaceHover + } + b.Background(bg).TextColor(k.Text).Transition(textInputFade) + b.Radius(buttonGroupCorners(i, last, opts.Vertical)...) + b.Border(theme.BorderWidth, k.ControlBorder).BorderWidth(buttonGroupEdges(i, opts.Vertical)...) + b.Children(func() { + if it.Icon != nil { + ui.Icon(c, it.Icon).Size(16, 16) + } + if it.Label != "" { + ui.Text(c, it.Label).SingleLine() + } + }) + if b.Clicked() { + r.clicked = i + slog.Debug("mujica: button_group click", "item", i) + } + } + }) + return r +} + +// buttonGroupCorners rounds only the outer corners of item i of 0..last. +func buttonGroupCorners(i, last int, vertical bool) []float32 { + var tl, tr, br, bl float32 + r := theme.ControlRadius + first, end := i == 0, i == last + if vertical { + if first { + tl, tr = r, r + } + if end { + br, bl = r, r + } + } else { + if first { + tl, bl = r, r + } + if end { + tr, br = r, r + } + } + return []float32{tl, tr, br, bl} +} + +// buttonGroupEdges draws each shared edge once: items after the first skip the leading one. +func buttonGroupEdges(i int, vertical bool) []float32 { + w := theme.BorderWidth + switch { + case i == 0: + return []float32{w} + case vertical: + return []float32{0, w, w, w} + default: + return []float32{w, w, w, 0} + } +} diff --git a/button_group_test.go b/button_group_test.go new file mode 100644 index 0000000..ead0aa7 --- /dev/null +++ b/button_group_test.go @@ -0,0 +1,84 @@ +package mujica + +import ( + "testing" + + "github.com/egoist/mygo/ui" + + "mujicaui/icons" +) + +var buttonGroupTestItems = []ButtonGroupItem{{Label: "Left"}, {Label: "Center", Disabled: true}, {Label: "Right"}} + +func TestButtonGroupClickAndArrows(t *testing.T) { + var got []int + tt := ui.NewTester(func(c *ui.Context) { + Use(c, Settings{}) + if i := ButtonGroup(c, buttonGroupTestItems, ButtonGroupOptions{Label: "Align"}).ClickedItem(); i >= 0 { + got = append(got, i) + } + }, 400, 200) + _ = tt.Click("Center") + _ = tt.Click("Left") + tt.Key(0, ui.KeyRight) + if !tt.Focused("Right") { + t.Fatal("Right arrow did not skip the disabled item to Right") + } + tt.Key(0, ui.KeyEnter) + tt.Key(0, ui.KeyRight) + if !tt.Focused("Left") { + t.Fatal("arrows do not wrap round") + } + if len(got) != 2 || got[0] != 0 || got[1] != 2 { + t.Fatalf("clicked %v, want [0 2]", got) + } +} + +func TestButtonGroupOneTabStop(t *testing.T) { + tt := ui.NewTester(func(c *ui.Context) { + Use(c, Settings{}) + ButtonGroup(c, buttonGroupTestItems, ButtonGroupOptions{Vertical: true}) + Button(c, "After", ButtonOptions{}) + }, 400, 300) + tt.Key(0, ui.KeyTab) + if !tt.Focused("Left") { + t.Fatal("Tab did not enter the group") + } + tt.Key(0, ui.KeyDown) + if !tt.Focused("Right") { + t.Fatal("Down did not move in a vertical group") + } + tt.Key(0, ui.KeyTab) + if !tt.Focused("After") { + t.Fatal("Tab did not leave the group") + } +} + +func TestButtonGroupDisabledGroup(t *testing.T) { + n := 0 + tt := ui.NewTester(func(c *ui.Context) { + Use(c, Settings{}) + if ButtonGroup(c, buttonGroupTestItems, ButtonGroupOptions{Disabled: true}).ClickedItem() >= 0 { + n++ + } + }, 400, 200) + _ = tt.Click("Left") + tt.Key(0, ui.KeyTab) + tt.Key(0, ui.KeyEnter) + if n != 0 { + t.Fatalf("disabled group clicked %d", n) + } +} + +func TestButtonGroupSnapshot(t *testing.T) { + snapshot(t, "button_group", 420, 200, func(c *ui.Context) { + ui.Row(c).Padding(24).Gap(24).AlignItems(ui.Start).Children(func() { + ButtonGroup(c, []ButtonGroupItem{{Label: "Day"}, {Label: "Week"}, {Label: "Month", Disabled: true}}, ButtonGroupOptions{}) + ButtonGroup(c, []ButtonGroupItem{ + {Icon: icons.Must("bold"), Name: "Bold"}, + {Icon: icons.Must("italic"), Name: "Italic"}, + {Icon: icons.Must("underline"), Name: "Underline"}, + }, ButtonGroupOptions{Vertical: true}) + }) + }) +} diff --git a/button_test.go b/button_test.go index de99f0d..8a1bc96 100644 --- a/button_test.go +++ b/button_test.go @@ -1,10 +1,12 @@ package mujica import ( + "strings" "testing" "github.com/egoist/mygo/ui" + "mujicaui/icons" "mujicaui/theme" ) @@ -80,16 +82,51 @@ func TestButtonMinHeight(t *testing.T) { } } -func TestButtonSnapshot(t *testing.T) { - snapshot(t, "button", 640, 140, func(c *ui.Context) { - ui.Row(c).Padding(24).Gap(12).Wrap().Children(func() { - Button(c, "Primary", ButtonOptions{}) - Button(c, "Secondary", ButtonOptions{Variant: Secondary}) - Button(c, "Outline", ButtonOptions{Variant: Outline}) - Button(c, "Ghost", ButtonOptions{Variant: Ghost}) - Button(c, "Delete", ButtonOptions{Variant: Danger}) - Button(c, "Saving", ButtonOptions{Loading: true}) - Button(c, "Disabled", ButtonOptions{Disabled: true}) +func TestButtonIconAndDanger(t *testing.T) { + n := 0 + tt := ui.NewTester(func(c *ui.Context) { + Use(c, Settings{}) + if Button(c, "", ButtonOptions{Variant: Danger, Icon: icons.Must("trash-2"), Label: "Delete"}).Clicked() { + n++ + } + }, 300, 200) + if err := tt.Click("Delete"); err != nil { + t.Fatal(err) + } + if n != 1 { + t.Fatalf("icon button clicked %d times", n) + } +} + +// buttonDensitySnapshots snapshots view at Compact and Comfortable as name-. +func buttonDensitySnapshots(t *testing.T, name string, w, h int, view func(c *ui.Context)) { + t.Helper() + for _, d := range []theme.Density{theme.Compact, theme.Comfortable} { + snapshot(t, name+"-"+strings.ToLower(d.String()), w, h, func(c *ui.Context) { + m := Light + if Tokens(c).Dark { + m = Dark + } + Use(c, Settings{Mode: m, Density: d}) + view(c) }) + } +} + +func TestButtonSnapshot(t *testing.T) { + snapshot(t, "button", 640, 140, buttonSnapshotView) + buttonDensitySnapshots(t, "button", 640, 160, buttonSnapshotView) +} + +func buttonSnapshotView(c *ui.Context) { + ui.Row(c).Padding(24).Gap(12).Wrap().Children(func() { + Button(c, "Primary", ButtonOptions{}) + Button(c, "Secondary", ButtonOptions{Variant: Secondary}) + Button(c, "Outline", ButtonOptions{Variant: Outline}) + Button(c, "Ghost", ButtonOptions{Variant: Ghost}) + Button(c, "Delete", ButtonOptions{Variant: Danger}) + Button(c, "Saving", ButtonOptions{Loading: true}) + Button(c, "Disabled", ButtonOptions{Disabled: true}) + Button(c, "Add", ButtonOptions{Variant: Secondary, Icon: icons.Must("plus")}) }) } diff --git a/checkbox.go b/checkbox.go new file mode 100644 index 0000000..de9b5a4 --- /dev/null +++ b/checkbox.go @@ -0,0 +1,134 @@ +package mujica + +import ( + "log/slog" + + "github.com/egoist/mygo/ui" + + "mujicaui/internal" + "mujicaui/theme" +) + +// CheckboxOptions configure a Checkbox. +type CheckboxOptions struct { + // Indeterminate shows the mixed mark; a click then checks the box. + Indeterminate bool + Disabled bool +} + +// checkboxSize is the box edge in DIPs. +const checkboxSize float32 = 16 + +// Checkbox toggles *value; a click on the box or its label toggles, as does Space. +func Checkbox(c *ui.Context, value *bool, label string, opts CheckboxOptions) *ui.Element { + if value == nil { + panic("mujica: Checkbox needs a value") + } + k := Tokens(c) + d := Density(c) + row := ui.CheckboxBase(c, value).Gap(d.Unit() * 2).MinHeight(d.ControlHeight()).FocusRing(false).Disabled(opts.Disabled) + if row.Changed() && opts.Indeterminate && !*value { + // From mixed, a click always checks. + *value = true + } + if opts.Indeterminate { + row.Description(msg(c, "checkbox.mixed")) + } + on, mixed := *value, opts.Indeterminate + hover, pressed := row.Hovered(), row.Pressed() + row.Children(func() { + box := ui.Box(c).Size(checkboxSize, checkboxSize).Shrink(0).Radius(theme.ControlRadius) + switch { + case on || mixed: + box.Background(k.Accent).Border(theme.BorderWidth, k.AccentText) + case pressed: + box.Background(k.Selection).Border(theme.BorderWidth, k.AccentText) + case hover: + box.Background(k.SurfaceHover).Border(theme.BorderWidth, k.AccentText) + default: + box.Background(k.Surface).Border(theme.BorderWidth, k.ControlBorder) + } + box.Transition(textInputFade) + box.DrawOver(func(p *ui.Painter, r ui.Rect) { + switch { + case mixed: + p.Fill(ui.Rect{X: r.X + r.W*0.25, Y: r.Y + r.H/2 - 1, W: r.W * 0.5, H: 2}, k.OnAccent, 1) + case on: + var path ui.Path + path.MoveTo(r.X+r.W*0.24, r.Y+r.H*0.52).LineTo(r.X+r.W*0.42, r.Y+r.H*0.7).LineTo(r.X+r.W*0.77, r.Y+r.H*0.3) + p.StrokePath(&path, 2, k.OnAccent) + } + if row.FocusVisible() { + internal.FocusRing(p, r, theme.ControlRadius, k.Focus) + } + }) + if label != "" { + ui.Text(c, label) + } + }) + if row.Changed() { + slog.Debug("mujica: checkbox toggle", "label", label, "checked", *value) + } + return row +} + +// CheckboxItem is one box of a CheckboxGroup. +type CheckboxItem struct { + Label string + Value *bool + Disabled bool +} + +// CheckboxGroupOptions configure a CheckboxGroup. +type CheckboxGroupOptions struct { + Disabled bool +} + +// CheckboxGroup shows a select-all box over items: checked, mixed or clear. +func CheckboxGroup(c *ui.Context, label string, items []CheckboxItem, opts CheckboxGroupOptions) InputResult { + if len(items) == 0 { + panic("mujica: CheckboxGroup needs items") + } + d := Density(c) + on, enabled, enabledOn := 0, 0, 0 + for _, it := range items { + if it.Value == nil { + panic("mujica: CheckboxItem needs a Value") + } + if *it.Value { + on++ + } + if !it.Disabled { + enabled++ + if *it.Value { + enabledOn++ + } + } + } + var r InputResult + r.Element = ui.Column(c).Shrink(0).Disabled(opts.Disabled || enabled == 0) + r.Element.Children(func() { + all := on == len(items) + head := Checkbox(c, &all, label, CheckboxOptions{Indeterminate: on > 0 && !all}) + r.Input = head + if head.Changed() { + // Select-all leaves disabled items alone. + target := enabledOn < enabled + for _, it := range items { + if !it.Disabled { + *it.Value = target + } + } + r.changed = true + slog.Debug("mujica: checkbox_group select_all", "label", label, "checked", target) + } + ui.Column(c).Padding(0, 0, 0, checkboxSize+d.Unit()*2).Role(ui.RoleGroup).Label(label).Children(func() { + for _, it := range items { + if Checkbox(c, it.Value, it.Label, CheckboxOptions{Disabled: it.Disabled}).Changed() { + r.changed = true + } + } + }) + }) + return r +} diff --git a/checkbox_test.go b/checkbox_test.go new file mode 100644 index 0000000..d8befba --- /dev/null +++ b/checkbox_test.go @@ -0,0 +1,104 @@ +package mujica + +import ( + "testing" + + "github.com/egoist/mygo/ui" +) + +func TestCheckboxLabelClickAndSpace(t *testing.T) { + on, changes := false, 0 + tt := ui.NewTester(func(c *ui.Context) { + Use(c, Settings{}) + if Checkbox(c, &on, "Remember me", CheckboxOptions{}).Changed() { + changes++ + } + }, 300, 200) + _ = tt.Click("Remember me") + if !on || changes != 1 { + t.Fatalf("label click: %v changes %d", on, changes) + } + tt.Key(0, ui.KeyTab) + tt.Key(0, ui.KeySpace) + if on || changes != 2 { + t.Fatalf("Space: %v changes %d", on, changes) + } +} + +func TestCheckboxIndeterminateChecks(t *testing.T) { + for _, start := range []bool{false, true} { + on := start + tt := ui.NewTester(func(c *ui.Context) { + Use(c, Settings{}) + Checkbox(c, &on, "Some", CheckboxOptions{Indeterminate: true}) + }, 300, 200) + _ = tt.Click("Some") + if !on { + t.Fatalf("from mixed with %v, click left the box clear", start) + } + } +} + +func TestCheckboxDisabled(t *testing.T) { + on := false + tt := ui.NewTester(func(c *ui.Context) { + Use(c, Settings{}) + Checkbox(c, &on, "Off", CheckboxOptions{Disabled: true}) + }, 300, 200) + _ = tt.Click("Off") + tt.Key(0, ui.KeyTab) + tt.Key(0, ui.KeySpace) + if on || tt.Focused("Off") { + t.Fatalf("disabled box changed %v or took focus", on) + } +} + +func TestCheckboxGroupSelectAll(t *testing.T) { + mail, cal, locked := true, false, false + changes := 0 + items := []CheckboxItem{{Label: "Mail", Value: &mail}, {Label: "Calendar", Value: &cal}, {Label: "Admin", Value: &locked, Disabled: true}} + tt := ui.NewTester(func(c *ui.Context) { + Use(c, Settings{}) + if CheckboxGroup(c, "Notifications", items, CheckboxGroupOptions{}).Changed() { + changes++ + } + }, 300, 300) + _ = tt.Click("Notifications") + if !mail || !cal || locked || changes != 1 { + t.Fatalf("select all: mail %v cal %v admin %v changes %d", mail, cal, locked, changes) + } + _ = tt.Click("Notifications") + if mail || cal || locked { + t.Fatalf("clear all: mail %v cal %v admin %v", mail, cal, locked) + } + _ = tt.Click("Calendar") + if !cal || changes != 3 { + t.Fatalf("item click: cal %v changes %d", cal, changes) + } + tt.Key(0, ui.KeyTab) + if !tt.Focused("Notifications") { + t.Fatal("Tab did not reach the select-all box") + } + tt.Key(0, ui.KeySpace) + if !mail || !cal { + t.Fatalf("Space on select-all: mail %v cal %v", mail, cal) + } +} + +func checkboxSnapshotView(c *ui.Context) { + a, b, m, x, y := false, true, false, true, false + ui.Row(c).Padding(24).Gap(32).AlignItems(ui.Start).Children(func() { + ui.Column(c).Children(func() { + Checkbox(c, &a, "Unchecked", CheckboxOptions{}) + Checkbox(c, &b, "Checked", CheckboxOptions{}) + Checkbox(c, &m, "Indeterminate", CheckboxOptions{Indeterminate: true}) + Checkbox(c, &b, "Disabled", CheckboxOptions{Disabled: true}) + }) + CheckboxGroup(c, "Notifications", []CheckboxItem{{Label: "Mail", Value: &x}, {Label: "Calendar", Value: &y}}, CheckboxGroupOptions{}) + }) +} + +func TestCheckboxSnapshot(t *testing.T) { + snapshot(t, "checkbox", 420, 220, checkboxSnapshotView) + buttonDensitySnapshots(t, "checkbox", 420, 260, checkboxSnapshotView) +} diff --git a/docs/components/021-button.md b/docs/components/021-button.md new file mode 100644 index 0000000..02824ad --- /dev/null +++ b/docs/components/021-button.md @@ -0,0 +1,43 @@ +# 021 Button 按钮 + +## 用途 + +触发一个操作。提供主按钮、次按钮、描边、无底色和危险五种变体,可带图标,可显示加载与禁用。基于 MyGo `ui.ButtonBase`。 + +## 最小示例 + +```go +if mujica.Button(c, "Save", mujica.ButtonOptions{Icon: icons.Must("check")}).Clicked() { + fmt.Println("saved") +} +``` + +签名:`func Button(c *ui.Context, label string, opts ButtonOptions) *ui.Element` + +## 参数 + +| 字段 | 类型 | 默认值 | 含义 | +| --- | --- | --- | --- | +| `label` | `string` | 必填(仅图标时可为空) | 按钮文字。 | +| `Variant` | `ButtonVariant` | `Primary` | `Primary`、`Secondary`、`Outline`、`Ghost`、`Danger`。 | +| `Icon` | `*ui.SVG` | `nil` | 文字前的 16 DIP 图标。 | +| `Loading` | `bool` | `false` | 以菱形加载标记替换图标,保留尺寸与文字,阻止点击。 | +| `Disabled` | `bool` | `false` | 不可点击、不进入 Tab 序列。 | +| `Label` | `string` | `""` | 无文字按钮的无障碍名称。 | + +## 状态 + +默认、悬停(底色加深或出现浅底)、按下、键盘聚焦(2 DIP 酒红焦点环,外留 2 DIP)、加载、禁用(MyGo 统一减淡)。高度不低于当前密度的控件高度(28/36/44 DIP)。 + +## 事件 + +`Clicked()`:鼠标在按钮内释放、Enter 或 Space 时为真,一次操作只报告一次。加载与禁用时始终为假。 + +## 键盘操作 + +Tab / Shift+Tab 聚焦;Enter 或 Space 触发。 + +## 限制 + +- 加载标记在减少动画模式下静止显示。 +- 危险变体的悬停与按下色由 `DangerFill` 混合黑色得到,未单独定义变量。 diff --git a/docs/components/022-icon-button.md b/docs/components/022-icon-button.md new file mode 100644 index 0000000..9efa694 --- /dev/null +++ b/docs/components/022-icon-button.md @@ -0,0 +1,42 @@ +# 022 IconButton 图标按钮 + +## 用途 + +只显示图标的方形按钮,用于工具栏与紧凑操作。操作名称必填,供辅助技术朗读,并作为默认提示文字。建立在 Button 之上。 + +## 最小示例 + +```go +if mujica.IconButton(c, icons.Must("settings"), "Settings", mujica.IconButtonOptions{Variant: mujica.Ghost}).Clicked() { + fmt.Println("open settings") +} +``` + +签名:`func IconButton(c *ui.Context, icon *ui.SVG, label string, opts IconButtonOptions) *ui.Element` + +## 参数 + +| 字段 | 类型 | 默认值 | 含义 | +| --- | --- | --- | --- | +| `icon` | `*ui.SVG` | 必填 | 图标;为 nil 时 panic。 | +| `label` | `string` | 必填 | 操作名称;为空时 panic。 | +| `Variant` | `ButtonVariant` | `Primary` | 与 Button 相同的五种变体;工具栏通常用 `Ghost`。 | +| `Tooltip` | `string` | 操作名称 | 悬停约 600 毫秒后显示的提示。 | +| `Loading` | `bool` | `false` | 显示加载标记并阻止点击。 | +| `Disabled` | `bool` | `false` | 禁用。 | + +## 状态 + +同 Button:悬停、按下、聚焦、加载、禁用。边长等于当前密度的控件高度,不小于 28 DIP。 + +## 事件 + +`Clicked()`,一次操作一次。 + +## 键盘操作 + +Tab 聚焦;Enter 或 Space 触发。 + +## 限制 + +- 禁用时不显示提示:MyGo 的 `Hovered` 对禁用元素返回 false,提示随之不出现;名称仍作为无障碍描述保留。 diff --git a/docs/components/023-button-group.md b/docs/components/023-button-group.md new file mode 100644 index 0000000..c394325 --- /dev/null +++ b/docs/components/023-button-group.md @@ -0,0 +1,53 @@ +# 023 ButtonGroup 按钮组 + +## 用途 + +把若干按钮拼成一条,共用边框与尺寸,只有外侧圆角。整个组是一个 Tab 停靠点,组内用方向键移动。基于 `ui.ButtonBase` 与 MyGo 焦点组 `FocusGroup`。 + +## 最小示例 + +```go +items := []mujica.ButtonGroupItem{{Label: "Day"}, {Label: "Week"}, {Label: "Month"}} +if i := mujica.ButtonGroup(c, items, mujica.ButtonGroupOptions{Label: "Range"}).ClickedItem(); i >= 0 { + fmt.Println("chose", items[i].Label) +} +``` + +签名:`func ButtonGroup(c *ui.Context, items []ButtonGroupItem, opts ButtonGroupOptions) ButtonGroupResult` + +## 参数 + +`ButtonGroupItem` + +| 字段 | 类型 | 默认值 | 含义 | +| --- | --- | --- | --- | +| `Label` | `string` | `""` | 文字;与 `Icon` 至少给一个,否则 panic。 | +| `Icon` | `*ui.SVG` | `nil` | 图标。 | +| `Name` | `string` | `""` | 仅图标项的无障碍名称。 | +| `Disabled` | `bool` | `false` | 单项禁用;方向键跳过该项。 | + +`ButtonGroupOptions` + +| 字段 | 类型 | 默认值 | 含义 | +| --- | --- | --- | --- | +| `Vertical` | `bool` | `false` | 纵向排列,使用上下方向键。 | +| `Label` | `string` | `""` | 组的无障碍名称(工具栏角色)。 | +| `Disabled` | `bool` | `false` | 禁用整组。 | + +`items` 为空时 panic。 + +## 状态 + +每项有悬停、按下、聚焦、禁用;相邻项共用一条 1 DIP 分隔边。 + +## 事件 + +`ClickedItem()` 返回本帧被点击项的下标,无点击时为 -1。 + +## 键盘操作 + +Tab 进入组(回到上次聚焦项);横向组 Left/Right、纵向组 Up/Down 移动并循环;Home、End 到首末项;Enter 或 Space 触发。 + +## 限制 + +- 组内没有选中概念;需要保持选中请使用 ToggleGroup(033)。 diff --git a/docs/components/024-text-input.md b/docs/components/024-text-input.md new file mode 100644 index 0000000..88649a9 --- /dev/null +++ b/docs/components/024-text-input.md @@ -0,0 +1,49 @@ +# 024 TextInput 文本输入框 + +## 用途 + +单行文本编辑。支持占位、前后插槽、清空、只读与错误。编辑核心是 MyGo `ui.TextInputBase`,因此保留选择、撤销重做、剪贴板、右键菜单与输入法组合。 + +## 最小示例 + +```go +r := mujica.TextInput(c, &name, mujica.TextInputOptions{Label: "Name", Placeholder: "Your name", Clearable: true}) +if r.Submitted() { + fmt.Println("hello", name) +} +``` + +签名:`func TextInput(c *ui.Context, value *string, opts TextInputOptions) InputResult` + +`InputResult` 内嵌外层 `*ui.Element`,`Input` 字段是内部编辑元素,`Changed()`、`Submitted()` 报告事件。 + +## 参数 + +| 字段 | 类型 | 默认值 | 含义 | +| --- | --- | --- | --- | +| `value` | `*string` | 必填 | 编辑的字符串;nil 时 panic。 | +| `Placeholder` | `string` | `""` | 空值时的占位文字(TextMuted 色)。 | +| `Label` | `string` | `""` | 无障碍名称,也供测试查找。 | +| `Leading` / `Trailing` | `func()` | `nil` | 在框内左侧、右侧构建内容,如图标或单位。 | +| `Clearable` | `bool` | `false` | 有内容时显示清空按钮;Escape 也会清空。 | +| `Disabled` | `bool` | `false` | 禁用,不可聚焦。 | +| `ReadOnly` | `bool` | `false` | 可聚焦、选择、复制,不可修改。 | +| `Error` | `string` | `""` | 非空时边框变为 Danger,并在下方显示该文字;辅助技术同时朗读。 | + +## 状态 + +默认(Surface 底、ControlBorder 边)、悬停(边框加深)、聚焦(边框 Focus 色;键盘聚焦另加焦点环)、只读(Background 底、Border 边)、错误、禁用。颜色变化 120 毫秒过渡,减少动画时直接切换。 + +## 事件 + +- `Changed()`:输入、粘贴、撤销或清空改变了值。只读时不会报告。 +- `Submitted()`:在框内按 Enter。 + +## 键盘操作 + +Tab 聚焦(全选内容);Enter 提交;Escape 清空(仅 `Clearable` 且有内容时,否则交给外层);Cmd/Ctrl+Z、Shift+Cmd+Z 撤销重做;Cmd/Ctrl+A/C/X/V;输入法组合期间不提交。 + +## 限制 + +- MyGo 没有公开的只读输入接口(只读模式仅用于 `Text.Selectable`,`editor.go:77`)。`ReadOnly` 通过让编辑器编辑一个副本并在同帧还原实现,撤销栈里可能出现被还原的步骤。 +- 清空按钮不进入 Tab 序列(与 MyGo SearchField 相同),键盘用 Escape 清空。 diff --git a/docs/components/025-text-area.md b/docs/components/025-text-area.md new file mode 100644 index 0000000..32205d8 --- /dev/null +++ b/docs/components/025-text-area.md @@ -0,0 +1,49 @@ +# 025 TextArea 多行文本框 + +## 用途 + +多行文本编辑,可设最小与最大高度,超出最大高度后在框内滚动;可显示字数;提交快捷键可配置。编辑核心是 MyGo `ui.TextAreaBase`,只排版可见段落,适合长文本。 + +## 最小示例 + +```go +r := mujica.TextArea(c, ¬es, mujica.TextAreaOptions{Label: "Notes", MaxHeight: 160, MaxLength: 200}) +if r.Submitted() { + fmt.Println("send", notes) +} +``` + +签名:`func TextArea(c *ui.Context, value *string, opts TextAreaOptions) InputResult` + +## 参数 + +| 字段 | 类型 | 默认值 | 含义 | +| --- | --- | --- | --- | +| `value` | `*string` | 必填 | 编辑的字符串。 | +| `Placeholder` | `string` | `""` | 占位文字。 | +| `Label` | `string` | `""` | 无障碍名称。 | +| `MinHeight` | `float32` | `0`(三行) | 编辑区最小高度,DIP。 | +| `MaxHeight` | `float32` | `0`(随内容增长) | 编辑区最大高度,超出后滚动;小于 `MinHeight` 时 panic。 | +| `MaxLength` | `int` | `0`(不显示) | 显示“当前 / 上限”字数(按 Unicode 字符计);超出时变红并提示超出数量。 | +| `SubmitKey` / `SubmitMods` | `ui.Key` / `ui.Modifiers` | Cmd+Enter(非 macOS 为 Ctrl+Enter) | 提交快捷键;`SubmitKey` 为零时使用默认值。 | +| `Disabled` | `bool` | `false` | 禁用。 | +| `ReadOnly` | `bool` | `false` | 只读,可选择复制。 | +| `Error` | `string` | `""` | 错误文字;为空且超出 `MaxLength` 时自动显示超出提示。 | + +## 状态 + +默认、悬停、聚焦、只读、错误、禁用,表现同 TextInput。 + +## 事件 + +`Changed()`:内容被用户改变。`Submitted()`:按下提交快捷键。 + +## 键盘操作 + +Enter 与 Shift+Enter 换行;提交快捷键提交;方向键、PageUp/PageDown 移动光标并滚动;撤销、剪贴板与输入法同 TextInput。 + +## 限制 + +- `MaxLength` 只提示,不截断输入,以免打断输入法组合与粘贴。 +- 提交快捷键不能是不带修饰键的 Enter:MyGo 编辑器先接收 Enter 并插入换行(`editor.go:161`)。 +- 只读实现方式同 TextInput。 diff --git a/docs/components/026-number-input.md b/docs/components/026-number-input.md new file mode 100644 index 0000000..82865f7 --- /dev/null +++ b/docs/components/026-number-input.md @@ -0,0 +1,49 @@ +# 026 NumberInput 数字输入框 + +## 用途 + +输入与步进数值,支持范围、步长、精度与空值。合并 MyGo 的 Number input(输入模式)与 Stepper(`StepperOnly` 仅步进模式)。 + +## 最小示例 + +```go +if mujica.NumberInput(c, &copies, mujica.NumberInputOptions{Min: 1, Max: 99, Label: "Copies"}).Changed() { + fmt.Println("copies", copies) +} +``` + +签名:`func NumberInput(c *ui.Context, value *float64, opts NumberInputOptions) InputResult` + +## 参数 + +| 字段 | 类型 | 默认值 | 含义 | +| --- | --- | --- | --- | +| `value` | `*float64` | 必填 | 数值;空值用 `math.NaN()` 表示,仅在 `AllowEmpty` 时允许。 | +| `Min` / `Max` | `float64` | 都为 0 | 范围;相等(含零值)表示不限范围;`Min > Max` 时 panic。 | +| `Step` | `float64` | `1` | 方向键与按钮的步长;负数 panic。 | +| `Precision` | `int` | 取 `Step` 的小数位 | 保留与显示的小数位。 | +| `AllowEmpty` | `bool` | `false` | 允许清空,值变为 NaN;从空值步进时从 0(夹在范围内)开始。 | +| `StepperOnly` | `bool` | `false` | 只显示数值与上下箭头,不可输入;必须给出 `Min < Max`,且不能与 `AllowEmpty` 同用。 | +| `Placeholder` | `string` | `""` | 空值时的占位。 | +| `Label` | `string` | `""` | 无障碍名称。 | +| `Disabled` | `bool` | `false` | 禁用。 | +| `Error` | `string` | `""` | 调用方给出的错误。 | + +## 状态 + +输入模式同 TextInput;输入非数字或超出范围时显示内置错误(“请输入数字”“请输入 a 到 b 之间的数字”),值保持上一次有效值;失去焦点后文本恢复为当前值并清除该错误。到达边界时对应按钮禁用。 + +## 事件 + +`Changed()`:值因输入、方向键、按钮或步进器改变。 + +## 键盘操作 + +输入模式:Up/Down 按步长增减。仅步进模式:Up/Right 增加、Down/Left 减少、Home 到最小、End 到最大;按住箭头持续步进。 + +## 限制 + +- 输入模式下的加减按钮不进入 Tab 序列,键盘用 Up/Down。 +- 仅步进模式复用 MyGo `ui.Stepper`,箭头高度来自主题间距,不随密度的控件高度变化。 +- MyGo `ui.Stepper` 的箭头不检查禁用(`indicators.go:216`),禁用时组件向它传入副本,保证值不被修改。 +- MyGo 的步进器吸附依赖有限边界(`widgets.go` 中 `snap`),所以仅步进模式要求有界。 diff --git a/docs/components/027-password-input.md b/docs/components/027-password-input.md new file mode 100644 index 0000000..20d4cab --- /dev/null +++ b/docs/components/027-password-input.md @@ -0,0 +1,42 @@ +# 027 PasswordInput 密码输入框 + +## 用途 + +遮蔽显示的密码输入,带显示/隐藏切换。允许粘贴;遮蔽时不允许复制与剪切。基于 `ui.TextInputBase(...).Password()`,辅助技术读不到遮蔽中的值。组件从不记录密码内容。 + +## 最小示例 + +```go +if mujica.PasswordInput(c, &password, mujica.PasswordInputOptions{Label: "Password"}).Submitted() { + fmt.Println("signing in") +} +``` + +签名:`func PasswordInput(c *ui.Context, value *string, opts PasswordInputOptions) InputResult` + +## 参数 + +| 字段 | 类型 | 默认值 | 含义 | +| --- | --- | --- | --- | +| `value` | `*string` | 必填 | 密码。 | +| `Placeholder` | `string` | `""` | 占位文字。 | +| `Label` | `string` | `""` | 无障碍名称。 | +| `Disabled` | `bool` | `false` | 禁用(切换按钮同时禁用)。 | +| `Error` | `string` | `""` | 错误文字。 | + +## 状态 + +默认、悬停、聚焦、错误、禁用;遮蔽 / 显示两种模式,切换按钮的图标与名称(“显示密码”“隐藏密码”)随之变化。 + +## 事件 + +`Changed()`:内容改变。`Submitted()`:Enter。日志只写切换事件(`mujica: password_input reveal`,含是否显示),不写内容。 + +## 键盘操作 + +Tab 依次到输入框、切换按钮;切换按钮上 Space 或 Enter 切换;输入框中 Enter 提交,Cmd/Ctrl+V 粘贴。 + +## 限制 + +- MyGo 的 `Password()` 一旦设置无法撤销(`editor.go:791`),因此遮蔽与显示两种模式各用一个编辑器,切换时撤销历史和光标位置不保留,焦点留在切换按钮上。 +- 显示模式下内容是普通文本,可复制,辅助技术可朗读——这是用户主动选择的结果。 diff --git a/docs/components/028-search-input.md b/docs/components/028-search-input.md new file mode 100644 index 0000000..8d074bc --- /dev/null +++ b/docs/components/028-search-input.md @@ -0,0 +1,51 @@ +# 028 SearchInput 搜索框 + +## 用途 + +搜索模式:放大镜、输入、清空与提交。查找栏模式(`Find`):在此基础上显示匹配数量、上一项与下一项,对应 MyGo 的 Search field 与 Find bar。匹配由调用方计算。 + +## 最小示例 + +```go +matches := 0 +if query != "" { + matches = strings.Count(text, query) +} +r := mujica.SearchInput(c, &query, mujica.SearchInputOptions{Find: true, Matches: matches, Current: ¤t}) +if r.Moved() { + fmt.Println("show match", current) +} +``` + +签名:`func SearchInput(c *ui.Context, query *string, opts SearchInputOptions) SearchInputResult` + +## 参数 + +| 字段 | 类型 | 默认值 | 含义 | +| --- | --- | --- | --- | +| `query` | `*string` | 必填 | 查询文字。 | +| `Placeholder` | `string` | “搜索”或“查找” | 占位文字。 | +| `Label` | `string` | 占位文字 | 无障碍名称。 | +| `Disabled` | `bool` | `false` | 禁用(查找快捷键也停用)。 | +| `Find` | `bool` | `false` | 开启查找栏模式。 | +| `Matches` | `int` | `0` | 匹配数量;负数 panic。 | +| `Current` | `*int` | `nil` | 当前匹配下标;`Find` 时必填,组件会把它夹在有效范围内。 | + +## 状态 + +同 TextInput;查找栏模式下显示“第 n 项,共 m 项”或“无匹配”(状态角色,变化时可被朗读),无匹配时上一项/下一项禁用。 + +## 事件 + +- `Changed()`:查询改变(含清空)。 +- `Submitted()`:搜索模式下 Enter。 +- `Moved()`:查找栏模式下当前匹配改变。 + +## 键盘操作 + +搜索模式:Enter 提交,Escape 清空。查找栏模式:Enter 下一项,Shift+Enter 上一项,macOS 上 Cmd+G / Shift+Cmd+G、其他平台 F3 / Shift+F3(窗口级),循环到首尾;Escape 清空。 + +## 限制 + +- MyGo 单行编辑器把 Shift+Enter 当作普通提交且不公开修饰键(`editor.go:489`,`submitMods` 未导出),组件用 `HandleInput` 先截获 Shift+Enter。 +- 没有“完成/关闭”按钮;是否显示查找栏由调用方决定。 diff --git a/docs/components/029-checkbox.md b/docs/components/029-checkbox.md new file mode 100644 index 0000000..2e88b84 --- /dev/null +++ b/docs/components/029-checkbox.md @@ -0,0 +1,51 @@ +# 029 Checkbox 复选框 + +## 用途 + +切换布尔值,支持选中、未选、半选;点击方框或标签都会切换。`CheckboxGroup` 提供组内全选(对应 MyGo CheckboxGroup)。基于 `ui.CheckboxBase`。 + +## 最小示例 + +```go +mujica.CheckboxGroup(c, "Notifications", []mujica.CheckboxItem{ + {Label: "Mail", Value: &mail}, + {Label: "Calendar", Value: &calendar}, +}, mujica.CheckboxGroupOptions{}) +``` + +签名: + +```go +func Checkbox(c *ui.Context, value *bool, label string, opts CheckboxOptions) *ui.Element +func CheckboxGroup(c *ui.Context, label string, items []CheckboxItem, opts CheckboxGroupOptions) InputResult +``` + +## 参数 + +`CheckboxOptions` + +| 字段 | 类型 | 默认值 | 含义 | +| --- | --- | --- | --- | +| `Indeterminate` | `bool` | `false` | 显示半选横线;此时点击总是变为选中。是否半选由调用方决定。 | +| `Disabled` | `bool` | `false` | 禁用。 | + +`CheckboxItem`:`Label string`、`Value *bool`(必填)、`Disabled bool`。`CheckboxGroupOptions`:`Disabled bool`。`items` 为空时 panic。 + +## 状态 + +未选(Surface 底、ControlBorder 边)、悬停与按下(AccentText 边)、选中(酒红底、象牙白勾)、半选(酒红底、象牙白横线)、键盘聚焦(方框外焦点环)、禁用。行高不低于密度控件高度。 + +全选框:全部选中为选中,部分选中为半选,全不选为未选。点击时若可用项未全选则全部勾选,否则全部清除;禁用项保持不变。 + +## 事件 + +- `Checkbox(...).Changed()`:用户切换了值。 +- `CheckboxGroup(...).Changed()`:全选或任一项被切换。 + +## 键盘操作 + +Tab 聚焦(每个复选框一个停靠点);Space 切换。 + +## 限制 + +- MyGo 只有内部字段能把复选框报告为“mixed”(`access.go:307`,`checked` 未导出)。半选时组件在无障碍描述中加入“部分选中”,但角色状态仍报告为未选中。 diff --git a/docs/components/030-radio-group.md b/docs/components/030-radio-group.md new file mode 100644 index 0000000..f421a8e --- /dev/null +++ b/docs/components/030-radio-group.md @@ -0,0 +1,52 @@ +# 030 RadioGroup 单选组 + +## 用途 + +在一组选项中选一个,横向或纵向排列;选项值是任意可比较类型,作为稳定标识。基于 `ui.RadioGroup` 与 `ui.RadioBase`,整组是一个 Tab 停靠点。 + +## 最小示例 + +```go +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() { + fmt.Println("size", size) +} +``` + +签名:`func RadioGroup[T comparable](c *ui.Context, value *T, items []RadioItem[T], opts RadioGroupOptions) InputResult` + +## 参数 + +`RadioItem[T]` + +| 字段 | 类型 | 默认值 | 含义 | +| --- | --- | --- | --- | +| `Value` | `T` | 必填 | 稳定选项值;重复时 panic。 | +| `Label` | `string` | `""` | 标签,点击标签也会选择。 | +| `Disabled` | `bool` | `false` | 单项禁用,方向键跳过。 | + +`RadioGroupOptions` + +| 字段 | 类型 | 默认值 | 含义 | +| --- | --- | --- | --- | +| `Horizontal` | `bool` | `false` | 横向排列。 | +| `Label` | `string` | `""` | 组的无障碍名称。 | +| `Disabled` | `bool` | `false` | 禁用整组。 | + +`value` 为 nil 或 `items` 为空时 panic。`*value` 不在选项中时没有选中项。 + +## 状态 + +未选(空心圆)、悬停与按下(AccentText 边)、选中(酒红实心圆与象牙白圆点)、键盘聚焦、禁用。 + +## 事件 + +`Changed()`:用户选择了另一个值(点击、Space 或方向键)。 + +## 键盘操作 + +Tab 进入组并落在已选项;Up/Down/Left/Right 移动并直接选择,循环,跳过禁用项;Home、End 选择首末可用项。 + +## 限制 + +- 方向键对横向与纵向组都同时响应上下左右(MyGo `RadioGroup` 固定使用 `Horizontal|Vertical`)。 diff --git a/example_input_test.go b/example_input_test.go new file mode 100644 index 0000000..9828a07 --- /dev/null +++ b/example_input_test.go @@ -0,0 +1,130 @@ +package mujica_test + +import ( + "fmt" + "strings" + + "github.com/egoist/mygo/ui" + + mujica "mujicaui" + "mujicaui/icons" +) + +func ExampleButton() { + view := func(c *ui.Context) { + mujica.Use(c, mujica.Settings{}) + if mujica.Button(c, "Save", mujica.ButtonOptions{Icon: icons.Must("check")}).Clicked() { + fmt.Println("saved") + } + } + ui.Render(view, 400, 300, 1) +} + +func ExampleIconButton() { + view := func(c *ui.Context) { + mujica.Use(c, mujica.Settings{}) + if mujica.IconButton(c, icons.Must("settings"), "Settings", mujica.IconButtonOptions{Variant: mujica.Ghost}).Clicked() { + fmt.Println("open settings") + } + } + ui.Render(view, 400, 300, 1) +} + +func ExampleButtonGroup() { + view := func(c *ui.Context) { + mujica.Use(c, mujica.Settings{}) + items := []mujica.ButtonGroupItem{{Label: "Day"}, {Label: "Week"}, {Label: "Month"}} + if i := mujica.ButtonGroup(c, items, mujica.ButtonGroupOptions{Label: "Range"}).ClickedItem(); i >= 0 { + fmt.Println("chose", items[i].Label) + } + } + ui.Render(view, 400, 300, 1) +} + +func ExampleTextInput() { + var name string + view := func(c *ui.Context) { + mujica.Use(c, mujica.Settings{}) + r := mujica.TextInput(c, &name, mujica.TextInputOptions{Label: "Name", Placeholder: "Your name", Clearable: true}) + if r.Submitted() { + fmt.Println("hello", name) + } + } + ui.Render(view, 400, 300, 1) +} + +func ExampleTextArea() { + var notes string + view := func(c *ui.Context) { + mujica.Use(c, mujica.Settings{}) + r := mujica.TextArea(c, ¬es, mujica.TextAreaOptions{Label: "Notes", MaxHeight: 160, MaxLength: 200}) + if r.Submitted() { + fmt.Println("send", notes) + } + } + ui.Render(view, 400, 300, 1) +} + +func ExampleNumberInput() { + copies := 1.0 + view := func(c *ui.Context) { + mujica.Use(c, mujica.Settings{}) + if mujica.NumberInput(c, &copies, mujica.NumberInputOptions{Min: 1, Max: 99, Label: "Copies"}).Changed() { + fmt.Println("copies", copies) + } + } + ui.Render(view, 400, 300, 1) +} + +func ExamplePasswordInput() { + var password string + view := func(c *ui.Context) { + mujica.Use(c, mujica.Settings{}) + if mujica.PasswordInput(c, &password, mujica.PasswordInputOptions{Label: "Password"}).Submitted() { + fmt.Println("signing in") + } + } + ui.Render(view, 400, 300, 1) +} + +func ExampleSearchInput() { + var query string + current := 0 + text := "rose lys rose crown rose" + view := func(c *ui.Context) { + mujica.Use(c, mujica.Settings{}) + matches := 0 + if query != "" { + matches = strings.Count(text, query) + } + r := mujica.SearchInput(c, &query, mujica.SearchInputOptions{Find: true, Matches: matches, Current: ¤t}) + if r.Moved() { + fmt.Println("show match", current) + } + } + ui.Render(view, 400, 300, 1) +} + +func ExampleCheckbox() { + var mail, calendar bool + view := func(c *ui.Context) { + mujica.Use(c, mujica.Settings{}) + mujica.CheckboxGroup(c, "Notifications", []mujica.CheckboxItem{ + {Label: "Mail", Value: &mail}, + {Label: "Calendar", Value: &calendar}, + }, mujica.CheckboxGroupOptions{}) + } + ui.Render(view, 400, 300, 1) +} + +func ExampleRadioGroup() { + size := "m" + view := func(c *ui.Context) { + mujica.Use(c, mujica.Settings{}) + 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() { + fmt.Println("size", size) + } + } + ui.Render(view, 400, 300, 1) +} diff --git a/examples/gallery/pages_input.go b/examples/gallery/pages_input.go index 72bd7b6..f1db221 100644 --- a/examples/gallery/pages_input.go +++ b/examples/gallery/pages_input.go @@ -1,3 +1,358 @@ package main -var inputPages = []page{} +import ( + "fmt" + "math" + "strings" + + "github.com/egoist/mygo/ui" + + mujica "mujicaui" + "mujicaui/icons" +) + +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"}, +} + +// 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) + fn() + }) +} + +// inputFlags builds one checkbox per named flag, logging each toggle. +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() { + log(fmt.Sprintf("option %s = %v", name, *flags[name])) + } + } + }) +} + +type inputButtonDemo struct { + variant mujica.ButtonVariant + icon, loading, disabled bool +} + +type inputTextDemo struct { + value, err string + clearable, readOnly, disabled, sl, fail bool +} + +type inputAreaDemo struct { + value string + max float64 + readOnly, disabled bool +} + +type inputNumberDemo struct { + value, step float64 + empty, stepper, disabled, fail bool +} + +type inputSearchDemo struct { + query, find string + current int + disabled bool +} + +type inputCheckDemo struct { + single, mixed, disabled bool + items [4]bool +} + +type inputGroupDemo struct { + vertical, disabled bool +} + +type inputRadioDemo struct { + value string + horizontal, disabledAll bool +} + +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() { + 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"}) + 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} + if s.icon { + opts.Icon = icons.Must("check") + } + ui.Row(c).Children(func() { + if mujica.Button(c, "Save changes", opts).Clicked() { + log("Button clicked") + } + }) + }) + 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() { + log(v.Label + " clicked") + } + } + mujica.Button(c, "Loading", mujica.ButtonOptions{Loading: true}) + mujica.Button(c, "Disabled", mujica.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() { + 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} }) + inputSection(c, "Parameters", func() { + mujica.RadioGroup(c, &s.variant, inputVariants, mujica.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() { + log("IconButton " + n + " clicked") + } + } + }) + }) + }}, + {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 { + fmt.Println("chose", items[i].Label) +}`, + Demo: func(c *ui.Context, log func(string)) { + s := ui.Local(c.Root(), "023", func() inputGroupDemo { return inputGroupDemo{} }) + inputSection(c, "Parameters", func() { + 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"}) + 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}) +if r.Submitted() { + fmt.Println("hello", name) +}`, + Demo: func(c *ui.Context, log func(string)) { + 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)"}) + }) + inputSection(c, "Live", func() { + opts := mujica.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) } + } + r := mujica.TextInput(c, &s.value, opts) + if r.Changed() { + log(fmt.Sprintf("TextInput changed: %q", s.value)) + } + if r.Submitted() { + log(fmt.Sprintf("TextInput submitted: %q", s.value)) + } + }) + 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"}).Grow(1) + mujica.TextInput(c, &ro, mujica.TextInputOptions{Label: "Read-only state", ReadOnly: true}).Grow(1) + mujica.TextInput(c, &off, mujica.TextInputOptions{Label: "Disabled state", Disabled: true}).Grow(1) + }) + }) + }}, + {ID: "025", Name: "TextArea", ZhName: "多行文本框", Category: inputCategory, Keys: "Enter 换行;Cmd/Ctrl+Enter 提交(可配置);滚轮与方向键滚动;撤销、剪贴板与输入法同 TextInput。", + Code: `r := mujica.TextArea(c, ¬es, mujica.TextAreaOptions{Label: "Notes", MaxHeight: 160, MaxLength: 200}) +if r.Submitted() { + fmt.Println("send", notes) +}`, + Demo: func(c *ui.Context, log func(string)) { + 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() { + 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, + MaxLength: int(s.max), ReadOnly: s.readOnly, Disabled: s.disabled}) + if r.Changed() { + log(fmt.Sprintf("TextArea changed: %d characters", len([]rune(s.value)))) + } + if r.Submitted() { + log("TextArea 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() { + 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() { + log(fmt.Sprintf("option Step = %v", s.step)) + } + }) + if s.stepper { + s.empty = false + } + if !s.empty && math.IsNaN(s.value) { + 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"} + if s.fail { + opts.Error = "Not in stock" + } + ui.Box(c).Width(240).Children(func() { + if mujica.NumberInput(c, &s.value, opts).Changed() { + log(fmt.Sprintf("NumberInput changed: %v", s.value)) + } + }) + }) + 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}).Width(160) + mujica.NumberInput(c, &e, mujica.NumberInputOptions{Step: 0.5, Label: "Error state", Error: "Must be whole"}).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() { + fmt.Println("signing in") +}`, + Demo: func(c *ui.Context, log func(string)) { + s := ui.Local(c.Root(), "027", func() inputTextDemo { return inputTextDemo{} }) + inputSection(c, "Parameters", func() { + 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} + if s.fail { + opts.Error = "At least 8 characters" + } + r := mujica.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)))) + } + if r.Submitted() { + log("PasswordInput submitted") + } + }) + }}, + {ID: "028", Name: "SearchInput", ZhName: "搜索框", Category: inputCategory, Keys: "Enter 提交(搜索模式)或下一项(查找栏);Shift+Enter 上一项;Cmd+G / F3 下一项,Shift 加上为上一项;Escape 清空。", + Code: `matches := 0 +if query != "" { + matches = strings.Count(text, query) +} +r := mujica.SearchInput(c, &query, mujica.SearchInputOptions{Find: true, Matches: matches, Current: ¤t}) +if r.Moved() { + fmt.Println("show match", current) +}`, + Demo: func(c *ui.Context, log func(string)) { + s := ui.Local(c.Root(), "028", func() inputSearchDemo { return inputSearchDemo{} }) + const text = "The rose of the court, the rose of the chapel, and the rose of the garden all bloom in May." + inputSection(c, "Parameters", func() { + 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}) + if r.Changed() { + log(fmt.Sprintf("SearchInput changed: %q", s.query)) + } + if r.Submitted() { + log(fmt.Sprintf("SearchInput submitted: %q", s.query)) + } + }) + inputSection(c, "Find bar mode", func() { + n := 0 + 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}) + 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) + }) + }}, + {ID: "029", Name: "Checkbox", ZhName: "复选框", Category: inputCategory, Keys: "Tab 聚焦;Space 切换;点击标签同样切换;全选框在组内勾选全部或清空。", + Code: `mujica.CheckboxGroup(c, "Notifications", []mujica.CheckboxItem{ + {Label: "Mail", Value: &mail}, + {Label: "Calendar", Value: &calendar}, +}, mujica.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() { + s.mixed = false + log(fmt.Sprintf("Checkbox changed: %v", s.single)) + } + }) + inputSection(c, "Group with select all", func() { + items := []mujica.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() { + 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() { + fmt.Println("size", size) +}`, + Demo: func(c *ui.Context, log func(string)) { + s := ui.Local(c.Root(), "030", func() inputRadioDemo { return inputRadioDemo{value: "m"} }) + inputSection(c, "Parameters", func() { + 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() { + log("RadioGroup chose " + s.value) + } + }) + }}, +} diff --git a/icon_button.go b/icon_button.go new file mode 100644 index 0000000..5de2c78 --- /dev/null +++ b/icon_button.go @@ -0,0 +1,33 @@ +package mujica + +import ( + "github.com/egoist/mygo/ui" +) + +// IconButtonOptions configure an IconButton. +type IconButtonOptions struct { + // Variant is the emphasis; the zero value is Primary, as for Button. + Variant ButtonVariant + // Tooltip shows on hover; empty uses the button's name. + Tooltip string + Loading bool + Disabled bool +} + +// IconButton is a square button showing only icon; label names its action. +func IconButton(c *ui.Context, icon *ui.SVG, label string, opts IconButtonOptions) *ui.Element { + if icon == nil { + panic("mujica: IconButton needs an icon") + } + if label == "" { + panic("mujica: IconButton needs an action name") + } + h := Density(c).ControlHeight() + b := Button(c, "", ButtonOptions{Variant: opts.Variant, Icon: icon, Loading: opts.Loading, Disabled: opts.Disabled, Label: label}) + b.Padding(0).Size(h, h) + tip := opts.Tooltip + if tip == "" { + tip = label + } + return b.Tooltip(tip) +} diff --git a/icon_button_test.go b/icon_button_test.go new file mode 100644 index 0000000..9b0794a --- /dev/null +++ b/icon_button_test.go @@ -0,0 +1,78 @@ +package mujica + +import ( + "testing" + + "github.com/egoist/mygo/ui" + + "mujicaui/icons" +) + +func TestIconButtonClickByNameAndKeyboard(t *testing.T) { + n := 0 + var w, h float32 + tt := ui.NewTester(func(c *ui.Context) { + Use(c, Settings{}) + ui.Row(c).Children(func() { + b := IconButton(c, icons.Must("settings"), "Settings", IconButtonOptions{Variant: Ghost}) + w, h = b.Bounds().W, b.Bounds().H + if b.Clicked() { + n++ + } + }) + }, 300, 200) + if err := tt.Click("Settings"); err != nil { + t.Fatal(err) + } + tt.Key(0, ui.KeyEnter) + if n != 2 { + t.Fatalf("clicks %d", n) + } + if w != 36 || h != 36 { + t.Fatalf("size %vx%v, want 36 square", w, h) + } +} + +func TestIconButtonBlockedStates(t *testing.T) { + for _, opts := range []IconButtonOptions{{Disabled: true}, {Loading: true}} { + n := 0 + tt := ui.NewTester(func(c *ui.Context) { + Use(c, Settings{}) + if IconButton(c, icons.Must("trash-2"), "Delete", opts).Clicked() { + n++ + } + }, 300, 200) + _ = tt.Click("Delete") + tt.Key(0, ui.KeyTab) + tt.Key(0, ui.KeySpace) + if n != 0 { + t.Fatalf("%+v: clicked %d", opts, n) + } + } +} + +func TestIconButtonNeedsName(t *testing.T) { + defer func() { + if recover() == nil { + t.Fatal("no panic without an action name") + } + }() + ui.NewTester(func(c *ui.Context) { + Use(c, Settings{}) + IconButton(c, icons.Must("x"), "", IconButtonOptions{}) + }, 100, 100) +} + +func TestIconButtonSnapshot(t *testing.T) { + snapshot(t, "icon_button", 360, 90, func(c *ui.Context) { + ui.Row(c).Padding(24).Gap(12).Children(func() { + IconButton(c, icons.Must("plus"), "Add", IconButtonOptions{}) + IconButton(c, icons.Must("settings"), "Settings", IconButtonOptions{Variant: Secondary}) + IconButton(c, icons.Must("star"), "Favorite", IconButtonOptions{Variant: Outline}) + IconButton(c, icons.Must("copy"), "Copy", IconButtonOptions{Variant: Ghost}) + IconButton(c, icons.Must("trash-2"), "Delete", IconButtonOptions{Variant: Danger}) + IconButton(c, icons.Must("refresh-cw"), "Refresh", IconButtonOptions{Loading: true}) + IconButton(c, icons.Must("lock"), "Lock", IconButtonOptions{Disabled: true}) + }) + }) +} diff --git a/messages_input.go b/messages_input.go new file mode 100644 index 0000000..5797897 --- /dev/null +++ b/messages_input.go @@ -0,0 +1,22 @@ +package mujica + +func init() { + addMessages(map[string][2]string{ + "text_input.clear": {"Clear", "清空"}, + "number_input.increase": {"Increase", "增加"}, + "number_input.decrease": {"Decrease", "减少"}, + "number_input.invalid": {"Enter a number", "请输入数字"}, + "number_input.range": {"Enter a number from %s to %s", "请输入 %s 到 %s 之间的数字"}, + "password_input.show": {"Show password", "显示密码"}, + "password_input.hide": {"Hide password", "隐藏密码"}, + "search_input.search": {"Search", "搜索"}, + "search_input.find": {"Find", "查找"}, + "search_input.previous": {"Previous match", "上一项"}, + "search_input.next": {"Next match", "下一项"}, + "search_input.count": {"%d of %d", "第 %d 项,共 %d 项"}, + "search_input.none": {"No matches", "无匹配"}, + "checkbox.mixed": {"Partly selected", "部分选中"}, + "text_area.count": {"%d of %d characters", "%d / %d 字"}, + "text_area.over": {"%d characters over the limit", "超出 %d 字"}, + }) +} diff --git a/number_input.go b/number_input.go new file mode 100644 index 0000000..a781378 --- /dev/null +++ b/number_input.go @@ -0,0 +1,227 @@ +package mujica + +import ( + "fmt" + "log/slog" + "math" + "strconv" + "strings" + + "github.com/egoist/mygo/ui" + + "mujicaui/icons" + "mujicaui/internal" + "mujicaui/theme" +) + +// NumberInputOptions configure a NumberInput. +type NumberInputOptions struct { + // Min and Max bound the value; equal bounds (the zero value) mean no bounds. + Min, Max float64 + // Step is what Up, Down and the buttons add; zero means 1. + Step float64 + // Precision is the decimals kept and shown; zero takes the decimals of Step. + Precision int + // AllowEmpty lets the field be empty, which sets the value to NaN. + AllowEmpty bool + // StepperOnly shows the value with step buttons and no typing; it needs bounds. + StepperOnly bool + Placeholder string + // Label names the field for assistive technology. + Label string + Disabled bool + // Error marks the value invalid and shows the message below. + Error string +} + +// numberInputSpec is NumberInputOptions resolved. +type numberInputSpec struct { + lo, hi, step float64 + decimals int +} + +func (s numberInputSpec) format(v float64) string { + if math.IsNaN(v) { + return "" + } + return strconv.FormatFloat(v, 'f', s.decimals, 64) +} + +// clamp bounds v and rounds it to the precision. +func (s numberInputSpec) clamp(v float64) float64 { + p := math.Pow(10, float64(s.decimals)) + return math.Round(math.Max(s.lo, math.Min(s.hi, v))*p) / p +} + +func numberInputResolve(opts NumberInputOptions) numberInputSpec { + s := numberInputSpec{lo: opts.Min, hi: opts.Max, step: opts.Step, decimals: opts.Precision} + if s.lo > s.hi { + panic("mujica: NumberInput Min is above Max") + } + if s.step < 0 || opts.Precision < 0 { + panic("mujica: NumberInput Step and Precision cannot be negative") + } + if s.lo == s.hi { + if opts.StepperOnly { + panic("mujica: NumberInput StepperOnly needs Min < Max") + } + s.lo, s.hi = math.Inf(-1), math.Inf(1) + } + if s.step == 0 { + s.step = 1 + } + if s.decimals == 0 { + if t := strconv.FormatFloat(s.step, 'f', -1, 64); strings.Contains(t, ".") { + s.decimals = len(t) - strings.IndexByte(t, '.') - 1 + } + } + return s +} + +// NumberInput edits *value as a number; Up and Down step it. +func NumberInput(c *ui.Context, value *float64, opts NumberInputOptions) InputResult { + if value == nil { + panic("mujica: NumberInput needs a value") + } + if opts.StepperOnly && opts.AllowEmpty { + panic("mujica: NumberInput StepperOnly cannot be empty") + } + if math.IsNaN(*value) && !opts.AllowEmpty { + panic("mujica: NumberInput value is NaN without AllowEmpty") + } + s := numberInputResolve(opts) + if opts.StepperOnly { + return numberInputStepper(c, value, s, opts) + } + var r InputResult + set := func(v float64) { + if !math.IsNaN(v) { + v = s.clamp(v) + } + if v != *value && !(math.IsNaN(v) && math.IsNaN(*value)) { + *value = v + r.changed = true + } + } + stepBy := func(n float64) { + base := *value + if math.IsNaN(base) { + base = math.Max(s.lo, math.Min(s.hi, 0)) + } + set(base + n*s.step) + } + look := &textInputLook{disabled: opts.Disabled, err: opts.Error} + r.Element, r.Input = textInputFrame(c, look, func(frame *ui.Element) *ui.Element { + text := ui.Local(frame, "text", func() string { return s.format(*value) }) + invalid := ui.Local(frame, "invalid", func() string { return "" }) + in := ui.TextInputBase(c, text).Grow(1).MinWidth(Density(c).Unit() * 12). + Placeholder(opts.Placeholder).Label(opts.Label) + if in.Changed() { + *invalid = numberInputParse(c, s, *text, opts.AllowEmpty, set) + } + switch { + case in.Shortcut(0, ui.KeyUp): + stepBy(1) + *text, *invalid = s.format(*value), "" + case in.Shortcut(0, ui.KeyDown): + stepBy(-1) + *text, *invalid = s.format(*value), "" + } + if !in.Focused() { + *text, *invalid = s.format(*value), "" + } + for _, b := range []struct { + name string + icon string + delta float64 + can bool + }{ + {msg(c, "number_input.decrease"), "minus", -1, math.IsNaN(*value) || *value > s.lo}, + {msg(c, "number_input.increase"), "plus", 1, math.IsNaN(*value) || *value < s.hi}, + } { + if numberInputButton(c, b.icon, b.name, b.can) { + stepBy(b.delta) + *text, *invalid = s.format(*value), "" + in.Focus() + } + } + if look.err == "" { + look.err = *invalid + } + return in + }, nil) + if r.changed { + slog.Debug("mujica: number_input change", "value", *value) + } + return r +} + +// numberInputParse applies typed text and returns why it is invalid, if it is. +func numberInputParse(c *ui.Context, s numberInputSpec, text string, allowEmpty bool, set func(float64)) string { + t := strings.TrimSpace(text) + if t == "" && allowEmpty { + set(math.NaN()) + return "" + } + v, err := strconv.ParseFloat(t, 64) + if err != nil || math.IsNaN(v) || math.IsInf(v, 0) { + return msg(c, "number_input.invalid") + } + if v < s.lo || v > s.hi { + return fmt.Sprintf(msg(c, "number_input.range"), s.format(s.lo), s.format(s.hi)) + } + set(v) + return "" +} + +// numberInputButton builds a pointer-only step button; Up and Down are its keys. +func numberInputButton(c *ui.Context, icon, name string, can bool) bool { + k := Tokens(c) + b := ui.Box(c).Size(theme.MinHitSize, theme.MinHitSize).Shrink(0).Center().Radius(theme.ControlRadius). + Role(ui.RoleButton).Label(name).Cursor(ui.CursorPointer).Disabled(!can) + fg := k.TextMuted + if b.Pressed() { + b.Background(k.Selection) + fg = k.Text + } else if b.Hovered() { + b.Background(k.SurfaceHover) + fg = k.Text + } + b.Children(func() { ui.Icon(c, icons.Must(icon)).Size(14, 14).TextColor(fg) }) + return b.Clicked() +} + +// numberInputStepper shows the value beside MyGo's stepper, restyled. +func numberInputStepper(c *ui.Context, value *float64, s numberInputSpec, opts NumberInputOptions) InputResult { + k := Tokens(c) + d := Density(c) + var r InputResult + r.Element = ui.Column(c).Gap(d.Unit()).Shrink(0).Disabled(opts.Disabled) + r.Element.Children(func() { + ui.Row(c).Gap(d.Unit() * 2).MinHeight(d.ControlHeight()).Children(func() { + ui.Text(c, s.format(*value)).FontFeatures("tnum").MinWidth(d.Unit() * 8).TextAlign(ui.End) + // MyGo's stepper arrows ignore Disabled: give them a copy. + target := value + if opts.Disabled { + held := *value + target = &held + } + st := ui.Stepper(c, target, s.lo, s.hi, s.step).Label(opts.Label).Width(d.Unit()*6). + Radius(theme.ControlRadius).Background(k.Surface).Border(theme.BorderWidth, k.ControlBorder) + if opts.Error != "" { + st.Error(opts.Error).Border(theme.BorderWidth, k.Danger) + } + internal.Ring(st, k.Focus, theme.ControlRadius) + r.Input = st + r.changed = st.Changed() && !opts.Disabled + }) + if opts.Error != "" { + ui.Text(c, opts.Error).TextColor(k.Danger).FontSize(theme.CaptionSize) + } + }) + if r.changed { + *value = s.clamp(*value) + slog.Debug("mujica: number_input step", "value", *value) + } + return r +} diff --git a/number_input_test.go b/number_input_test.go new file mode 100644 index 0000000..5259557 --- /dev/null +++ b/number_input_test.go @@ -0,0 +1,141 @@ +package mujica + +import ( + "math" + "testing" + + "github.com/egoist/mygo/ui" +) + +func numberInputTester(v *float64, changes *int, opts NumberInputOptions) *ui.Tester { + return ui.NewTester(func(c *ui.Context) { + Use(c, Settings{}) + if NumberInput(c, v, opts).Changed() { + *changes++ + } + Button(c, "Elsewhere", ButtonOptions{}) + }, 400, 300) +} + +func TestNumberInputStepsAndBounds(t *testing.T) { + v, n := 9.0, 0 + tt := numberInputTester(&v, &n, NumberInputOptions{Min: 0, Max: 10, Label: "Copies"}) + _ = tt.Click("Copies") + tt.Key(0, ui.KeyUp) + tt.Key(0, ui.KeyUp) + if v != 10 || n != 1 { + t.Fatalf("value %v changes %d, want 10 and 1", v, n) + } + _ = tt.Click("Decrease") + _ = tt.Click("Decrease") + if v != 8 || n != 3 { + t.Fatalf("after buttons %v changes %d", v, n) + } + _ = tt.Click("Increase") + _ = tt.Click("Increase") + _ = tt.Click("Increase") + if v != 10 { + t.Fatalf("increase passed Max: %v", v) + } +} + +func TestNumberInputTypingPrecisionAndErrors(t *testing.T) { + v, n := 1.0, 0 + tt := numberInputTester(&v, &n, NumberInputOptions{Min: 0, Max: 2, Step: 0.25, Label: "Ratio"}) + _ = tt.Click("Ratio") + tt.Command("selectAll") + tt.Type("1.5") + if v != 1.5 { + t.Fatalf("typed value %v", v) + } + tt.Key(0, ui.KeyDown) + if v != 1.25 { + t.Fatalf("Down from 1.5 by 0.25 gave %v", v) + } + tt.Command("selectAll") + tt.Type("abc") + if v != 1.25 || !tt.HasText("Enter a number") { + t.Fatalf("invalid text changed value to %v or showed no error: %q", v, tt.Texts()) + } + tt.Command("selectAll") + tt.Type("7") + if v != 1.25 || !tt.HasText("Enter a number from 0.00 to 2.00") { + t.Fatalf("out of range: value %v texts %q", v, tt.Texts()) + } + _ = tt.Click("Elsewhere") + if tt.HasText("Enter a number") { + t.Fatal("error stays after leaving the field, which shows the value again") + } +} + +func TestNumberInputEmpty(t *testing.T) { + v, n := 3.0, 0 + tt := numberInputTester(&v, &n, NumberInputOptions{AllowEmpty: true, Label: "Limit"}) + _ = tt.Click("Limit") + tt.Command("selectAll") + tt.Key(0, ui.KeyBackspace) + if !math.IsNaN(v) { + t.Fatalf("emptied value %v, want NaN", v) + } + tt.Key(0, ui.KeyUp) + if v != 1 { + t.Fatalf("Up from empty gave %v, want 1", v) + } +} + +func TestNumberInputStepperOnly(t *testing.T) { + v, n := 2.0, 0 + tt := numberInputTester(&v, &n, NumberInputOptions{Min: 1, Max: 3, StepperOnly: true, Label: "Guests"}) + tt.Key(0, ui.KeyTab) + tt.Key(0, ui.KeyUp) + tt.Key(0, ui.KeyUp) + if v != 3 || n != 1 || !tt.HasText("3") { + t.Fatalf("value %v changes %d texts %q", v, n, tt.Texts()) + } + tt.Key(0, ui.KeyHome) + if v != 1 { + t.Fatalf("Home gave %v", v) + } +} + +func TestNumberInputDisabled(t *testing.T) { + for _, stepper := range []bool{false, true} { + v, n := 2.0, 0 + tt := numberInputTester(&v, &n, NumberInputOptions{Min: 1, Max: 3, StepperOnly: stepper, Disabled: true, Label: "Off"}) + _ = tt.Click("Increase") + _ = tt.Click("Off") + tt.Key(0, ui.KeyUp) + if v != 2 || n != 0 { + t.Fatalf("stepper %v: disabled changed to %v (%d changes)", stepper, v, n) + } + } +} + +func TestNumberInputBadOptionsPanic(t *testing.T) { + for _, opts := range []NumberInputOptions{{Min: 3, Max: 1}, {StepperOnly: true}, {Step: -1}} { + func() { + defer func() { + if recover() == nil { + t.Errorf("%+v: no panic", opts) + } + }() + v := 0.0 + ui.NewTester(func(c *ui.Context) { + Use(c, Settings{}) + NumberInput(c, &v, opts) + }, 200, 100) + }() + } +} + +func TestNumberInputSnapshot(t *testing.T) { + snapshot(t, "number_input", 420, 260, func(c *ui.Context) { + a, b, e, s := 12.0, math.NaN(), 7.5, 4.0 + ui.Column(c).Padding(24).Gap(12).Width(260).Children(func() { + NumberInput(c, &a, NumberInputOptions{Min: 0, Max: 99, Label: "A"}) + NumberInput(c, &b, NumberInputOptions{AllowEmpty: true, Placeholder: "No limit", Label: "B"}) + NumberInput(c, &e, NumberInputOptions{Step: 0.5, Label: "E", Error: "Must be whole"}) + NumberInput(c, &s, NumberInputOptions{Min: 1, Max: 8, StepperOnly: true, Label: "S"}) + }) + }) +} diff --git a/password_input.go b/password_input.go new file mode 100644 index 0000000..25d07ca --- /dev/null +++ b/password_input.go @@ -0,0 +1,75 @@ +package mujica + +import ( + "log/slog" + + "github.com/egoist/mygo/ui" + + "mujicaui/icons" + "mujicaui/internal" + "mujicaui/theme" +) + +// PasswordInputOptions configure a PasswordInput. +type PasswordInputOptions struct { + Placeholder string + // Label names the field for assistive technology. + Label string + Disabled bool + // Error marks the value invalid and shows the message below. + Error string +} + +// PasswordInput is a masked field with a show and hide toggle. +func PasswordInput(c *ui.Context, value *string, opts PasswordInputOptions) InputResult { + if value == nil { + panic("mujica: PasswordInput needs a value") + } + var r InputResult + look := &textInputLook{disabled: opts.Disabled, err: opts.Error} + r.Element, r.Input = textInputFrame(c, look, func(frame *ui.Element) *ui.Element { + shown := ui.Local(frame, "shown", func() bool { return false }) + var in *ui.Element + // MyGo's mask cannot be lifted, so each mode keeps its own editor. + mode := "masked" + if *shown { + mode = "shown" + } + ui.Row(c).Key(mode).Grow(1).Children(func() { + in = ui.TextInputBase(c, value).Grow(1).MinWidth(Density(c).Unit() * 10). + Placeholder(opts.Placeholder).Label(opts.Label) + if !*shown { + in.Password() + } + }) + r.changed = in.Changed() + r.submitted = in.Submitted() + if passwordInputToggle(c, *shown) { + *shown = !*shown + slog.Debug("mujica: password_input reveal", "shown", *shown) + } + return in + }, nil) + return r +} + +// passwordInputToggle builds the show or hide button and reports a click. +func passwordInputToggle(c *ui.Context, shown bool) bool { + k := Tokens(c) + name, icon := msg(c, "password_input.show"), "eye" + if shown { + name, icon = msg(c, "password_input.hide"), "eye-off" + } + b := ui.ButtonBase(c).Size(theme.MinHitSize, theme.MinHitSize).Radius(theme.ControlRadius).Label(name).Tooltip(name) + internal.Ring(b, k.Focus, theme.ControlRadius) + fg := k.TextMuted + if b.Pressed() { + b.Background(k.Selection) + fg = k.Text + } else if b.Hovered() { + b.Background(k.SurfaceHover) + fg = k.Text + } + b.Children(func() { ui.Icon(c, icons.Must(icon)).Size(16, 16).TextColor(fg) }) + return b.Clicked() +} diff --git a/password_input_test.go b/password_input_test.go new file mode 100644 index 0000000..72f82c0 --- /dev/null +++ b/password_input_test.go @@ -0,0 +1,101 @@ +package mujica + +import ( + "testing" + + "github.com/egoist/mygo/ui" +) + +func TestPasswordInputMaskPasteAndReveal(t *testing.T) { + pw, changes, submits := "", 0, 0 + tt := ui.NewTester(func(c *ui.Context) { + Use(c, Settings{}) + r := PasswordInput(c, &pw, PasswordInputOptions{Label: "Password"}) + if r.Changed() { + changes++ + } + if r.Submitted() { + submits++ + } + }, 400, 200) + _ = tt.Click("Password") + tt.SetClipboard("s3cr") + tt.Command("paste") + tt.Type("et") + if pw != "s3cret" || changes != 2 { + t.Fatalf("value %q changes %d", pw, changes) + } + tt.SetClipboard("") + tt.Command("selectAll") + tt.Command("copy") + if tt.Clipboard() != "" { + t.Fatalf("masked field copied %q", tt.Clipboard()) + } + for _, s := range tt.Texts() { + if s == "s3cret" { + t.Fatal("masked password shows in the texts") + } + } + if err := tt.Click("Show password"); err != nil { + t.Fatal(err) + } + if _, ok := tt.Find("Hide password"); !ok || pw != "s3cret" { + t.Fatalf("after reveal: value %q, texts %q", pw, tt.Texts()) + } + _ = tt.Click("Password") + tt.Command("selectAll") + tt.Command("copy") + if tt.Clipboard() != "s3cret" { + t.Fatalf("revealed field copied %q", tt.Clipboard()) + } + tt.Key(0, ui.KeyEnter) + if submits != 1 { + t.Fatalf("submits %d", submits) + } + _ = tt.Click("Hide password") + if _, ok := tt.Find("Show password"); !ok { + t.Fatal("hide did not mask again") + } +} + +func TestPasswordInputToggleByKeyboard(t *testing.T) { + pw := "x" + tt := ui.NewTester(func(c *ui.Context) { + Use(c, Settings{}) + PasswordInput(c, &pw, PasswordInputOptions{Label: "PIN"}) + }, 400, 200) + tt.Key(0, ui.KeyTab) + tt.Key(0, ui.KeyTab) + tt.Key(0, ui.KeySpace) + if _, ok := tt.Find("Hide password"); !ok { + t.Fatal("Space on the toggle did not reveal") + } +} + +func TestPasswordInputDisabled(t *testing.T) { + pw := "locked" + tt := ui.NewTester(func(c *ui.Context) { + Use(c, Settings{}) + PasswordInput(c, &pw, PasswordInputOptions{Label: "Off", Disabled: true}) + }, 400, 200) + _ = tt.Click("Show password") + _ = tt.Click("Off") + tt.Type("zz") + if pw != "locked" { + t.Fatalf("disabled value %q", pw) + } + if _, ok := tt.Find("Show password"); !ok { + t.Fatal("disabled toggle revealed") + } +} + +func TestPasswordInputSnapshot(t *testing.T) { + snapshot(t, "password_input", 420, 220, func(c *ui.Context) { + a, b, e := "", "versailles", "short" + ui.Column(c).Padding(24).Gap(12).Width(320).Children(func() { + PasswordInput(c, &a, PasswordInputOptions{Label: "A", Placeholder: "Password"}) + PasswordInput(c, &b, PasswordInputOptions{Label: "B"}) + PasswordInput(c, &e, PasswordInputOptions{Label: "E", Error: "At least 8 characters"}) + }) + }) +} diff --git a/radio_group.go b/radio_group.go new file mode 100644 index 0000000..df0b3d4 --- /dev/null +++ b/radio_group.go @@ -0,0 +1,90 @@ +package mujica + +import ( + "log/slog" + + "github.com/egoist/mygo/ui" + + "mujicaui/internal" + "mujicaui/theme" +) + +// RadioItem is one choice of a RadioGroup; Value is its stable identity. +type RadioItem[T comparable] struct { + Value T + Label string + Disabled bool +} + +// RadioGroupOptions configure a RadioGroup. +type RadioGroupOptions struct { + // Horizontal lays the choices in a row; the default is a column. + Horizontal bool + // Label names the group for assistive technology. + Label string + Disabled bool +} + +// RadioGroup chooses one item's Value into *value; arrows move the choice. +func RadioGroup[T comparable](c *ui.Context, value *T, items []RadioItem[T], opts RadioGroupOptions) InputResult { + if value == nil { + panic("mujica: RadioGroup needs a value") + } + if len(items) == 0 { + panic("mujica: RadioGroup needs items") + } + seen := make(map[T]bool, len(items)) + for _, it := range items { + if seen[it.Value] { + panic("mujica: RadioGroup has a duplicate value") + } + seen[it.Value] = true + } + k := Tokens(c) + d := Density(c) + var r InputResult + r.Element = ui.RadioGroup(c, func() { + for _, it := range items { + if radioGroupItem(c, k, d, value, it) { + r.changed = true + slog.Debug("mujica: radio_group choose", "label", it.Label) + } + } + }).Label(opts.Label).Disabled(opts.Disabled).Gap(0) + if opts.Horizontal { + r.Element.Row().Gap(d.Unit() * 6) + } + return r +} + +// radioGroupItem builds one radio and reports whether it was chosen. +func radioGroupItem[T comparable](c *ui.Context, k theme.Tokens, d theme.Density, value *T, it RadioItem[T]) bool { + row := ui.RadioBase(c, value, it.Value).Gap(d.Unit() * 2).MinHeight(d.ControlHeight()).FocusRing(false).Disabled(it.Disabled) + on := *value == it.Value + hover, pressed := row.Hovered(), row.Pressed() + row.Children(func() { + dot := ui.Box(c).Size(checkboxSize, checkboxSize).Shrink(0).Radius(checkboxSize / 2) + switch { + case on: + dot.Background(k.Accent).Border(theme.BorderWidth, k.AccentText) + case pressed: + dot.Background(k.Selection).Border(theme.BorderWidth, k.AccentText) + case hover: + dot.Background(k.SurfaceHover).Border(theme.BorderWidth, k.AccentText) + default: + dot.Background(k.Surface).Border(theme.BorderWidth, k.ControlBorder) + } + dot.Transition(textInputFade) + dot.DrawOver(func(p *ui.Painter, r ui.Rect) { + if on { + s := r.W * 0.375 + p.Fill(ui.Rect{X: r.X + (r.W-s)/2, Y: r.Y + (r.H-s)/2, W: s, H: s}, k.OnAccent, s/2) + } + if row.FocusVisible() { + internal.FocusRing(p, r, checkboxSize/2, k.Focus) + } + }) + ui.Text(c, it.Label) + }) + return row.Changed() +} diff --git a/radio_group_test.go b/radio_group_test.go new file mode 100644 index 0000000..e4a2099 --- /dev/null +++ b/radio_group_test.go @@ -0,0 +1,99 @@ +package mujica + +import ( + "testing" + + "github.com/egoist/mygo/ui" +) + +var radioGroupTestItems = []RadioItem[string]{ + {Value: "s", Label: "Small"}, + {Value: "m", Label: "Medium", Disabled: true}, + {Value: "l", Label: "Large"}, +} + +func radioGroupTester(v *string, changes *int, opts RadioGroupOptions) *ui.Tester { + return ui.NewTester(func(c *ui.Context) { + Use(c, Settings{}) + if RadioGroup(c, v, radioGroupTestItems, opts).Changed() { + *changes++ + } + Button(c, "After", ButtonOptions{}) + }, 400, 300) +} + +func TestRadioGroupClickAndArrows(t *testing.T) { + v, n := "s", 0 + tt := radioGroupTester(&v, &n, RadioGroupOptions{Label: "Size"}) + _ = tt.Click("Medium") + if v != "s" || n != 0 { + t.Fatalf("disabled item chosen: %q", v) + } + _ = tt.Click("Large") + if v != "l" || n != 1 { + t.Fatalf("click: %q changes %d", v, n) + } + tt.Key(0, ui.KeyDown) + if v != "s" || n != 2 { + t.Fatalf("Down wraps past the disabled item to Small, got %q changes %d", v, n) + } + tt.Key(0, ui.KeyUp) + if v != "l" { + t.Fatalf("Up skipped to %q", v) + } + tt.Key(0, ui.KeyHome) + if v != "s" { + t.Fatalf("Home chose %q", v) + } +} + +func TestRadioGroupTabStopsAtChosen(t *testing.T) { + v, n := "l", 0 + tt := radioGroupTester(&v, &n, RadioGroupOptions{Horizontal: true}) + tt.Key(0, ui.KeyTab) + if !tt.Focused("Large") { + t.Fatal("Tab did not go to the chosen radio") + } + tt.Key(0, ui.KeyLeft) + if v != "s" { + t.Fatalf("Left in a row chose %q", v) + } + tt.Key(0, ui.KeyTab) + if !tt.Focused("After") { + t.Fatal("Tab did not leave the group") + } +} + +func TestRadioGroupDisabledGroup(t *testing.T) { + v, n := "s", 0 + tt := radioGroupTester(&v, &n, RadioGroupOptions{Disabled: true}) + _ = tt.Click("Large") + tt.Key(0, ui.KeyTab) + tt.Key(0, ui.KeyDown) + if v != "s" || n != 0 { + t.Fatalf("disabled group chose %q", v) + } +} + +func TestRadioGroupDuplicateValuePanics(t *testing.T) { + defer func() { + if recover() == nil { + t.Fatal("no panic for duplicate values") + } + }() + v := 1 + ui.NewTester(func(c *ui.Context) { + Use(c, Settings{}) + RadioGroup(c, &v, []RadioItem[int]{{Value: 1, Label: "A"}, {Value: 1, Label: "B"}}, RadioGroupOptions{}) + }, 200, 100) +} + +func TestRadioGroupSnapshot(t *testing.T) { + snapshot(t, "radio_group", 460, 220, func(c *ui.Context) { + v, h := "m", 2 + ui.Column(c).Padding(24).Gap(16).Children(func() { + RadioGroup(c, &v, []RadioItem[string]{{Value: "s", Label: "Sonnet"}, {Value: "m", Label: "Madrigal"}, {Value: "o", Label: "Ode", Disabled: true}}, RadioGroupOptions{}) + RadioGroup(c, &h, []RadioItem[int]{{Value: 1, Label: "Matins"}, {Value: 2, Label: "Vespers"}, {Value: 3, Label: "Compline"}}, RadioGroupOptions{Horizontal: true}) + }) + }) +} diff --git a/search_input.go b/search_input.go new file mode 100644 index 0000000..7a1af8f --- /dev/null +++ b/search_input.go @@ -0,0 +1,140 @@ +package mujica + +import ( + "fmt" + "log/slog" + "runtime" + + "github.com/egoist/mygo/ui" + + "mujicaui/icons" + "mujicaui/theme" +) + +// SearchInputOptions configure a SearchInput. +type SearchInputOptions struct { + // Placeholder is empty for the built-in "Search" or "Find". + Placeholder string + // Label names the field; empty uses the placeholder. + Label string + Disabled bool + // Find turns on find-bar mode: match count, previous and next. + Find bool + // Matches is how many matches the caller found for the query. + Matches int + // Current is the chosen match index; Find mode requires it. + Current *int +} + +// SearchInputResult adds find-bar moves to InputResult. +type SearchInputResult struct { + InputResult + moved bool +} + +// Moved reports that the user went to another match this frame. +func (r SearchInputResult) Moved() bool { return r.moved } + +// SearchInput is a search field with clear, or a find bar with match navigation. +func SearchInput(c *ui.Context, query *string, opts SearchInputOptions) SearchInputResult { + if query == nil { + panic("mujica: SearchInput needs a query") + } + if opts.Find && opts.Current == nil { + panic("mujica: SearchInput Find mode needs Current") + } + if opts.Matches < 0 { + panic("mujica: SearchInput Matches is negative") + } + k := Tokens(c) + place := opts.Placeholder + if place == "" { + place = msg(c, "search_input.search") + if opts.Find { + place = msg(c, "search_input.find") + } + } + name := opts.Label + if name == "" { + name = place + } + var r SearchInputResult + step := func(d int) { + if opts.Matches == 0 { + return + } + *opts.Current = (*opts.Current + d + opts.Matches) % opts.Matches + r.moved = true + slog.Debug("mujica: search_input move", "current", *opts.Current) + } + if opts.Find { + *opts.Current = max(0, min(*opts.Current, opts.Matches-1)) + if !opts.Disabled { + mac := runtime.GOOS == "darwin" + switch { + case mac && c.Shortcut(ui.Cmd, ui.KeyG), !mac && c.Shortcut(0, ui.KeyF3): + step(1) + case mac && c.Shortcut(ui.Shift|ui.Cmd, ui.KeyG), !mac && c.Shortcut(ui.Shift, ui.KeyF3): + step(-1) + } + } + } + look := &textInputLook{disabled: opts.Disabled} + r.Element, r.Input = textInputFrame(c, look, func(frame *ui.Element) *ui.Element { + ui.Icon(c, icons.Must("search")).Size(16, 16).Shrink(0).TextColor(k.TextMuted) + back := ui.Local(frame, "back", func() bool { return false }) + in := ui.TextInputBase(c, query).Grow(1).MinWidth(Density(c).Unit() * 10).Placeholder(place).Label(name) + r.changed = in.Changed() + if opts.Find { + // Shift+Enter would reach the editor as a plain submit. + in.HandleInput(func(ev ui.InputEvent) bool { + if ev.Kind == ui.InputKeyDown && ev.Key == ui.KeyEnter && ev.Mods == ui.Shift { + *back = true + return true + } + return false + }) + switch { + case *back: + *back = false + step(-1) + case in.Submitted(): + step(1) + } + } else { + r.submitted = in.Submitted() + } + if *query != "" && textInputClear(c, in) { + *query = "" + r.changed = true + slog.Debug("mujica: search_input clear") + } + if opts.Find { + searchInputFindControls(c, *query, opts.Matches, *opts.Current, step) + } + return in + }, nil) + return r +} + +// searchInputFindControls shows the match count with previous and next buttons. +func searchInputFindControls(c *ui.Context, query string, matches, current int, step func(int)) { + k := Tokens(c) + status := msg(c, "search_input.none") + switch { + case query == "": + status = "" + case matches > 0: + status = fmt.Sprintf(msg(c, "search_input.count"), current+1, matches) + } + ui.Text(c, status).TextColor(k.TextMuted).FontSize(theme.CaptionSize).FontFeatures("tnum"). + SingleLine().Shrink(0).Role(ui.RoleStatus) + ui.Box(c).Width(theme.BorderWidth).Height(16).Background(k.Border).Shrink(0) + opts := IconButtonOptions{Variant: Ghost, Disabled: matches == 0} + if IconButton(c, icons.Must("chevron-up"), msg(c, "search_input.previous"), opts).Size(theme.MinHitSize, theme.MinHitSize).MinHeight(theme.MinHitSize).Clicked() { + step(-1) + } + if IconButton(c, icons.Must("chevron-down"), msg(c, "search_input.next"), opts).Size(theme.MinHitSize, theme.MinHitSize).MinHeight(theme.MinHitSize).Clicked() { + step(1) + } +} diff --git a/search_input_test.go b/search_input_test.go new file mode 100644 index 0000000..dc6cfb3 --- /dev/null +++ b/search_input_test.go @@ -0,0 +1,129 @@ +package mujica + +import ( + "runtime" + "strings" + "testing" + + "github.com/egoist/mygo/ui" +) + +func TestSearchInputSearchClearSubmit(t *testing.T) { + q, changes, submits := "", 0, 0 + tt := ui.NewTester(func(c *ui.Context) { + Use(c, Settings{}) + r := SearchInput(c, &q, SearchInputOptions{}) + if r.Changed() { + changes++ + } + if r.Submitted() { + submits++ + } + }, 400, 200) + _ = tt.Click("Search") + tt.Type("rose") + tt.Key(0, ui.KeyEnter) + if q != "rose" || changes != 1 || submits != 1 { + t.Fatalf("query %q changes %d submits %d", q, changes, submits) + } + _ = tt.Click("Clear") + if q != "" || changes != 2 { + t.Fatalf("after clear %q changes %d", q, changes) + } + tt.Type("lys") + tt.Key(0, ui.KeyEscape) + if q != "" || changes != 4 { + t.Fatalf("after Escape %q changes %d", q, changes) + } +} + +// searchInputFindTester counts matches of the query in a fixed text. +func searchInputFindTester(q *string, cur, moves *int, locale Locale) *ui.Tester { + const text = "rose lys rose crown rose" + return ui.NewTester(func(c *ui.Context) { + Use(c, Settings{Locale: locale}) + n := 0 + if *q != "" { + n = strings.Count(text, *q) + } + if SearchInput(c, q, SearchInputOptions{Find: true, Matches: n, Current: cur, Label: "Find in page"}).Moved() { + *moves++ + } + }, 500, 200) +} + +func TestSearchInputFindBarNavigation(t *testing.T) { + q, cur, moves := "", 0, 0 + tt := searchInputFindTester(&q, &cur, &moves, "") + _ = tt.Click("Find in page") + tt.Type("rose") + if !tt.HasText("1 of 3") { + t.Fatalf("count missing: %q", tt.Texts()) + } + tt.Key(0, ui.KeyEnter) + tt.Key(0, ui.KeyEnter) + if cur != 2 || !tt.HasText("3 of 3") { + t.Fatalf("after two Enters current %d texts %q", cur, tt.Texts()) + } + tt.Key(0, ui.KeyEnter) + if cur != 0 { + t.Fatalf("Enter did not wrap, current %d", cur) + } + tt.Key(ui.Shift, ui.KeyEnter) + if cur != 2 || q != "rose" { + t.Fatalf("Shift+Enter: current %d query %q", cur, q) + } + _ = tt.Click("Previous match") + _ = tt.Click("Next match") + _ = tt.Click("Next match") + if cur != 0 || moves != 7 { + t.Fatalf("buttons: current %d moves %d", cur, moves) + } + if runtime.GOOS == "darwin" { + tt.Key(ui.Cmd, ui.KeyG) + } else { + tt.Key(0, ui.KeyF3) + } + if cur != 1 { + t.Fatalf("find-next shortcut: current %d", cur) + } +} + +func TestSearchInputFindNoMatches(t *testing.T) { + q, cur, moves := "", 0, 0 + tt := searchInputFindTester(&q, &cur, &moves, ZhCN) + _ = tt.Click("Find in page") + tt.Type("tulip") + if !tt.HasText("无匹配") { + t.Fatalf("no-match status missing: %q", tt.Texts()) + } + tt.Key(0, ui.KeyEnter) + _ = tt.Click("下一项") + if cur != 0 || moves != 0 { + t.Fatalf("moved without matches: current %d moves %d", cur, moves) + } +} + +func TestSearchInputFindNeedsCurrent(t *testing.T) { + defer func() { + if recover() == nil { + t.Fatal("no panic without Current") + } + }() + q := "" + ui.NewTester(func(c *ui.Context) { + Use(c, Settings{}) + SearchInput(c, &q, SearchInputOptions{Find: true}) + }, 200, 100) +} + +func TestSearchInputSnapshot(t *testing.T) { + snapshot(t, "search_input", 460, 220, func(c *ui.Context) { + a, b, f, cur := "", "Marie", "rose", 1 + ui.Column(c).Padding(24).Gap(12).Width(400).Children(func() { + SearchInput(c, &a, SearchInputOptions{}) + SearchInput(c, &b, SearchInputOptions{}) + SearchInput(c, &f, SearchInputOptions{Find: true, Matches: 3, Current: &cur}) + }) + }) +} diff --git a/testdata/snapshots/darwin/button-comfortable-dark.png b/testdata/snapshots/darwin/button-comfortable-dark.png new file mode 100644 index 0000000000000000000000000000000000000000..6167924ba821c21f80d3cb5d2d9a9b33d650138a GIT binary patch literal 11237 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 XyB_OP|5cs~cpDO;vLcm2dVc>4AxVu4 literal 0 HcmV?d00001 diff --git a/testdata/snapshots/darwin/button-comfortable-light.png b/testdata/snapshots/darwin/button-comfortable-light.png new file mode 100644 index 0000000000000000000000000000000000000000..cf71679819667b130e0fc7b212bc81dc43f81297 GIT binary patch literal 10937 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 B-Q@rP literal 0 HcmV?d00001 diff --git a/testdata/snapshots/darwin/button-compact-dark.png b/testdata/snapshots/darwin/button-compact-dark.png new file mode 100644 index 0000000000000000000000000000000000000000..73e1a88ed013849f898b767833825b09ccedbf32 GIT binary patch literal 11338 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 Gi2ngsi%cZ| literal 0 HcmV?d00001 diff --git a/testdata/snapshots/darwin/button-compact-light.png b/testdata/snapshots/darwin/button-compact-light.png new file mode 100644 index 0000000000000000000000000000000000000000..6eab1a07a8e246dc03c3e1f5d3610e074cf6eaad GIT binary patch literal 11075 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 SLpJ}hJF-%Wk`>}#1OEqM)OB3| literal 0 HcmV?d00001 diff --git a/testdata/snapshots/darwin/button-dark.png b/testdata/snapshots/darwin/button-dark.png index 26539164df773103d20ce043f54387410cef7a7c..df1f5e4c49c85428038885829cfe0315ecb2f396 100644 GIT binary patch delta 2991 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 kdc!I)bV18#r| zGMLfLc#{+mvGf~Pg9f`w6PEjGWkyc z{P84^g=7rC1&`80eH^%es1}wfeqJsM&3$JV8(6;nHkY5Uo>mjCGOF@|?t2{ZnZ(Xx zAVDODCTI^C#3q5BvVJ`>5-5tO=L1QAxZHN87;5CrDp6(shQT3j5b$j&OKjMVJuay; z!k}~iww)#BrY)6dme)nn`Fz;7?O#v+8rw5XB~Zj|8m3HIB=NM)bXy+7%mZGNGYq`L z+9UaGI8=?Jju&rRgG5cVS{6U#UCF{$>Y;{=I)>(XuwyaS)Feup#x^@5&vhFt4$bTG z)?O25Y{<%kRLql@u}`2}9PG77KvYIcFPK+OPE&g-UtCsYGb{!o~zN0an~M z)k5oEBX*?1?I7e1jY~38vLAGxrD_-eY~AdZ>y0G?p)<2b2Io3$YeXU?BUZ1)ZdLma z;6`hj7Q%`g@iIzbo-PbLAs)SOhXR54iOn=mob_QLqh(XdH2DN}15i?`?B1JHQY)w!Mk7*O zQO^%G3&F=A#Be7pj#Qz$Z?47NjP zaLKt*M+e$r%xT66nC*nmn62f$kt;AjO)YsP@A9ynRN=@YRC31Dv25km?m9bnYMC@n`p4M48kUzQ}hw%l%1Hf;x78QT{^PuOY9MtN`%V!ez z$o`|C(PXK33Wt+A5MHpdJT_uJX&Z7VW1jm)xgDiaCneVdJ; zFX>t;ahI<70fy1;<@|EnTkY%tw8cTMa=xzN_Hf}k=1bupPRBF7-gds#qy$+y!NKiD z2eAuKSOmc?T0Zh)Aefl_EPN}mXBY!(UZ_wM3yFxQ9PY6H1uxwanKh5+L4Lj zI<9^L6^kfM%2jZ!T@@H*4_#fOl-e()rJwJr1A-^vVUWtoI=^xPoi}~{*83ost$lO< z+wm%f;(f!O=(Qy(U{?8QYDOAu{%#^j+Tu+cesVG8ymFW)eSqAnFUNYyLqO1KZe`@1 zn9h^>L=XckC*hSFQ_z`0Lk8(S^96Q zAijs6s`?-TfORG+JZx%y>gU|zmXGugW8xKxh5*}pxs^AeFFhX>x_3|-e7vwa+o0s< z2WO`29_TtW9gGdFY2xvWbl2Z7u;I$W8ux_19hT0zo-DIwci_klOt=f2#TH zMbN*E=s$ZKKU$IB-8Hjv$IWMU*8JXg_-z?e8+FBY(V}`f9AD}HNRGKkp6Aq5<_Hu2 zyYVl>x~^({4=T?7V#k)i%9|?r5Dqu_dKsXaJRQG=}AqBGe|A6f_?u}9o5MCkWiOf`v QJ7}k!fm)e9GjWUg7qD^=Bme*a diff --git a/testdata/snapshots/darwin/button-light.png b/testdata/snapshots/darwin/button-light.png index b85d2e5dfb032decc3efaea3a1739c793db53815..c977763d9d0458e5987fe8636f107b9e5742c5cc 100644 GIT binary patch delta 2797 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 delta 1830 zcmb7EdsNbA82(9`<|SKbm^XA;MrfIrLup%SWrf4SC63NQrEsDwguJ9CKee(lH7B)A z6t`vOoHx`8BN3dDc|lARC1-ePWr~@ZhN3X*l-t?a&d%QFyzlwW_r2dA@AG`$Gj+HP zlf4eIfmyZQ#1@p(`($hNU!F;_-xXz!*nfr8eGMs}e$AVB7&Nkb+I-HIKlWLZzw(}6 z-F~yUY~5Xud$F!xX5HFz40*A9+mP0a2drE{TaFUUP9;Cv4e|ZI0N-8EkRTeO*uqny zpu9LSFy<_XQB%cplfk`Ct6i4xvge?%ym-sAii0&2Xd>+py2dGAwYVWS1C|Q`3t(9Y za2g5h(`4A3IPtX#^F_ZdV4SSuvUtcfbmXdFqUZ5n`A8lVIbHDAk!+IW{Q3w8*~=kUX|+b=rU{e$T`e!HzUMq-=cf%?WG)k z;2lNCS3hjQ_l$~{;e+J5Jo}%lRxg!Kl-%Sw%2-{CxB&55hh4?%Z%~!_84R8xwC2%V z9FGeL0&MQp085cZ7z!N=n7Tn6Im30N47sh}1Xg1aJifQPk=%N5n)jnR0vpciLKWM| zwA3Fhd3fibnYir*aQ1Cx{A&#;>2wIC?)mA^Mzc}>Zn$w2%OPb9>JwJjeBG@#exB3C zaU8cT>SA=|-%MosAFOG6#kavrJGbR}77m_YBmUX$;?;$;uv}}&zM7DiFOGxlo*!LI zC&E#IZZOF+f?_S#Qz?}dv3zOV3YM?y<@v$z=tl&9C_mhMoJDlnY<%A~(My(GwLHTk zq)g|SiAAI^F49DWE}|)8)zHl1mU zKtwN;xZ&u$r0544ldUa!Aod%+CVN=b!e=9mnsa`kz&|;JX@qZcZ*578r4=*kNAjfn z6sCyW@R%56r?YMeiaWZeNo4_L#F44zp6xBHmdX4HC;I63!x?CVA;jlGPh4G!n@!et zbVm`Jb$t_UTQK`}z1lt#>YX#AVdPf^_P?0*sm6oOM%SX|luaE|+Y9=V=vOKOeao}( zh)D=IGv7^7R>+E{Q=2zK-Ya9AJmD^!f0bLN{I!$nZj`feGllwG5BYs9L4miosU_6o z%2B4e2h7Y#)4$1H8M@}3BG;)*$=3Q6#jtYZWy0rZv`OMZ?X8XJ)%T22^QBS;@aS_t zgURzNm&C6_0Hrs)4OYk_UOvQ`QN%KMYpl4D$mNTU4 zdW$k$f!`Mj=mkPB@{^p=5hwuQVEO$Rp zX!iu451Q}6c6LqTCELlZyho?#6kK}BSTk>QCXL@Sfg5XJ#T4MW=ZBh|SR`vnY5Y3Y zIT(qpcI$WDS3KVRrW@@KbJ*%94YwTmw&cmp*=Tznp{)i9y#1MP-|~Q$KBufi)c-YM zAW(WJ(^?#^7Ddt<{6rn&!;QjeM|J^NMz)sht#QdDqDxNX;`p0N(2UtHWKH%Z)k&{~ z@T^R_cfga-HyQkDi_gD0%;74B8xP!Vy5tjDTyXZ`c`djz##c6jTNpg^yMD>jn7i>n z$OmVLgM^?-N1Z2}zdcrH_j+zO7iG*BRpuBREy8(P-7Um5{X*@|(+96~mblUUCDraW zb)O& zl(F(lfmW&xXsl39gpr?4+teK4S+KDi%?!`YrHbw>pp>e(Zcw`bk5vUmOzx%S5LSOP ztc$JnY)*&ivBG+S>u)m7a83^(F_nz$T9xk{6vjxH9}d)p#R>Z69LohgEI<6O>ytVhAPB#2J)d1nwfLuLbf19<8r@Ne#| zxdC-)I=WMUy)}pmje&kpPO(7K~c;R>J@Q diff --git a/testdata/snapshots/darwin/button_group-dark.png b/testdata/snapshots/darwin/button_group-dark.png new file mode 100644 index 0000000000000000000000000000000000000000..e8eb37015d8b592a5841b73ec71b5fd3a384d5a8 GIT binary patch literal 5061 zcmdT|XHb*dx(>>ws5EIxRX_zq5D*XoD4hTT(u*W?kX}O*5HW2%PR5-1;J`%X)Aya1ZzJAFg$#4--Yd1 zvQnuGk?l4EFucs6QuYe55_-q9uS~H9jex!#s-!~o(F;~%cTwIb>45t(%6=|anB0vS zNT-|g0Ouv53bR{{Rnr~NAp5z!_$M7OapYJGy{GlB2#RQeN=jRca^kP~k0 zy_ND}UuEI?mSl)F#g{?45WnhC_-kf5RJnDj=Tig+!pettSq;)E}Us?HZnY7YmlfkWxHP` z$+V-AAe)B+Qn13eRT9qx$)@OW&9*2=iozSLPDcG zY3KMMuecc4juZ5<54scbF*gD2>MJm9O?ql}1LetqF-$!J~j zN&f7{{hwmW$v1J6X%S)783njIFZ<3V5gYh$)3Y#Uj%Vr-zY?EHd2zF&yxjfc_jL9j zLik{bFXq1H8IHo4anA1!C7z}3@4Lpu$!}J337n?PEOmaBo1V{iUY5G%>ls=8>eji^ z-dc6VD;ncYX~vA=C!vg55-9hJD0f#KK{BK+vk)5G`C_5z+4ixR3QuNVs6H5bS#p1s zMh+y`(pWMF%gp5+Xlb!l(Q$G49PRFXiRBNb5SGNOV%rB1 zqTp$hqU#^x2L^MfozNC6MBJ>lIJ+fMT~Nb9k|&(Twz&x%x!=RC02KFvvXl@^@494e za_QI0#7(!o8bE1Id~jJBh8&_$wjyU1aN+A}5i0ZGD2R*84}1(e2GZn1?5P;?eZP>M z#viY;O#6@voFqAc!LW?2w_OCOJ2+2Ol@3hZ?j-}}82)+IkSNI2;ji@cmzXn#9)$1l zoJ+EKXD1{q{B4>NW?&vnz4v1B-lO=#5@D0Hrlr{Ux9Bg$#bo(Nwl?Rh<9Fj;CycWu&BX+kbmym7bu{|OVN?;JGO<8PeYDi-1pq#@h z#(ApP`TGl#FM`jrJj}>IeN>(4_{=eJ2a)~m^$A#b-)-#s0DFkNRG6pgmnU2g!yGud z?|OLHTUw@ZI0Ja|$A2tZ(qpVE`+Js&eLZEiyZ_6it=UBNsUWv0`O>> z!<$QE{)dfQq&oMN+l`t%U(Vt)sH~@^gjDmKgZia%yBZrc+3|vc9r-gFXY`3go0Bi* zAH9FiePZ#I#aXYc;rfn3mJ<2buT5TiINuL+vrN|)j-LRY?%4Y&92Hx{(T{Pyjp zD-=#`Ufv;=^MQe8ulj`4@BM_UL{P>KNw2Q}1qF)_4r2TJqv%=vF0>TW)7~y!u$ccO zi$un>VwCUi?&ZStkkQq!lv{l7jT(1{Bj|om!TCH0{*}y>?SPhTa#Z@<`FXS`l`|wnAkg3c zf@Jxpu|B$cs3GFSRmC;L7eg#U;1$H7T#o?4Cn^89XnkS?8L~*BgoK9LNLxqki`6(x zOcFDTZ6y||XYHULa@%oXv#$*$Dh0iO(@&#ywQk(-^YNgZR!>sk>$024ujJ_Hw}@%b zmd>SZbai)^&wVZa7~CRY{2EqMlWT#@>)s1MuFmNbu6h-Gd8!0I_u!Rp**22N(lo#& za>Sjkr$MQNPN~9%hPCDw{S_6Rq>nI0E%$kzt7klAW#6W!6OQlQ=xsQb`Ko;^?LZO& z)b{Rc_D*~*1dN;8_tfl z2&h$B_nO!;5&i^$h9;WpxM4R%lZqT$!Q2`v=S8h%zT#3TOG~9z(x9x!nIJ2ppZ-a` zQ{|Pr+cULrM->b%D`%WYOwjnP`aL#9V0^r2lDU0U?tA_{zvhw=-n50+8>6E!$87<0 z<@f%$IDkqaq((ND*<0Q5+s3;uhQ3!S&$qWLIQ%rTILuA=G}jkmhFRa|^9y>t8MQLQ zx2mfj&>;Dk@$c(tqO&s!ScvR$jK}Qh8FZR4aw41tp9K3|9O(Zt^s&n5`t>EGMe1i~ zv7u3UE%O@gBr9p!&Ey9WQ4g$sUK;h^Ui@CRJ}S_hcJSE$F-gnQi_#-|Eosk)GA&zX zBKbDPf2ljHu5%8(yjHbMUpbn)ZdlUrr%VQ9aCEfRJ=nzj+GJWMhz>isUK7p+ zzjehleFhu4Ob!c~@!TC%>gJ%3b2AIM1)33psxz&fp-RnlDZ41s_%ev4v1#YVY$OIu z>wh*mx$zCc!F~^TTt7DUrOCw@s;Xe7p-g7AW!V+B0znUqb8b2u$D-R4ZU@u;(^&IM_Q0}#WCyvpB z6=4O|7%hFHuBE^+J_!~vazA{!kliY2;i6MHMeW78(LmSD>XnL4I>?7zMG0F=w8gu= zwsPD#)|%FWEtYzwrk_uq6vI|+kK9xTPp)~$%}wnYri++4Up=!v_vMMah85D{0@zS3 zc5V^B?Nj3bxg??r9mV5A`R=Yo9&K|V|m zAxk%bLubUX%6@l1Mups~`;!{L$`bhICZY8tC?z9Kb3Z=rvAk~Z8Lxsd1M=9$n-b0H z-X6bGaQ&F6yaSjVflLGMK`XHll7qNSKowKo# zkr4VVWhbP*xJWNwmfSk|I8{Okfp9XTa%7uJKMVM{au&qQAU~d{d@sIQD-&YAc3f_G z&sgPA^(PJn2Kk?E?w_b5tBR2<#}aOGEHqKZ%?xwy3Nfz?lfrGFDH9qGrmMQC9vrN^ zHl=6vGD9onCDkKVco5o&=U@v~PGV{XDFj5wPjf9qB9}J4_krvm2U_c#E>{UtCZ*~k zN#~k7qUP53oF=!T+PY7PMmDX-Jl!qCS{jutJ5~HCOmSugj(lc^{;#1}TR>E}qxT0( zIQZ7(>}VzO{q_SU-A5BC8EJ=M*H`MaIO->RZ-Lh4mkTZKO34&p6}|ZTo=jw;zH)3W z?TW82+5kzx?vavrh?YxaY;22>@K8vNjr5g=$gqcw!cI4_c;G<)@gr4H4C4qej{-0(9-9!^{>q@O=_bp6Gr*nx!EX! zDX-VjDp?VVK3=w_rlz)fUja(pi2Mb1ajm-Zu17P5>uJkxMx1y?cu`*25~^{2&mEi^ zqnCE4r%@}qo|mxMq)y6l|FACEbaRmQENaOV)Qx(kQ1%ARaP}cV)+{f`g?r&yC+rIl%Su9Q1pmw8|4>3bN z-%@)_kK$`9AYA}ytM#KnbbaWhBZ!Otx2!ZiLM*w~rVZhtm6_j4f>@7mt-40cwxfes z?K|R5Rw)1r5&wN?C&eAAfU{Hhm-)wGTB(XLTg#(X{CI}#Nk2BZdgG8=jIa|wrk;x} zO!aDwAs@six)C4W;tam6tE>BjG2BuGuMIRzF@f=zXn8XmSn52!=59je{j4L*7;di7^lPl{0tvhrXU|#rZ5&|IW04y zHG-tGM{PF5-Almtr~&y0?2gK;_#~xD<}x3@c6g3!@cHg4^P}2xB2+GlZ1~i)nF}Lr z94R6j+J78zpo)JtG-}#~9Lk;TT{*zkRXxcuMiZgl6#~9WM4z`qBTq2xwGnE9#f}`J z|X+Vg|R{Wl)-ceVRJSf69X zfr{FLh2D@Zq|bngl207D)ayqR&ezqf5w!T(*5G?}MW>Qq_D+2$m~l-{QC?_ GC;k_JPuSW3 literal 0 HcmV?d00001 diff --git a/testdata/snapshots/darwin/button_group-light.png b/testdata/snapshots/darwin/button_group-light.png new file mode 100644 index 0000000000000000000000000000000000000000..db5d4363198397fdf0e2141d5f2bd99312f360c0 GIT binary patch literal 4956 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 literal 0 HcmV?d00001 diff --git a/testdata/snapshots/darwin/checkbox-comfortable-dark.png b/testdata/snapshots/darwin/checkbox-comfortable-dark.png new file mode 100644 index 0000000000000000000000000000000000000000..f50031bba6a89b99485998e924924a0cc01f1452 GIT binary patch literal 12926 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 LRFbF^GYt428ub)s literal 0 HcmV?d00001 diff --git a/testdata/snapshots/darwin/checkbox-compact-light.png b/testdata/snapshots/darwin/checkbox-compact-light.png new file mode 100644 index 0000000000000000000000000000000000000000..546718db88286c3303011a8162cd80ec484e4ad7 GIT binary patch literal 11910 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 literal 0 HcmV?d00001 diff --git a/testdata/snapshots/darwin/checkbox-dark.png b/testdata/snapshots/darwin/checkbox-dark.png new file mode 100644 index 0000000000000000000000000000000000000000..8a1d8c72d719fd39f4acda5c10234fe34b552808 GIT binary patch literal 12385 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 literal 0 HcmV?d00001 diff --git a/testdata/snapshots/darwin/checkbox-light.png b/testdata/snapshots/darwin/checkbox-light.png new file mode 100644 index 0000000000000000000000000000000000000000..116207f24dad51597fd5f6ac597345d5f770239a GIT binary patch literal 11756 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 literal 0 HcmV?d00001 diff --git a/testdata/snapshots/darwin/icon_button-dark.png b/testdata/snapshots/darwin/icon_button-dark.png new file mode 100644 index 0000000000000000000000000000000000000000..ff1961b86f207444990a1c6eba6e81ab337d4f44 GIT binary patch literal 3561 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 literal 0 HcmV?d00001 diff --git a/testdata/snapshots/darwin/icon_button-light.png b/testdata/snapshots/darwin/icon_button-light.png new file mode 100644 index 0000000000000000000000000000000000000000..6a4ca9bad08db334cd27e3b9d60c1bfa7fde53d6 GIT binary patch literal 3551 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 G2>vhn!c4ya literal 0 HcmV?d00001 diff --git a/testdata/snapshots/darwin/password_input-dark.png b/testdata/snapshots/darwin/password_input-dark.png new file mode 100644 index 0000000000000000000000000000000000000000..e893138b47697d22d7572dc36cce4c34ca4ad658 GIT binary patch literal 7652 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 NN($=WIyuXT{{v{wT2BA~ literal 0 HcmV?d00001 diff --git a/testdata/snapshots/darwin/password_input-light.png b/testdata/snapshots/darwin/password_input-light.png new file mode 100644 index 0000000000000000000000000000000000000000..cec5085c0e34e1bb7ef858f3da5d1748db1d014d GIT binary patch literal 7542 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 literal 0 HcmV?d00001 diff --git a/testdata/snapshots/darwin/radio_group-dark.png b/testdata/snapshots/darwin/radio_group-dark.png new file mode 100644 index 0000000000000000000000000000000000000000..6f77a865ca1558f76ae60366e71655046b8f8b91 GIT binary patch literal 10665 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 D{C`}s literal 0 HcmV?d00001 diff --git a/testdata/snapshots/darwin/radio_group-light.png b/testdata/snapshots/darwin/radio_group-light.png new file mode 100644 index 0000000000000000000000000000000000000000..609f18c8893fd2a9780f705f2d76b59c2bc24675 GIT binary patch literal 10032 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 T=L6zTI3!s~C5dw2H~;?yVG{>! literal 0 HcmV?d00001 diff --git a/testdata/snapshots/darwin/search_input-dark.png b/testdata/snapshots/darwin/search_input-dark.png new file mode 100644 index 0000000000000000000000000000000000000000..fb6d0e4fc95bbcf024143d9770af3633193416f1 GIT binary patch literal 7926 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%0AxNs(NyAx=&^PwTg?4uzn2c^Acm+RI{~#nW^W& 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 literal 0 HcmV?d00001 diff --git a/testdata/snapshots/darwin/text_area-light.png b/testdata/snapshots/darwin/text_area-light.png new file mode 100644 index 0000000000000000000000000000000000000000..b47461597ca28879db07ff97c99a8f6ef7a6dbf2 GIT binary patch literal 15926 zcmd_Rg;yM1(=Qq%!CiwBEO>B-FgU^8-CYL;O>o!X7Tnzeg9LYX4{n3I-+7+*JAc4A z_k8QFZ{1aUs;BqrReN`J{iW^~7+l0^__*@QX_#Y_T%BT@>g9Pa%knI6 z8PCbu&9ZH=hpBgEQ@gN(jxD)P1%721D|#6$14BT-uowuDvyBQHfG9&jy;IARVP?wW zv~18=c=Pn4XbHUQe422uxJ=vdCukPTDvPH;40xXIF(&vsR+%)Yq+G!hgitbOCMq_->!3u|#JKJHP?oSb=!^-&Dv9@JT)y z^H3nNs{m%)bnBlz$6p0Lf_1s0=Wc8$NDBhpsFe5th!V<)Vm*!RL_oNjCzwU!+lVDb zi)t$S+X(W~r`Vsakpp7TG2&tV@8}Cmjwg^_REduQ?&mD;GpS!%9Jki?pJxWnp#+*27W(@Ep(=R5NO-z_ zB$%(7SbV@+QLJOXv-dnS2act|1XA({6mNA)0Hpm5-%3pfZum)8K*z41c&e*fL!h#& z)YJ)fc0rl@cQ#}M>YjNNZ-dHBIA`@ePmptAaQw9}_$)Q-zzPu7*0H%B^f4&zahZ`* zeqnC5?nO?sfHPg+wTaDipyPI;IrtM6rT9uYUjq+^IQE_vsj{=k#2H>}q)xp<)|}1z ze-!r;Rg3d*4-X%)i>3fMvatcsB+$0Bi8*soCa6tlEXRM`rg4viSIf0!*rrx|sNh>ND^3(q zpIpNv7=u!BYV<#J>G65nO3 z*G4#w1vM@0xYze3*}=4c#Qkwx>scFXJ&2a0BhX`uS8+hrNu zZ~uV62f-RTv8j1(uZ9#(Zs6he$gz!$5X;eGJk|30dXlY%z^zC^(%St=Gaz0XpWtb7 zng&;buOlZol3du2Q?q<3NHVC}(b%0jnYeWas_EwxlEx*%)HZ6Zgj%FA6wd2dsu*kz0vHX zQ6`erMCV&KV}Qz5#SSRR+#KAw+T|C+Vb5F%o^C?;fFROp(x<=eJJVd~?%`gWH4a(2 zPzN186fD($G;qE0ZUvuYgm)P&wt^zd%5A5pz6H`L4#{n(Yimd%mNGG}yxnBWz`TAV z(_JrKAjsh#i`fxW1W4Pft3MK01L7y92a=J3^BS0Qnz(spINNXXW1;5RH2xafZ3(44c8lT5_R6%d+ zQTmnTL~Zf+Ve}+Y1~8V=5*8rOY#J)ZO-Db+pm@e6-_$INl+>Mz%T-l%=LYT7q;_y% zpmu+ltDGt?Z*e=7dwhY1UoyGtXO-e-V1<*#ZGEm36&2lYt5p4U>)pV|ChlfVUq(fP^IuOAwQyf5%koqf%6>&_nsV1L_BFZO%a;d!<-z4Gxp5pOw{MDw}EMc5|b1%c`9>n*`$FsR|S= z1C*{RDxV`h1)V?m$cXn&7B`<%+Cvd7aJ%Wm%b0D@XGdeE^8Kyw^0NE{S=Ho zR!_w_^2msYNkQ_LchwzkX(!J8b{4PG!4sXY>gV@93-_=;{^%Z%Od5ZdpTVyW9sqZU z7*ZG!R1erR{_b~feC_DCTVJ%|&d4`Y*|^vS|CDXAyiXkq^l26)8QBx9zk18-&W205 z!dZ>^D)t28%H9tlFO-liv{WRxwc#v{#An;O^xTY(Z{g!3L8oK-l(}{+S*w#8nzZ$c z0w8VY)5gRyf5GF$Upc>Rd=TtP^J;SOjrdgoaq9TyAvCr|XdFJQFDNd#XwTCdW-ebt z;53D88v#LGT}`mTEj*AramtDvO$B$C6~CFAryedYxt|tz;ngcDD(})_YoqN_WEGGD z1e~%yO#2AR+uo#;mtPA6%`r z;0AQ_dD)HOF#yd?ZcLnxaOTEJuT>^2EEw85Ze%rO6tD3eeQavDo!0>QzIZ!2EVug#P*T6HTjLS1WRHpf8};LpS{xTUKXtHD zLW6!^ZI2W`Zp11iQ%Inbh=dbKV)SG%B9Z|7bJ#RAme*o~jGzzw52v*rQ)u3Fb75m+ zgNC}A#hC~ujz|*gITPWU>lf3r`B-ia8;|l0&b^juR1aF z(BV^Vbs)@-AAnq^2 z%D;VLK1oy@4Yb%m+FqzI+*e*2^ZwFUSZaL*K6B;GY$>Iezm!~1s%r8&sTUC0XtKW= z;FO<`&9*$?!F*D{d4ATY*3U>vdw9*rNFC@^IZQ0ip$!_%&Z#&(9UmJS8eZk$qc)RN z$t(ZuisttQ$KlSeZ`?KHh;9x7ftbC_N;$dbE$V5;y(oAD_Rn)G=#6vPREyK)`f~}) z$%7soyL-dS7pjGR#y@oR%ftSlkfqga**i4uAU}HikfgFSSK6>-2L}iJTRL~m?m?{1 zkfd_R_9Q6=&L=8ViuG;CbD>xm#*nAxkf^=gt_BjqEdFPw)4NHGdK^{X*y| zxE?(sx&1x9XBQPb@xAL0smt#<8!mK0$Mf7Oe;W4rm6ey*{3SawPBe6($?d$=l|O&} zlql!(Zfve+=DWH3WR6+5l5TZGt)_t-dr*!IeUkLW%;$njRPG$ z3@yh=N$+S20-fEH;>i;Jb^|M}dZV^l`TH=BimDI3G-*W*$i2ljRqpxo4ZIkP5 z5d)%rO>(ZyU!U-9TtGr{6kmJR(+0TQD|~&>VFH+w}nfX)X?zEz_0|$_}c3xx%d)lWdX?e5xVABjh`1xuq5Ekl%3#Ukv@9^PU%SKKGhBO zc}sx&6kmC-M)|fg>2iFsEcd?b-;lO*PnVa@)Um6iw`69M3E%$s`iqScSwc_4KvZ5I zMW8=FEICBh2h|n$#S-DaSkKrJ4CEc~oxpd}Kj=mXHE@gn9jB-A-KUNA@JcTACFeGN zD0cAgh++OnzB6^BdI#ciUnXe;Eh5xBDH>~fa&4?y91cX}7$G3S6ER{tT&O`EYCL zUz?d_#=KJAzP=zV-jN?w+XmAq6l_oH;=n-leQF<^Re4eu_tyz3$i&p|^~wf@C0gUg z{YIcCkBEo}3*dc6?IWyM+}=*A+3`_AYQPKR(5~%MUTNPT)Z%mzd*6=WbXXbw4q}P$ zv=m+W0$CSQ*U+%Fww6o%0wb^<#j?MiKu9nL;;Stl^AU}$S|kdeW*y>*KO59{-YQdU;R&0W_T5$c(hm8Fr~R9b3{n7gyH zjoO+O(wZIk5)ly4d5$-suIA=?W1}gL=Ii~0*VmkF*^7sl6d8p&6vZp_Qcyp$pp%Z$ z@p!Z{>bhhmNrFvsda}EJvMV*iE~h9GUX7#U?G3J0`~wT8>MLZ*!=s2Um5TTJ>YZHJ zXX1k#5xLUra3UErxjXVe+kah9U|K|rx^Tm!7t1doARl!|LsOWRw$v}Fv1{((DQF<= z#Je^;{&i#L=}br>++N!0ceUbIC8d&%?C?$4`o%@w)erpF-=oocJ9 zj+nHRmHLK2l`Agprl3%j()@%!arV}xQ)B8)2q3lptN90+u+Qx3D!bcn zis)cYAW32E6Atzb+BE>sOc^vfDu+*$ZfU-onwB;@H|Mzed)+Db$0h)dYC9Mi`SL#E zwGIlxg6E^NGcio@y%xyB)xLa=Ff*M%3Ly7)g-dc$(%k&K7l@vUg9F^s^58j~Ydur+ zDah03^)*P`h2GS#dtyhWpEX>DGu=Ym3=uGB!8X&ielfV2pPqh%NcsN#LtwZ!4K=ls z_k@~?i}$IXI_(THWkG4__^wmV0$Serxe=*85erf@04sln8F;oXV4IAcW&Ch`I5RuT z>F-ZYx4(->sjr`2*tgZz#<;gvgh;8aotESGC<*{{cXz9Oa}`wpRwZ6tQiaCa*xE{n zhhV~Y8ENTooH(ygMSgEYTU+k}&NI;j#T5hv0k~~zT0%s`pIo9Z z?f2XdF0J22W(ZVZXU;lCdiu)p^1I9BquoaLftWAI5)h!wQh^Ne5z!SKY*V$tiP!nx z&BK{usIJfqrwr%0uUlImiFqO(p0hJE^=yeh0Him*YRbwEua5RF(EXh*96375QB0)> zuEx~SwtlG4mZd(#1cZb<{@!bmm325iJ`Q_VP$>p>KW?h|K53_(1moX|k8hE}qHn>T zEG{X|!_(|@>+kA3_Ddy$XVhr__Jkc8vmAdd=mH`lIEv*n7>>dtBG{L3tgWq~V8UnWs{!!zsVIIQZ#TUY?%1b~^{tukbMgwUjpA> zpRt$VCM6+ZYqg~;ZNCl$xvH^2Fn5M|@cMSARLyc9ieL|JhsCzu@!E}L6H7~iB!=*B z(h-la$U`0OK%k6D>Ee$PHJK?z{U}1-?<_3i;hnt`PkPKyub6dGH#n#dRng;^2zDSR zOnZ-mI0^y2yfL;?oWS^*Rm{uTU+n5_B%{K@Is{17{|@6d`rmzE$|nyM4#*4 zXn*04zD=jOIgy2_+=~Yva`LyH(1f)$9vK-KcMp$+`E@#wb0^SjVyv{REG`mBwOHXW z>vDU3b{2gKCxGH-lE~r8)$xMkU$N^Wtb>CC!8GTPJ{Ntk0~g@nJa=cqPhd_rP$5%V z#PxCY;-{F|wR8prjk>xzb8GUN>STRd}(W9oK z;dwwGe05p%g;3*V{IGa;q{9vP_&A<5EEP*8;&q=2xrHnZ@0j^pTWj0c=m2jYa??GB zXysKVr*cmg$$*r1D#}x1XKQU{X0%6#*4vLTX6EL8kJo2NPw2_X$-zP5xEpQ3wvw1I z4C*dhy%?8QZ*8u{>^ygQR;A5(dFOT*2MDP-~r zr6ncTcN^S)O@|0vSlQBU=jJ?W;r`7I2&91UqGslM&}M}2$VNL1Dx}I-`|Y6!IFHFv zQJHmIsBm$rp+f9lSrMY8b(qM8XGpS<;PE)k$~bducU3{m?R>Fkk1oo00D7!h<@)0&vPyjyNe*ef(v^|nN<`XOh9~vyltiAP_jt&ew;ZarwL!If)PD4ff z+Hc2=uF1*4Qq^KlZ!kw{^3Ehu&a`2!v8eF+D{AXG{@!RBDSlJdXep1K76Eyu2s5*o zvU5>d8e0pmfVe=n|9he87dBd($k4O?=nQacE7Ul)ZhPgj1s`_t!we{c^fW3f+TSQ> zX<0ZqRpd`NppZCbMOzJtekY3ghPryx%oQivtf~~(tUbD#6Sw$8lkMnu78WalN&W^A z-5o4ZvbAlEjC6fXDt~2M>&()>~2>#w)gfAeiYhQ5%q zbHpgf8EH%HQnZVU3+^zA*m!?Gt+w_@f(irIom zDJeZMFtfA0@kvQoIK(z3Bb!b`kbAtFogr0Zi3-OEI=$1Ki}VcL5G@U|`BpF(dZ+9D zZ@v&hNkOtRs28uR>$&zhKtzNOfQ|(Mwd9IXtJvC>f2D{^PVSF+-MV_{@yj);UibvN z+PQLxp%)Op=I?LD(*U)@KcN#h3|51~{GB3{7whfI2l`lxcXHU+*kXz3l$;Qx9aqcy z^Q7x81i0tfzP>&wG5$-SWJ3`>3yUz*;lFx>^WzF!s$u#t{SqsKK+7HAI?&e8z?l$B zn-CiaP0W=H!(*rXqVR3 z_fxEvAcZ6St{e5`C==?t`plT7ra84b z9dIn;(3ZCSVt6Kb*mTT35)v}WZZa~K>viae&9HNF*CC^#?p=7^U*~eBkE7n3P}I>m zR4}*lM<(<0#v6n9*v+1xDf!;sJpVEZR7xi(e{md`7Sg(oEJff_zSEUXRO1LW&E8Z4 zUGKO1S~X}oo1U(-DnAJ>^{@9hQKp!j-lgv5N#Vpi(Ds4-C-E&CXVB(ts2QJI!5*a##<%EK0V?AMVMWokuLj(>uKH zI`9~=pW%E|RY4x7t7|P(iJMB20~W@{UAh@|jv{uKW;u8zU@W8u4xm1mO*GGEnV92B%-JIOd zQI$ay^H5;@?%b(3dp4Swr#i`3@oehu3r4>cTijQtj@;bMgW7sro=zFRr@P_-ShyFo zaXItCJ)+|#`;2y9+Tf55zhu_ys~oV)u0T4ttXB<#q$f8jhWWGA`g#SN11tE$dxU9I zc;KJYHv%wH_OcgIb$B+l%sP1Cml}gif*>ms3q3s*B7k=08ezRdU*V<1%HP3pjg0Ha zT%Sr&?PpV>5_N04n>DIXCFZeWwLCpb>(?{0WCKFQ_KTd)_p5&m0w@eTjSC!xccCHS zWFtjio!-y9Ar$;n;%XVZyDK6TC(FoCrl)6^cSkYGnF_jko@usZLPXEtu+;eb{H}~p znwp`xVaLo2k{j{E#@4^8y87&NH0m&uDSOOfX=$mo)h6d>etw`zshO~vq5>|oQV&Ng zU~9{GgdwS4FqW>a&xJY=Vftk^O|Pm2u>beK$kuj+?<)PJZnqZw7ULx?U#+a-fYe(n zWyY}m%j46L$A(s2bdJGG0Pzt}*V|ilCh&`cxUTNX^`WX1zkp_>?LjE!92#8DPY97*YE`F_K8K&5tHs1T?yQGf2@Q65mtMkle>9)#-bgb3M=o;< z?Zcahh~Yl0&9%9I5MpZL9;2m4l_VRA`VoM4P@~0!fpNE$SABGRmW7k)eBvWoEMjqm z2DOpNMd$qB?!2Er#g7&gp-Qry>hk2uRh1IMl`Ss!pBir!Annn?67`+@%&J|wVha{ z|CXK0QcTzW<>cfM>SZACOP!^Yc&7zLB{i57pj2!`)B9vevi`D z%--Jn)z!ARxbuL3#Mai##YHP}@-t@L>3}^~q=YZaD5|RZ-aCULGB;xWghyqr_s_1? zp-hjT_f7rZTJ-Pc7-8A<+S&wj{9iE~St1m4`iMpRAXRemrXjJh9^$=uFHiMwuz-k2 z*89itdD7@sg-WZQ{vlZ8QZUK}w3SU`?^C-ixG`ncOwIy|3Q!e|;B-I0R62cNU$-Zw{Z|Gv}KM z{b$D(wMVG!7J@)v(+6F`BbQWf<6iZ-YK64bHBHW1{$fU!tTTNrU0q#b2d*olD111! z%pqYWsJv5aKg6Si3t2vq zw1;vNM6fvVW)kQd6!2IjYiJ)2ZPPif{q9>Cbaiu?wfhuSS8g8sK9~vrn|v8TXQOA^ zg6Eh<0I&O&+F_DP0`=PJn#p<$^sVo;@o3gtt?Q|>^85q&o1m*XOT@7G-xSSW7*P&9 zEIev>7#L-L?RAE`28Wd*+#==LO82G$app>iwU{_rP$_QGA}6bsWw}r!MIdY|e989r zMT$YxNz^YB*)2Y7VfI*3u^DG~3$A+)2D>?+th=YD(T8`y@1#Z^Z(ZX?U#N?z1+@D4 zKDY`GVMX@Y&R0?9K`J0^fgqG@t-q6_NDrhpYP@I%hN4F6xv^21=Rw2rq6OZNz|Hy9L*MFFV9xpZSX*B6Q+NuLe;cl z$ z|0UO^dJdu}l~T8fDj{>rkyW9!ZJh17{5f&z#F3~E1NK={Rt;KA(P+_us~>88-D)56 zz@cE3C7P8OU#p52q8K!-6^dq2xE;Yq8s+LKe><|c+CiMf8>jCb$2%(!7}|1Wf7S|D`f6ES;ODVkK<=0Y%p?f9v@2< zOM+VFc|PnG?#dRb9DOp+s1Q{zp1UCfS|oxNZuMV(ykaS1*+s3cuGzS8;aGpdnme$0 z1m6;bOE>}lLms_LNWtB3Pv~mCh7#h1mfwzTjv8addv`rdJS=n8Rfa2>PWhaDW^DL&ml!pESYA|rYEZ$AlMP)WvwMTJ9*IT-NrRoMPvwK$A;&CSg# zhE2%u`gLkOHy;SnBIRgJ;n27}kf<sRyfbtyvu{bdnD^>xWJ` zly8osdjNmeg9AN)zP2y=PxVrR)_^DpD7A01;hz{-K~apb2=fn^|L-5xs$qc&jPOo} zPJVr-t9#4*-@Fl!VSZu0Lxx#ZpnO-%^FynG1ak6pP+{!)sdWMd*o+L0SZnF3u2hpJ z8u&ng*s9s9d*2$c6~MCr&N1F^AY9SYMVS8KMzJO&&2PIV3$xD!@^Kwg=YF@i_5l)S z5D%rZ#_I^yL&eSlSh^epQA{^Q3s zlm)W2WdaK)g`;e34O(8l_SlfWQv+ef>zs@vf5n97GxQ(^W@nRSXKS>&FCu>tor8PYE?c|Du)@*xE=8EO`KMwssAz*-oeIYljbFy%w#%fP$S~vPKyi-SI zNP>g>9tD5gu;clzY4>_FbSV^l@5qg7bJLj0CxiemduVjIAH64w^c3P`87cr?Ut4;i zYiww+Y1C{o^ZDZc+7=v1W@}}oce`R)&T1g5V8F& zs^a3%cS#^eiz843zP#%6qTf|#WmUI1ij*y6=i}SGKK%4)`uG@wY_#1+`>DH(x^bgq zEQ8&~wz9Cp1)ao?(Z0R2)6m9dIJ&6FUblH+e!jNnKY;iP@j8L{wNx7B;Iq@Dh6%r1 z|G^MB@HkuAJUNjNH>u^N+ODRjXRM42%T_d~^}H|@$K|Cp+6VeeCq`kPIi#@@YuFHeyN1E<$)w?vH}X=;*3`#v~J(Aj2a8|Cig$#_##!YL{_<#AC<`W3yjf{k+|4~#;1L(ISO)hX4W~Fn0Tfg+|D^XY} zwYix)kCBIjL#FZ>cYnh0KrmOAk&!Wno4NN^8jx$`BJE^t-O?zwvtNuK*(YA0#^CD{ za_q(>IFXWKn94m@Ye0=HkA!pzr1%e>vv7V7lCuVyAl z_tGY&-L!`7gA#|B;$@fK$rL7xkJ{3aZvVPDP1xowR57-5u;?-4T=)3^jU7;PbtQuE zuN+MIK}#B0ok@)Jd~~?T&(E_>WOzAKS|VXA(3dQnoMkVPwZ8hj&Y5pkjgqd(XiDLG zdSAbb9N+x<^XGd{=w*~771p5}7o9?;3CDkV9glawO^i?^_|+rz>P!J$ujj>~>uV27 zamnAmc?Fj<9IKul%R8a=rrmyohk+3rjzezp)XGRndFlv~s$=&s;C-vU+gN!Vj0`_N`7d2F}9Dn zbt7L5lQ`@*O~z6x_BlVIO19|H;v(A%@%stBEr|$L(?1kzF|iXy!6BdTma8k=OhRL zQX=2ZV9vN~*LhubQe!8=^72d~3r!EFY3#7s@c*SI47Mgk`N~^)(Bb=V z@m1e$8T-o}nUB1$4kjr_l3zk@PQyZC z32#>fo(=!nGp+Y;{loaz`}1;hbNQP8o1)5pN2L1ym&`R^7=a&Jqq?5+ffNVp*p8d; zP1ug1(HBNw*XXP@z~rB9GHCblE2wA#(~*0!{&j3UE0oLm#P^-Zk_;>xCaS>a&%Jdm z6coSD=F8k|BkkALTk%fHZ`?tR4oSxjQ_DJyO_R$jt#hqWyXg%;wLisID+0zrR0vfu8Ib(oe!PKZHI@Ea1`c5 zDdH5c_#=1!?(Xr}GpS#ApjH~hD++~h324;^{)d5^=KtfP{z-HH_k;Xtut_m)A5SucM!at1XwjTW&Xg_pYyy zE)W-7gkR30@+U&)q%;ZUz>Ef(?kae*YJzM+RE=V5(7r(>x2Jf@8tT&_-<~zI%qvif zBjRPZC-`sWRn@iHYAJ2ZT`@XX?E5?_>J@kL@^O1j=pc~ab-NB>#L^lwG(Js+#TC|D zyRA5&DCm2gpYWNsFLTv-6J8JsX6%744C%5+gBQz1ChIB@P2yp#2%1Y z^tgFmWmWiGf5vxv8M$~V42~3=dMJ;O)6Pjc<0Ng}k3v%8i5n-BC}OhfF&TO5&t}7M zs90P-3zXpe5NJd9Es1l&)C#^)0$)s?RFi27@x4X2T)s%!MVGZ5LS}zDC;!rqGr)R= z^}}W}QWZ}@<+3pyw&a;UBQpB$S`&mS9*Uqca|{w6!4D8EQA_qS2o>dLe*elNpu^7m zZtmH{kVtmH;LrFRJ$L&d#RWKme(61h1B}#4JX>(ZZNplY={l; zTJ3KCzGSHNT}oMI^S{wN#VQ_LJdVWcrr7R8R50fty@0p90?m=+R8^{e?zx{3%tAh= zN@_m3rmH&r`4hXK{6hI5l$5L)mvYu)3~=TwRxw~{?a(Wd3_^|k-M(ETQ%>cve55bv zaq&mUR{OSXV;)juU{4IySJey-Z&kTk8S=Nht%XHC6jc+Ra4G=8scvzlk}$t-+H+Kc zmzcbMy_ncxLooAS;xb!e5i1VS1BN8-^5!`{4DsA14uJ{---oeX%g~8u_XiCz`^aP8 zFZEAr9N@NUKg@d&812+?bBLC!~BH+%+p1~HaK=@bpt)#$K3lmT>KwRyK8f$leD&KaAhg8yaAWYr%yucI` zCrGpAI{U{VaqvOebG5D&A(#n4SQFCwx8}XyI$~~JIggN(BnXu%*6-C(k%8Zz+35CX z(b~Kalfway>%ktIpfmAJe`S1ek#{uN!PW4WAf>an3~&1`FAh=Yct49ReoNjZy(KI) z8d3}>D%Y$>w<38i6IT~O1qEOHO${uDJkvVP(|H+^7N$kad-u|?u;?x%Va zC#O2fVSzI_5#o9PVQ z)P-${X7mhn{62X0UVCu6mk&>h1OVEstL1RM=q z>aK`XkAsY85rg6e!eGoe03^jMv#r9(?RUr1JDjHN>EmX|aIHMohDX>~vfNa?uI_#s z@aoE?pHww|x{ZB;9m%$wu}>jehpDHvv=<6%B5@+07P)UvgRr2_mdBTsjM4}eY*MBWENbX>Rda(HU^`O1AXm@RF6GI`!kg|B*;6#y*pa$@tW+6wt%y?2eaW%h<)N$@ zS|Ca8_sj;4G&vhL{?KSQuQJk0f^UEG2svU#(0vAWmT$Z)z2=kQe0bRAr11+m6>+gA zuaCrglzn_AKH@+!Nh?G5AC8@tpMQp9MS{CWb+3 zC|fPOJO=Jl^kvp=L=zId=gyW+ym&kgb31tA4u(_X%68vIno*qe#RUoW*@}g88Tk2~ ziUA`Y3~t$0vcoj4#qT?La46OMgd(>;*J#UYSnO+S{K=36?F&yxm_5)NUH-NR_+m=i zYtfayc2%|1f_5sxWW4e>Fav>nm88y&8ywjaC*~}#7$1af2+Of1;zGn@(np}e(%#fi z_(bNEmA$>#r$AjH*QuJgGP-b*=H~Swqwmg*Yj&34g|Q_UJ-jC%B2mT0bpZrfd_*M!nd5Aw5TihX70_}bX=^8n=}qTu6604>pgF?GiEz@Er!QhK^+~G8csS+L=ESIJ4Vy7|>Cs>20Ll zBqbJhMLIg%B4x_)?!7`CUk!uezKMqg#tsC_poG%c-I4M97VhNb0_Mw8-QQ1>ZBY;@tCf}SVl&b8a7}%6 zbn`jUvihzwgf&{9zPUT6jnOE(I8I_peMXGcWmtPL2l{*p96{2;2QTuDVF zs)mE(s*#rx*YL`MfP7Uo7RiuKHb6*h-fW|yUgZ(Uh+0?#R49y$X2~}Av){C6OZGND z&F;i^TM62h243*|_;h6Pj*QE^8BC*OrXCziq{bb@ZYKSm50&-)PZxxBh!qMwhHRUR zQh3m830!MDd(UWF3Shcp(!cNDx^a0^ldc|CAGG zPkeR+PY!o`1$Q$eEjPa$C0rgHUJxtu$86TcBFl>Q=F0kA@*@Pm2A)te z2PJZYYOjkymV{nXZhKn2uN|;%>JOzH5||S7Dk(0)D`$~JruSQF9frv@;!?hk^kvkc zN4{pQ*N>;8Ogss32IS_v(B9J~jQ_H{K-={{6sYkVY+yZqXyeI63pIuqM*yK=A$4)| zv$C-<%QN3rTbmVN+2` z>gG<^!~8tE{w?l{82>^BaL`m%>Ns0Ob6)$mnTzYpS?~V&y4P-T?&G1S1C}_}PWkS0 zOtyzlhx128scaGdcY}^hD_9hj7Qs++TR+J9%rQ#)m{Y6grQOrn@P1<>W+Vd@Yl+4A zz;Da9yuhxm%Kh!h3Ua}BQX1cLfxJSWS$|w@P;_dzbpf@vSCc}+pHG_KyKs!W{Q=(E zt!&j_i5S}0aCQb?B{QWUy}7x*wC`zimv(+<_T0$!fk_*W5T7+Rnk+M|ZFXg%V|Zf{ zwpvVEJ}fWsHLp_p{B(m=Sw##hT1xDBnzZ^jJ5mvMhZDTo6EMhaYsK|t65|^3WzwmB zy)lz8HQHLq__x{Vkt~KnA&=tH#>IOPF z##wz_^*Wju`E5C9B+3ty$>&yZy-*ERCpXj&baPm1Dr}30o!X3Q_$KUO?dhzjA1Oe6U8NHE?=4IYY ze^qBROjPq890`h?9`-UWwD>se1POvJVXF05y%a_o{+rf7{`^x23Ozu1fMG|fsr}?u zyz3N%3lqwoxEt6re!8fJ%o7u`8PgU#-3P<6MMuvcrcMr?{DHVzNs{l)!R5pA+RlDH zn=t6Nj>>(cT8)Yv#rEl`-$rD+7FLLeH$A>v7eQLGQnHxI)>gu@Qi($m^I%)ec^<;< zhmpruboAE{<^=h|&il5OxSH+Ki8`LYEmQP6AxwaPZbcSzGEjQw3;(*1|H7_oy?92p zKYtmQg|!vKZbM$)&y$n7x3e_Z{euH(ECjXgQWuf3O!=y18bd4uN7It{ZekRKgxgF} zzB~xRuyui}%MJR1pFcl3cdgAF-q#mTY!S3LNdWhcy~G3{=ow0W=E6>|UdZWw0wZxb{PrIzQWNt&_lVo> z@a1`LzU{Vgi-qzUBm9TqZnP=`778L47jI+#7C0}g#Hw$qPDQ;mn8y!Sn)-U<>3enG zdFSjdUUa{M$OaEaxO0IotNnc>$ZedU(!*|KwvH%9(kKFS*>;|=_>aBbA8_(=uWIQ| z$R1Z&sYr%@dg>7iXwc^R>9WQa@IdEXFi*@mqh3&)m@w@^Ju*I#BrEG5aC&xfurzmZ zk^6i{?A^djHDQ;X(Yj~w#^VjOf>Hga%2`~s7Fe&seP~ylrhFvn!mj^af9gEX;II-i1n2Yi@06{_WAAEf{Eu}iCkwB54N7 zxf`gEb&*0Pr+lr)c`?o`LDi;3M>+TL%ZqhpCO8*{ZK(|u#7Gn0_b{7353hF(N+p9_ z;GRLIGA33Z^@s>i6amK#f*qa1IBV6T%q$qdB3<2<;aav8R5za18j z79!s3)%x;Tbv%-j(9kG#@3%vJ;!3MODVq~8*oF7z(fZ)$G_}W8YrC>P9sdx*Iwc9!<^6to&s%2X>!j+6%-P zjFj%hJ}%Rg&wcmmYtZ2Kz##q7x8w+E>f^FxK0IW^SKXJn=;B!{nF~c0WTL}XOUojC z+1at?W)ghjf=rlLv9Ym@ed%eRO7b_cc{7FLb!|P+;&lQgK^9AB(r^r~K4QZ~!ByeI zQzCy8Zu|#i|2bzRv!076_T6AT@i)o|{J3p2nL-d70zk!#uZ%cg*|^sV`5V6)e;n%B zHQ5~6`*9g_R8K>q=rx4%x8CBblzB<~w=ew| z$5&AiBR~q|qqg{f&C}}PUJ0$U{Ko^s|sslyY$%d@Aoed)OtpqWnkP zB7{rcK3+8R!)%KSkNHhm5dlq%9|YtB9%UM>Y$8M~p>oB)YHDg6tjO&yAHRM@>KVE7 zR&w;W)VxelI-pIo>{hXKzuhjhN^jD*qR!K8CgnXIs}fB34P1bp#qIPqVh_6{E4+mEXi+h z)x2&R4$9c$%-QN#)Dox~+IAhRKDSsFmHOR0?NGf&ym|g}&(wYV$Z|#bP3LXyhnaW|Co-5fVhYkj)+i5C}XJNnV$-HlhN^V-C= zOKz>a(kDOdt!>ciYOb-@q3h-RaP_6ipcHTC47zb2nVP@S`t24N60IgRQ z+2miI%3m|A?Z(kKO;z-SJ-t_BRopn~-#Hr6-a7wOR9tLa!f=vrU0vhn`FT*E%i8h`ZhpS{70OCG z)BaYm9`(p_t0cj7N^WlL6o`eF_wEAr>~OmpswYwh7$5xl^~w8|*0y|DMC3Z|9SdDS zo$7hGZ5)2KXoholH!G`PoyJ{9baeDD6BAf0c_Lwn}hwj1Nv+sW?i?26>Ku!oCL6%pZnBO_Fg zyASbnbnYof2GkXT&)T|c`n5=GXD95>8?@7Errtfvl2S%SIZ{m)r}l=1B9QyBqWt_& zy4krIavW+855v6qIMFAXozVDQQBdSYc6xV?|1Unzy>ZaCzQLVc zlBtZu{HSX5>OC9wciBOX-mx`BgjBAPpl|F8wvPdU=YwOg^>h~;82xpYuRtIjBt!_( z;p+oiw}&m-y`@UQi7egdlwWgPoLkfLsyMK91B7+B-|B5G9NYJ5BR=`7=^a{GW>L$e z^Mb5aPR)_t5;L#>J@%Cw8-E~xvJWnO`qK1DNwZi8qIEZUQ}3^Vf`UL~s%xl+hW4U} zBWF#mG1I33aFgJQB78V!pRj;$NdfKFe*6)f8RqO+S1N z`L4?;WZWar+>GAmoS*=1r5d_1)#cTzzmf+dZWrL&k2+~>eXEjZ9qP7#tQlJ4N=)G8 z7RC+=eD-tFJ;Cu@T*gnHZ{kxH7B*<5k(t)s8Hm-FA+!z+=Txqz^ON38m8PY|k4AWS zOLd7J>q;*xA*Zn;h!n1C$|MOnw#>*E+s7hr7Z5^4jn2w?$Lsi@@?xglMh#Q&%QcjS zSGQgm_gq~uFIWHqfe(CwyV{q*C3_2@(F8QVW%2OwNk!p9ePqCwcVnJzv8q5VEe!A{ zTF~gAGBp9JHoeWZOGrswpHwfny~n4$w1j5rjey#ThrI3UL6^JyWxlQ)TvpU~!$O-7 z2o`ZYtDtL_%=yWQ^{u64D^Yg97qjFo` z-JJz?`!&CMeocWdY>9yl*H#1lP8PZ?l|bw39L%G`H`Z@ayZN&ll+rU2RW&{IZ)EzO zwd1zE0)qCv@0_K+Z^lHfq$$nqL?s9b&hCF6fi{I83@XoRXpLd<(tPh^SvycW*if=4-VGn!V=+BU4Q-abF%%p!oA-$;RIueBQR<#stq|gO83V z&d2a(#047@G5GK^`<40`@K7!bQ2)bWBPAlCxP9WV^j@Sq)iOdIw7xIGn2=9FhP8Uz zjGpY!0#ybshA`8$owzQ$asGSL`5%#0KCWE_R=0_vT#i*N%%9#cpoDVDy9jSoE*R97 zaU7b90Eerr!zq!?ChC9YM@r&`mk5w&{KvyxB5VLUePl`iSSxM@5 z_UgaIU4xi2F3(-yZUPIV@`Va66wcSX&wkZA2C_`1TP$bfuRT)0x z|Hs|T|4T%Hcn}@j!1eRN!NJvU^<`BR$#TEDyWMPk`9(lou}UlWQ{gZdAEvY$m&%stcI+)tJob5d_s zK5Kpt98$JYBn?hZP8T%ayQpfteiRahZX%kO%!IdUGWD!bH4pbi5)W%#{ew|qK-lhT|ifZPu=PkuAX1% zn0wuCSKX#nt$5#*e5E^0t*B#B*{1(t;oB?f>O4vz3CfSEiQnbvU^JU%YA)WBcNe$! zPyB=FzSeKfWkvVbg@#XBOcs|HWY7<#DQ-&*N9RmSRj1O^m&e6wg0pS)i2V#0Y@B<| z>Tz%;Ev@(QQj=a&M8m>9&&?CV@j9&}r)qys)vj}yb%IMf*p?5Fk4AF`be^s9 z^w>tr1(&$GoqNIr-1i`C`1l1U5&RiidU_c@CSn zS6_#>YhRbv^T+nvGAo;BmeiNq5(mr@$QL)12Z$COo8Da=@ACS6XXngsXsD=AFwkVl z5wTY&F4LnffBv$`9HwU6>HZqd9ao>HkZx9I93>;R`E~oJXb6acCSmR~UNpCj0$%{P z)2K5&dpeJKVh;I!>S#~Ggsi!}y`h$onic@F=~jftziJ=Y(z2iu$Pz(j7QWOqzw`mG z$!YpHQn$gYMnarox~7{0tf6e%k!n_00- zPe^!7TKu*|1&9FY$AUgr1U`4y1S3fyZ!=2IXZRu;3c*&|H^qawnISvDZ@?j^g?nQe z>gwv`_}N~YW+TgPsj{(t`w!_~;8Ynk(a~ozG6dm}(FNv;-RNEAFZp2tW-cttj@TJm zRU37)V@<)~gQ^rT4o%H1$&J@+X=47H-%9SuJJd`=*p=y$&(YB2NKd>n9MeB3lVAz` z#;BKs7$E}V3BX|Uii`G_F2p=`vx5`6u=~T|5qLeL&tRBO%T~4jv zT)^m)M_3IM1kU=_dwX%<?AZw$E+i|0F4ciY zD`oGr{@3#z$Hkxg!j4;4Tifd)^4NMuOUmZ+eewlJvRbB{ysW31B>0@H(OGV#X?&5pjhMw=;xLmfiw|m#rOt7*l6S5Ug zH6^~Ap{AlQpudYl*8$G;N*o+amHJ+@%EpBa@5UBUylCS^DK#FDq!n&ZNHYw4Fh$P| zDT4m!=;+AFY34Oqjj}}5uDQX2J3OL5q=>$`I^j5$J&)pZTyLCs{_&G-0q$qDz9+!% zR>_`Ai?i@`vO=*2um8CzxMA`Y-~D77bkwSml$3Nb2l%edl1<=hS|h3gp%toM@31NO z9B-@V{nT44zVSS_V*ks8y;f6)pk!~7f9KZK{(2FCubSRBP%$QUwP864`qXFKXVq9; z&0NH}Y#iyA|E1jkce`aPV@0<6X>yE4K-|^T!&;^>yEI&W7;!KyA?m z-zU4{#`lcL3fGUz&-^H%1>TV--YARUFWDH2;PnWPj>RofJe`-Vw&7n0{m&kN`vY-W zW4%lWg&M+tnHUwSGN(ivF5_`)RMx?mz96!8T&mQp{lV89#Y+UBJGeN&ho@&o#$Px4I%fHV#UT5w$dWL_>w{b!Y zx`o1&h=rRFa&t3sTR$*<@_QBqPbaw%0@1r+tLYi|n%a&c#OWD<^scuVu!&sJ)a=)A zNPA-{Y|O~re)X~4PkY0&VV4lvQt-`1(_<+PQ8Nh~q}l;TtG3mFUamd6aZjo)t7JHo zr+LlofhP5r0R}zPhr~8mz=_-qB#bEYAf}-BsyAK{KkB5o5{}a5*VakphmEY;<>8i5 zMmA?(`^>7u020;J&%=?Y~!0q=pO0gdA>a03p=~ZJa z9KGawDM(h6^dd^jP`<{_7OPFnjg*Cm)=%kg;P&X~952wDvZuaoETj$2zT@@S`%W(6 z*`FKJmT~rVP;TREFb@dMsp~GpKwi|H6$}#fe>4bBsI(gy^q$(nE;OGB*dQj{mbU^L zj*lU9(r0%hbc=fxn6I`}cKXq%gL8iC!124Yeh}Txh&0U%oVfp3VPil+-kZOK^nAKW z$}QFZ7yDlDyUtFXmEvh0sy{7 zN^6^uu2%o^+7iu?TB`|!pjT?eqcYDj83aVcwEGVNZTgE1_WtSybyv;5$Cq^<2;86ju?r3i<<<3Emz!QVl_bXK}uR2ApScHX{9+RZN_LUc9Y9*0XNEgCOD{5Y;N@|sZw`nAq zpUAm)&IYQp1>I>IpA0ci;WE73$Ow^t-*A^U1srmFqJy;^0l#GjRcfYh z?ZY-j@Q{2)2U{wc;*;fY83$7r-P~xAz8Uo{xBrR5OtM;Vy09$XOi49)-Sii(iJJwld@53B z;Wp3aX9fl`i~En8C5P@^b#LaA+MUP;!ou{;LhL2u?^RB+Ip&m zkCdvkuXO;t(k=p7vZ9GI26)ky7C$RfMLO+&nbqIC>3kt`ZlHc`o>w|pHqXXgyW`65 z5aqL@eU9$Msh96?d!3@gOweyuo<_%UE1c6*U++!$FMGPYWXeu7_RK!F;pO-b+Wrmm z4=pymcD|YrsTjVG*;avuD87PM|61TWcc_5>^_7;Mw%**K-D+XA0n4kyPTv+ShAP1l zotEr!oyjURGPl?I_*o%AYZaV^t!7AL>Z3}^^%5U3JVFrVKu$`=N}yk_zdo7~p^dxMsvliFk;C*QNdO`rrGL7GL+$F3-Effp zdfmtkHJbI{(tp#G=|vir8&Y&Xz?D|>(WAk~5QoU2VYL?in?YkkvH}lmwcg1<1%m}+95^j>}wdF|v+;&!@j+yylZ&Ug2)U`I?#JrZ4 zE0qzZrObaJ{e3VW@nSpDAkfd(eeCp&Ggjjl zW#x`#)sBC@@H;(r;vyqT6)j$;Q_vnB9g#krGx>?;_HCkxng?{(#!qgeXF)Fy-63aP z{%;Cr!7sY)ZZa#+%#7~I^&E)&Y<_mm!T+*?O*R3cTTIXXWdi|&HJjH(b6nI}J)?|B z6{Hq>%3fNq9puFIET(}fc#AO!J^Ph6Hf~p?lM7fUv(?5#DK7bL52Ws0U$>e|UAUY4 z{3vwwSJ}hMZReq_sCe`$kV16hfr8VFiOCq|?Qt0^Q<&ht19Vk(F`~hNDn;#|dbN9} znR4`*g=KqjL2f(&!NdHgrcyT*9vs-E%KHSV22pyAcRDLi*NqLdfR-ck_dOl+I5^!e zGcRU?0}6t(ktSPcK29RrsEZi0iBVm5hOY2wC)XU8mB&9!q}M6 zUyz=7DURWL9=MZ>znGD!`4#oi&W`l((5Y_p05Y|l|AK42hKUIiZW44)-%2Pn=Q{3b zcwj(~b8EgL(4RcDZqb69cEEX~g|?-n#q=l+vARV_fOg)9QGTWCyjhcpHO2qaUrH;T zbzUbi{`{GOllGmvFEK;WMn2TsBaK{OQ{7!NKNx})%LX^}umOQ3j=^VF8+@i7^M6Fp5ILnv(AABf9!}< z6wF%I9ZApbF9DYF_2ljBjqgDkM>v-L?p;DZCpb;DjW=$92(bEEEX4G(&A8{(Ac=fc zF~5Iz!2Cb|cO4i6`a@AJp0Aps`{O}U(5mie{-~%00AWHDXJ%5@m#;Ot-l)6*EvY>? zIoQhr!;dr)K6>@^SVT|`d4-y$8Fke(7iJpT`ih!0TMz1}2O@o+oMb zk+Bpv909*+F!=iil#$^Rv3PCyzFWPOuVl1)?!HRZ(sO*@tW>O!*)l3)Oa9Re7i}Po z{|^%8OOOC^`X_xP9+>Z5oc9v|hF-5duwPq1({e>S-26OZ7IpqRIhPysfWZaipT9B< zd6@>ATk^2sNZ|9Zf4;RxR88J#psXrc#08_9cxpflcfy@4VVe=-!`5sImPeaI)}pO3hGRS8vDyYd#{c3C3ox|H`^ z)D`rlAbho+ut_C&|AiA-e(R${KCJAMIrpa-))|rG28rt@7oQY#@Bj} z{=B0KvD6I@p4@E(2a+=qg@K{2FlY)v;VAba8gSf>HKq!zjWJ3$==yuf?l3Ebiw!TteW?cRk`%4ORX2iV@cGiAL$A7G zzT&7a7(iO9(Xi|QJ))mnWs-fUN>bLjIHJKJ-3(swToPYaZ@Ukr z6yCMy^VZ}#dHm?^!DTk=@^L5ESfpC>I^Z;YI&adVA$^QMlBilsQu})a&9ox5JWgK| zWKj86Lm~|!st`Jags|b0o*v$d-qxkBj1m56!NtsT#TN6X>l>v-t1I*BL!SoYMBZW- zMz{CrS@LvV;E72=<^PM4Y=IZhX`NSPz$LrgPBadUz z4R0Uc^VOx(NzvH;^>z?z?B3j3^z~~Yi6M1n@)KYB0omYu_Q7VO!=rI$!ocV7@cLa( z_@3*D$v_gcQ1mzco~fBN|Ck_Pp((w5!*m`6N#`8DS|^rnMy~Ov;ze++!AJvhbrqep zQRqXLh$~&}jD>Y;%XXK(m6d_ic={J~*I;5|_0E+9=F)@|soNVXiFU08y4CIo!moPeH4rtG@5`=#yhNn$ZxoULy6 zq}%7Xv7#b<=GTak?EkwM^eQ7lKtR|AlKx+dp8wA$JTJM}LUR)}jKgEU-o1G3HAr{_9Bi-YNO^u>g$U}p|AizCD`NI7N{`4(#yUPfb{C? zO-=#h@NUC*oz=XzFHcB_3C%%N8zsjvcz_hr1odYZyEn_0pPJCxSWJ R%LhDg^3uvuH4-M@{ud|9{qg_+ literal 0 HcmV?d00001 diff --git a/testdata/snapshots/darwin/text_input-comfortable-light.png b/testdata/snapshots/darwin/text_input-comfortable-light.png new file mode 100644 index 0000000000000000000000000000000000000000..281d895ea6b35a41da5c1e04912b0d71d1b0db47 GIT binary patch literal 12823 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 literal 0 HcmV?d00001 diff --git a/testdata/snapshots/darwin/text_input-compact-dark.png b/testdata/snapshots/darwin/text_input-compact-dark.png new file mode 100644 index 0000000000000000000000000000000000000000..3ddd459019f9fd09e9300c134e3677d283378460 GIT binary patch literal 13079 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 C`<<`= literal 0 HcmV?d00001 diff --git a/testdata/snapshots/darwin/text_input-compact-light.png b/testdata/snapshots/darwin/text_input-compact-light.png new file mode 100644 index 0000000000000000000000000000000000000000..30cc1d1672d20328014c5a6b0b78b5e10eecc863 GIT binary patch literal 12710 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 Hli>daVGrHK literal 0 HcmV?d00001 diff --git a/testdata/snapshots/darwin/text_input-dark.png b/testdata/snapshots/darwin/text_input-dark.png new file mode 100644 index 0000000000000000000000000000000000000000..18bde1fe3d845dd977c429c14c7259c1a550fd73 GIT binary patch literal 12964 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 literal 0 HcmV?d00001 diff --git a/testdata/snapshots/darwin/text_input-light.png b/testdata/snapshots/darwin/text_input-light.png new file mode 100644 index 0000000000000000000000000000000000000000..d600c90803ba7f2642dd18d73f5b52e086fd6d94 GIT binary patch literal 12498 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 literal 0 HcmV?d00001 diff --git a/text_area.go b/text_area.go new file mode 100644 index 0000000..e87274b --- /dev/null +++ b/text_area.go @@ -0,0 +1,88 @@ +package mujica + +import ( + "fmt" + "unicode/utf8" + + "github.com/egoist/mygo/ui" + + "mujicaui/theme" +) + +// TextAreaOptions configure a TextArea. +type TextAreaOptions struct { + Placeholder string + // Label names the field for assistive technology. + Label string + // MinHeight is the least height in DIPs; zero fits three lines. + MinHeight float32 + // MaxHeight caps the height in DIPs, then the text scrolls; zero grows freely. + MaxHeight float32 + // MaxLength shows a character count against it; zero hides the count. + MaxLength int + // SubmitKey with SubmitMods submits; a zero key means Cmd+Enter. + SubmitKey ui.Key + SubmitMods ui.Modifiers + Disabled bool + ReadOnly bool + // Error marks the value invalid and shows the message below. + Error string +} + +// TextArea is a multi-line field editing *value. +func TextArea(c *ui.Context, value *string, opts TextAreaOptions) InputResult { + if value == nil { + panic("mujica: TextArea needs a value") + } + if opts.MinHeight < 0 || opts.MaxHeight < 0 || opts.MaxHeight > 0 && opts.MaxHeight < opts.MinHeight { + panic("mujica: TextArea needs 0 <= MinHeight <= MaxHeight") + } + if opts.MaxLength < 0 { + panic("mujica: TextArea MaxLength is negative") + } + key, mods := opts.SubmitKey, opts.SubmitMods + if key == 0 { + key, mods = ui.KeyEnter, ui.Cmd + } + d := Density(c) + n := utf8.RuneCountInString(*value) + over := opts.MaxLength > 0 && n > opts.MaxLength + errMsg := opts.Error + if errMsg == "" && over { + errMsg = fmt.Sprintf(msg(c, "text_area.over"), n-opts.MaxLength) + } + count := textAreaCount(c, n, opts.MaxLength) + var r InputResult + look := &textInputLook{disabled: opts.Disabled, readOnly: opts.ReadOnly, err: errMsg, area: true} + r.Element, r.Input = textInputFrame(c, look, func(frame *ui.Element) *ui.Element { + in := textInputEditor(c, frame, value, opts.ReadOnly, true).Grow(1).Padding(d.Unit()*2, d.Unit()*3). + Placeholder(opts.Placeholder).Label(opts.Label) + if opts.MinHeight > 0 { + in.MinHeight(opts.MinHeight) + } + if opts.MaxHeight > 0 { + in.MaxHeight(opts.MaxHeight) + } + r.changed = in.Changed() && !opts.ReadOnly + r.submitted = in.Shortcut(mods, key) + return in + }, count) + return r +} + +// textAreaCount builds the character count against max, or nil without one. +func textAreaCount(c *ui.Context, n, max int) func() { + if max == 0 { + return nil + } + return func() { + k := Tokens(c) + color := k.TextMuted + over := n > max + if over { + color = k.Danger + } + ui.Text(c, fmt.Sprintf("%d / %d", n, max)).TextColor(color).FontSize(theme.CaptionSize). + FontFeatures("tnum").Shrink(0).Label(fmt.Sprintf(msg(c, "text_area.count"), n, max)) + } +} diff --git a/text_area_test.go b/text_area_test.go new file mode 100644 index 0000000..4e51b59 --- /dev/null +++ b/text_area_test.go @@ -0,0 +1,94 @@ +package mujica + +import ( + "strings" + "testing" + + "github.com/egoist/mygo/ui" +) + +func TestTextAreaNewlinesAndSubmit(t *testing.T) { + s, submits := "", 0 + tt := ui.NewTester(func(c *ui.Context) { + Use(c, Settings{}) + if TextArea(c, &s, TextAreaOptions{Label: "Notes"}).Submitted() { + submits++ + } + }, 400, 300) + _ = tt.Click("Notes") + tt.Type("one") + tt.Key(0, ui.KeyEnter) + tt.Type("two") + if s != "one\ntwo" || submits != 0 { + t.Fatalf("value %q submits %d", s, submits) + } + tt.Key(ui.Cmd, ui.KeyEnter) + if submits != 1 || s != "one\ntwo" { + t.Fatalf("Cmd+Enter: value %q submits %d", s, submits) + } +} + +func TestTextAreaCustomSubmitKey(t *testing.T) { + s, submits := "", 0 + tt := ui.NewTester(func(c *ui.Context) { + Use(c, Settings{}) + if TextArea(c, &s, TextAreaOptions{Label: "Chat", SubmitKey: ui.KeyEnter, SubmitMods: ui.Ctrl | ui.Shift}).Submitted() { + submits++ + } + }, 400, 300) + _ = tt.Click("Chat") + tt.Type("hi") + tt.Key(ui.Cmd, ui.KeyEnter) + tt.Key(ui.Ctrl|ui.Shift, ui.KeyEnter) + if submits != 1 { + t.Fatalf("submits %d", submits) + } +} + +func TestTextAreaHeightsAndCount(t *testing.T) { + s := strings.Repeat("line\n", 40) + "end" + var h float32 + tt := ui.NewTester(func(c *ui.Context) { + Use(c, Settings{}) + ui.Column(c).AlignItems(ui.Start).Width(300).Children(func() { + r := TextArea(c, &s, TextAreaOptions{Label: "Log", MinHeight: 60, MaxHeight: 120, MaxLength: 100}) + h = r.Input.Bounds().H + }) + }, 400, 400) + tt.Frame() + if h != 120 { + t.Fatalf("height %v, want the 120 cap", h) + } + if !tt.HasText("203 of 100 characters") || !tt.HasText("103 characters over the limit") { + t.Fatalf("count missing: %q", tt.Texts()) + } + s = "ok" + tt.Frame() + if h < 60 || h >= 120 || !tt.HasText("2 of 100 characters") { + t.Fatalf("height %v texts %q", h, tt.Texts()) + } +} + +func TestTextAreaBadOptionsPanic(t *testing.T) { + defer func() { + if recover() == nil { + t.Fatal("no panic for MaxHeight below MinHeight") + } + }() + s := "" + ui.NewTester(func(c *ui.Context) { + Use(c, Settings{}) + TextArea(c, &s, TextAreaOptions{MinHeight: 100, MaxHeight: 50}) + }, 300, 200) +} + +func TestTextAreaSnapshot(t *testing.T) { + snapshot(t, "text_area", 420, 360, func(c *ui.Context) { + a, b, e := "", "Les lys d’or sur fond d’azur.\nUne cour, un bal, un serment.", "Trop long" + ui.Column(c).Padding(24).Gap(12).Width(360).Children(func() { + TextArea(c, &a, TextAreaOptions{Label: "A", Placeholder: "Write a dedication…"}) + TextArea(c, &b, TextAreaOptions{Label: "B", MaxLength: 120}) + TextArea(c, &e, TextAreaOptions{Label: "E", MinHeight: 40, MaxLength: 5}) + }) + }) +} diff --git a/text_input.go b/text_input.go new file mode 100644 index 0000000..07107cc --- /dev/null +++ b/text_input.go @@ -0,0 +1,172 @@ +package mujica + +import ( + "log/slog" + + "github.com/egoist/mygo/ui" + + "mujicaui/icons" + "mujicaui/internal" + "mujicaui/theme" +) + +// InputResult is a value input's outer element and its events. +type InputResult struct { + *ui.Element + // Input is the text field inside, for focus and keys. + Input *ui.Element + changed bool + submitted bool +} + +// Changed reports that the user changed the value this frame. +func (r InputResult) Changed() bool { return r.changed } + +// Submitted reports Enter, or the submit shortcut of a TextArea. +func (r InputResult) Submitted() bool { return r.submitted } + +// TextInputOptions configure a TextInput. +type TextInputOptions struct { + Placeholder string + // Label names the field for assistive technology. + Label string + // Leading and Trailing build content inside the field's edges. + Leading func() + Trailing func() + // Clearable shows a clear button while there is text; Escape clears too. + Clearable bool + Disabled bool + // ReadOnly keeps the text selectable and copyable but not editable. + ReadOnly bool + // Error marks the value invalid and shows the message below. + Error string +} + +// TextInput is a single-line field editing *value. +func TextInput(c *ui.Context, value *string, opts TextInputOptions) InputResult { + if value == nil { + panic("mujica: TextInput needs a value") + } + var r InputResult + look := &textInputLook{disabled: opts.Disabled, readOnly: opts.ReadOnly, err: opts.Error} + r.Element, r.Input = textInputFrame(c, look, func(frame *ui.Element) *ui.Element { + if opts.Leading != nil { + opts.Leading() + } + in := textInputEditor(c, frame, value, opts.ReadOnly, false).Grow(1).MinWidth(Density(c).Unit() * 10). + Placeholder(opts.Placeholder).Label(opts.Label) + r.changed = in.Changed() && !opts.ReadOnly + r.submitted = in.Submitted() + if opts.Clearable && *value != "" && !opts.ReadOnly && !opts.Disabled { + if textInputClear(c, in) { + *value = "" + r.changed = true + slog.Debug("mujica: text_input clear") + } + } + if opts.Trailing != nil { + opts.Trailing() + } + return in + }, nil) + return r +} + +// textInputFade eases hover, focus and error colors. +var textInputFade = ui.ElementTransition{Colors: true, Duration: theme.StateDuration} + +// textInputLook is the state a field frame shows. +type textInputLook struct { + disabled, readOnly, area bool + err string +} + +// textInputFrame builds the bordered field around what build returns as the input. +func textInputFrame(c *ui.Context, look *textInputLook, build func(frame *ui.Element) *ui.Element, footer func()) (outer, in *ui.Element) { + k := Tokens(c) + d := Density(c) + outer = ui.Column(c).Gap(d.Unit()).Shrink(0).Disabled(look.disabled) + outer.Children(func() { + frame := ui.Row(c).Radius(theme.ControlRadius).Gap(d.Unit() * 2).Cursor(ui.CursorText) + if look.area { + frame.AlignItems(ui.Stretch) + } else { + frame.MinHeight(d.ControlHeight()).Padding(0, d.Unit()*3) + } + frame.Children(func() { in = build(frame) }) + in.FocusRing(false) + if look.err != "" { + in.Error(look.err) + } + if frame.Clicked() { + in.Focus() + } + bg, border := k.Surface, k.ControlBorder + switch { + case look.readOnly: + bg, border = k.Background, k.Border + case frame.Hovered(): + border = k.ControlBorder.Mix(k.Text, 0.35) + } + switch { + case look.err != "": + border = k.Danger + case in.Focused() && !look.readOnly: + border = k.Focus + } + frame.Background(bg).Border(theme.BorderWidth, border).Transition(textInputFade) + frame.DrawOver(func(p *ui.Painter, r ui.Rect) { + if in.FocusVisible() { + internal.FocusRing(p, r, theme.ControlRadius, k.Focus) + } + }) + if look.err == "" && footer == nil { + return + } + ui.Row(c).Gap(d.Unit() * 2).AlignItems(ui.Start).Children(func() { + if look.err != "" { + ui.Text(c, look.err).TextColor(k.Danger).FontSize(theme.CaptionSize).Grow(1) + } else { + ui.Spacer(c) + } + if footer != nil { + footer() + } + }) + }) + return outer, in +} + +// textInputEditor builds the MyGo editor; read-only edits are undone. +func textInputEditor(c *ui.Context, frame *ui.Element, value *string, readOnly, area bool) *ui.Element { + base := ui.TextInputBase + if area { + base = ui.TextAreaBase + } + if !readOnly { + return base(c, value) + } + shown := ui.Local(frame, "readonly", func() string { return *value }) + *shown = *value + in := base(c, shown) + *shown = *value + return in +} + +// textInputClear builds the clear button and reports a clear by click or Escape. +func textInputClear(c *ui.Context, in *ui.Element) bool { + k := Tokens(c) + b := ui.Box(c).Size(theme.MinHitSize, theme.MinHitSize).Shrink(0).Center().Radius(theme.ControlRadius). + Role(ui.RoleButton).Label(msg(c, "text_input.clear")).Cursor(ui.CursorPointer) + fg := k.TextMuted + if b.Hovered() { + b.Background(k.SurfaceHover) + fg = k.Text + } + b.Children(func() { ui.Icon(c, icons.Must("x")).Size(14, 14).TextColor(fg) }) + if b.Clicked() || in.Shortcut(0, ui.KeyEscape) { + in.Focus() + return true + } + return false +} diff --git a/text_input_test.go b/text_input_test.go new file mode 100644 index 0000000..8758dbf --- /dev/null +++ b/text_input_test.go @@ -0,0 +1,141 @@ +package mujica + +import ( + "testing" + + "github.com/egoist/mygo/ui" + + "mujicaui/icons" +) + +func TestTextInputTypingAndSubmit(t *testing.T) { + name, changes, submits := "", 0, 0 + tt := ui.NewTester(func(c *ui.Context) { + Use(c, Settings{}) + r := TextInput(c, &name, TextInputOptions{Label: "Name", Placeholder: "Your name"}) + if r.Changed() { + changes++ + } + if r.Submitted() { + submits++ + } + }, 400, 200) + if err := tt.Click("Name"); err != nil { + t.Fatal(err) + } + tt.Type("Ada") + tt.Key(0, ui.KeyEnter) + if name != "Ada" || changes == 0 || submits != 1 { + t.Fatalf("name %q changes %d submits %d", name, changes, submits) + } +} + +func TestTextInputUndoClipboardIME(t *testing.T) { + s := "" + tt := ui.NewTester(func(c *ui.Context) { + Use(c, Settings{}) + TextInput(c, &s, TextInputOptions{Label: "Note"}) + }, 400, 200) + _ = tt.Click("Note") + tt.Compose("にほ", 2) + if s != "" { + t.Fatalf("composition leaked into value: %q", s) + } + tt.Type("日本") + tt.Command("selectAll") + tt.Command("copy") + if tt.Clipboard() != "日本" { + t.Fatalf("clipboard %q", tt.Clipboard()) + } + tt.Type("x") + tt.Command("undo") + if s != "日本" { + t.Fatalf("after undo %q", s) + } +} + +func TestTextInputClear(t *testing.T) { + s, changes := "draft", 0 + tt := ui.NewTester(func(c *ui.Context) { + Use(c, Settings{}) + if TextInput(c, &s, TextInputOptions{Label: "Draft", Clearable: true}).Changed() { + changes++ + } + }, 400, 200) + if err := tt.Click("Clear"); err != nil { + t.Fatal(err) + } + if s != "" || changes != 1 { + t.Fatalf("after clear %q, changes %d", s, changes) + } + if _, ok := tt.Find("Clear"); ok { + t.Fatal("clear button stays on an empty field") + } + tt.Type("again") + tt.Key(0, ui.KeyEscape) + if s != "" || changes != 3 { + t.Fatalf("after Escape %q, changes %d", s, changes) + } +} + +func TestTextInputReadOnlyAndDisabled(t *testing.T) { + ro, off := "fixed", "locked" + tt := ui.NewTester(func(c *ui.Context) { + Use(c, Settings{}) + ui.Column(c).Gap(8).Children(func() { + TextInput(c, &ro, TextInputOptions{Label: "RO", ReadOnly: true, Clearable: true}) + TextInput(c, &off, TextInputOptions{Label: "Off", Disabled: true}) + }) + }, 400, 200) + _ = tt.Click("RO") + tt.Command("selectAll") + tt.Command("copy") + tt.Type("zz") + tt.Key(0, ui.KeyBackspace) + if ro != "fixed" || tt.Clipboard() != "fixed" { + t.Fatalf("read-only value %q clipboard %q", ro, tt.Clipboard()) + } + if _, ok := tt.Find("Clear"); ok { + t.Fatal("read-only field offers clear") + } + _ = tt.Click("Off") + tt.Type("zz") + if off != "locked" || tt.Focused("Off") { + t.Fatalf("disabled value %q focused %v", off, tt.Focused("Off")) + } +} + +func TestTextInputSlotsAndError(t *testing.T) { + s := "x" + tt := ui.NewTester(func(c *ui.Context) { + Use(c, Settings{}) + TextInput(c, &s, TextInputOptions{ + Label: "Price", + Leading: func() { ui.Text(c, "¥") }, + Trailing: func() { ui.Text(c, "CNY") }, + Error: "Not a price", + }) + }, 400, 200) + for _, want := range []string{"¥", "CNY", "Not a price"} { + if !tt.HasText(want) { + t.Fatalf("missing %q in %q", want, tt.Texts()) + } + } +} + +func TestTextInputSnapshot(t *testing.T) { + view := func(c *ui.Context) { + a, b, e, ro, dis := "", "Elisabeth", "1789-13-01", "Read only", "Disabled" + ui.Column(c).Padding(24).Gap(12).Width(360).Children(func() { + TextInput(c, &a, TextInputOptions{Placeholder: "Placeholder", Label: "A"}) + TextInput(c, &b, TextInputOptions{Label: "B", Clearable: true, Leading: func() { + ui.Icon(c, icons.Must("user")).Size(16, 16).TextColor(Tokens(c).TextMuted) + }}) + TextInput(c, &e, TextInputOptions{Label: "E", Error: "No such date"}) + TextInput(c, &ro, TextInputOptions{Label: "R", ReadOnly: true}) + TextInput(c, &dis, TextInputOptions{Label: "D", Disabled: true}) + }) + } + snapshot(t, "text_input", 420, 320, view) + buttonDensitySnapshots(t, "text_input", 420, 360, view) +} From 6c399117b010304d18df9135768bb8a00434bed0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=9B=B7=E7=94=B5=E8=8A=BD=E8=A1=A3?= Date: Mon, 5 Oct 2026 12:18:12 -0400 Subject: [PATCH 3/4] Display: components 011-020 --- avatar.go | 82 +++ avatar_group.go | 128 +++++ avatar_group_test.go | 90 ++++ avatar_test.go | 62 +++ badge.go | 144 +++++ badge_test.go | 114 ++++ docs/components/011-text.md | 69 +++ docs/components/012-heading.md | 43 ++ docs/components/013-icon.md | 47 ++ docs/components/014-image.md | 60 +++ docs/components/015-avatar.md | 47 ++ docs/components/016-avatar-group.md | 54 ++ docs/components/017-badge.md | 50 ++ docs/components/018-tag.md | 63 +++ docs/components/019-link.md | 53 ++ docs/components/020-kbd.md | 55 ++ example_display_test.go | 134 +++++ examples/gallery/pages_display.go | 495 +++++++++++++++++- examples/gallery/pages_display_parts.go | 32 ++ heading.go | 64 +++ heading_test.go | 78 +++ icon.go | 98 ++++ icon_test.go | 88 ++++ image.go | 86 +++ image_test.go | 113 ++++ internal/display.go | 27 + kbd.go | 80 +++ kbd_test.go | 80 +++ link.go | 81 +++ link_test.go | 95 ++++ messages_display.go | 16 + tag.go | 109 ++++ tag_test.go | 92 ++++ testdata/snapshots/darwin/avatar-dark.png | Bin 0 -> 6314 bytes testdata/snapshots/darwin/avatar-light.png | Bin 0 -> 6194 bytes .../snapshots/darwin/avatar_group-dark.png | Bin 0 -> 7889 bytes .../snapshots/darwin/avatar_group-light.png | Bin 0 -> 7581 bytes testdata/snapshots/darwin/badge-dark.png | Bin 0 -> 6189 bytes testdata/snapshots/darwin/badge-light.png | Bin 0 -> 6239 bytes testdata/snapshots/darwin/heading-dark.png | Bin 0 -> 16066 bytes testdata/snapshots/darwin/heading-light.png | Bin 0 -> 15798 bytes testdata/snapshots/darwin/icon-dark.png | Bin 0 -> 5241 bytes testdata/snapshots/darwin/icon-light.png | Bin 0 -> 5115 bytes testdata/snapshots/darwin/image-dark.png | Bin 0 -> 7050 bytes testdata/snapshots/darwin/image-light.png | Bin 0 -> 6942 bytes testdata/snapshots/darwin/kbd-dark.png | Bin 0 -> 2646 bytes testdata/snapshots/darwin/kbd-light.png | Bin 0 -> 2550 bytes testdata/snapshots/darwin/link-dark.png | Bin 0 -> 10794 bytes testdata/snapshots/darwin/link-light.png | Bin 0 -> 10421 bytes testdata/snapshots/darwin/tag-dark.png | Bin 0 -> 5625 bytes testdata/snapshots/darwin/tag-light.png | Bin 0 -> 5269 bytes testdata/snapshots/darwin/text-dark.png | Bin 0 -> 21270 bytes testdata/snapshots/darwin/text-light.png | Bin 0 -> 20426 bytes text.go | 85 +++ text_test.go | 137 +++++ 55 files changed, 3150 insertions(+), 1 deletion(-) create mode 100644 avatar.go create mode 100644 avatar_group.go create mode 100644 avatar_group_test.go create mode 100644 avatar_test.go create mode 100644 badge.go create mode 100644 badge_test.go create mode 100644 docs/components/011-text.md create mode 100644 docs/components/012-heading.md create mode 100644 docs/components/013-icon.md create mode 100644 docs/components/014-image.md create mode 100644 docs/components/015-avatar.md create mode 100644 docs/components/016-avatar-group.md create mode 100644 docs/components/017-badge.md create mode 100644 docs/components/018-tag.md create mode 100644 docs/components/019-link.md create mode 100644 docs/components/020-kbd.md create mode 100644 example_display_test.go create mode 100644 examples/gallery/pages_display_parts.go create mode 100644 heading.go create mode 100644 heading_test.go create mode 100644 icon.go create mode 100644 icon_test.go create mode 100644 image.go create mode 100644 image_test.go create mode 100644 internal/display.go create mode 100644 kbd.go create mode 100644 kbd_test.go create mode 100644 link.go create mode 100644 link_test.go create mode 100644 messages_display.go create mode 100644 tag.go create mode 100644 tag_test.go create mode 100644 testdata/snapshots/darwin/avatar-dark.png create mode 100644 testdata/snapshots/darwin/avatar-light.png create mode 100644 testdata/snapshots/darwin/avatar_group-dark.png create mode 100644 testdata/snapshots/darwin/avatar_group-light.png create mode 100644 testdata/snapshots/darwin/badge-dark.png create mode 100644 testdata/snapshots/darwin/badge-light.png create mode 100644 testdata/snapshots/darwin/heading-dark.png create mode 100644 testdata/snapshots/darwin/heading-light.png create mode 100644 testdata/snapshots/darwin/icon-dark.png create mode 100644 testdata/snapshots/darwin/icon-light.png create mode 100644 testdata/snapshots/darwin/image-dark.png create mode 100644 testdata/snapshots/darwin/image-light.png create mode 100644 testdata/snapshots/darwin/kbd-dark.png create mode 100644 testdata/snapshots/darwin/kbd-light.png create mode 100644 testdata/snapshots/darwin/link-dark.png create mode 100644 testdata/snapshots/darwin/link-light.png create mode 100644 testdata/snapshots/darwin/tag-dark.png create mode 100644 testdata/snapshots/darwin/tag-light.png create mode 100644 testdata/snapshots/darwin/text-dark.png create mode 100644 testdata/snapshots/darwin/text-light.png create mode 100644 text.go create mode 100644 text_test.go diff --git a/avatar.go b/avatar.go new file mode 100644 index 0000000..fdc2848 --- /dev/null +++ b/avatar.go @@ -0,0 +1,82 @@ +package mujica + +import ( + "unicode" + + "github.com/egoist/mygo/ui" + + "mujicaui/theme" +) + +// AvatarOptions configure an Avatar. +type AvatarOptions struct { + // Image is the picture; nil falls back to initials, then a person icon. + Image *ui.Bitmap + // Size is the diameter in DIPs; zero means 32. + Size float32 +} + +// Avatar shows a person as a round picture, their initials or an icon. +// Assistive technology reads it as an image named name. +func Avatar(c *ui.Context, name string, opts AvatarOptions) *ui.Element { + size := opts.Size + if size < 0 { + panic("mujica: Avatar Size must not be negative") + } + if size == 0 { + size = 32 + } + k := Tokens(c) + label := name + if label == "" { + label = msg(c, "avatar.unnamed") + } + e := ui.Box(c).Size(size, size).Radius(size / 2).Clip().Shrink(0).Center().Role(ui.RoleImage).Label(label) + initials := avatarInitials(name) + e.Children(func() { + switch { + case opts.Image != nil: + ui.Image(c, opts.Image).Fit(ui.Cover).Size(size, size) + case initials != "": + e.Background(k.Selection).Border(theme.BorderWidth, k.Border) + ui.Text(c, initials).Font(theme.SerifFont).FontSize(size * avatarInitialsScale(initials)).FontWeight(600). + TextColor(k.AccentText).SingleLine() + default: + e.Background(k.SurfaceHover).Border(theme.BorderWidth, k.Border) + Icon(c, "user", IconOptions{Size: size * 0.5, Tone: IconMuted}) + } + }) + return e +} + +// avatarInitials returns the first letters of the first and last words. +func avatarInitials(name string) string { + var first, last rune + inWord := false + for _, r := range name { + letter := unicode.IsLetter(r) || unicode.IsDigit(r) + if letter && !inWord { + if first == 0 { + first = unicode.ToUpper(r) + } else { + last = unicode.ToUpper(r) + } + } + inWord = letter + } + switch { + case first == 0: + return "" + case last == 0: + return string(first) + } + return string([]rune{first, last}) +} + +// avatarInitialsScale fits two letters inside the circle. +func avatarInitialsScale(initials string) float32 { + if len([]rune(initials)) > 1 { + return 0.34 + } + return 0.42 +} diff --git a/avatar_group.go b/avatar_group.go new file mode 100644 index 0000000..0a755f0 --- /dev/null +++ b/avatar_group.go @@ -0,0 +1,128 @@ +package mujica + +import ( + "fmt" + "log/slog" + + "github.com/egoist/mygo/ui" + + "mujicaui/internal" + "mujicaui/theme" +) + +// AvatarItem is one person of an AvatarGroup. +type AvatarItem struct { + Name string + Image *ui.Bitmap +} + +// AvatarGroupOptions configure an AvatarGroup. +type AvatarGroupOptions struct { + // Max is how many avatars show before the "+N" button; zero means 4. + Max int + // Size is each avatar's diameter; zero means 32. + Size float32 +} + +type avatarGroupKey struct{} + +// AvatarGroupView is a shown AvatarGroup and its full list's state. +type AvatarGroupView struct { + Element *ui.Element + // Open reports whether the full list shows. + Open bool + // Toggled reports the full list opened or closed this frame. + Toggled bool +} + +// AvatarGroup shows overlapping avatars. Past Max, a "+N" button opens a +// popover with the full list of names. +func AvatarGroup(c *ui.Context, items []AvatarItem, opts AvatarGroupOptions) AvatarGroupView { + limit := opts.Max + if limit < 0 { + panic("mujica: AvatarGroup Max must not be negative") + } + if limit == 0 { + limit = 4 + } + size := opts.Size + if size < 0 { + panic("mujica: AvatarGroup Size must not be negative") + } + if size == 0 { + size = 32 + } + k := Tokens(c) + shown := min(limit, len(items)) + ring := float32(2) + row := ui.Row(c).AlignItems(ui.Center).Role(ui.RoleGroup) + open := ui.Local(row, avatarGroupKey{}, func() bool { return false }) + was := *open + row.Children(func() { + for i, it := range items[:shown] { + cell := ui.Box(c).Key(i).Padding(ring).Radius(size/2 + ring).Background(k.Background).Shrink(0) + if i > 0 { + cell.Margin(0, 0, 0, -size/4) + } + cell.Children(func() { Avatar(c, it.Name, AvatarOptions{Image: it.Image, Size: size}) }) + } + rest := len(items) - shown + if rest == 0 { + *open = false + return + } + more := avatarGroupMore(c, k, size, ring, rest, len(items)) + if more.Clicked() { + *open = !*open + } + ui.PopoverBase(c, more, open, func(panel *ui.Element) { + avatarGroupList(c, k, panel, items) + }) + }) + v := AvatarGroupView{Element: row, Open: *open, Toggled: *open != was} + if v.Toggled { + slog.Debug("mujica: avatargroup toggle", "open", v.Open) + } + return v +} + +func avatarGroupMore(c *ui.Context, k theme.Tokens, size, ring float32, rest, total int) *ui.Element { + cell := ui.Box(c).Key("more").Padding(ring).Radius(size/2+ring).Background(k.Background). + Margin(0, 0, 0, -size/4).Shrink(0) + var b *ui.Element + cell.Children(func() { + b = ui.ButtonBase(c).Size(size, size).Radius(size / 2).Label(fmt.Sprintf(msg(c, "avatar_group.more"), total)) + bg := k.Surface + if b.Pressed() { + bg = k.Selection + } else if b.Hovered() { + bg = k.SurfaceHover + } + b.Background(bg).Border(theme.BorderWidth, k.ControlBorder).TextColor(k.Text) + internal.Ring(b, k.Focus, size/2) + internal.Fade(b, Motion(c, theme.StateDuration)) + b.Children(func() { + ui.Text(c, fmt.Sprintf("+%d", rest)).FontSize(size * 0.36).FontWeight(600).SingleLine() + }) + }) + return b +} + +func avatarGroupList(c *ui.Context, k theme.Tokens, panel *ui.Element, items []AvatarItem) { + u := Density(c).Unit() + panel.Margin(u, 0, 0, 0).Padding(u*2).Radius(theme.PopupRadius).Background(k.Surface). + Border(theme.BorderWidth, k.Border).MinWidth(200) + if d := Motion(c, theme.PopupDuration); d > 0 { + panel.Transition(ui.ElementTransition{Duration: d, Enter: &ui.Motion{Y: -4}}) + } + ui.Scroll(c).MaxHeight(240).Children(func() { + ui.Column(c).Gap(u * 2).Role(ui.RoleList).Children(func() { + for i, it := range items { + ui.Row(c).Key(i).Gap(u * 2).AlignItems(ui.Center).Role(ui.RoleListItem).Children(func() { + Avatar(c, it.Name, AvatarOptions{Image: it.Image, Size: 24}) + ui.Text(c, it.Name).SingleLine() + }) + } + }) + }) +} diff --git a/avatar_group_test.go b/avatar_group_test.go new file mode 100644 index 0000000..229add8 --- /dev/null +++ b/avatar_group_test.go @@ -0,0 +1,90 @@ +package mujica + +import ( + "testing" + + "github.com/egoist/mygo/ui" +) + +var avatarGroupPeople = []AvatarItem{ + {Name: "Sakiko Togawa"}, {Name: "Uika Misumi"}, {Name: "Mutsumi Wakaba"}, + {Name: "Umiri Yahata"}, {Name: "Nyamu Yutenji"}, {Name: "Tomori Takamatsu"}, +} + +func TestAvatarGroupMaxAndRest(t *testing.T) { + tt := ui.NewTester(func(c *ui.Context) { + Use(c, Settings{}) + AvatarGroup(c, avatarGroupPeople, AvatarGroupOptions{Max: 3}) + }, 400, 300) + tt.Frame() + if !tt.HasText("+3") { + t.Fatalf("texts %v", tt.Texts()) + } + if _, ok := tt.Find("Umiri Yahata"); ok { + t.Fatal("fourth avatar shown past Max") + } +} + +func TestAvatarGroupNoOverflowNoButton(t *testing.T) { + tt := ui.NewTester(func(c *ui.Context) { + Use(c, Settings{}) + AvatarGroup(c, avatarGroupPeople[:2], AvatarGroupOptions{}) + }, 400, 300) + tt.Frame() + if tt.HasText("+") { + t.Fatalf("overflow button with 2 of 4: %v", tt.Texts()) + } +} + +func TestAvatarGroupExpandByKeyboard(t *testing.T) { + var toggles []bool + tt := ui.NewTester(func(c *ui.Context) { + Use(c, Settings{}) + if v := AvatarGroup(c, avatarGroupPeople, AvatarGroupOptions{Max: 2}); v.Toggled { + toggles = append(toggles, v.Open) + } + }, 400, 400) + tt.Key(0, ui.KeyTab) + if !tt.Focused("Show all 6 people") { + t.Fatal("overflow button not focused") + } + tt.Key(0, ui.KeyEnter) + for _, p := range avatarGroupPeople { + if !tt.HasText(p.Name) { + t.Fatalf("list lacks %q: %v", p.Name, tt.Texts()) + } + } + tt.Key(0, ui.KeyEscape) + if tt.HasText("Tomori Takamatsu") { + t.Fatal("list still open after Escape") + } + if len(toggles) != 2 || !toggles[0] || toggles[1] { + t.Fatalf("toggles %v, want [true false]", toggles) + } +} + +func TestAvatarGroupExpandByClick(t *testing.T) { + tt := ui.NewTester(func(c *ui.Context) { + Use(c, Settings{}) + AvatarGroup(c, avatarGroupPeople, AvatarGroupOptions{Max: 4}) + }, 400, 400) + if err := tt.Click("+2"); err != nil { + t.Fatal(err) + } + if !tt.HasText("Tomori Takamatsu") { + t.Fatal("list not open") + } + tt.ClickAt(390, 390) + if tt.HasText("Tomori Takamatsu") { + t.Fatal("list open after outside click") + } +} + +func TestAvatarGroupSnapshot(t *testing.T) { + snapshot(t, "avatar_group", 320, 260, func(c *ui.Context) { + ui.Column(c).Padding(20).Gap(16).AlignItems(ui.Start).Children(func() { + AvatarGroup(c, avatarGroupPeople[:3], AvatarGroupOptions{}) + AvatarGroup(c, avatarGroupPeople, AvatarGroupOptions{Max: 3, Size: 40}) + }) + }) +} diff --git a/avatar_test.go b/avatar_test.go new file mode 100644 index 0000000..ff44c7d --- /dev/null +++ b/avatar_test.go @@ -0,0 +1,62 @@ +package mujica + +import ( + "testing" + + "github.com/egoist/mygo/ui" +) + +func TestAvatarInitials(t *testing.T) { + for name, want := range map[string]string{ + "Sakiko Togawa": "ST", + "uika": "U", + "Anon Chihaya Mortis": "AM", + " ": "", + "张伟": "张", + "mutsumi (wakaba) 若叶睦": "M若", + } { + if got := avatarInitials(name); got != want { + t.Errorf("%q: %q, want %q", name, got, want) + } + } +} + +func TestAvatarFallbacks(t *testing.T) { + var size ui.Rect + tt := ui.NewTester(func(c *ui.Context) { + Use(c, Settings{}) + ui.Row(c).AlignItems(ui.Start).Children(func() { + size = Avatar(c, "Sakiko Togawa", AvatarOptions{Size: 48}).Bounds() + Avatar(c, "", AvatarOptions{}) + Avatar(c, "Photo Person", AvatarOptions{Image: imageTestBitmap()}) + }) + }, 300, 100) + tt.Frame() + if size.W != 48 || size.H != 48 { + t.Fatalf("size %v", size) + } + if !tt.HasText("ST") { + t.Fatal("initials missing") + } + if _, ok := tt.Find("Unnamed"); !ok { + t.Fatal("unnamed avatar not labeled") + } + if _, ok := tt.Find("Photo Person"); !ok { + t.Fatal("image avatar not labeled") + } + if tt.HasText("PP") { + t.Fatal("initials shown over image") + } +} + +func TestAvatarSnapshot(t *testing.T) { + snapshot(t, "avatar", 320, 90, func(c *ui.Context) { + ui.Row(c).Padding(20).Gap(12).AlignItems(ui.Center).Children(func() { + Avatar(c, "Sakiko Togawa", AvatarOptions{Size: 48}) + Avatar(c, "Uika", AvatarOptions{}) + Avatar(c, "", AvatarOptions{Size: 40}) + Avatar(c, "Photo", AvatarOptions{Image: imageTestBitmap(), Size: 40}) + Avatar(c, "Mutsumi Wakaba", AvatarOptions{Size: 24}) + }) + }) +} diff --git a/badge.go b/badge.go new file mode 100644 index 0000000..8697378 --- /dev/null +++ b/badge.go @@ -0,0 +1,144 @@ +package mujica + +import ( + "fmt" + "strconv" + + "github.com/egoist/mygo/ui" + + "mujicaui/theme" +) + +// BadgeTone is a badge's emphasis. The zero value is BadgeAccent. +type BadgeTone int + +const ( + BadgeAccent BadgeTone = iota + BadgeNeutral + BadgeSuccess + BadgeWarning + BadgeDanger + BadgeInfo +) + +// BadgePosition places a badge. The zero value lays it out inline. +type BadgePosition int + +const ( + BadgeInline BadgePosition = iota + BadgeTopRight + BadgeTopLeft + BadgeBottomRight + BadgeBottomLeft +) + +// BadgeOptions configure a Badge. +type BadgeOptions struct { + // Text shows as is; when empty, Count shows instead. + Text string + // Count must not be negative; zero with no Text hides the badge. + Count int + // Max caps the shown count as "Max+"; zero means 99. + Max int + Tone BadgeTone + Position BadgePosition + // Label says what the badge counts; it is read before the full value. + Label string +} + +// Badge shows a short text or count. Inline badges take no content; corner +// positions pin the badge on content's corner. +func Badge(c *ui.Context, opts BadgeOptions, content func()) *ui.Element { + if opts.Count < 0 || opts.Max < 0 { + panic("mujica: Badge Count and Max must not be negative") + } + if (opts.Position == BadgeInline) != (content == nil) { + panic("mujica: Badge needs content exactly when Position is not BadgeInline") + } + shown, full := badgeText(opts) + if opts.Position == BadgeInline { + if shown == "" { + return ui.Box(c).Size(0, 0) + } + return badgePill(c, opts, shown, full) + } + at := badgeAnchor(opts.Position) + wrap := ui.Box(c).Shrink(0).AlignSelf(ui.Start).AlignItems(ui.Start) + wrap.Children(func() { + content() + if shown != "" { + badgePill(c, opts, shown, full).Attach(at, ui.AnchorCenter) + } + }) + return wrap +} + +// badgeText returns what the badge shows and what it reads. +func badgeText(opts BadgeOptions) (shown, full string) { + if opts.Text != "" { + return opts.Text, opts.Text + } + if opts.Count == 0 { + return "", "" + } + limit := opts.Max + if limit == 0 { + limit = 99 + } + full = strconv.Itoa(opts.Count) + if opts.Count > limit { + return strconv.Itoa(limit) + "+", full + } + return full, full +} + +func badgePill(c *ui.Context, opts BadgeOptions, shown, full string) *ui.Element { + k := Tokens(c) + bg, fg, border := badgeColors(k, opts.Tone) + label := full + if opts.Label != "" { + label = opts.Label + " " + full + } + p := ui.Box(c).Row().Center().MinWidth(18).Height(18).Padding(0, 5).Radius(theme.CardRadius). + Background(bg).Shrink(0).Role(ui.RoleStatus).Label(label) + if border.A > 0 { + p.Border(theme.BorderWidth, border) + } + p.Children(func() { + ui.Text(c, shown).FontSize(theme.CaptionSize - 1).FontWeight(600).TextColor(fg). + FontFeatures("tnum").SingleLine() + }) + return p +} + +func badgeColors(k theme.Tokens, t BadgeTone) (bg, fg, border ui.Color) { + switch t { + case BadgeAccent: + return k.Accent, k.OnAccent, ui.Color{} + case BadgeNeutral: + return k.SurfaceHover, k.Text, k.Border + case BadgeSuccess: + return k.Surface, k.Success, k.Success + case BadgeWarning: + return k.Surface, k.Warning, k.Warning + case BadgeDanger: + return k.DangerFill, k.OnDanger, ui.Color{} + case BadgeInfo: + return k.Surface, k.Info, k.Info + } + panic(fmt.Sprintf("mujica: unknown BadgeTone %d", t)) +} + +func badgeAnchor(p BadgePosition) ui.Anchor { + switch p { + case BadgeTopRight: + return ui.AnchorTopRight + case BadgeTopLeft: + return ui.AnchorTopLeft + case BadgeBottomRight: + return ui.AnchorBottomRight + case BadgeBottomLeft: + return ui.AnchorBottomLeft + } + panic(fmt.Sprintf("mujica: unknown BadgePosition %d", p)) +} diff --git a/badge_test.go b/badge_test.go new file mode 100644 index 0000000..399de54 --- /dev/null +++ b/badge_test.go @@ -0,0 +1,114 @@ +package mujica + +import ( + "testing" + + "github.com/egoist/mygo/ui" +) + +func TestBadgeText(t *testing.T) { + for _, tc := range []struct { + opts BadgeOptions + shown, full string + }{ + {BadgeOptions{Count: 7}, "7", "7"}, + {BadgeOptions{Count: 99}, "99", "99"}, + {BadgeOptions{Count: 100}, "99+", "100"}, + {BadgeOptions{Count: 12, Max: 9}, "9+", "12"}, + {BadgeOptions{Count: 0}, "", ""}, + {BadgeOptions{Text: "New", Count: 5}, "New", "New"}, + } { + shown, full := badgeText(tc.opts) + if shown != tc.shown || full != tc.full { + t.Errorf("%+v: %q %q", tc.opts, shown, full) + } + } +} + +func TestBadgeReadsFullValue(t *testing.T) { + tt := ui.NewTester(func(c *ui.Context) { + Use(c, Settings{}) + Badge(c, BadgeOptions{Count: 1204, Label: "Unread"}, nil) + }, 300, 100) + tt.Frame() + if !tt.HasText("99+") { + t.Fatalf("texts %v", tt.Texts()) + } + if _, ok := tt.Find("Unread 1204"); !ok { + t.Fatal("full value not read") + } +} + +func TestBadgeCornerPosition(t *testing.T) { + for _, tc := range []struct { + pos BadgePosition + right, down bool + }{{BadgeTopRight, true, false}, {BadgeTopLeft, false, false}, {BadgeBottomRight, true, true}, {BadgeBottomLeft, false, true}} { + var host ui.Rect + tt := ui.NewTester(func(c *ui.Context) { + Use(c, Settings{}) + ui.Column(c).Padding(40).Children(func() { + Badge(c, BadgeOptions{Count: 3, Position: tc.pos}, func() { + host = ui.Box(c).Size(60, 60).Bounds() + }) + }) + }, 300, 300) + tt.Frame() + r, ok := tt.Find("3") + if !ok { + t.Fatalf("%v: badge missing", tc.pos) + } + cx, cy := r.X+r.W/2, r.Y+r.H/2 + wantX, wantY := host.X, host.Y + if tc.right { + wantX += host.W + } + if tc.down { + wantY += host.H + } + if d := cx - wantX; d < -2 || d > 2 { + t.Errorf("%v: x %v, want %v", tc.pos, cx, wantX) + } + if d := cy - wantY; d < -2 || d > 2 { + t.Errorf("%v: y %v, want %v", tc.pos, cy, wantY) + } + } +} + +func TestBadgeBadOptionsPanic(t *testing.T) { + for i, build := range []func(c *ui.Context){ + func(c *ui.Context) { Badge(c, BadgeOptions{Count: -1}, nil) }, + func(c *ui.Context) { Badge(c, BadgeOptions{Count: 1, Position: BadgeTopRight}, nil) }, + func(c *ui.Context) { Badge(c, BadgeOptions{Count: 1}, func() {}) }, + func(c *ui.Context) { Badge(c, BadgeOptions{Count: 1, Tone: BadgeTone(42)}, nil) }, + } { + func() { + defer func() { + if recover() == nil { + t.Fatalf("case %d: no panic", i) + } + }() + tt := ui.NewTester(func(c *ui.Context) { + Use(c, Settings{}) + build(c) + }, 100, 100) + tt.Frame() + }() + } +} + +func TestBadgeSnapshot(t *testing.T) { + snapshot(t, "badge", 460, 100, func(c *ui.Context) { + ui.Row(c).Padding(24).Gap(14).AlignItems(ui.Center).Children(func() { + Badge(c, BadgeOptions{Count: 7}, nil) + Badge(c, BadgeOptions{Count: 250}, nil) + Badge(c, BadgeOptions{Text: "Draft", Tone: BadgeNeutral}, nil) + Badge(c, BadgeOptions{Text: "Live", Tone: BadgeSuccess}, nil) + Badge(c, BadgeOptions{Text: "Late", Tone: BadgeWarning}, nil) + Badge(c, BadgeOptions{Text: "Info", Tone: BadgeInfo}, nil) + Badge(c, BadgeOptions{Count: 3, Tone: BadgeDanger, Position: BadgeTopRight}, func() { + Avatar(c, "Sakiko Togawa", AvatarOptions{}) + }) + }) + }) +} diff --git a/docs/components/011-text.md b/docs/components/011-text.md new file mode 100644 index 0000000..8bb00a7 --- /dev/null +++ b/docs/components/011-text.md @@ -0,0 +1,69 @@ +# 011 Text 文本 + +## 用途 + +正文文本:自动换行、按行数省略、可选择复制。`EditableText` 是 Text 的可编辑模式,就地修改一段短文本(如文件名),并展示调用方的保存进度与保存结果。 + +## 最小示例 + +```go +name := "Coronation.md" +saveErr := "" +view := func(c *ui.Context) { + mujica.Use(c, mujica.Settings{}) + 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() { + saveErr = "" // save name here; set saveErr on failure + } +} +``` + +## 参数 + +`func Text(c *ui.Context, s string, opts TextOptions) *ui.Element` + +| 字段 | 类型 | 默认值 | 含义 | +| --- | --- | --- | --- | +| `MaxLines` | `int` | `0` | 超过该行数时以省略号截断;`0` 不截断;`1` 为单行。负数 panic。 | +| `Selectable` | `bool` | `false` | 允许用指针或键盘选择并复制文本。 | + +`func EditableText(c *ui.Context, value *string, opts EditableTextOptions) *ui.Element` + +| 字段 | 类型 | 默认值 | 含义 | +| --- | --- | --- | --- | +| `Saving` | `bool` | `false` | 调用方正在保存:显示加载标记,并禁止再次编辑。 | +| `Error` | `string` | `""` | 调用方保存失败的说明,显示在文本下方并报告给辅助技术。 | +| `Label` | `string` | `""` | 辅助技术读出的名称。 | + +## 状态 + +- 默认:可编辑文本带虚线下边,提示可以编辑。 +- 悬停:背景变为 `SurfaceHover`,尺寸不变。 +- 聚焦:2 DIP 焦点环。 +- 编辑中:显示 MyGo 文本输入框,酒红边框,选中扩展名之前的部分。 +- 保存中(`Saving`):显示加载标记,阻止进入编辑。 +- 错误(`Error`):虚线变为 `Danger`,下方显示错误说明,保留已确认的值。 + +## 事件 + +- `EditableText(...).Changed()`:用户确认了一个新值,`*value` 已更新。调用方在此保存,并通过 `Saving` 与 `Error` 回传结果。 +- 取消编辑不产生事件,`*value` 不变。 + +## 键盘操作 + +| 按键 | 作用 | +| --- | --- | +| Tab | 聚焦可编辑文本 | +| Enter / 双击 | 开始编辑 | +| Enter | 确认 | +| Escape | 取消并恢复原值 | +| 焦点移走 | 视为确认 | +| Cmd/Ctrl+A、Cmd/Ctrl+C | 在 `Selectable` 文本上全选、复制 | + +## 限制 + +- 编辑模式基于 `ui.EditableText`。MyGo 不报告“取消”事件,组件只能保证取消后值不变,不能通知调用方。 +- 编辑模式只支持单行文本。 +- 值为空字符串时文本区域没有可见文字;组件保留最小宽度和虚线,保证仍可点击。 +- MyGo 无法检测文本是否真的被截断,所以截断时不会自动显示完整内容的提示。 diff --git a/docs/components/012-heading.md b/docs/components/012-heading.md new file mode 100644 index 0000000..17425b4 --- /dev/null +++ b/docs/components/012-heading.md @@ -0,0 +1,43 @@ +# 012 Heading 标题 + +## 用途 + +页面与区块的三级衬线标题,可带副标题与旧金色装饰分隔线(对称细线加中央菱形)。 + +## 最小示例 + +```go +view := func(c *ui.Context) { + mujica.Use(c, mujica.Settings{}) + mujica.Heading(c, "The Grand Hall", mujica.HeadingOptions{Subtitle: "Winter ceremonies", Divider: true}) + mujica.Heading(c, "Guest Ledger", mujica.HeadingOptions{Level: 2}) +} +``` + +## 参数 + +`func Heading(c *ui.Context, title string, opts HeadingOptions) *ui.Element` + +| 字段 | 类型 | 默认值 | 含义 | +| --- | --- | --- | --- | +| `Level` | `int` | `0`(即 1) | 1、2、3 级,对应 28、22、18 DIP。其他值 panic。 | +| `Subtitle` | `string` | `""` | 标题下方的说明文字,使用 `TextMuted`。 | +| `Divider` | `bool` | `false` | 在下方绘制装饰分隔线,颜色为 `Ornament`。 | + +## 状态 + +纯展示,仅默认状态。字号按系统文本缩放(`Preferences().TextScale`)放大。 + +## 事件 + +无。 + +## 键盘操作 + +不获取焦点。装饰线不进入无障碍树。 + +## 限制 + +- 标题字体为 `theme.SerifFont`(Georgia、宋体类回退);系统缺字时由系统回退字体显示。 +- MyGo v0.2.10 没有 Heading 角色(`ui/access.go` 的 Role 列表中没有),辅助技术把标题读作普通文本,无法按标题层级导航。 +- 分隔线只在 Heading 内绘制,不依赖 Divider 组件。 diff --git a/docs/components/013-icon.md b/docs/components/013-icon.md new file mode 100644 index 0000000..886485f --- /dev/null +++ b/docs/components/013-icon.md @@ -0,0 +1,47 @@ +# 013 Icon 图标 + +## 用途 + +按名称显示 `icons` 包内置的 Lucide SVG 图标,可设尺寸与语义颜色。名称不存在时显示可见的回退标记并写警告日志。 + +## 最小示例 + +```go +view := func(c *ui.Context) { + mujica.Use(c, mujica.Settings{}) + 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"}) + }) +} +``` + +## 参数 + +`func Icon(c *ui.Context, name string, opts IconOptions) *ui.Element` + +`name` 必填,空字符串 panic。可用名称见 `icons.Names()`。 + +| 字段 | 类型 | 默认值 | 含义 | +| --- | --- | --- | --- | +| `Size` | `float32` | `0`(即 16) | 宽高(DIP),建议 16、20、24。负数 panic。 | +| `Tone` | `IconTone` | `IconInherit` | `IconInherit` 沿用文字颜色;另有 `IconMuted`、`IconAccent`、`IconSuccess`、`IconWarning`、`IconDanger`、`IconInfo`。未知值 panic。 | +| `Label` | `string` | `""` | 有含义的图标的无障碍名称;为空时图标视为装饰,不进入无障碍树。 | + +## 状态 + +- 默认:按语义颜色显示图标。 +- 加载失败(名称不存在):在同尺寸方框中用 `Danger` 色绘制虚线框与斜线,提示文字为“缺少图标:名称”,无障碍名称相同。每个元素对每个缺失名称记录一次 `slog.Warn("mujica: icon missing", "name", ...)`。 + +## 事件 + +无。 + +## 键盘操作 + +不获取焦点。 + +## 限制 + +- 只能使用 `icons` 包内置的图标。要显示任意 SVG,可直接用 `ui.Icon`。 +- 回退标记带悬停提示,但图标本身不获取焦点,键盘用户看不到这个提示,只能依赖无障碍名称。 diff --git a/docs/components/014-image.md b/docs/components/014-image.md new file mode 100644 index 0000000..b19325b --- /dev/null +++ b/docs/components/014-image.md @@ -0,0 +1,60 @@ +# 014 Image 图片 + +## 用途 + +显示位图(`*ui.Bitmap`)或 SVG(`*ui.SVG`),可选包含或裁切填充;提供占位、加载中、加载失败三种状态和替代说明。 + +## 最小示例 + +```go +var photo *ui.Bitmap // decode once with ui.DecodeBitmap, outside the view +loading, loadErr := false, errors.New("timeout") +view := func(c *ui.Context) { + mujica.Use(c, mujica.Settings{}) + img := mujica.Image(c, photo, mujica.ImageOptions{ + Width: 160, Height: 120, Crop: true, Alt: "Hall of mirrors", + Loading: loading, Err: loadErr, + }) + if img.Retry { + loading, loadErr = true, nil // start loading again here + } +} +``` + +## 参数 + +`func Image(c *ui.Context, src ui.ImageSource, opts ImageOptions) ImageView` + +`src` 为 `nil`(未赋值的接口)时显示占位。 + +| 字段 | 类型 | 默认值 | 含义 | +| --- | --- | --- | --- | +| `Width`, `Height` | `float32` | `0` | 盒子尺寸(DIP)。为 0 时图片使用自身尺寸;占位、加载、错误状态为 160×120。负数 panic。 | +| `Crop` | `bool` | `false` | `true` 覆盖并裁切(`ui.Cover`);`false` 完整放入(`ui.Contain`)。 | +| `Alt` | `string` | `""` | 替代说明,供辅助技术读取;在其他状态下作为描述。 | +| `Loading` | `bool` | `false` | 调用方正在加载。 | +| `Err` | `error` | `nil` | 调用方加载失败;优先于 `Loading`。 | + +返回值 `ImageView`:`Element` 为外层元素;`Retry` 表示本帧点击了重试。 + +## 状态 + +优先级:错误 > 加载中 > 占位 > 图片。 + +- 占位:浅色框内显示图片图标,名称“暂无图片”。 +- 加载中:框内显示 Spinner,名称“正在加载图片”。 +- 错误:显示错误图标、“图片加载失败”和“重试”按钮,`Err.Error()` 作为无障碍错误说明。 + +## 事件 + +- `ImageView.Retry`:用户点击了重试,或在按钮上按 Enter/Space。 + +## 键盘操作 + +只有错误状态的“重试”按钮可以聚焦(Tab),用 Enter 或 Space 触发。 + +## 限制 + +- 组件不读取文件,也不访问网络。解码(`ui.DecodeBitmap`、`ui.ParseSVG`)和异步加载由调用方完成,结果通过 `Window.Update` 回到界面线程,再设置 `Loading` 与 `Err`。 +- SVG 的绘制范围以 MyGo 支持的部分为准:不支持文本、嵌入图片、pattern、marker 和 filter。 +- 接口里装着 nil 指针(如 `(*ui.Bitmap)(nil)`)时不算 `nil`,不会显示占位。 diff --git a/docs/components/015-avatar.md b/docs/components/015-avatar.md new file mode 100644 index 0000000..fedac25 --- /dev/null +++ b/docs/components/015-avatar.md @@ -0,0 +1,47 @@ +# 015 Avatar 头像 + +## 用途 + +圆形人物头像。依次回退:图片 → 姓名首字母 → 默认人物图标。 + +## 最小示例 + +```go +view := func(c *ui.Context) { + mujica.Use(c, mujica.Settings{}) + ui.Row(c).Gap(8).Children(func() { + mujica.Avatar(c, "Sakiko Togawa", mujica.AvatarOptions{Size: 40}) + mujica.Avatar(c, "", mujica.AvatarOptions{}) + }) +} +``` + +## 参数 + +`func Avatar(c *ui.Context, name string, opts AvatarOptions) *ui.Element` + +| 字段 | 类型 | 默认值 | 含义 | +| --- | --- | --- | --- | +| `Image` | `*ui.Bitmap` | `nil` | 头像图片,按圆形裁切填充。 | +| `Size` | `float32` | `0`(即 32) | 直径(DIP)。负数 panic。 | + +`name` 用作无障碍名称和首字母来源;为空时无障碍名称是“未命名”。 + +## 状态 + +- 图片:圆形裁切。 +- 首字母:取第一个和最后一个词的首字母(大写),衬线字体,`AccentText` 文字配 `Selection` 底色。 +- 无可用首字母:`SurfaceHover` 底色加人物图标。 + +## 事件 + +无。 + +## 键盘操作 + +不获取焦点。辅助技术把头像读作以 `name` 命名的图片。 + +## 限制 + +- 中文姓名没有空格时按一个词处理,只取第一个字,例如“张伟”显示为“张”。 +- 头像不使用 MyGo `ui.Avatar`,因为它的尺寸固定为字号的 2.25 倍,无法配置(`ui/indicators.go:383`)。 diff --git a/docs/components/016-avatar-group.md b/docs/components/016-avatar-group.md new file mode 100644 index 0000000..4924c2f --- /dev/null +++ b/docs/components/016-avatar-group.md @@ -0,0 +1,54 @@ +# 016 AvatarGroup 头像组 + +## 用途 + +把多个头像重叠排列。超过最大数量时显示“+N”按钮,点开后在弹层中列出完整名单。 + +## 最小示例 + +```go +people := []mujica.AvatarItem{ + {Name: "Sakiko Togawa"}, {Name: "Uika Misumi"}, {Name: "Mutsumi Wakaba"}, + {Name: "Umiri Yahata"}, {Name: "Nyamu Yutenji"}, +} +view := func(c *ui.Context) { + mujica.Use(c, mujica.Settings{}) + mujica.AvatarGroup(c, people, mujica.AvatarGroupOptions{Max: 3}) +} +``` + +## 参数 + +`func AvatarGroup(c *ui.Context, items []AvatarItem, opts AvatarGroupOptions) AvatarGroupView` + +`AvatarItem`:`Name string`、`Image *ui.Bitmap`。 + +| 字段 | 类型 | 默认值 | 含义 | +| --- | --- | --- | --- | +| `Max` | `int` | `0`(即 4) | 最多直接显示的头像数。负数 panic。 | +| `Size` | `float32` | `0`(即 32) | 每个头像的直径。负数 panic。 | + +返回值 `AvatarGroupView`:`Element`;`Open` 表示完整名单正在显示;`Toggled` 表示本帧打开或关闭了名单。 + +## 状态 + +- 默认:头像之间重叠四分之一直径,用 2 DIP 窗口背景色描边分隔。 +- “+N”按钮:悬停、按下改变背景;聚焦时显示 2 DIP 焦点环。 +- 展开:按钮下方弹出 6 DIP 圆角面板,列出全部头像与姓名,超过 240 DIP 时滚动;遵循减少动画设置。 + +## 事件 + +- `AvatarGroupView.Toggled` / `Open`:名单展开或关闭(点击按钮、Enter、Space、Escape、点击外部)。同时记录 `slog.Debug("mujica: avatargroup toggle")`。 + +## 键盘操作 + +| 按键 | 作用 | +| --- | --- | +| Tab | 聚焦“+N”按钮(读作“显示全部 N 人”) | +| Enter / Space | 展开或收起完整名单 | +| Escape | 关闭名单 | + +## 限制 + +- 分隔描边使用窗口 `Background` 颜色,放在 `Surface` 卡片上时描边颜色会与卡片背景不同。 +- 名单中的条目只用于展示,不可选择。 diff --git a/docs/components/017-badge.md b/docs/components/017-badge.md new file mode 100644 index 0000000..ebba68b --- /dev/null +++ b/docs/components/017-badge.md @@ -0,0 +1,50 @@ +# 017 Badge 徽标 + +## 用途 + +显示短文字或计数。计数超过上限时显示为“上限+”。徽标可以内联排列,也可以钉在内容的某个角上。辅助技术朗读完整数值。 + +## 最小示例 + +```go +unread := 120 +view := func(c *ui.Context) { + mujica.Use(c, mujica.Settings{}) + 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{}) + }) +} +``` + +## 参数 + +`func Badge(c *ui.Context, opts BadgeOptions, content func()) *ui.Element` + +`Position` 为 `BadgeInline` 时 `content` 必须为 `nil`;为角落位置时 `content` 必须不为 `nil`。否则 panic。 + +| 字段 | 类型 | 默认值 | 含义 | +| --- | --- | --- | --- | +| `Text` | `string` | `""` | 显示的文字;非空时忽略 `Count`。 | +| `Count` | `int` | `0` | 计数。`Text` 为空且计数为 0 时不显示徽标。负数 panic。 | +| `Max` | `int` | `0`(即 99) | 显示上限,超过时显示 `Max+`。负数 panic。 | +| `Tone` | `BadgeTone` | `BadgeAccent` | `BadgeAccent`、`BadgeNeutral`、`BadgeSuccess`、`BadgeWarning`、`BadgeDanger`、`BadgeInfo`。 | +| `Position` | `BadgePosition` | `BadgeInline` | `BadgeTopRight`、`BadgeTopLeft`、`BadgeBottomRight`、`BadgeBottomLeft`:徽标中心对准内容的对应角。 | +| `Label` | `string` | `""` | 计数的含义,朗读时放在完整数值前,如“Unread 1204”。 | + +## 状态 + +纯展示。强调和危险色调使用实色填充(`OnAccent` 配 `Accent`,`OnDanger` 配 `DangerFill`);成功、警告、信息色调只把语义色用在文字和边框上。 + +## 事件 + +无。 + +## 键盘操作 + +不获取焦点。徽标的无障碍名称为 `Label` 加完整数值。 + +## 限制 + +- 角落徽标的位置相对于 `content` 所在的盒子。内容本身有外边距时,徽标不会跟着偏移。 +- 徽标使用 `RoleStatus`,部分屏幕阅读器可能在数值变化时主动播报。 diff --git a/docs/components/018-tag.md b/docs/components/018-tag.md new file mode 100644 index 0000000..f551e6c --- /dev/null +++ b/docs/components/018-tag.md @@ -0,0 +1,63 @@ +# 018 Tag 标签 + +## 用途 + +短标签,可带图标。可以点击,也可以关闭,点击标签和关闭标签是两个独立的操作,各有自己的焦点停靠点。 + +## 最小示例 + +```go +tags := []string{"Baroque", "Gothic"} +view := func(c *ui.Context) { + mujica.Use(c, mujica.Settings{}) + ui.Row(c).Gap(8).Children(func() { + for i, t := range tags { + v := mujica.Tag(c, t, mujica.TagOptions{Icon: "star", Clickable: true, Closable: true}) + v.Element.Key(t) + if v.Closed { + tags = append(tags[:i:i], tags[i+1:]...) + } + } + }) +} +``` + +## 参数 + +`func Tag(c *ui.Context, label string, opts TagOptions) TagView` + +`label` 必填,空字符串 panic。 + +| 字段 | 类型 | 默认值 | 含义 | +| --- | --- | --- | --- | +| `Icon` | `string` | `""` | 图标名称,见 Icon 组件。 | +| `Clickable` | `bool` | `false` | 标签文字部分作为按钮,可以聚焦和点击。 | +| `Closable` | `bool` | `false` | 显示 28×28 DIP 的关闭按钮。 | +| `Disabled` | `bool` | `false` | 禁用点击和关闭,并移出 Tab 顺序。 | + +返回值 `TagView`:`Element`;`Clicked` 表示标签被激活;`Closed` 表示请求关闭。 + +## 状态 + +- 默认:`Surface` 底色、细边框、2 DIP 圆角。 +- 悬停 / 按下:可点击部分和关闭按钮分别变为 `SurfaceHover` / `Selection`。 +- 聚焦:对应部分显示 2 DIP 焦点环。 +- 禁用:由 MyGo 统一调暗,不响应输入。 + +## 事件 + +- `TagView.Clicked`:点击标签文字,或在聚焦时按 Enter/Space。点击关闭按钮不会触发它。 +- `TagView.Closed`:点击关闭按钮,或在关闭按钮上按 Enter/Space,或在聚焦的可点击标签上按 Delete/Backspace。组件不会自行移除标签,由调用方删除数据。 + +## 键盘操作 + +| 按键 | 作用 | +| --- | --- | +| Tab | 先聚焦标签文字(仅 `Clickable`),再聚焦关闭按钮 | +| Enter / Space | 激活当前聚焦的部分 | +| Delete / Backspace | 在可点击标签上请求关闭(仅 `Closable`) | + +## 限制 + +- 不可点击的标签不获取焦点,只能通过关闭按钮操作。 +- 标签不提供“选中”状态;需要可选标签时请使用 Toggle 类组件。 diff --git a/docs/components/019-link.md b/docs/components/019-link.md new file mode 100644 index 0000000..a6a760f --- /dev/null +++ b/docs/components/019-link.md @@ -0,0 +1,53 @@ +# 019 Link 链接 + +## 用途 + +带下划线的链接文本:打开外部地址、在 MyGo Router 中跳转路径,或作为应用内动作。可以显示打开失败的反馈。 + +## 最小示例 + +```go +details := false +view := func(c *ui.Context) { + mujica.Use(c, mujica.Settings{}) + mujica.Link(c, "Read the charter", mujica.LinkOptions{URL: "https://example.com/charter"}) + if mujica.Link(c, "Show details", mujica.LinkOptions{}).Clicked() { + details = !details + } +} +``` + +## 参数 + +`func Link(c *ui.Context, label string, opts LinkOptions) *ui.Element` + +`label` 必填,空字符串 panic。 + +| 字段 | 类型 | 默认值 | 含义 | +| --- | --- | --- | --- | +| `URL` | `string` | `""` | 有协议的地址用系统默认应用打开。没有协议的路径(如 `/notes/42`、`?tab=info`)在 MyGo Router 中跳转。为空时是应用内动作。 | +| `Error` | `string` | `""` | 调用方提供的打开失败说明,显示在链接下方,也报告给辅助技术。 | + +## 状态 + +- 默认:`AccentText` 颜色,始终带 1 DIP 下划线。外部地址后面带“外部链接”小图标。 +- 悬停 / 按下:文字与下划线变为 `Text` 颜色,布局不变。 +- 聚焦:2 DIP 焦点环。 +- 错误:格式不正确的地址(无法解析,或 http/https 缺少主机名)不会打开,下方显示“无法打开此链接:地址”;`Error` 非空时优先显示 `Error`。 + +## 事件 + +- `Clicked()`:每次点击或按 Enter 都会报告,无论是否打开了地址。同时记录 `slog.Debug("mujica: link activate")`。 + +## 键盘操作 + +| 按键 | 作用 | +| --- | --- | +| Tab | 聚焦链接 | +| Enter | 激活 | + +## 限制 + +- MyGo v0.2.10 不报告打开失败。`ui.Link` 和 `Context.OpenURL` 不返回结果(`ui/context.go:270`、`ui/window.go:583`)。窗口宿主在 goroutine 中调用 `Shell.OpenExternal` 并丢弃它的错误(`content.go:55`)。因此组件只能自行检查地址格式,系统打开失败无法自动显示。需要这种反馈时,传空 `URL`,在 `Clicked()` 后由调用方在 goroutine 中调用 `mygo.Shell.OpenExternal(url)`。它会返回 error,把结果通过 `Window.Update` 写回 `Error`。 +- 路径是否交给 Router 由 MyGo 决定。没有 Router 时,路径会被当作 URL 交给系统打开。 +- 链接不能嵌入 `ui.RichText` 句子中作为行内元素,因为外部图标和错误说明需要额外的行布局。行内链接请直接使用 `ui.Link`。 diff --git a/docs/components/020-kbd.md b/docs/components/020-kbd.md new file mode 100644 index 0000000..cda7863 --- /dev/null +++ b/docs/components/020-kbd.md @@ -0,0 +1,55 @@ +# 020 Kbd 快捷键 + +## 用途 + +把单键或组合键显示为键帽。按平台显示:macOS 用 ⌘ ⇧ ⌥ ⌃ 等符号且不加连接符;其他平台显示 Ctrl、Shift、Alt,键之间用“+”连接。只做展示,不注册快捷键。 + +## 最小示例 + +```go +view := func(c *ui.Context) { + mujica.Use(c, mujica.Settings{}) + ui.Row(c).Gap(6).Children(func() { + mujica.Text(c, "Command palette", mujica.TextOptions{}) + mujica.Kbd(c, "Mod", "Shift", "P") + }) +} +``` + +## 参数 + +`func Kbd(c *ui.Context, keys ...string) *ui.Element` + +至少要传一个键,键名不能为空,否则 panic。键名不区分大小写: + +| 键名 | macOS | 其他平台 | +| --- | --- | --- | +| `Mod` | ⌘(Command) | Ctrl | +| `Ctrl` | ⌃ | Ctrl | +| `Alt` | ⌥(Option) | Alt | +| `Shift` | ⇧ | Shift | +| `Enter` | ↩ | Enter | +| `Esc` | esc | Esc | +| `Tab` | ⇥ | Tab | +| `Backspace` | ⌫ | Backspace | +| `Delete` | ⌦ | Delete | +| `Up` `Down` `Left` `Right` | ↑ ↓ ← → | ↑ ↓ ← → | + +其他键名按原样显示;单个字符转为大写。 + +## 状态 + +纯展示。键帽使用等宽字体、`Surface` 底色、`ControlBorder` 边框,底边 2 DIP。 + +## 事件 + +无。 + +## 键盘操作 + +不获取焦点,也不注册任何快捷键。辅助技术读出完整的组合键文字,例如“Command+Shift+P”或“Ctrl+Shift+P”。 + +## 限制 + +- 平台在编译时由 `runtime.GOOS` 决定。Gallery 的网页镜像显示的是运行 Go 进程那台机器的平台。 +- `Mod` 与 MyGo 的 `ui.Cmd` 一致:macOS 上是 Command,其他平台是 Control。 diff --git a/example_display_test.go b/example_display_test.go new file mode 100644 index 0000000..a3e05f4 --- /dev/null +++ b/example_display_test.go @@ -0,0 +1,134 @@ +package mujica_test + +import ( + "errors" + + "github.com/egoist/mygo/ui" + + mujica "mujicaui" +) + +func ExampleText() { + name := "Coronation.md" + saveErr := "" + view := func(c *ui.Context) { + mujica.Use(c, mujica.Settings{}) + 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() { + saveErr = "" // save name here; set saveErr on failure + } + } + ui.Render(view, 400, 300, 1) +} + +func ExampleHeading() { + view := func(c *ui.Context) { + mujica.Use(c, mujica.Settings{}) + mujica.Heading(c, "The Grand Hall", mujica.HeadingOptions{Subtitle: "Winter ceremonies", Divider: true}) + mujica.Heading(c, "Guest Ledger", mujica.HeadingOptions{Level: 2}) + } + ui.Render(view, 400, 300, 1) +} + +func ExampleIcon() { + view := func(c *ui.Context) { + mujica.Use(c, mujica.Settings{}) + 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"}) + }) + } + ui.Render(view, 400, 300, 1) +} + +func ExampleImage() { + var photo *ui.Bitmap // decode once with ui.DecodeBitmap, outside the view + loading, loadErr := false, errors.New("timeout") + view := func(c *ui.Context) { + mujica.Use(c, mujica.Settings{}) + img := mujica.Image(c, photo, mujica.ImageOptions{ + Width: 160, Height: 120, Crop: true, Alt: "Hall of mirrors", + Loading: loading, Err: loadErr, + }) + if img.Retry { + loading, loadErr = true, nil // start loading again here + } + } + ui.Render(view, 400, 300, 1) +} + +func ExampleAvatar() { + view := func(c *ui.Context) { + mujica.Use(c, mujica.Settings{}) + ui.Row(c).Gap(8).Children(func() { + mujica.Avatar(c, "Sakiko Togawa", mujica.AvatarOptions{Size: 40}) + mujica.Avatar(c, "", mujica.AvatarOptions{}) + }) + } + ui.Render(view, 400, 300, 1) +} + +func ExampleAvatarGroup() { + people := []mujica.AvatarItem{ + {Name: "Sakiko Togawa"}, {Name: "Uika Misumi"}, {Name: "Mutsumi Wakaba"}, + {Name: "Umiri Yahata"}, {Name: "Nyamu Yutenji"}, + } + view := func(c *ui.Context) { + mujica.Use(c, mujica.Settings{}) + mujica.AvatarGroup(c, people, mujica.AvatarGroupOptions{Max: 3}) + } + ui.Render(view, 400, 300, 1) +} + +func ExampleBadge() { + unread := 120 + view := func(c *ui.Context) { + mujica.Use(c, mujica.Settings{}) + 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{}) + }) + } + ui.Render(view, 400, 300, 1) +} + +func ExampleTag() { + tags := []string{"Baroque", "Gothic"} + view := func(c *ui.Context) { + mujica.Use(c, mujica.Settings{}) + ui.Row(c).Gap(8).Children(func() { + for i, t := range tags { + v := mujica.Tag(c, t, mujica.TagOptions{Icon: "star", Clickable: true, Closable: true}) + v.Element.Key(t) + if v.Closed { + tags = append(tags[:i:i], tags[i+1:]...) + } + } + }) + } + ui.Render(view, 400, 300, 1) +} + +func ExampleLink() { + details := false + view := func(c *ui.Context) { + mujica.Use(c, mujica.Settings{}) + mujica.Link(c, "Read the charter", mujica.LinkOptions{URL: "https://example.com/charter"}) + if mujica.Link(c, "Show details", mujica.LinkOptions{}).Clicked() { + details = !details + } + } + ui.Render(view, 400, 300, 1) +} + +func ExampleKbd() { + view := func(c *ui.Context) { + mujica.Use(c, mujica.Settings{}) + ui.Row(c).Gap(6).Children(func() { + mujica.Text(c, "Command palette", mujica.TextOptions{}) + mujica.Kbd(c, "Mod", "Shift", "P") + }) + } + ui.Render(view, 400, 300, 1) +} diff --git a/examples/gallery/pages_display.go b/examples/gallery/pages_display.go index dde3607..cf3f0a8 100644 --- a/examples/gallery/pages_display.go +++ b/examples/gallery/pages_display.go @@ -1,3 +1,496 @@ package main -var displayPages = []page{} +import ( + "errors" + "fmt" + "strings" + + "github.com/egoist/mygo/ui" + + mujica "mujicaui" +) + +var displayPages = []page{ + {ID: "011", Name: "Text", ZhName: "文本", Category: "Display", Demo: textDemo, Code: textCode, + Keys: "Selectable 文本:点击后 Cmd/Ctrl+A 全选、Cmd/Ctrl+C 复制。可编辑:Tab 聚焦,Enter 或双击开始编辑,Enter 确认,Escape 取消,移走焦点也确认。"}, + {ID: "012", Name: "Heading", ZhName: "标题", Category: "Display", Demo: headingDemo, Code: headingCode, + Keys: "纯展示,不获取焦点。"}, + {ID: "013", Name: "Icon", ZhName: "图标", Category: "Display", Demo: iconDemo, Code: iconCode, + Keys: "纯展示,不获取焦点。"}, + {ID: "014", Name: "Image", ZhName: "图片", Category: "Display", Demo: imageDemo, Code: imageCode, + Keys: "错误状态的「重试」按钮可 Tab 聚焦,Enter 或 Space 触发。"}, + {ID: "015", Name: "Avatar", ZhName: "头像", Category: "Display", Demo: avatarDemo, Code: avatarCode, + Keys: "纯展示,不获取焦点。"}, + {ID: "016", Name: "AvatarGroup", ZhName: "头像组", Category: "Display", Demo: avatarGroupDemo, Code: avatarGroupCode, + Keys: "Tab 聚焦「+N」按钮,Enter 或 Space 展开完整名单,Escape 或点击外部关闭。"}, + {ID: "017", Name: "Badge", ZhName: "徽标", Category: "Display", Demo: badgeDemo, Code: badgeCode, + Keys: "纯展示,不获取焦点;辅助技术朗读完整数值。"}, + {ID: "018", Name: "Tag", ZhName: "标签", Category: "Display", Demo: tagDemo, Code: tagCode, + Keys: "可点击标签:Tab 聚焦,Enter 或 Space 点击,Delete 或 Backspace 关闭。关闭按钮是独立的 Tab 停靠点。"}, + {ID: "019", Name: "Link", ZhName: "链接", Category: "Display", Demo: linkDemo, Code: linkCode, + Keys: "Tab 聚焦链接,Enter 激活。"}, + {ID: "020", Name: "Kbd", ZhName: "快捷键", Category: "Display", Demo: kbdDemo, Code: kbdCode, + Keys: "纯展示,不注册快捷键,不获取焦点。"}, +} + +type textState struct { + maxLines float64 + selectable bool + name string + saving bool + failNext bool + saveErr string +} + +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}) + displaySection(c, "Parameters", func() { + displayControls(c, func() { + ui.Text(c, fmt.Sprintf("MaxLines %d", int(s.maxLines))) + ui.StepSlider(c, &s.maxLines, 0, 4, 1).Width(160) + ui.Checkbox(c, &s.selectable, "Selectable") + ui.Checkbox(c, &s.saving, "Saving") + ui.Checkbox(c, &s.failNext, "Next save fails") + }) + }) + 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}) + }) + }) + displaySection(c, "Editable", func() { + e := mujica.EditableText(c, &s.name, mujica.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." + log("save failed: " + s.name) + } else { + s.saveErr = "" + log("saved: " + s.name) + } + } + }) + displayCode(c, textCode, log) +} + +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() { + saveErr = "" // save name here; set saveErr on failure +}` + +type headingState struct { + level float64 + subtitle, divide bool + scale float64 +} + +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}) + displaySection(c, "Parameters", func() { + displayControls(c, func() { + ui.Text(c, fmt.Sprintf("Level %d", int(s.level))) + if ui.StepSlider(c, &s.level, 1, 3, 1).Width(120).Changed() { + log(fmt.Sprintf("level %d", int(s.level))) + } + ui.Checkbox(c, &s.subtitle, "Subtitle") + ui.Checkbox(c, &s.divide, "Divider") + }) + }) + displaySection(c, "Preview", func() { + sub := "" + 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}) + }) + displaySection(c, "Levels", func() { + for l := 1; l <= 3; l++ { + mujica.Heading(c, fmt.Sprintf("Level %d · 宫廷", l), mujica.HeadingOptions{Level: l}) + } + }) + displayCode(c, headingCode, log) +} + +const headingCode = `mujica.Heading(c, "The Grand Hall", mujica.HeadingOptions{Subtitle: "Winter ceremonies", Divider: true}) +mujica.Heading(c, "Guest Ledger", mujica.HeadingOptions{Level: 2})` + +type iconState struct { + name string + size string + tone string + 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} + +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}) + displaySection(c, "Parameters", func() { + displayControls(c, func() { + if ui.Select(c, &s.name, []string{"star", "bell", "settings", "moon", "sun", "no-such-icon"}).Changed() { + log("name " + s.name) + } + if ui.Select(c, &s.size, []string{"16", "20", "24"}).Changed() { + log("size " + s.size) + } + if ui.Select(c, &s.tone, []string{"Inherit", "Muted", "Accent", "Success", "Warning", "Danger", "Info"}).Changed() { + log("tone " + s.tone) + } + ui.Checkbox(c, &s.label, "Label") + }) + }) + displaySection(c, "Preview", func() { + var size float32 + fmt.Sscan(s.size, &size) + label := "" + if s.label { + label = "Preview icon" + } + mujica.Icon(c, s.name, mujica.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) + } + mujica.Icon(c, "missing-name", mujica.IconOptions{Size: 20}) + }) + }) + displayCode(c, iconCode, log) +} + +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"}) +})` + +type imageState struct { + crop, loading, fail, empty bool + err error +} + +var displayPicture = func() *ui.SVG { + return ui.MustParseSVG([]byte(` + +`)) +}() + +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}) + displaySection(c, "Parameters", func() { + displayControls(c, func() { + ui.Checkbox(c, &s.crop, "Crop") + ui.Checkbox(c, &s.empty, "No source") + ui.Checkbox(c, &s.loading, "Loading") + if ui.Checkbox(c, &s.fail, "Error").Changed() { + s.err = nil + if s.fail { + s.err = errors.New("connection reset") + } + } + }) + }) + displaySection(c, "Preview", func() { + var src ui.ImageSource = displayPicture + 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}) + if v.Retry { + s.fail, s.err = false, nil + log("retry") + } + }) + 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 { + log("retry (static example)") + } + }) + }) + displayCode(c, imageCode, log) +} + +const imageCode = `img := mujica.Image(c, photo, mujica.ImageOptions{ + Width: 160, Height: 120, Crop: true, Alt: "Hall of mirrors", + Loading: loading, Err: loadErr, +}) +if img.Retry { + loading, loadErr = true, nil // start loading again here +}` + +type avatarState struct { + name string + size float64 + img bool +} + +var displayBitmap = func() *ui.Bitmap { + return ui.NewBitmap(ui.Render(func(c *ui.Context) { + mujica.Use(c, mujica.Settings{Mode: mujica.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}) + displaySection(c, "Parameters", func() { + displayControls(c, func() { + if ui.TextInput(c, &s.name).Width(200).Changed() { + log("name " + s.name) + } + ui.Text(c, fmt.Sprintf("Size %d", int(s.size))) + ui.StepSlider(c, &s.size, 24, 80, 4).Width(160) + ui.Checkbox(c, &s.img, "Image") + }) + }) + displaySection(c, "Preview", func() { + var img *ui.Bitmap + if s.img { + img = displayBitmap + } + mujica.Avatar(c, s.name, mujica.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}) + }) + }) + displayCode(c, avatarCode, log) +} + +const avatarCode = `ui.Row(c).Gap(8).Children(func() { + mujica.Avatar(c, "Sakiko Togawa", mujica.AvatarOptions{Size: 40}) + mujica.Avatar(c, "", mujica.AvatarOptions{}) +})` + +var displayPeople = []mujica.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"}, +} + +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}) + displaySection(c, "Parameters", func() { + displayControls(c, func() { + ui.Text(c, fmt.Sprintf("Max %d", int(s.max))) + if ui.StepSlider(c, &s.max, 1, 8, 1).Width(160).Changed() { + log(fmt.Sprintf("max %d", int(s.max))) + } + ui.Text(c, fmt.Sprintf("People %d", int(s.count))) + if ui.StepSlider(c, &s.count, 1, 8, 1).Width(160).Changed() { + log(fmt.Sprintf("people %d", int(s.count))) + } + }) + }) + 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}), + } { + if g.Toggled { + log(fmt.Sprintf("group %d open=%v", i+1, g.Open)) + } + } + }) + displayCode(c, avatarGroupCode, log) +} + +const avatarGroupCode = `mujica.AvatarGroup(c, people, mujica.AvatarGroupOptions{Max: 3})` + +type badgeState struct { + count, max float64 + text string + 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} + +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}) + displaySection(c, "Parameters", func() { + displayControls(c, func() { + ui.Text(c, fmt.Sprintf("Count %d", int(s.count))) + ui.StepSlider(c, &s.count, 0, 200, 1).Width(160) + ui.Text(c, fmt.Sprintf("Max %d", int(s.max))) + ui.StepSlider(c, &s.max, 9, 999, 10).Width(120) + ui.TextInput(c, &s.text).Placeholder("Text overrides count").Width(180) + if ui.Select(c, &s.tone, []string{"Accent", "Neutral", "Success", "Warning", "Danger", "Info"}).Changed() { + log("tone " + s.tone) + } + if ui.Select(c, &s.pos, []string{"TopRight", "TopLeft", "BottomRight", "BottomLeft"}).Changed() { + log("position " + s.pos) + } + }) + }) + opts := mujica.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) + }) + }) + 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}) }) + }) + }) + displayCode(c, badgeCode, log) +} + +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{}) +})` + +type tagState struct { + tags []string + icon, clickable, closable, dis bool +} + +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}) + displaySection(c, "Parameters", func() { + displayControls(c, func() { + ui.Checkbox(c, &s.icon, "Icon") + ui.Checkbox(c, &s.clickable, "Clickable") + ui.Checkbox(c, &s.closable, "Closable") + ui.Checkbox(c, &s.dis, "Disabled") + if mujica.Button(c, "Reset tags", mujica.ButtonOptions{Variant: mujica.Ghost}).Clicked() { + s.tags = []string{"Baroque", "Gothic", "Rococo", "Court"} + log("reset") + } + }) + }) + displaySection(c, "Preview", func() { + icon := "" + if s.icon { + icon = "star" + } + ui.Row(c).Gap(8).Wrap().Children(func() { + for i := 0; i < len(s.tags); i++ { + t := s.tags[i] + v := mujica.Tag(c, t, mujica.TagOptions{Icon: icon, Clickable: s.clickable, Closable: s.closable, Disabled: s.dis}) + v.Element.Key(t) + if v.Clicked { + log("clicked " + t) + } + if v.Closed { + log("closed " + t) + s.tags = append(s.tags[:i:i], s.tags[i+1:]...) + i-- + } + } + }) + }) + displayCode(c, tagCode, log) +} + +const tagCode = `for i, t := range tags { + v := mujica.Tag(c, t, mujica.TagOptions{Icon: "star", Clickable: true, Closable: true}) + v.Element.Key(t) + if v.Closed { + tags = append(tags[:i:i], tags[i+1:]...) + } +}` + +type linkState struct { + url string + external string +} + +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}) + displaySection(c, "Parameters", func() { + displayControls(c, func() { + ui.TextInput(c, &s.url).Placeholder("URL (empty = in-app action)").Width(320) + if mujica.Button(c, "Simulate open failure", mujica.ButtonOptions{Variant: mujica.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() { + s.external = "" + } + }) + }) + displaySection(c, "Preview", func() { + if mujica.Link(c, "Read the court charter", mujica.LinkOptions{URL: s.url, Error: s.external}).Clicked() { + log("activated " + s.url) + } + if mujica.Link(c, "Show the guest list", mujica.LinkOptions{}).Clicked() { + log("in-app action") + } + }) + displayCode(c, linkCode, log) +} + +const linkCode = `mujica.Link(c, "Read the charter", mujica.LinkOptions{URL: "https://example.com/charter"}) +if mujica.Link(c, "Show details", mujica.LinkOptions{}).Clicked() { + details = !details +}` + +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}) + displaySection(c, "Parameters", func() { + displayControls(c, func() { + if ui.TextInput(c, &s.combo).Placeholder("Keys joined by +").Width(220).Changed() { + log("keys " + s.combo) + } + }) + }) + displaySection(c, "Preview", func() { + var keys []string + for _, k := range strings.Split(s.combo, "+") { + if k = strings.TrimSpace(k); k != "" { + keys = append(keys, k) + } + } + if len(keys) > 0 { + mujica.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) + }) + } + }) + displayCode(c, kbdCode, log) +} + +const kbdCode = `ui.Row(c).Gap(6).Children(func() { + mujica.Text(c, "Command palette", mujica.TextOptions{}) + mujica.Kbd(c, "Mod", "Shift", "P") +})` diff --git a/examples/gallery/pages_display_parts.go b/examples/gallery/pages_display_parts.go new file mode 100644 index 0000000..57d8d45 --- /dev/null +++ b/examples/gallery/pages_display_parts.go @@ -0,0 +1,32 @@ +package main + +import ( + "github.com/egoist/mygo/ui" + + mujica "mujicaui" + "mujicaui/theme" +) + +// 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}) + fn() + }) +} + +// displayControls lays out a row of parameter controls. +func displayControls(c *ui.Context, fn func()) { + ui.Row(c).Gap(16).Wrap().AlignItems(ui.Center).Children(fn) +} + +// displayCode shows a page's snippet with a copy button. +func displayCode(c *ui.Context, code string, log func(string)) { + displaySection(c, "Code", func() { + if mujica.Button(c, "Copy code", mujica.ButtonOptions{Variant: mujica.Secondary}).AlignSelf(ui.Start).Clicked() { + c.WriteClipboard(code) + log("code copied") + } + mujica.Text(c, code, mujica.TextOptions{Selectable: true}).Font(theme.MonoFont).FontSize(12) + }) +} diff --git a/heading.go b/heading.go new file mode 100644 index 0000000..7734afb --- /dev/null +++ b/heading.go @@ -0,0 +1,64 @@ +package mujica + +import ( + "github.com/egoist/mygo/ui" + + "mujicaui/internal" + "mujicaui/theme" +) + +// HeadingOptions configure a Heading. The zero value is a level 1 heading. +type HeadingOptions struct { + // Level is 1, 2 or 3; zero means 1. + Level int + // Subtitle shows below the title in muted text. + Subtitle string + // Divider draws an ornament rule with a center diamond below. + Divider bool +} + +// Heading shows a serif title that follows the desktop's text scale. +func Heading(c *ui.Context, title string, opts HeadingOptions) *ui.Element { + k := Tokens(c) + size := headingSize(opts.Level) + scale := c.Preferences().TextScale + if scale <= 0 { + scale = 1 + } + u := Density(c).Unit() + col := ui.Column(c).Gap(u).Shrink(0) + col.Children(func() { + ui.Text(c, title).Font(theme.SerifFont).FontSize(size * scale).FontWeight(600). + LineHeight(1.25).TextColor(k.Text) + if opts.Subtitle != "" { + ui.Text(c, opts.Subtitle).FontSize(theme.BodySize * scale).LineHeight(theme.LineHeight).TextColor(k.TextMuted) + } + if opts.Divider { + ui.Box(c).FillWidth().Height(9).Margin(u, 0, 0, 0).Draw(func(p *ui.Painter, r ui.Rect) { + headingOrnament(p, r, k.Ornament) + }) + } + }) + return col +} + +func headingSize(level int) float32 { + switch level { + case 0, 1: + return theme.H1Size + case 2: + return theme.H2Size + case 3: + return theme.H3Size + } + panic("mujica: Heading Level must be 1, 2 or 3") +} + +// headingOrnament draws symmetric rules around a center diamond. +func headingOrnament(p *ui.Painter, r ui.Rect, c ui.Color) { + cx, cy := r.X+r.W/2, r.Y+r.H/2 + const d, gap = 7, 6 + internal.Rule(p, r.X, cx-gap, cy, c) + internal.Rule(p, cx+gap, r.X+r.W, cy, c) + p.FillPath(internal.Diamond(cx, cy, d), c) +} diff --git a/heading_test.go b/heading_test.go new file mode 100644 index 0000000..0121972 --- /dev/null +++ b/heading_test.go @@ -0,0 +1,78 @@ +package mujica + +import ( + "testing" + + "github.com/egoist/mygo/ui" +) + +func headingHeights(t *testing.T, p ui.Preferences) (h1, h2, h3 float32) { + t.Helper() + tt := ui.NewTester(func(c *ui.Context) { + Use(c, Settings{}) + ui.Column(c).AlignItems(ui.Start).Children(func() { + h1 = Heading(c, "Court", HeadingOptions{}).Bounds().H + h2 = Heading(c, "Court", HeadingOptions{Level: 2}).Bounds().H + h3 = Heading(c, "Court", HeadingOptions{Level: 3}).Bounds().H + }) + }, 400, 400) + tt.SetPreferences(p) + tt.Frame() + return +} + +func TestHeadingLevels(t *testing.T) { + h1, h2, h3 := headingHeights(t, ui.Preferences{TextScale: 1}) + if !(h1 > h2 && h2 > h3 && h3 > 0) { + t.Fatalf("heights %v %v %v not decreasing", h1, h2, h3) + } +} + +func TestHeadingTextScale(t *testing.T) { + h1, _, _ := headingHeights(t, ui.Preferences{TextScale: 1}) + big, _, _ := headingHeights(t, ui.Preferences{TextScale: 2}) + if big < h1*1.8 { + t.Fatalf("scaled height %v, base %v", big, h1) + } +} + +func TestHeadingSubtitleAndDivider(t *testing.T) { + var plain, full float32 + tt := ui.NewTester(func(c *ui.Context) { + Use(c, Settings{}) + ui.Column(c).AlignItems(ui.Start).Children(func() { + plain = Heading(c, "Archive", HeadingOptions{Level: 2}).Bounds().H + full = Heading(c, "Ledger", HeadingOptions{Level: 2, Subtitle: "Entries since 1802", Divider: true}).Bounds().H + }) + }, 400, 300) + tt.Frame() + if !tt.HasText("Entries since 1802") { + t.Fatal("subtitle missing") + } + if full < plain+20 { + t.Fatalf("subtitle and divider add %v DIPs", full-plain) + } +} + +func TestHeadingBadLevelPanics(t *testing.T) { + defer func() { + if recover() == nil { + t.Fatal("no panic") + } + }() + tt := ui.NewTester(func(c *ui.Context) { + Use(c, Settings{}) + Heading(c, "x", HeadingOptions{Level: 4}) + }, 100, 100) + tt.Frame() +} + +func TestHeadingSnapshot(t *testing.T) { + snapshot(t, "heading", 420, 260, func(c *ui.Context) { + ui.Column(c).Padding(20).Gap(16).Width(380).Children(func() { + Heading(c, "The Grand Hall", HeadingOptions{Subtitle: "Ceremonies of the winter season", Divider: true}) + Heading(c, "Guest Ledger", HeadingOptions{Level: 2}) + Heading(c, "宫廷礼仪", HeadingOptions{Level: 3, Subtitle: "第三章"}) + }) + }) +} diff --git a/icon.go b/icon.go new file mode 100644 index 0000000..ec0cc19 --- /dev/null +++ b/icon.go @@ -0,0 +1,98 @@ +package mujica + +import ( + "fmt" + "log/slog" + + "github.com/egoist/mygo/ui" + + "mujicaui/icons" + "mujicaui/theme" +) + +// IconTone is an icon's semantic color. The zero value takes the text color. +type IconTone int + +const ( + IconInherit IconTone = iota + IconMuted + IconAccent + IconSuccess + IconWarning + IconDanger + IconInfo +) + +// IconOptions configure an Icon. +type IconOptions struct { + // Size is the icon's width and height in DIPs; zero means 16. + Size float32 + Tone IconTone + // Label names a meaningful icon for assistive technology; empty hides it. + Label string +} + +type iconMissingKey struct{} + +// Icon shows the named icon of the icons package. A missing name shows a +// dashed fallback box in the Danger color and logs a warning. +func Icon(c *ui.Context, name string, opts IconOptions) *ui.Element { + if name == "" { + panic("mujica: Icon needs a name") + } + size := opts.Size + if size < 0 { + panic("mujica: Icon Size must not be negative") + } + if size == 0 { + size = 16 + } + k := Tokens(c) + svg, ok := icons.Get(name) + if !ok { + return iconMissing(c, name, size, k) + } + e := ui.Icon(c, svg).Size(size, size).Shrink(0) + if col, set := iconColor(k, opts.Tone); set { + e.TextColor(col) + } + if opts.Label != "" { + e.Label(opts.Label) + } + return e +} + +func iconMissing(c *ui.Context, name string, size float32, k theme.Tokens) *ui.Element { + text := fmt.Sprintf(msg(c, "icon.missing"), name) + e := ui.Box(c).Size(size, size).Shrink(0).Role(ui.RoleImage).Label(text).Tooltip(text) + logged := ui.Local(e, iconMissingKey{}, func() string { return "" }) + if *logged != name { + *logged = name + slog.Warn("mujica: icon missing", "name", name) + } + return e.Draw(func(p *ui.Painter, r ui.Rect) { + in := ui.Rect{X: r.X + 1, Y: r.Y + 1, W: r.W - 2, H: r.H - 2} + p.StrokeDashed(in, k.Danger, theme.ControlRadius, 1) + p.Line(in.X+3, in.Y+3, in.X+in.W-3, in.Y+in.H-3, 1, k.Danger) + }) +} + +func iconColor(k theme.Tokens, t IconTone) (ui.Color, bool) { + switch t { + case IconInherit: + return ui.Color{}, false + case IconMuted: + return k.TextMuted, true + case IconAccent: + return k.AccentText, true + case IconSuccess: + return k.Success, true + case IconWarning: + return k.Warning, true + case IconDanger: + return k.Danger, true + case IconInfo: + return k.Info, true + } + panic(fmt.Sprintf("mujica: unknown IconTone %d", t)) +} diff --git a/icon_test.go b/icon_test.go new file mode 100644 index 0000000..32ada91 --- /dev/null +++ b/icon_test.go @@ -0,0 +1,88 @@ +package mujica + +import ( + "bytes" + "log/slog" + "strings" + "testing" + + "github.com/egoist/mygo/ui" +) + +func TestIconSizeAndLabel(t *testing.T) { + var def, big ui.Rect + tt := ui.NewTester(func(c *ui.Context) { + Use(c, Settings{}) + ui.Row(c).AlignItems(ui.Start).Children(func() { + def = Icon(c, "star", IconOptions{}).Bounds() + big = Icon(c, "bell", IconOptions{Size: 24, Tone: IconAccent, Label: "Alerts"}).Bounds() + }) + }, 200, 100) + tt.Frame() + if def.W != 16 || def.H != 16 || big.W != 24 || big.H != 24 { + t.Fatalf("sizes %v %v", def, big) + } + if _, ok := tt.Find("Alerts"); !ok { + t.Fatal("label not exposed") + } +} + +func TestIconMissingFallbackIsVisibleAndLogged(t *testing.T) { + var buf bytes.Buffer + old := slog.Default() + slog.SetDefault(slog.New(slog.NewTextHandler(&buf, nil))) + defer slog.SetDefault(old) + var r ui.Rect + tt := ui.NewTester(func(c *ui.Context) { + Use(c, Settings{}) + ui.Row(c).AlignItems(ui.Start).Children(func() { + r = Icon(c, "no-such-icon", IconOptions{Size: 20}).Bounds() + }) + }, 200, 100) + tt.Frame() + tt.Frame() + if r.W != 20 || r.H != 20 { + t.Fatalf("fallback size %v", r) + } + if _, ok := tt.Find("Missing icon: no-such-icon"); !ok { + t.Fatal("fallback not labeled") + } + if n := strings.Count(buf.String(), "mujica: icon missing"); n != 1 { + t.Fatalf("logged %d times: %s", n, buf.String()) + } + if !strings.Contains(buf.String(), "name=no-such-icon") { + t.Fatalf("log lacks name: %s", buf.String()) + } +} + +func TestIconBadOptionsPanic(t *testing.T) { + for name, opts := range map[string]IconOptions{"": {}, "star": {Size: -1}, "x": {Tone: IconTone(99)}} { + func() { + defer func() { + if recover() == nil { + t.Fatalf("%q %+v: no panic", name, opts) + } + }() + tt := ui.NewTester(func(c *ui.Context) { + Use(c, Settings{}) + Icon(c, name, opts) + }, 100, 100) + tt.Frame() + }() + } +} + +func TestIconSnapshot(t *testing.T) { + snapshot(t, "icon", 360, 80, func(c *ui.Context) { + ui.Row(c).Padding(24).Gap(14).AlignItems(ui.Center).Children(func() { + Icon(c, "star", IconOptions{}) + Icon(c, "star", IconOptions{Size: 20, Tone: IconMuted}) + Icon(c, "star", IconOptions{Size: 24, Tone: IconAccent}) + Icon(c, "circle-check", IconOptions{Size: 20, Tone: IconSuccess}) + Icon(c, "triangle-alert", IconOptions{Size: 20, Tone: IconWarning}) + Icon(c, "circle-x", IconOptions{Size: 20, Tone: IconDanger}) + Icon(c, "info", IconOptions{Size: 20, Tone: IconInfo}) + Icon(c, "no-such-icon", IconOptions{Size: 20}) + }) + }) +} diff --git a/image.go b/image.go new file mode 100644 index 0000000..52edd9f --- /dev/null +++ b/image.go @@ -0,0 +1,86 @@ +package mujica + +import ( + "log/slog" + + "github.com/egoist/mygo/ui" + + "mujicaui/theme" +) + +// ImageOptions configure an Image. +type ImageOptions struct { + // Width and Height size the box in DIPs; zero keeps the picture's size, + // or 160×120 for the placeholder, loading and error states. + Width, Height float32 + // Crop covers the box and crops the rest; false fits the picture inside. + Crop bool + // Alt describes the picture for assistive technology. + Alt string + // Loading shows the caller's load in progress. + Loading bool + // Err shows the caller's load failure with a retry button. + Err error +} + +// ImageView is a shown Image and what the user did to it this frame. +type ImageView struct { + Element *ui.Element + // Retry reports a click on the retry button of the error state. + Retry bool +} + +// Image shows a *ui.Bitmap or *ui.SVG. A nil src shows a placeholder. +func Image(c *ui.Context, src ui.ImageSource, opts ImageOptions) ImageView { + if opts.Width < 0 || opts.Height < 0 { + panic("mujica: Image Width and Height must not be negative") + } + if opts.Err == nil && !opts.Loading && src != nil { + e := ui.Image(c, src).Radius(theme.ControlRadius) + if opts.Crop { + e.Fit(ui.Cover) + } + if opts.Width > 0 { + e.Width(opts.Width) + } + if opts.Height > 0 { + e.Height(opts.Height) + } + if opts.Alt != "" { + e.Label(opts.Alt) + } + return ImageView{Element: e} + } + k := Tokens(c) + u := Density(c).Unit() + w, h := opts.Width, opts.Height + if w == 0 { + w = 160 + } + if h == 0 { + h = 120 + } + var v ImageView + frame := ui.Column(c).Size(w, h).Shrink(0).Center().Gap(u*2).Padding(u*2).Clip(). + Radius(theme.ControlRadius).Background(k.SurfaceHover).Border(theme.BorderWidth, k.Border) + v.Element = frame + frame.Children(func() { + switch { + case opts.Err != nil: + frame.Role(ui.RoleGroup).Label(msg(c, "image.error")).Description(opts.Alt).Error(opts.Err.Error()) + Icon(c, "circle-alert", IconOptions{Size: 20, Tone: IconDanger}) + ui.Text(c, msg(c, "image.error")).FontSize(theme.CaptionSize).TextColor(k.Danger).TextAlign(ui.Center) + if Button(c, msg(c, "image.retry"), ButtonOptions{Variant: Secondary}).Clicked() { + v.Retry = true + slog.Debug("mujica: image retry", "alt", opts.Alt) + } + case opts.Loading: + frame.Role(ui.RoleImage).Label(msg(c, "image.loading")).Description(opts.Alt) + Spinner(c, SpinnerOptions{Size: 20, Label: msg(c, "image.loading")}) + default: + frame.Role(ui.RoleImage).Label(msg(c, "image.placeholder")).Description(opts.Alt) + Icon(c, "image", IconOptions{Size: 24, Tone: IconMuted}) + } + }) + return v +} diff --git a/image_test.go b/image_test.go new file mode 100644 index 0000000..395fa69 --- /dev/null +++ b/image_test.go @@ -0,0 +1,113 @@ +package mujica + +import ( + "errors" + "image" + "image/color" + "testing" + + "github.com/egoist/mygo/ui" +) + +func imageTestBitmap() *ui.Bitmap { + img := image.NewRGBA(image.Rect(0, 0, 80, 40)) + for y := range 40 { + for x := range 80 { + img.Set(x, y, color.RGBA{uint8(100 + x), 32, 52, 255}) + } + } + return ui.NewBitmap(img) +} + +func TestImageLoadedSizes(t *testing.T) { + bmp := imageTestBitmap() + var natural, sized ui.Rect + tt := ui.NewTester(func(c *ui.Context) { + Use(c, Settings{}) + ui.Column(c).AlignItems(ui.Start).Children(func() { + natural = Image(c, bmp, ImageOptions{Alt: "Gallery"}).Element.Bounds() + sized = Image(c, bmp, ImageOptions{Width: 50, Height: 50, Crop: true}).Element.Bounds() + }) + }, 300, 300) + tt.Frame() + if natural.W != 80 || natural.H != 40 { + t.Fatalf("natural %v", natural) + } + if sized.W != 50 || sized.H != 50 { + t.Fatalf("sized %v", sized) + } + if _, ok := tt.Find("Gallery"); !ok { + t.Fatal("alt text not exposed") + } +} + +func TestImageStates(t *testing.T) { + for _, tc := range []struct { + name string + src ui.ImageSource + opts ImageOptions + label string + }{ + {"placeholder", nil, ImageOptions{}, "No image"}, + {"loading", imageTestBitmap(), ImageOptions{Loading: true}, "Loading image"}, + {"error", imageTestBitmap(), ImageOptions{Loading: true, Err: errors.New("404")}, "Image failed to load"}, + } { + var r ui.Rect + tt := ui.NewTester(func(c *ui.Context) { + Use(c, Settings{}) + ui.Column(c).AlignItems(ui.Start).Children(func() { + r = Image(c, tc.src, tc.opts).Element.Bounds() + }) + }, 300, 300) + tt.Frame() + if r.W != 160 || r.H != 120 { + t.Errorf("%s: size %v", tc.name, r) + } + if _, ok := tt.Find(tc.label); !ok { + t.Errorf("%s: no %q", tc.name, tc.label) + } + } +} + +func TestImageRetry(t *testing.T) { + retries := 0 + tt := ui.NewTester(func(c *ui.Context) { + Use(c, Settings{}) + if Image(c, nil, ImageOptions{Width: 200, Height: 140, Err: errors.New("timeout")}).Retry { + retries++ + } + }, 300, 300) + if err := tt.Click("Retry"); err != nil { + t.Fatal(err) + } + tt.Key(0, ui.KeyTab) + tt.Key(0, ui.KeyEnter) + if retries != 2 { + t.Fatalf("retries %d, want 2", retries) + } +} + +func TestImageNegativeSizePanics(t *testing.T) { + defer func() { + if recover() == nil { + t.Fatal("no panic") + } + }() + tt := ui.NewTester(func(c *ui.Context) { + Use(c, Settings{}) + Image(c, nil, ImageOptions{Width: -1}) + }, 100, 100) + tt.Frame() +} + +func TestImageSnapshot(t *testing.T) { + bmp := imageTestBitmap() + snapshot(t, "image", 600, 180, func(c *ui.Context) { + ui.Row(c).Padding(20).Gap(12).AlignItems(ui.Start).Children(func() { + Image(c, bmp, ImageOptions{Width: 120, Height: 120, Crop: true, Alt: "Cropped"}) + Image(c, nil, ImageOptions{Width: 120, Height: 120}) + Image(c, nil, ImageOptions{Width: 120, Height: 120, Loading: true}) + Image(c, nil, ImageOptions{Width: 160, Height: 140, Err: errors.New("404")}) + }) + }) +} diff --git a/internal/display.go b/internal/display.go new file mode 100644 index 0000000..f84e2fc --- /dev/null +++ b/internal/display.go @@ -0,0 +1,27 @@ +package internal + +import ( + "math" + "time" + + "github.com/egoist/mygo/ui" +) + +// Fade animates e's background and border colors over d; zero skips it. +func Fade(e *ui.Element, d time.Duration) *ui.Element { + if d > 0 { + e.Transition(ui.ElementTransition{Colors: true, Duration: d}) + } + return e +} + +// Rule draws a 1 DIP horizontal line from x0 to x1 at y, on device pixels. +func Rule(p *ui.Painter, x0, x1, y float32, c ui.Color) { + s := float64(p.Scale()) + px := math.Floor(float64(y) * s) + if int(math.Round(s))%2 == 1 { + px += 0.5 + } + yy := float32(px / s) + p.Line(x0, yy, x1, yy, 1, c) +} diff --git a/kbd.go b/kbd.go new file mode 100644 index 0000000..5e2575d --- /dev/null +++ b/kbd.go @@ -0,0 +1,80 @@ +package mujica + +import ( + "runtime" + "strings" + + "github.com/egoist/mygo/ui" + + "mujicaui/theme" +) + +// Kbd shows a key or key combination as key caps. It registers nothing. +// "Mod" is Command on macOS and Ctrl elsewhere; Ctrl, Alt, Shift, Enter, +// Esc, Tab, Backspace, Delete, Up, Down, Left and Right follow the platform. +func Kbd(c *ui.Context, keys ...string) *ui.Element { + if len(keys) == 0 { + panic("mujica: Kbd needs at least one key") + } + k := Tokens(c) + mac := runtime.GOOS == "darwin" + spoken := make([]string, len(keys)) + for i, key := range keys { + if key == "" { + panic("mujica: Kbd key must not be empty") + } + _, spoken[i] = kbdKey(key, mac) + } + row := ui.Row(c).Gap(2).AlignItems(ui.Center).Shrink(0).Role(ui.RoleText).Label(strings.Join(spoken, "+")) + row.Children(func() { + for i, key := range keys { + if i > 0 && !mac { + ui.Text(c, "+").FontSize(theme.CaptionSize).TextColor(k.TextMuted) + } + face, _ := kbdKey(key, mac) + ui.Box(c).Key(i).Row().Center().MinWidth(20).Height(20).Padding(0, 5). + Radius(theme.ControlRadius).Background(k.Surface).BorderColor(k.ControlBorder). + BorderWidth(1, 1, 2, 1).Children(func() { + ui.Text(c, face).Font(theme.MonoFont).FontSize(theme.CaptionSize).TextColor(k.Text).SingleLine() + }) + } + }) + return row +} + +// kbdNames maps a key to its macOS cap, macOS word and other platforms' cap. +var kbdNames = map[string]struct{ macCap, macWord, other string }{ + "mod": {"⌘", "Command", "Ctrl"}, + "ctrl": {"⌃", "Control", "Ctrl"}, + "alt": {"⌥", "Option", "Alt"}, + "shift": {"⇧", "Shift", "Shift"}, + "enter": {"↩", "Return", "Enter"}, + "esc": {"esc", "Escape", "Esc"}, + "tab": {"⇥", "Tab", "Tab"}, + "backspace": {"⌫", "Delete", "Backspace"}, + "delete": {"⌦", "Forward Delete", "Delete"}, + "up": {"↑", "Up", "↑"}, + "down": {"↓", "Down", "↓"}, + "left": {"←", "Left", "←"}, + "right": {"→", "Right", "→"}, +} + +// kbdKey returns how key shows and how it is read on the platform. +func kbdKey(key string, mac bool) (shown, spoken string) { + n, ok := kbdNames[strings.ToLower(key)] + if !ok { + if len([]rune(key)) == 1 { + key = strings.ToUpper(key) + } + return key, key + } + if mac { + return n.macCap, n.macWord + } + word := n.other + switch strings.ToLower(key) { + case "up", "down", "left", "right": + word = n.macWord + } + return n.other, word +} diff --git a/kbd_test.go b/kbd_test.go new file mode 100644 index 0000000..35ba0e4 --- /dev/null +++ b/kbd_test.go @@ -0,0 +1,80 @@ +package mujica + +import ( + "runtime" + "testing" + + "github.com/egoist/mygo/ui" +) + +func TestKbdKeyPlatforms(t *testing.T) { + for _, tc := range []struct { + key string + mac bool + shown, spoken string + }{ + {"Mod", true, "⌘", "Command"}, + {"Mod", false, "Ctrl", "Ctrl"}, + {"shift", true, "⇧", "Shift"}, + {"Alt", false, "Alt", "Alt"}, + {"Alt", true, "⌥", "Option"}, + {"Up", false, "↑", "Up"}, + {"k", false, "K", "K"}, + {"F5", true, "F5", "F5"}, + } { + shown, spoken := kbdKey(tc.key, tc.mac) + if shown != tc.shown || spoken != tc.spoken { + t.Errorf("%q mac=%v: %q %q", tc.key, tc.mac, shown, spoken) + } + } +} + +func TestKbdShowsPlatformCombo(t *testing.T) { + tt := ui.NewTester(func(c *ui.Context) { + Use(c, Settings{}) + Kbd(c, "Mod", "Shift", "p") + }, 300, 100) + tt.Frame() + spoken, mod := "Ctrl+Shift+P", "Ctrl" + if runtime.GOOS == "darwin" { + spoken, mod = "Command+Shift+P", "⌘" + } + if _, ok := tt.Find(spoken); !ok { + t.Fatalf("not read as %q", spoken) + } + if !tt.HasText(mod) || !tt.HasText("P") { + t.Fatalf("texts %v", tt.Texts()) + } + tt.Key(0, ui.KeyTab) + if tt.Focused(spoken) { + t.Fatal("Kbd took focus") + } +} + +func TestKbdEmptyPanics(t *testing.T) { + for _, keys := range [][]string{nil, {"Mod", ""}} { + func() { + defer func() { + if recover() == nil { + t.Fatalf("%q: no panic", keys) + } + }() + tt := ui.NewTester(func(c *ui.Context) { + Use(c, Settings{}) + Kbd(c, keys...) + }, 100, 100) + tt.Frame() + }() + } +} + +func TestKbdSnapshot(t *testing.T) { + snapshot(t, "kbd", 360, 80, func(c *ui.Context) { + ui.Row(c).Padding(24).Gap(16).AlignItems(ui.Center).Children(func() { + Kbd(c, "Mod", "K") + Kbd(c, "Mod", "Shift", "P") + Kbd(c, "Esc") + Kbd(c, "Alt", "Up") + }) + }) +} diff --git a/link.go b/link.go new file mode 100644 index 0000000..6b47b66 --- /dev/null +++ b/link.go @@ -0,0 +1,81 @@ +package mujica + +import ( + "fmt" + "log/slog" + "net/url" + + "github.com/egoist/mygo/ui" + + "mujicaui/internal" + "mujicaui/theme" +) + +// LinkOptions configure a Link. An empty URL makes an in-app action link. +type LinkOptions struct { + // URL opens in the browser, or goes there in a MyGo router for a path. + URL string + // Error is the caller's open failure, shown below the link. + Error string +} + +// Link is underlined text activated by a click or Enter. Clicked reports +// every activation. A malformed URL is not opened and shows an error. +func Link(c *ui.Context, label string, opts LinkOptions) *ui.Element { + if label == "" { + panic("mujica: Link needs a label") + } + k := Tokens(c) + u := Density(c).Unit() + external := false + problem := opts.Error + target := opts.URL + if opts.URL != "" { + ok := false + external, ok = linkCheck(opts.URL) + if !ok { + external, target = false, "" + if problem == "" { + problem = fmt.Sprintf(msg(c, "link.invalid"), opts.URL) + } + } + } + var l *ui.Element + ui.Column(c).Gap(u).AlignItems(ui.Start).Children(func() { + ui.Row(c).Gap(u).AlignItems(ui.Center).Children(func() { + l = ui.Link(c, label, target).Underline().DecorationThickness(1) + internal.Ring(l, k.Focus, theme.ControlRadius) + color := k.AccentText + if l.Pressed() || l.Hovered() { + color = k.Text + } + l.TextColor(color).DecorationColor(color) + if external { + Icon(c, "external-link", IconOptions{Size: 12, Tone: IconAccent}) + } + }) + if problem != "" { + l.Error(problem) + ui.Text(c, problem).TextColor(k.Danger).FontSize(theme.CaptionSize) + } + }) + if l.Clicked() { + slog.Debug("mujica: link activate", "url", opts.URL, "opened", target != "") + } + return l +} + +// linkCheck reports whether raw has a scheme, and whether it can be opened. +func linkCheck(raw string) (external, ok bool) { + p, err := url.Parse(raw) + if err != nil { + return false, false + } + switch p.Scheme { + case "": + return false, p.Path != "" || p.RawQuery != "" + case "http", "https": + return true, p.Host != "" + } + return true, p.Opaque != "" || p.Host != "" || p.Path != "" +} diff --git a/link_test.go b/link_test.go new file mode 100644 index 0000000..3b85ae4 --- /dev/null +++ b/link_test.go @@ -0,0 +1,95 @@ +package mujica + +import ( + "slices" + "testing" + + "github.com/egoist/mygo/ui" +) + +func TestLinkOpensURL(t *testing.T) { + tt := ui.NewTester(func(c *ui.Context) { + Use(c, Settings{}) + Link(c, "Charter", LinkOptions{URL: "https://example.com/charter"}) + }, 300, 100) + if err := tt.Click("Charter"); err != nil { + t.Fatal(err) + } + tt.Key(0, ui.KeyTab) + tt.Key(0, ui.KeyEnter) + want := []string{"https://example.com/charter", "https://example.com/charter"} + if got := tt.OpenedURLs(); !slices.Equal(got, want) { + t.Fatalf("opened %v", got) + } +} + +func TestLinkAction(t *testing.T) { + n := 0 + tt := ui.NewTester(func(c *ui.Context) { + Use(c, Settings{}) + if Link(c, "Show details", LinkOptions{}).Clicked() { + n++ + } + }, 300, 100) + tt.Key(0, ui.KeyTab) + tt.Key(0, ui.KeyEnter) + if n != 1 || len(tt.OpenedURLs()) != 0 { + t.Fatalf("clicks %d, opened %v", n, tt.OpenedURLs()) + } +} + +func TestLinkInvalidURLIsNotOpened(t *testing.T) { + n := 0 + tt := ui.NewTester(func(c *ui.Context) { + Use(c, Settings{}) + if Link(c, "Broken", LinkOptions{URL: "https://"}).Clicked() { + n++ + } + }, 400, 100) + if err := tt.Click("Broken"); err != nil { + t.Fatal(err) + } + if n != 1 || len(tt.OpenedURLs()) != 0 { + t.Fatalf("clicks %d, opened %v", n, tt.OpenedURLs()) + } + if !tt.HasText("This link cannot be opened: https://") { + t.Fatalf("texts %v", tt.Texts()) + } +} + +func TestLinkCallerError(t *testing.T) { + tt := ui.NewTester(func(c *ui.Context) { + Use(c, Settings{Locale: ZhCN}) + Link(c, "手册", LinkOptions{URL: "https://example.com", Error: "没有可以打开此链接的应用"}) + }, 400, 100) + tt.Frame() + if !tt.HasText("没有可以打开此链接的应用") { + t.Fatalf("texts %v", tt.Texts()) + } +} + +func TestLinkCheck(t *testing.T) { + for raw, want := range map[string][2]bool{ + "https://example.com": {true, true}, + "https://": {true, false}, + "mailto:a@b.c": {true, true}, + "/notes/42": {false, true}, + "?tab=info": {false, true}, + "http://[::1": {false, false}, + } { + ext, ok := linkCheck(raw) + if ext != want[0] || ok != want[1] { + t.Errorf("%q: external %v ok %v", raw, ext, ok) + } + } +} + +func TestLinkSnapshot(t *testing.T) { + snapshot(t, "link", 420, 140, func(c *ui.Context) { + ui.Column(c).Padding(20).Gap(12).AlignItems(ui.Start).Children(func() { + Link(c, "Read the court charter", LinkOptions{URL: "https://example.com"}) + Link(c, "Show the guest list", LinkOptions{}) + Link(c, "Broken link", LinkOptions{URL: "https://"}) + }) + }) +} diff --git a/messages_display.go b/messages_display.go new file mode 100644 index 0000000..b92688a --- /dev/null +++ b/messages_display.go @@ -0,0 +1,16 @@ +package mujica + +func init() { + addMessages(map[string][2]string{ + "editable_text.saving": {"Saving", "正在保存"}, + "icon.missing": {"Missing icon: %s", "缺少图标:%s"}, + "image.placeholder": {"No image", "暂无图片"}, + "image.loading": {"Loading image", "正在加载图片"}, + "image.error": {"Image failed to load", "图片加载失败"}, + "image.retry": {"Retry", "重试"}, + "avatar.unnamed": {"Unnamed", "未命名"}, + "avatar_group.more": {"Show all %d people", "显示全部 %d 人"}, + "tag.remove": {"Remove %s", "移除%s"}, + "link.invalid": {"This link cannot be opened: %s", "无法打开此链接:%s"}, + }) +} diff --git a/tag.go b/tag.go new file mode 100644 index 0000000..3ba3161 --- /dev/null +++ b/tag.go @@ -0,0 +1,109 @@ +package mujica + +import ( + "fmt" + "log/slog" + + "github.com/egoist/mygo/ui" + + "mujicaui/internal" + "mujicaui/theme" +) + +// TagOptions configure a Tag. The zero value is a plain label. +type TagOptions struct { + // Icon names an icon of the icons package shown before the label. + Icon string + // Clickable makes the label a button reported by TagView.Clicked. + Clickable bool + // Closable adds a remove button reported by TagView.Closed. + Closable bool + Disabled bool +} + +// TagView is a shown Tag and what the user did to it this frame. +type TagView struct { + Element *ui.Element + // Clicked reports the label was activated; never set by the remove button. + Clicked bool + // Closed reports the remove button, or Delete or Backspace on the label. + Closed bool +} + +// Tag shows a short label. Clicking the label and removing the tag are +// separate actions with separate focus stops. +func Tag(c *ui.Context, label string, opts TagOptions) TagView { + if label == "" { + panic("mujica: Tag needs a label") + } + k := Tokens(c) + u := Density(c).Unit() + var v TagView + box := ui.Row(c).AlignItems(ui.Center).MinHeight(theme.MinHitSize).Radius(theme.ControlRadius). + Background(k.Surface).Border(theme.BorderWidth, k.Border).Shrink(0).Disabled(opts.Disabled) + v.Element = box + box.Children(func() { + var main *ui.Element + if opts.Clickable { + main = ui.ButtonBase(c).Label(label) + internal.Ring(main, k.Focus, theme.ControlRadius) + } else { + main = ui.Row(c).AlignItems(ui.Center) + } + main.Gap(u*1.5).Padding(0, u*2).FillHeight().Radius(theme.ControlRadius).TextColor(k.Text) + if opts.Clickable && !opts.Disabled { + if main.Pressed() { + main.Background(k.Selection) + } else if main.Hovered() { + main.Background(k.SurfaceHover) + } + internal.Fade(main, Motion(c, theme.StateDuration)) + } + if opts.Closable { + main.Padding(0, u, 0, u*2) + } + main.Children(func() { + if opts.Icon != "" { + Icon(c, opts.Icon, IconOptions{Size: 14}) + } + ui.Text(c, label).FontSize(theme.BodySize - 1).SingleLine() + }) + if opts.Clickable && main.Clicked() { + v.Clicked = true + } + if opts.Closable { + if opts.Clickable && !opts.Disabled && (main.Shortcut(0, ui.KeyDelete) || main.Shortcut(0, ui.KeyBackspace)) { + v.Closed = true + } + if tagClose(c, k, label).Clicked() { + v.Closed = true + } + } + }) + if v.Clicked { + slog.Debug("mujica: tag click", "label", label) + } + if v.Closed { + slog.Debug("mujica: tag close", "label", label) + } + return v +} + +func tagClose(c *ui.Context, k theme.Tokens, label string) *ui.Element { + b := ui.ButtonBase(c).Size(theme.MinHitSize, theme.MinHitSize). + Radius(theme.ControlRadius).Label(fmt.Sprintf(msg(c, "tag.remove"), label)) + fg := k.TextMuted + if b.Pressed() { + b.Background(k.Selection) + fg = k.Text + } else if b.Hovered() { + b.Background(k.SurfaceHover) + fg = k.Text + } + internal.Ring(b, k.Focus, theme.ControlRadius) + internal.Fade(b, Motion(c, theme.StateDuration)) + b.TextColor(fg).Children(func() { + Icon(c, "x", IconOptions{Size: 12}) + }) + return b +} diff --git a/tag_test.go b/tag_test.go new file mode 100644 index 0000000..f2b76d0 --- /dev/null +++ b/tag_test.go @@ -0,0 +1,92 @@ +package mujica + +import ( + "testing" + + "github.com/egoist/mygo/ui" +) + +type tagCounts struct{ clicked, closed int } + +func tagTester(opts TagOptions, n *tagCounts) *ui.Tester { + return ui.NewTester(func(c *ui.Context) { + Use(c, Settings{}) + ui.Row(c).AlignItems(ui.Start).Children(func() { + v := Tag(c, "Velvet", opts) + if v.Clicked { + n.clicked++ + } + if v.Closed { + n.closed++ + } + }) + }, 300, 100) +} + +func TestTagClickAndCloseAreSeparate(t *testing.T) { + var n tagCounts + tt := tagTester(TagOptions{Clickable: true, Closable: true, Icon: "star"}, &n) + if err := tt.Click("Remove Velvet"); err != nil { + t.Fatal(err) + } + if n != (tagCounts{0, 1}) { + t.Fatalf("after close click %+v", n) + } + if err := tt.Click("Velvet"); err != nil { + t.Fatal(err) + } + if n != (tagCounts{1, 1}) { + t.Fatalf("after label click %+v", n) + } +} + +func TestTagKeyboard(t *testing.T) { + var n tagCounts + tt := tagTester(TagOptions{Clickable: true, Closable: true}, &n) + tt.Key(0, ui.KeyTab) + tt.Key(0, ui.KeyEnter) + tt.Key(0, ui.KeyDelete) + tt.Key(0, ui.KeyTab) + if !tt.Focused("Remove Velvet") { + t.Fatal("remove button not second focus stop") + } + tt.Key(0, ui.KeySpace) + if n != (tagCounts{1, 2}) { + t.Fatalf("counts %+v", n) + } +} + +func TestTagPlainTakesNoFocus(t *testing.T) { + var n tagCounts + tt := tagTester(TagOptions{}, &n) + _ = tt.Click("Velvet") + tt.Key(0, ui.KeyTab) + tt.Key(0, ui.KeyEnter) + if n != (tagCounts{}) || tt.Focused("Velvet") { + t.Fatalf("plain tag reacted: %+v", n) + } +} + +func TestTagDisabled(t *testing.T) { + var n tagCounts + tt := tagTester(TagOptions{Clickable: true, Closable: true, Disabled: true}, &n) + _ = tt.Click("Velvet") + _ = tt.Click("Remove Velvet") + tt.Key(0, ui.KeyTab) + tt.Key(0, ui.KeyEnter) + tt.Key(0, ui.KeyDelete) + if n != (tagCounts{}) { + t.Fatalf("disabled tag reacted: %+v", n) + } +} + +func TestTagSnapshot(t *testing.T) { + snapshot(t, "tag", 480, 80, func(c *ui.Context) { + ui.Row(c).Padding(24).Gap(8).AlignItems(ui.Center).Children(func() { + Tag(c, "Baroque", TagOptions{}) + Tag(c, "Favorite", TagOptions{Icon: "star", Clickable: true}) + Tag(c, "Archive", TagOptions{Closable: true}) + Tag(c, "Locked", TagOptions{Icon: "lock", Closable: true, Disabled: true}) + }) + }) +} diff --git a/testdata/snapshots/darwin/avatar-dark.png b/testdata/snapshots/darwin/avatar-dark.png new file mode 100644 index 0000000000000000000000000000000000000000..07be84f8b2ee0e452ae6c71c1be5630c5377453a GIT binary patch literal 6314 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 literal 0 HcmV?d00001 diff --git a/testdata/snapshots/darwin/avatar-light.png b/testdata/snapshots/darwin/avatar-light.png new file mode 100644 index 0000000000000000000000000000000000000000..588269918ffca0e05655bb45b9bbbc05153d118a GIT binary patch literal 6194 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 literal 0 HcmV?d00001 diff --git a/testdata/snapshots/darwin/avatar_group-dark.png b/testdata/snapshots/darwin/avatar_group-dark.png new file mode 100644 index 0000000000000000000000000000000000000000..14b0a7849a35501270a4c01b8bf7e3be736e6977 GIT binary patch literal 7889 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 literal 0 HcmV?d00001 diff --git a/testdata/snapshots/darwin/badge-dark.png b/testdata/snapshots/darwin/badge-dark.png new file mode 100644 index 0000000000000000000000000000000000000000..d97c0e4ec198fc0e53f810f41e37d06ed5ce1b63 GIT binary patch literal 6189 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 F{{qQA)pY;> literal 0 HcmV?d00001 diff --git a/testdata/snapshots/darwin/badge-light.png b/testdata/snapshots/darwin/badge-light.png new file mode 100644 index 0000000000000000000000000000000000000000..1b5c0296c32d5c2ed5c7a4d3746a45cd480196b3 GIT binary patch literal 6239 zcmeI1Wl$7eyTBJj=`IQBkPamj5a~`qSa9iPDUp(rmX?-W5di^V=?(?SrE^)jyOw5Q z_wxSV_ukL<D#Qfz1ONblSWQ(?9{|7_yq9h8u?L;4zn)qMU(m&VDYw@f)KX%*aADIm>I~hd?StJ@>9q>cSWeP_vWDp23}0&2)YJ zE}REcG#tGD-MYB`O##C7CK7EB>r!2FCMG0Y5nfOMSu? z>Q&;1%+|c;d(A%XqregWLryPZTLAw3=ojl;!3yR7KgYhz10V$x3oGlwxqc3;>_vKh zyFOUI6qvR0_)+~lnKGHA+UL7~)+psi#c}64k4a3)Y{>sZOZczVcZ+Gk!3AYML#Vy~ zinN|hdEM;3)Y5wh_!=zF2v%IdrV@Pp?_0UW&L3B$NXF=Lw0r!46w1*f|w*6HN8~Tr!=wi^Eg)I(sz-X z#Jh14Gv^xg&s>K}m)B9Lw9rX=^U!#_>VI=7X=BRbJSoQT!6dsod<`3mhbCqLU;fh? z$1(smMr2*+7+V5_&Xn@xKlXOwmRH4ZlNQb)8<6gP58d2-3ICrckt$5v)o)KamCdRq zxiY!Raqxr-2t;CG7vSYk=msDFTBd{3c5rV6*N`Br6OVIXlVPuDXitLr?5)|lHK`f= zm>R6|Md=^fj9Bjta%Nz_i%V9&<7Vygh@;zZo0l++9%GH zCIMLJ{wNVZdAhY9EU?5}VH3u*YsLcUq*g%!sUGfTC)_4>?z zd_k#D)AMc0c#tbS|C;zYqy5$oGlDK*fO{xa%wN|$Q3)S9u)FGuLZx0$&kBQd9x z4FfqnF4r_qVo@$9LqWJ1hIrS>DQveU3rIF3avdU67e{GOn2;p?j1xeRZjEbfYV zMHXp^olJl{$(<41Khb!RpiqCGlh2lF)a;eh&lY`}a@Zol&B3>mdik8ryP zek{oa(xjBPy~8F2jThpswK%xP&T>$+czG$CXX|oQebuU*Y`d=-E&5D=Mj-SDT+fJi zz@o%|Dt&`EA($-vU18q}r85k9uL70pYBO$>eqL{ce=eg|H{LaSn=)%B5{+|M`Q43! zqH&I&tE``hkId%Cu1}ngekEU{`OG4KmUvk7dF0aAU)mg&TmmazF{jMh#EuG=A@j}h zNl6c8eNc-0>S9V7WAXaRlW)%Yzx6Nmoz6r{8q1!2v=L>xJMf1YP!2sL7(d#WiD};8jobJl;xLpl{OGrC zjW)FW&4u0Z9G#D~Ua4E0ZPRV2Rk^lL@Tv>cQ-sLV$u{1SE_olFvf#D4ye+4Iixcsi zKt(B;qwD2KLKV>hMU+=LImgDN4tZDQj}+i{uGj-7r8Y%HVtm3+UKdJ8o-%Fz0-z=r zmLueft6b2zcQLZsnT@o>sN=Od8xo(GM|*_hAr-8Wze|^m)uk#~UTy%{T{$73_XBSA zepM7t?t+wP_QLs&35`tKS^7ia*1ZE<5)3Mf6$F`XrEs@byWg%hNy@qNXI$*mpK0B~ zlsK0p>;p?j<1zjMny~ph1UNE8kwKhWqZ|b+8M(^N6zd1dBXBI=X<_&w(Di zugoCfc6W#hRIvVjq!TyNMu$+R+yn#>3*6fJYq zBE~?^px$AQTfSs{TUNsP)NdxFYPP7tq}hIBX)jjY9*W7Ha(G$o&AvaT&B60L`$J`T z=R+UQ#&2P9^a~+k45aN1JInsr!$((i0E_9JT&9hST?w!*GWL{#ʭ`eZzLn&!qm;vYj0j5RN7@{T z>ePpg(d;$31%Y4Sc*+Jwtu;=VA4bLpep`k+0DdQ(V|ZSA4BOR9pLb_jK4WV9cvU;9 z6>za9lW|c@U1+OZ9QQ>0G$dyM*W}};lCnI~gjlri09%G44%XKfGKImIARVDB6k@rN#mKrKZa#56b@#^G|`oey2AtMl^m zl|q7ot|wFRqevi?#=jB=gKkdao$v0}-7Sn9|KJ7(zF*bMY(W*Hyurpd%Eqrd!hppR zM@x0K*G_Hym98^05r!K{fAaY@`C|^5v80-GV@gBA<}gK#=|XAOoHDoi`;EQrA20O) zzoQrS=$&Eun}wSYjF>fs%Vq7n@=x?Lh@biTW+_f$8TGlF%Zo44R?B1iY;L6er=;9m zf5V4RbKZd8^$&nvya&#nDT!ODV5zTOZE_5hOvf45lnS>t!)?m`)-SR;8s6Gl8oSSb zaem>4TDmJXQq?{C@dJ2N@dJUoY9esK>QDO`uq-+I(OGf59}V@ZmY`;&m9arK!aDMF zsnWWdUwvuHyo`PD=)^n-^HUh`NyoTjT74Jr z8Q$S8(9Li*)URK9*;mk@ty#}ayoZz?@pB@l`HTVb)oUe3b9ZHLr*6N76o;yArBI@E zUdH@x|3NWZrA&S68_f9`epUl=@6#wNZbO^G9RHim4r0J(tt=Tfzl!Rj?P38wwn)w) zV0}}5{aFT7-?F1UW2)!rS@yFdch3`p3#7V(5$2@IbFz*`J+gm$t z%BjYrT#FuLU=wc4kGx1Helj4n`Mya)Es0%)D2#mp->1(>7uisAB z>A-F&!}Hy3^ok>GjXROs_?|k{h?oFBO6FbR`YcT2y^g^qq|k*1VJ9M97RIiEl!b(> zyb2))s?=8zS=VI62r*K&_!(^4BR!ll4sfsl(qb-WKexZ|DovzAMCsDw8Q`P<_ zE2)Y$x?(a-9(CWpM%f{_=T1o>Nds-Eg=wDC#k?BO6K7ZM5o;W#Oe&!?f9hQ6;0bc? zq+F?Btlla)y$3Nd*Eagm>pi6>u|ggBGYnkCaXvm7ot_x7tRQp!-3(^t;o(Y=v-L6S zJyY?o*M1*yZ~7htu6N;*AU}BeTyp@fHt(+O3Iw?<(-$NGuw&Uwvv$_U+P(vRiyoi$ z?zHO#IVg~QlnuAAI&@@*ogj2<=P8Uhh0p+_fmLKqp)R+WBaAxh?-9%g5rj-Op;fv)VnP!a$77 zvKG2m;}BWq7yenmVo2q>T8}bqF{D_rvs*Uc#$uOHIo|b*^4a`d06~Hs06-FWb^Yg3 ze9DYRT-?!gX7$5)so%Cs?X^cB4RTfv%HU$4r16;Tg+Qy3m@7;H}l%EOpT`mKX_tBlU&*Qdm!Fi zL<#5KmvQ^Fywn=cBRBD@PT16a1!Z+O95OokJs6cFPfj(zq%K_ltjI_OsSCBFnt6zY z<(Cg+b&hU}boGvi?A1x2*|9~m$qbcdUwQ?q8b~iorrQ>%XpK3i4(zfKIbY7eJaXPV zXT_gC{KZ(+*vFFa!2D3w$fV$gRbXaLVSK3r#3tP9!Ljl;Blf%Ea~DUWDJvYMCcpk5 z(4F2*kqhoLcvM1M3wZwhw^pZp92il-3tc4@cY_M?M##L3DH5RK#BOj_zJDRD_pfq{ zv64HDKETHc!?g)G57W+X2GRO%pIwP%#EidM$h%#={T#m1!*zh3f=tpPm*J;3h~AlY z^xuxf+Dw7jB}{Uir|dq~*Z3SMM%LQJj?dUmwc?hj!OZ`ht5PbX-iHEo9F;mlqh9$gU@UQaU2Usp)?)pDzTpK{LK=^lG8!B5Ud{ z=I@hg!|ghb$}`Ot6}sMF5&+Wzdv^Dpu>FqM{XbY1(*hoo<=MK#$ei zgzES3%|1BMpoufD2ITpg&N`%Ak$P`@fYKE?KlN-*kqNxMD$_C?mU-1z7Q9^J|0=RD zFHR>qJWnntK26L8!j(wb=!0Q#0fh~Ld&RczG)pxIZIFP@5HM!guFmB8$)SM}2`%is zM=^+uj&n zSB|-wXiYPRtuF}4kBe*U9pJt8Z#bqol^nJijVbg_M>6aU__!IjZkK~9zcUD!v|!e3 z6Cd521;i(aW@5O*%`Xj<@OGq4LcydKbsr~lYc2| zN4}GQ@6+7tLiL<~;3UO=wEM7Myhh2;Y$3f;fQh)Q^ruHor4Prav3v;o@)tIkJ1wrsofM>bZt4m6Dczh}#uL@c$X|HwXNtGH??sGLkw zRfi{4mixBtJhl1?>cj>N2o~Zh{r#hV8H0Z+n>NQs`k?C*s1xaN&6g&!qCNjanT1AA z-eYU)!@0hl^D|B=_D*y{_=fc>w_hfY68$;#U-M{G&}B0&n6fdGQDD}IpkDyA@kfpK zW&it=8q^y2XzR%Hvrl8szJhbp6swE)t_w{~%b+RChqE%a?>N2hYqGMu%nq5XLddqB ztSxF+pTCU~BaXShYt_UG>M*B0USD(I5>kpLcuzu?Jw@T;7NUELnQej0DDOT6EbrXz z{&1YIu?D@9BNqx~lK$ySE$w@pc)n{1ja^=qqXCk?qpJHgUNpp&sbQXu3#i zNb%Lt0^-Hc%EZj&UAx4{%Hp7gKRPA`Jpo~oxmxY_AzegWoo-~p4cX!gP)oPIB7@B; zxI=f6#&>J=c7jGv&`8uqhPe#-=41pCcy-aXNRKH#61(W>ihZit#d6bN52aGd+)6>~ zHP9ZpRll4TgqyVd9zQgFhgdkQMdW74v?h}VU!l*I$kM2FKK9WRUBT2IGf zm%wh0K3m5N4TnKUbT;#^nyq7KLa$r#R4tg<)lSeuSeiKHr_a&bB1YDhCpHme646Si zy|WqT0C^8FmD$-^#x^8k&!aSM!H%w_KkpaWYGjpU-$5?bh9nFn6y%-ThYZq7Nc86p zUudpucooM|_>PYhp;F@{-YN+hvY#olSD6LsC6G$_kRUMKc=$G~guuQ-it2dfwMu`+ z@IM=~%v0_N%@!jJiJQB55>@lgHQjoC( z5rC<^3VfSdz#+4}=x3PmjRb^^YfjS67%+9X>@3zBW?%5fR;0g^CAwg9)l<~6+Bini zZE(}pPz&~6KNTpJ-Gdr>i!#PCg<${djC~Ro)TOlXD|)T!Ur&*x{OKsmu(qdhtCHH6 z*+aEcq8^nC(GL!=?+FDt(cZEr+YO8&=ywS+_}*2x9}H3pUUz8r$^UC7{(pS?e-V85 hzl(+c8ygHGz}!d+{IlAO=l(|`vz^UCQSUGHDu;}+kqmzxwR0{l)QtKU&IxJ>9 zY(1Rus>9RrG3*3yC_fr*;OCE6`{o3=0&oJ~N&a9MAyvaeUculz{y%xD?`b^HKi)~} zdg(t?tS~({H#IdiKQ)#7Qko9}ff`Qh@;y)f2IjoJF0w$nOG|&>;9j5H*qH2~8*XcD z?T2%}XvlYeS4to~KcC#8+K+pRb(pbz7Vqe=GP>V}7k~*YU}cr@LXIT?P8*7pY+72H zNt0tiY3auPA6J*diMl3x8=JC{k}_Xqzp8zIxv2Ld7lw0(e|C0vpPdSRm6o!37&5i+ z^4V#TMNk%wXYm}>;sN$JGKT;|Db^R7FsX6cqs9x5R&j;i2_K2i&M#1Fo!9nH0kMSj$%JOmoIjZ_NRdsie8Uq&>*Zii7GseyiUN-Ls&*wt~czD0eBOD%2 z@j4uB=I7fVvh3{ay5{T?A2oc+xnnupJ_`C$KmzVNvlZtXRV8ZXVn>wtXUQP%* zlfizF#V=P5PQ>QikxET4=Su%FbaK2ZG3Grd1{oP~ntDK{?bS6js+B9;_MSwjOC z|1MVSgBBCM>q4yZVMt{~#pWX!%er*wh{%gKJ)1UL%+8Z$aZP~g*cdY;DWd@1cTu@qTl| z!q}mxxcCvKjhn+t$G{--a?BDvYg91*9KY0w1WsQHPxRXpwzWx+%`xR}W#L_c^p>v#49BdOid7xpHy-f@g^@@$#GvoUt6=2oZhoZp{!CxyU&fG5X}Bm|K6;)F>(L z9wZ$y+3cKOQ?6_>fLl2{eE0%w7Mi!DB63u2ZfVVYJhxq`RQuEBIqz4WpeKQ{+-#wA zwMVP?_p_ZrxSbZ4`^7f}1@xz86`mOm4G^-eop0EZ+nv#NFLKqUWazklb7s1St1D!H zreBhmwkhgdCZqAB2)%=8bc{k|k<+DDIrAHNG6t3?0G@9UI&2*sqi|)9&nvSY&I3?GQ=R-3W zSsU-xJfns{*1^%?J#=?HqAavFm|D@~g?B&^d!?c~0nFLnLH(CEQC9?bpN_8|I>s%sEK z(qlMYziMQ1@^@|RgWC!pre_mZk}-pd;E(>nl*Z`r#~JS2C07#EO3DMBWTr?6)XGeUHKiUs(<^5TM4 z>uNa%uw~nP3y+n4eF?;7YprRjqi3w@MJ_~arYk>`h8W-SxseOgQwXMJE>ncW_WJw4 zGG-1ISmO)uBh!x#NvFcTd)reb44H+7_EEvGL{CmC-vGZRvbm@YI%*GlJe?`MFA9v$ zr!ga@8{3|IzJi<9@y+b)N~)>`_E51z$-BxQu2u8eD%viH^q!n|7LIoCFkyBeXH(3+ z9UI9Iq^%OLjR+Ra1Gz}&TiWg2lS-Ag*8o>)52+S-e&!2uv&S>&R5g~$0=EkvalwW` zwY9RUs=-5M38)4IeEg8|(5%Krd=I=$8*3BITpj@-JLRwE>)WMuIFaH^$;!sJ&zJ<+ z!uHaNGS^xBf#0EpA=^bX8!f1jsC%6b=H^q4U+AeD-FS&H?4QTG^^k@0y5Yet;AD@) zk0he|u{PM4H8g{JsPEDGZ98xU=>KB9C9GL?m7z*--DBO7rYD{7fG}qB=9L#pJYAo2 zFlsVUdo$5C{r+8@DsCW^1@~VQ$zOU-aDkN#E-k4T8e&Sh2t3`B!luoQtt%UIo11?n zb8+R8Vf(p$`Cg|QKs=?cqT+ust4%!BK<*~xD_UDT@cW3zAAG&6pO^00{cEujKI{6* zt-RbeE9>Dj8p3Oxhw1LX$a=7UV_>H4ZZ8*=+Sn0^Ra;k=o7;9~z~ud8Eh2VYr>!3Vqqvbwrlc8a*y69~LQlLxV>=Mfn6^_fG|2j?qk zM~?YgLLur_MQds>2I_Y|tQxqKvP(js^-zJHk^(tJ8U6FFhrCS{7qa{&3nyyotG~I& z0=KzdUajGd+ENLMO!%yZH8V1K2^YJze`MvXPNZOexJR&+K0uDurPev@xrnd7*0gjZX#M8l3?F zU~|W@;;|1(!~$o#-EcbC#B(+_WRiAka*t(rFHP^HNPhSed_OcyD?Q1X?3fLv!!ct| zlavh0x`N9mTFZPl<-?v9?eWem{q&8CZW`}qnC^Ce;R<-}Ie;spN8{0i%{ptQaVuJ1 z{1GaqA)B81-g6q6)AW{g6y-ZPXPL!roN8+XBc0G-!k0QehOGwPg%}PJ3iY9< zlqa+LtM0@OA#e5X6P4Kgb>T{aHP(O1H<(`ds&4Q1Yzkwjx0v%GYwLt*!HR>C&^v`?!x1XvaoFncqL? z8_*B#*i6S}Sv$7?`FdTNoF{4 z5~6duk6xge5&p)W#ZbxzP-!+eb_Kf-s5^HW^g~aun-Rok&?z{ zIRbmNzDHNZ+E&rQe>kb0vSw{FbA{!}Ez?_suC%}3ayA(2B1Fl&#>UT{hRMwf1Mp92>H@xkP zbbH!uU8tk0n~|YAC+$lGlx(Ct!WA2WO@b*m^%B)N-e8VpV($j$eQ zX@6D))CWU8o7>ZT9kDjky!(!gbGjjhE0y%bAzKV_W+J2#d4yG&kM?7{^muPpj-Wlu z@i-1MOOp&;eN&UQ62Hl~gN4PR@HeB!;H!$pl<%qB2GL47)!z_6zgt^c%mGXqk_(o| z+m(vj_VluNNs{tO;Pmrr-{@(ZO^Ds6%bO1Evxp((s>s(|o1N`#w};EfOlW=i%+$~k z-psZ4qEP{@b{247NbNGRa^htf$2iuOTuC)?usA&4nUZ0QH8Mk@1h0}VUgrW9007nF zh8x(0*r$Ai`dk#`9UDk@R|7{Wx}2Y;C6Hj zn!8&)(OnR*@sL^h+iSuW?u}VfTRXY#WJ5NOhmYUXc~Ux1TT??UI5^h}q2sCgM9b4z zC{Y0r1QRxdAWJ{>4P#>m?%p1Bw)cdDnHLwsB6XJ$?SvGLR#uavlvFDRRZqd&+0D6Bw5;K47Jr}DqkTjzy3IAn1?i%a4Kz4>5>SgzFpHVr`?0q)lGN~vObQiW{dol=?>9+toKVL3lx zC-7v9p`#CtbaHoLZsh(-$qsPPhM4$^B^ro~cP#Z;-=z13w3QqO(_&kUM>^x2Mz9&Wy_ zS9jm_NC}DWHeHE?ACCOfe4YByye)KW&O`%HLc1O9{<1wrUtsK7#rMiZ$LMg67#9^7 zraw=tlr4F0>J0Rro6uHmFrQ_M6sXOk62|R4jhcP(=)9(-9niLEu#hf&dYG)CF+Q@= zf7hjdA?lspBk$8UshlJJU_5VGWw*F}+*i596T*x!b##TN&b5{^+|~Jhw7*L$TS32R za_zR=FBRSln1P?mbjlj3wY9A!1OmGs$=JJK!At(-ck$QIh1Gz*!`XEW3?61+c=qKb zz1$r{!lQNZudFD|Sou)*<@c1;<0@2LUAOZ)M?h@Ga>nU#e418pMoe6x5wJZH?%YUg zIXPiS`mqM_y}MJgX;@0`Tgps3>HHHePlLS7A4@F4^)V#_y+WhB!HqHU76lI6B`$bIO7BV*DB>!HYVHi))-QPd2q zSd(TJnXoEltzK=+IT@aZvv;oVesfMVEM|<%aIux2ykp(Fw7Q+rdvW@1C;n|Nq$FZ2 z?ci-L;J4fy`vH)v*T5}z_(QhelDHazigrH-bmBRW)I6hwMa|e9mb4t-q;OOBN-pql zs;ezXhK9Gd+M`B_Z-#}L6;OLdde^?B$>k~=EZ;cZcx+V!WLgMp5~wS?5ZRNhQvQh$H|1&I6Y3`CiX}OzJ0ZUO!CWKdHmg)=Vbr`-9 zoZK{Z=QXpjYBVC=?BMotqKUP=RxtDXYxv8H)P-eQKF^H=#p3ePtu2d$n?`AKe|BcHYcWA6Xf2)TJb*OeZfrC{9L)LHPY=QD)-{9SsVJtyA28IO%8`Znie zA9m7(rgH5aY7!w^s9gFB2*VUq6lc#4Ln+BOF=M%1p7v-&8KI^Id7^e&Uts;izDi0V z$^bC2VvaJeC5QU0LP#%d?t&W1HBY>JEVF*sB(u`dcbfB8c8Qc+d_hp;4^ftIS9rRR zZX9TP2&2gxZs+=#xRo!mlO@pZhl3w%OpPJABx}y@oYOlIPI-v8T&pFLIXtA7Gi=8G zgzt2xpupdrZDLX#gJok!&eDlDQc7Ym4#T;Yr+&)|d!t~n8#UHXJGnMtCm^)haZ`En z_9Vf}bwyjgp9u-+sjU3)P_ycr&CBX-`OzJodV{+AwZ{KxwYu#zSzf3@-^*Aq z6|4PA@qHF~h!ebZsEM@mBlq7TeIX%ucCW;0_uJKq=KY9uxR=TGKknsTr@l{-2czss zl5?r`VU9$|!hI%7K{bx^JzU()xSVeg?&JzoY6@O-oDW~N-BHQV62tN3MZb*`Px z%6PmbfIjS#Rx_`_VvW|}Iq6WU!o_TvLb49%ILk$9mE4$1<^K&DXT=nmbJ zQQ^xsAWQ;F%q1jiRoeT^>tjBn{n-Oqqy8#kK zJQ%6k`^EMU@7CQTihZGY(rWhaD{UOdP@d&W(5i$nWCmh9CCbmZ1F4Mvt}a0zkB= zgD=}dn4dmMi?>kcr65X;YoWWpBmMlseENjCj+E#5uNs#l1xkD~i~ zmtIUXl>EL27ZFZ2k2&{g z@(@zhyLVDd=s@;~Uhq8T2GqCM4+5st7)1yf5mvT)-)hduW}P5&I6eIEqWLkVDN&Bm z$7IkynoaWi(Tg7^tvP3A6wK*`Etcblx2boUD4OMfsK|omCC_uCB$X{3bqcz{b_m!F zpp6JG+BfS{`5Lcn4>Y>@1+*-AphvK1Ks;+`s!P0*ixI*5fSTAfd2~(5Fc$m; z$p37#Z$si>9WPg2Cb(B2wp-(5Y_@M>%j4ZlVY(lfuw>tK3*Y(Nk^B|<(<894k)?Fe zBcLGVp+)FZiqEb+Xhs$#k3X#?ZF^;k>3YMt!8gexuDm zw;5CtvFQ*nzcgRCRQ5Z_>Z^14kHYyvkb2FMjxKY9mNPt$_-B+LiaSqG2_5*OF(%eq zP4nB`Q#MC5UGE=5TKo}Jly!(X(z&Zj8in#)YazzNn#88r>H=qAD{nF{HQH4*xbTfO+0~>{=Ab1wBKddfx z-`by{cHF4sRLRmRGnLzW1pLDIz z1)lYIjIy%Q5BrO8PX=^Ajr`&sulc%6R$5+0T3#FLlYpD{$CLl+yKoeQiKZYT-UTpu z#!Jt_I*k@V%bt;lqCMg0t0XlNy1G$+7_wp~?kwx+tSVJc4Wug{`C7C!9oMl73#RA) zNR&r6havCf0m^&4UJ6I$1FKdn<^i|)+E^EhnJGCD>LP?knCLOKd}f7@!wJ=|#aciD zU`P*z6@U!PQ2lVKT|0+zILyf4j4H(76%9Rk2eS}v&p?hR5%+L(Fl#cO*>h_RN)K?P!I-uXDw>;CikvaF>GbY- zXGfjK5!$uzaTEe6i7QX6s|${e^{}SzENR}}1qrZg6Ol?05)uX@dSBJRX)Z?*1j2zkqUhaRF>+B_-}wR`m4rmUjGNVwxWwyoqC<^v&xU z>Kn%U3_o#jo!^iS&YICs70sd?o!8$IOe+Bimqk|l49cESAY7YSbY5GOmy-Q<|~SVAb=QLL+7Np9;CESLePDCBBWR6 z@9N<2N;G1iZV|Vpud53Klfhk>jatwyRF{lk<%`xGqC}Z-Y=E@Iy!*;CyNadvTk?s& zCuzGOlil7BLta#g}ZEPZiYr7QDBlq2k; zU4FW*{M}b>Ymd>WG`5jHV#D zXEa-RZ@3aPh(RF`0W)~N=#E01EyW15NdOUhhSlnG+BM8Y6pl_(ke&&`o9*lE*Vaj8 zHsHj(rROw$Dv9q9+hLKFo1p4xFDoNdRW%LySy*`giypUf?XEmL2CqS_qO1~BT8ges zith^h4g?od^7uKfCAO%4e%R50%V6OqaK@@X-p%9XRm#zx3TXyq%&Vxoe+`d)A$3P1 z@7*D^TfO7!NDze=d`wSE8`S@0sO-D7n?=NP8p483#VTzfm+&I5xz`b%ZyNPOE^#w% z-8MfJ!dn?noH{Xzyg##@04CPLx(DC#5rQAa2AtM@0?gJ$bJg!!RCDuPa3*Byyf>V-+oF z)c77pVK+1Xb`5)#3f60a{J|FfpJWEEG}cr;dkYRpd+%Bk1@m4~Bt9*^R&!^`F+88! z&1#-&(90yLi4sobKL{>Obdn=yS@_6P^_eE|tFESe(Jo|)vOkuT+|F(Z`+O!}stViV zP<}#t=RLbY&k4sbfBN!$WD93}*F{PC^(#_*Ou*w9O$lJZ>wmCXZKkq^O zn%TSP=EWPbvSQa?e~Tq*VL`*Lt$SeVZ(y)7wN*UiXgJn{fQBYNXFyNHnAq^_vX13I zE~Gy$y=L4jkt$Dfq!uRH_HnjI<$Hf!gNA%)(aJ@>n)2@_-H)bEzHZK8U;YvxSs7Mf z>hlKN0E6i1(GZLuvKYr8$fkH)5OK4gfW=go5w;c=Smyl$=}tls{-;rO}up0 z0Uo}`+x#$$4-rsHTWC5~f*XBl%5%Tp?1JmBH3()few7AwI{$4S$-iv!B*{1&YYBY% z+s3aa?0UpYaK}Or@9OJ_$|eH&+#W8C;#pli=k4`*c3cjh*jsJww8U3B+m6F#6d7Yn zb+)K2VveTv_fVb<4$C%?`+%*%%??n;g0{By?Ch*0nGn~2$eWxHAPJOSaIS=hVUkTB z94sgnm6yxEJ5!rReM^2y4!=7_ZNnSVP2*nV`B(+UB;DQ0pFB*Glb4US;#?ZU{!u|! z(_m@!`Pi`)5E~7rq}Osw2~1AbWIAs(ml{SxP>}~bj_-a{$bQ}HcuUS~5iX=E-6{HT z4U+BjU`Iz)z3lW;ky_N!m4w|Ll4#O0xPN<)7j^JAX7yRUvQ{mzi33nIGSM=Ag86PD ztHnm*Q^%h=_al#+TOSO)p|(ag-?@5B+AW7oHvcdB?br)H2f0xWg|onB^=lD7sb9VE zBh2t$AxgXa=-7Stb(?BbnY*wgv)%%TaH4bp^Yk@QN71m!pnf;Pt2)GQ zHzb=}aJSgW?$?s%`6>#C`BIp+K*FJl06Gaplf&Kl+S{J;0^uJdPU3!RtdEew-Shm5w2^D~pWaUPHix%u$?rv`B>Ec-)4|E)yv!~{3iBu9_jDeMj zOUWb_gMUt#W~Z#o&B|g0k)^!J`Z zTtZk_7zo7m!55&7UKip;RaJ~QT;;oruXo4vK9ogxP4v-{Mf1z%PeGXG!HNCKaf`@r zt_^xf*9SYnzV1Epz@-UCD_xET#rH+ohv3`rV7enTMa$6(0jAVsyGJ0Bjbz-_!2M5b)czmqnLe_BJd5~P zlFn@>{XM4@==%A&TT!$d17QV$z%EJ$%S`plQKd9NiZ_2_>|E<;vuz*fq(K$WnO>#g z_iKI=Mk_p2QRR0{6d|9@4AG2b{AsCpM1PID)pgEzbtsLWw@@Ok5@rq{1kaJBUF6QZ9ngc^s+BjPHuCY1m%c3OuVq8(lI44if z#d>4DgDKmM0mg@$bKz=M|NWm|sEVKc5re0emdbqRMP2&Su}EIQy?&K5vg7NylMV)7 z#*2YR$GqJ5pVw0aSc+(4$Ot6+^OQ?E)BTYIws=YmS77uCV`B;mV_C>_%!w_$a+y@I zGezRI%WwwS4qF4EJJ5s<5l3gBqdbPS*ef+~L;-h3dnt zuW#U<_A6vygV$x}udxGPp^EsP?$uEk6F2?eqyCGE{kUFIs4D zf}{(h(fJzXuhvy8<_kxe;IL4R7wrxgv9P407W-*rgvBN~TU1Pjp|{;>CO zkXrk-G{>32XwvJhCtfIJ-&9xNK&P<&$97y|;%4*5j`%NAI~T)Sk&keI`Q87I)$6IO zsj*%O(K^QaoU>Z>>W>n2;pzB&>QxWYrNu-JQO3`2E-!&^5@z~0B`%+zAK*X#`P6x! zYC6rT$8DZb-`M!-J2>NJXesm7ay}dPvQlSh#s+X{i=4rE)iN z!qHdZ)$Fj>gXdKfaNp~ztxV(#;+mxVyGU$pM^-zQUhZ!-E8{Cmo$(|xZZ-0>CNuRC^cS2_?KrVv*dI?E> zl>SGncOajg3t4t0B6y@LXHQo|IN%XAUvoK^CZ3Tw3Ei(n2za=-2na-|_}$1;CZlAXQgacm6EjpWA;z#$LnGB!0npX!btdEtz6t zP`B;w%F-$JpgJ(C?LZ^^G^#{ZuOv6Y$)LP1+{isI&vF1##sv>~X>&%w(_E*HmZ+rF;G5z$u zm5W8dgB0#N5b&`SNbJ2@mwWvz+KnfYilj1DVjztiem_z;Zbv=(_y|tIm{$$|ofB?d z#~n@{Ii%hoa&mJ!^R@&3A~XFtS7rO+$9mK1m6K~4yQzV?y1OMIJ39-jew)$5?EA*Z zNYStAIvR==Zi$73>xrEchG2hPd;9k9j?Vjr0ssfP13E!`xLtYMYip}U%%!xLVr%v^(H=vZ6&1agIlmrLI&T5$=NKYyBFU(^YFL1!r$~54b zza7YW+@L4SM0FWO^ihz;*)Hus=gJP#>#s)pqITgwAGV+ZkQzx6Nah@^h!Mw+&fyQJ7O{~rq=##6|ZQs7e{>n;C z0;i_NFkY-rNPvd|=YEau#t&7dmjs{(NONFP zqn|W4Yqrm-KSD(fF+DXsJ@@7nO=MA{3PL3~3FrE>ftPNwvg7YnBj-Jy{m#n>7P>yH zE`CChl0qVllha|1&&aq;{rNR3z;roKylspA0UN}9-|DFsNH+k*1e^{~myTeO*-;Vq2ETS~| zeoI}CI1H3oAt!mEEGJlRX>hpGl^NoHQmG;)7?PIQ-T&r8*Z8>NBmW!FqrWV!DaDpa zai)-Gjh+EEfdI826P+MaNrn91eP-Q5rNx zuQl#O=!cABoV$Cx6L0(LHDW}#({_$AI03?q-=LW5K)-X~O^qmF?WBbC_-~wE(8I!9 za-hR}vCdKZVxS~h`%UJE|A}2={)8ew;vX40qnJ;JukY`2jpI=;uI|=mxPE)gOREBk5k&M zSxMg9J^R?Cp`talIJ`fI#YKGrsXJ5X0ogPoy%x2=(?E}))?rK~G|%RCwF+%*$c){$ zt(_QV?{m$rUOp#Vc!L1(>6=(UK83V~N#-NJd9oGRhAp|C^|^$^#HgzRD0Rt^@cBQE zNnjKn7#Mi|B3RP7m-4T#{=<^~@tuFN=D!T>|B2IQb>rLq$u3~yFN2zjCh2CD3TnWU zd7^3G-QcFND)@>6Xu{-!Uxo z@GC#RV|)8X*s7run9hIfM*{mI^bxa7aE04pOPerP}zBh5dAo zs=AskX$UAh$4G8%?W84UVEzVtMmU@F_C#cK2wW({Hm~x#q|ATF3=It}nsl?t;hb|c z;AcjiN2fe1X44N@3448^97h9HRV28|@q{Ch-NFL>kESn-5@Co^QqgR(ga`XWs(Y*! zgqBnPq4Ou0E*?u+{<=E8hL^MYsK>{?2A6h@jtg1ArZzVGqXCnwq0q6mrysJRb>em_ zQy-Wa)LYu}3gmARg#Y6sk6T758Lak>jy#!XSh#pM^<@t(^IULeWDuoIItuuPsfmT~ zu5UxEjU{RbO;P-;Uudd+4l%CgaJ84VXfIA5r;4jKB1a8*@AXJf1^&n5s!%*$qee-P zjh6a8sCoLACl!7NwiCLkM1$sTQ8pGg2|*QYdDCzLVrmQXhleeQ+86g8+}s7B|9v}_ zNA^i2+}ZIxDqzN?E%$y&x@-+|3w`QmBS%|RgIp2Hb26eWnFo>b%F5M!Ckg(T*#F!b zfB|C~^`b5`{}NbLr^N&nHF0HGQ2DcvlW%3~dVY~)I#fJ#_M)NA#bpw&%!D(&&xuM} z)!5OIuMgfVXe|mudsWeI!i8U6qZQ)^WNYZa2-N;zd1_ZMyG%VP(T9=k;1@S)wcj(K ziq%eJ%JD=$#bLkyq()Uw%e;Pd$YnP9oy=jEn_EbP3Vhd<5B!~g|KIzs=dSTsdlUBA zfc3MkzbuNILCX@lo~Lmc!rHe>J!jJ8r(;O2j4@f4F~TNX(HHngCvV zTC_`m$HkZPIb=*+oT8RF_TT3IPk=wyRBxR(C*}{}<3pj<_ItgvntwrLmT!?tNok*{OGt?)&gFM zQfWxQ|JK?v$cT519z%7mYi`o*;+(L;(kg}XpVWM7`3IxvTQ_sIQxxsW=0SjY6z%^N z<~(y5-v+?uyMQ;K;a;-^9FZEYFiCotN&WxF|2r$-yr}On^V1v-pvu~&b literal 0 HcmV?d00001 diff --git a/testdata/snapshots/darwin/heading-light.png b/testdata/snapshots/darwin/heading-light.png new file mode 100644 index 0000000000000000000000000000000000000000..03c5e0a6c22fd11196e6d025171a0b49d07c355c GIT binary patch literal 15798 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 literal 0 HcmV?d00001 diff --git a/testdata/snapshots/darwin/icon-light.png b/testdata/snapshots/darwin/icon-light.png new file mode 100644 index 0000000000000000000000000000000000000000..65c3fec0f715fde2640beb849aa645fd4d0b3348 GIT binary patch literal 5115 zcmds5hcnzk|Nd&xNl5e>f`sD`o#;^yQKCi(qTJ~cy+oAgM356Dh#Ea{I?;0^N|e(s zB3zWy&f%iJH@`pPH}Aaj?0oi_-JRLl`F!TteKyg=NQa(=g9ZQqdOck&Qve`wCvp=i zQey0w4?YF}<~}_wb@Sl-?SddXGt1?##@>9feAKb*IaIreR5YfCcbl!68BDODOMD2= z!S*dECHG@&<@6-3vI^7Z429vrhg)JLXQO1%Ak^C}_D8#E= zto~Y7SwAKdTt44b=wW`oCF%f>lfIlFr{m%C`(Ht)-k-|5At57j`)_?5AxHx@%|B{H z1WOO{dr=h9^HC2=%;r!-M`MYkZ%*wcvsPRyLc=bZnLMSdDntI4-6OQ!Bgh~5lp|Gn z^~XtfvY%2LWq{+^tO9@czSn^~hwKx2f#|R3moR2oI)du0@g!PiX*JQfM0O(mk+9gG zeDN^r%N?l#&a8stw+{MW?veF3+>m`Cop0yX)+&9t(cpZg#xyWmVn;*ayFL7W64}Q@)bNn z#mOwO&c6qF;j*#Wm&RdsljY+40F;)1Z%wx(0ZMYoWe>Djyp7D#!)buvh6}Y2{bN4P z(V%(}hsd_!v0T;gwFckvSM_q8y-^R|l3cVn8yK7bfRYj?+lF!7llGiTFgNg9YS4AE zNgKR$rM0biux(#GnnWN0Hd%cbqQVZ&UoEZ-UcR)=IMbXf8&PZ|DcE)NP#NgcKREzJ zA(^QKL7-=1rN-eoZRs489fVtPHJH=!#ed_{*--^6;Obbx`jxzKZG+d8C_n^kd#g>0C@D-zf;Bhg_$02(z+A4Sc{V`ljrFQUPG_ zwxqGu;`D7fXGv04h#zMyhE~E;@Am-u^4XZP;|ELKCaG1w;}x?#dxl+q3RkWL7cLo@ z=79ApPQX_}HsyzG{K{odZ_-Bke!Ag26hprtMb}Azrh09a zWaCd_VlteblpTlJoQ!+CZI_e~9YViCH|*#XirP6-V(2`+Dq zw-eFgw4Zd%S2=&CIbQ00$-B0c=KKZATO}!_pjE0%9BS0E4*(2u|B-vsN3UG2oMvOI zmdvjpmq2tz7Jn96_)HuZ$9hJu@v_Li6Y^c6=u031bvy$^w+gIFi7(4GHCja-iIi*Z zD5~LyK;O^-$8MZvcrvJJlcY?-iqZb3dk<9b%stglukskr3WKImug4!xGOls^??Xl(VK z%`f95n+1T#lOxz#?dZdYV!@5|Yc7~`zg@;K%Cl5nA9CP4bQ^Wse)3>ubl8}SvBO=j ztD&h}OM%+P>|PD%7K_TaYrq8j)$9`mm}9kJo}fi@4`_bUbeRfZlbT8Na&v{bdB8@U z82^G5p6$zS{S@@Fj#$GgCa$%3g@rXe>`x@S(sV{;CY8UjRrSko7ugme=5Yv

&;{p+8@9Ze^5%Q{~X>)JSa9H@7L#FknHCueG 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 NdfG->bsCN_{{cZTf0qCN literal 0 HcmV?d00001 diff --git a/testdata/snapshots/darwin/image-dark.png b/testdata/snapshots/darwin/image-dark.png new file mode 100644 index 0000000000000000000000000000000000000000..286963c4894e311f1315462bd2fd54a7e22c95a3 GIT binary patch literal 7050 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 Fe**}EN`n9Z literal 0 HcmV?d00001 diff --git a/testdata/snapshots/darwin/image-light.png b/testdata/snapshots/darwin/image-light.png new file mode 100644 index 0000000000000000000000000000000000000000..9b207d68a367f4f5b462842b9d58c180b428b8b1 GIT binary patch literal 6942 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 literal 0 HcmV?d00001 diff --git a/testdata/snapshots/darwin/kbd-dark.png b/testdata/snapshots/darwin/kbd-dark.png new file mode 100644 index 0000000000000000000000000000000000000000..119484d20c9c643d3f62d0dd237a520ea850b3f5 GIT binary patch literal 2646 zcmd6p`9BkmAIB&4ag-x9>Vq_?RqiB=B4R(n3aV1-{JrOKmvNj{00CZ z!^A=1>d@nZL8@aFlw;2hYTBDuh!o1U3yUismJ>2)0RpQj-sHp^7Hy;$vCGHZ zXdk5NTk&XE#K1Ct100sm-)b~Yh=@q`JQ+N1DZTgd0kIq^Kp{rhA0XK*qz@38_>Ya;D^mF};?V^kIUV&;1 zRRErR;3=Vgp~T1|%mSDf8VFZ&3?WU44#i|7(^th6iAaX(oYDz=dvE2iLeHxBvj8{w znvI30beJoB)=Km~1{1ZuUe?(+=nRB7l1rWF#+C}WEL62Y1_ubBA)lD-1h%slJscvd*I)&Ps#noN=koqY#Qt?G*oX=T6BGFEqGNizg&w~*1bdP zA`!8!#?wss^lJsAp?7X(jSgpn0-$G5ELFKw_v8ZoaLA>E*2$ma~<4TwXqB?t6W~Z6zKyXzMM4m7D3} zgKkZ;W{ZKy%cENxvzypMjqa)wR*MpHE50-8@arg)89_*7YSNViw~|`E6Zj2Mff-$I zZa$iJx9`;*bSsj2Rt-!@!SH6_hyZ;BNs!9w!UiVNSTbAQOH8+ysSaxEcRD4ta|%rM z-{{t=aI_)tB%Zb!VAnUjS$shcwR3m$WaCTaQ&zd3~!ONL6yUiNCQYP9%mzs%X_jZkr5Vt=kBy|lr!i1 zXcETVq$5euz=OLkkrTXy-PFiKyOYf#|LSUOf?C6r=uOKRpNYs$o%0?Z(}{bg*YZTH zU$QP1FZpJEmJ<=V{sS*jHFHQ2dhNgwCsp z!&PA4%F6-?b9Y93qbh?xw=o2^M^kgJc#N{d?Z8uoa31NfIrsPw^p(b_RlR^2d^hQ z`VWmAp6py7<-PGmnjaM@3Q85i=m-nD|{rtwb~im9K}^$IS}`<`^)cyGVDOi^P{@C(l=aoQej#oq?}wpT!gQO2Qw)r zL=@DYe^K)+b$f`=ddz4&XnY%uRc%yGr{ExBB0WZe+O=WQ!tuNdf%*!4CJ7^D4C6cZ zW@l$)@kcJ1D+j1H28=B6HTB;c_*lI865abTW4~Ef3^mU%BwMcG=h)WMcneks{^+Oa z2kQI;&-1(u@#)Tkm$DFHSlD=5TP_T`5k3^_(zP76x(=o1+ND$Ql~d1ShwdrFrDQ!4 zn(bF`uR;WX`4aLFPq)sWZ`p2=lFF1yzo&Fy1&GZ{>CMSuF#=2hWRgE=zOK& zda6qg2`o{B`wm4+e0KKmAUP&>z~8@a&?UY%(KyFe^o3s%C2T?IXl9^d9z~KbZ>l$- zCzeo_(-Q1pZ~w#>4w7EsoUfu(ltqG)pLO?W%8DqY>;1+i~HiyVYn}{Avh}$mM;D#FA>Qe6NJ2Ua1?)GgN$RjP)jK@W9-u!}kFmp8t zmmb=`$Z;yRd-C-capuy5X0#AYFMFv+q)wu=?!_MW}tn_lsdD- z4##FE40u%|oVZ|-h^48i=~>nK)O2Yr4s&aunRBR0=g>p`d|1=FhB7Fzj0lc9EQ@j| z`})S_aifvQAK%5)CXM`&(RW4ZYBJT7%GV}!_qVu8eM)(P_PvlKt&_41w1elt_K^23 zBrekl1dE7Xa@#nOZjCBzxXj`u!iWsc?JY(1#(>S07qxYPr6}8aXXl8#N*&^e5~J$F z2cToFlLG7he9{D*z|cLqEzPQJ>&Pcb$eQ~g&>kK^Cke+-PRoE)wtYTiW~w}xwY-oM zv^ZQ*%SccsfF`*YbxN;3#}D6dbfoGkm%n-QB7BkYu=e7)tw_(lZbv#$_}t7jojDts$m16TCTfwiHjEfZ(}j9|dfsJTH=Ft~ zboZueecCONLQPPj_u{RhGN}zOU@{F|g~;9^Y3i!$=&IjTk*OKkdQ5QWPfcZT3}dTQ zxrPE6f9z>}El8Uk>;7EN&Pe*SN>s8SYNN zbSx)5di3kIWQ|9U@c)Za@T`{q0uCLBn;I7e43{pZYF7I??eZc3Y5_B^GR|sSv2yY((VHNx<>{ zSW_I^`CLgMs~fnvinTbr5v=$ki`1$j;odd9jx4qf3e5CzfZ*?>!l=6E<6 za5V|SlT3Hr6|9Buo{Wc1xOy%+bd*lV8QCMRoLb=>QgkVZ3HCwnXB81$mX}M3E*Piw zB1>hX($kAeC?4;iiSF@~-ABaBKdDE{O!$k#Z1GX^)K=PvLu#u89-zxpkYcUM@u^QR zP8Swb4K(hX?tkqf5_ z3_Cwoj1si%jCyknUkLeAyP$wE&yP++K|MAws#Xxestb^%W)_e7^XJc~PfP5zoP`U~ z)ty^E41d+g%5HjJQfyvxnOTq~f1M%>hRq*N($UOBsFP%q*YgaqlwiLiyN{0MkDR#+ zmNYlWRcny>%o@|`$a!;(RA@quUa*I2*27L--|Va$3Rza`bBXodG--LcNKOvx<@MvZ z)KiV>3b@JlT>2(K-_viR{7*=6_0)$kFbm6(W(&t+h9b?>$i=CEvahP zD>JYOVP^+DbEd2!+Ll^p35V0OGHBJ+bSyR%l8$#&%U%$lWJ(7J7>SDV+R(qN-#2n# z#s&H9p0vt>z%WT9^@Qp-98gpGffCKry|XCDce$QGfarxI;{wbd%yw?&TA*MJ&M?^8 zAMK#|{riEyx0c<^+TTU?+C+ca6nFb*Q2ddQiQ$7LQpzMT5a@RvBmd1ClldE73*})! zrl(pO@vyp;7UIO9mkQBoGWS*1o4=m-klUu1%$EeG41yDr#j>DIfa_qbA*?PQ29r3e zd|pbrrm`|IDap&mwY{v949sls~Fp$VE6p~Je{DTi( z((WS07`nx&2DkI^Zc!?YaeTQ zlV3P&_VsM-C@9u3)Ik0Idry{)d~{k6oARwk=h54K=~Gv(-qZ`(Nvx|Isz?~G#aK+u z%zVFK{VhI*A_fjzS$^WZu)ge@*3@+6X{QA}e{6I#D?1{tV*0%KU2b|2-(YNfTo5}V z#*o{k7u?8~LL2VZh@fsieZOkdPoWfg-x(0o4%wcwnI>APAdw}J?enrGy?uSc!aIY5 z($5wBl;!0ujc^YB6QAL5xRbp-!rA%F3Co2hU&8IkJJr#q&ue;4dZt@6HJw`dwL47m zpMm4-${l-ryd$Bc(~aEJ6!Jj`*3l`7e^N(>*xl{jn^B&wfxKOIS;D;|Ur$J=W^Em> z*J52LJL<6)7gn>g`>DMAhNq~cB6{X~Z0tTDC3x(g%Dt57?%6njLhft+nfhi$&Tq>g zcuu5%#niB*3I2+b{}Pq>b=7+3H_-`|5J8dv2&eZ&!!WL3L#Ki zRH5>KwkJ8y2*rFDZ<6*ZYi54ls^o=oruFA(e2abwnUiCZhycYj7b__ z)ElzOs4S_fN|RaJ8VD^i8-d>qj?{s{U>FQZ=c8i6%aPoGel2hZBcOTcovY#jj9c?f zAt0TRm@HcXJ9_1QV(?f>8#+8Z;mNs3u00IBD~hCQACOPDR^hB2vYc@)yCIW_O=65| zO4LBqI;d25(F$6+aNyxq+Ow-jom|$P9DA3p&f>253`OT z`G$XHib!6Ez^#R~%h?jQEu;5_UY{b%n3#?3&iz?Ga_$_s?aZyRA%W_>>%3eyt#kFR z7u>RUFes1(wft?O>uXN)cmS@iX*jlefkY=-sT(34HtsjF0-7sK6)@SgSNDFkZo7i{ zS){5e`ajdextZgk3PxeY6FC+ny$K)A8VJNO;x>#q;a%FoH+ZSZhH7eR&;&$lc+~GH zoQ;i5MMYZ;gPxHYtlz7rrzZjCxrJ~CFrwFsPe*Lm>)x68C!h(zAJL+gk1I4q1{)in z$Tg!KlHE&DIeXRy(lpCpn1RH^#OBuhFwXFsIl8eGKG>CXJeL~+ukV72zmAIZq>a3I zzqGtK-042ru&L_j2aW+6JbY?Et`>-)NTcjdg>-`Cf-ZZd~PTdO31L=3}9 zNaB(4jfj-8rJw!%e{%Cx>Gh9(bnCrJ%bR5~+4E#=iXzn7cR6c`>i^;L;zf6}!7Jd7 zg+9JzyW2Hgw7$!&e&2920-<=&6db6wS^|GAP%?v&%shImMKvKK7?ZbXE^Huve>ttp{z32rI|73%;vrNeA32qm67;$Lp`y=)J zEoCbL?D5qU!_U(S9n-2YN>*f4q9os`tF*tZ`7$AnI}mX`BxI;HogpW)+u=K2`&Q}L zVJ)q7-e}hFusmxbBd|$=JQsh)UrAYNyY99c2k$S5e;37}S#1wwBR6P0Z1Kcs#_9WL z2NK0;Rzcwtc6KE>2rch3MYHXXn*i@k>s_&I>Zb=zwdCimk01ue$L AYybcN literal 0 HcmV?d00001 diff --git a/testdata/snapshots/darwin/link-dark.png b/testdata/snapshots/darwin/link-dark.png new file mode 100644 index 0000000000000000000000000000000000000000..9d8705f36be6417df8d5ece0ee0f8feeeb69c5b2 GIT binary patch literal 10794 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 literal 0 HcmV?d00001 diff --git a/testdata/snapshots/darwin/link-light.png b/testdata/snapshots/darwin/link-light.png new file mode 100644 index 0000000000000000000000000000000000000000..81476194926cdbfb9ed9044705199a01758b3efe GIT binary patch literal 10421 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

f(+)x{ArO+jb!4SEsiiM&nmY)m|zXy#vL36WT zxb9+B(m+&QCa4YTPR+#Me=>YOF$JPl{jp_D8a}xw5_ec|UC#LO$>8C2(0tW4D#LZk z4cQ`Z@%G3oY9-oi`KD=gV4Ire>d&{BTWGX}lhrMMB)le8`cNO&Em7W(J7c5>vg-rm zt)kbCJW;Hj;9P>Rtc*Z?1R=y{lw$CsPlX-);EcoE3X@mvY>-saGR9oO^VUQ|31Q?d z&#%|Q1|WB2e)HX&?rO!p8fruOEL_^_cYUVN;{eBhY4&v&bU{_snN8I?dVRtF4LScmDg%G&^LGoPD)v_THG}FzW^9au5SPU literal 0 HcmV?d00001 diff --git a/testdata/snapshots/darwin/tag-dark.png b/testdata/snapshots/darwin/tag-dark.png new file mode 100644 index 0000000000000000000000000000000000000000..71ce0bf8fb50125d760779812ec054304daa0a33 GIT binary patch literal 5625 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 GME?gsLlKVv literal 0 HcmV?d00001 diff --git a/testdata/snapshots/darwin/text-dark.png b/testdata/snapshots/darwin/text-dark.png new file mode 100644 index 0000000000000000000000000000000000000000..a6d1fb0d1f690790c5037c6bf155a1d425012291 GIT binary patch literal 21270 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≫&>r<8z=?>1ru~dp)qlJ7JkX>G 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 literal 0 HcmV?d00001 diff --git a/testdata/snapshots/darwin/text-light.png b/testdata/snapshots/darwin/text-light.png new file mode 100644 index 0000000000000000000000000000000000000000..28658cae435373d1c4a78d501198ce689b30d170 GIT binary patch literal 20426 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 literal 0 HcmV?d00001 diff --git a/text.go b/text.go new file mode 100644 index 0000000..52cea10 --- /dev/null +++ b/text.go @@ -0,0 +1,85 @@ +package mujica + +import ( + "log/slog" + + "github.com/egoist/mygo/ui" + + "mujicaui/internal" + "mujicaui/theme" +) + +// TextOptions configure a Text. The zero value wraps every line. +type TextOptions struct { + // MaxLines cuts the text with an ellipsis after that many lines; zero never cuts. + MaxLines int + // Selectable lets the user select and copy the text. + Selectable bool +} + +// Text shows body text that wraps at the width it gets. +func Text(c *ui.Context, s string, opts TextOptions) *ui.Element { + if opts.MaxLines < 0 { + panic("mujica: Text MaxLines must not be negative") + } + e := ui.Text(c, s).LineHeight(theme.LineHeight) + switch { + case opts.MaxLines == 1: + e.SingleLine() + case opts.MaxLines > 1: + e.MaxLines(opts.MaxLines) + } + if opts.Selectable { + e.Selectable() + } + return e +} + +// EditableTextOptions configure the editable mode of Text. +type EditableTextOptions struct { + // Saving shows the caller's save in progress and blocks editing. + Saving bool + // Error is the caller's save failure, shown below the text. + Error string + // Label names the text for assistive technology. + Label string +} + +// EditableText is Text's editable mode. Double click or Enter edits; Enter +// or leaving confirms, Escape cancels. Changed reports a confirmed new value. +func EditableText(c *ui.Context, value *string, opts EditableTextOptions) *ui.Element { + k := Tokens(c) + u := Density(c).Unit() + line := k.ControlBorder + if opts.Error != "" { + line = k.Danger + } + var box *ui.Element + ui.Column(c).Gap(u).AlignItems(ui.Start).Children(func() { + ui.Row(c).Gap(u * 2).AlignItems(ui.Center).Disabled(opts.Saving).Children(func() { + box = ui.EditableText(c, value) + pad := u / 2 + box.Padding(pad+1, pad*2+1).MinWidth(theme.MinHitSize*2).MinHeight(theme.MinHitSize).Radius(theme.ControlRadius). + BorderWidth(0, 0, 1, 0).BorderColor(line).BorderStyle(ui.BorderDashed) + internal.Ring(box, k.Focus, theme.ControlRadius) + if box.Hovered() { + box.Background(k.SurfaceHover) + } + internal.Fade(box, Motion(c, theme.StateDuration)) + if opts.Label != "" { + box.Label(opts.Label) + } + if opts.Saving { + Spinner(c, SpinnerOptions{Size: 12, Label: msg(c, "editable_text.saving")}) + } + }) + if opts.Error != "" { + box.Error(opts.Error) + ui.Text(c, opts.Error).TextColor(k.Danger).FontSize(theme.CaptionSize) + } + }) + if box.Changed() { + slog.Debug("mujica: editabletext changed", "len", len(*value)) + } + return box +} diff --git a/text_test.go b/text_test.go new file mode 100644 index 0000000..6b12e7d --- /dev/null +++ b/text_test.go @@ -0,0 +1,137 @@ +package mujica + +import ( + "testing" + + "github.com/egoist/mygo/ui" +) + +func TestTextSelectAndCopy(t *testing.T) { + tt := ui.NewTester(func(c *ui.Context) { + Use(c, Settings{}) + Text(c, "ID-4821", TextOptions{Selectable: true}) + }, 300, 100) + if err := tt.Click("ID-4821"); err != nil { + t.Fatal(err) + } + tt.Command("selectAll") + tt.Command("copy") + if got := tt.Clipboard(); got != "ID-4821" { + t.Fatalf("clipboard %q", got) + } +} + +func TestTextNotSelectableByDefault(t *testing.T) { + tt := ui.NewTester(func(c *ui.Context) { + Use(c, Settings{}) + Text(c, "plain", TextOptions{}) + }, 300, 100) + _ = tt.Click("plain") + tt.Command("selectAll") + tt.Command("copy") + if got := tt.Clipboard(); got != "" { + t.Fatalf("clipboard %q, want empty", got) + } +} + +func TestTextMaxLinesCuts(t *testing.T) { + long := "The court gathers in the hall of mirrors while candles burn low and the music fades away." + var free, cut float32 + tt := ui.NewTester(func(c *ui.Context) { + Use(c, Settings{}) + ui.Column(c).Width(120).Children(func() { + free = Text(c, long, TextOptions{}).Bounds().H + cut = Text(c, long, TextOptions{MaxLines: 1}).Bounds().H + }) + }, 300, 400) + tt.Frame() + if cut <= 0 || free < cut*3 { + t.Fatalf("free %v, one line %v", free, cut) + } +} + +func TestTextNegativeMaxLinesPanics(t *testing.T) { + defer func() { + if recover() == nil { + t.Fatal("no panic") + } + }() + tt := ui.NewTester(func(c *ui.Context) { + Use(c, Settings{}) + Text(c, "x", TextOptions{MaxLines: -1}) + }, 100, 100) + tt.Frame() +} + +func TestEditableTextConfirm(t *testing.T) { + name := "report.pdf" + changed := 0 + tt := ui.NewTester(func(c *ui.Context) { + Use(c, Settings{}) + if EditableText(c, &name, EditableTextOptions{Label: "File name"}).Changed() { + changed++ + } + }, 300, 120) + tt.Key(0, ui.KeyTab) + tt.Key(0, ui.KeyEnter) + tt.Type("summary") + tt.Key(0, ui.KeyEnter) + if name != "summary.pdf" || changed != 1 { + t.Fatalf("name %q, changed %d", name, changed) + } +} + +func TestEditableTextCancel(t *testing.T) { + name := "report.pdf" + changed := 0 + tt := ui.NewTester(func(c *ui.Context) { + Use(c, Settings{}) + if EditableText(c, &name, EditableTextOptions{}).Changed() { + changed++ + } + }, 300, 120) + tt.Key(0, ui.KeyTab) + tt.Key(0, ui.KeyEnter) + tt.Type("draft") + tt.Key(0, ui.KeyEscape) + if name != "report.pdf" || changed != 0 { + t.Fatalf("name %q, changed %d", name, changed) + } + if !tt.HasText("report.pdf") { + t.Fatal("text not shown after cancel") + } +} + +func TestEditableTextSavingBlocksAndErrorShows(t *testing.T) { + name := "notes" + tt := ui.NewTester(func(c *ui.Context) { + Use(c, Settings{}) + EditableText(c, &name, EditableTextOptions{Saving: true, Error: "disk full"}) + }, 300, 120) + tt.Key(0, ui.KeyTab) + tt.Key(0, ui.KeyEnter) + tt.Type("x") + tt.Key(0, ui.KeyEnter) + if name != "notes" { + t.Fatalf("edited while saving: %q", name) + } + if !tt.HasText("disk full") { + t.Fatal("save error not shown") + } + if _, ok := tt.Find("Saving"); !ok { + t.Fatal("saving spinner not labeled") + } +} + +func TestTextSnapshot(t *testing.T) { + name := "Coronation.md" + snapshot(t, "text", 420, 280, func(c *ui.Context) { + ui.Column(c).Padding(20).Gap(12).Width(380).Children(func() { + Text(c, "Velvet curtains fall over the long gallery, and every candle leans toward the open door.", TextOptions{}) + Text(c, "A single line that is far too long to fit and so ends in an ellipsis.", TextOptions{MaxLines: 1}) + Text(c, "Selectable: ID-4821", TextOptions{Selectable: true}) + EditableText(c, &name, EditableTextOptions{Error: "The name is already taken."}) + EditableText(c, &name, EditableTextOptions{Saving: true}) + }) + }) +} From fe806037c7857974a441b727ecd80083832a43cf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=9B=B7=E7=94=B5=E8=8A=BD=E8=A1=A3?= Date: Mon, 5 Oct 2026 12:18:49 -0400 Subject: [PATCH 4/4] Layout: components 001-010 --- app_shell.go | 120 +++++ app_shell_test.go | 149 ++++++ aspect_ratio.go | 49 ++ aspect_ratio_test.go | 113 ++++ container.go | 85 +++ container_test.go | 97 ++++ divider.go | 65 +++ divider_test.go | 107 ++++ docs/components/001-app-shell.md | 53 ++ docs/components/002-title-bar.md | 43 ++ docs/components/003-status-bar.md | 63 +++ docs/components/004-container.md | 45 ++ docs/components/005-stack.md | 49 ++ docs/components/006-grid.md | 57 ++ docs/components/007-scroll-area.md | 49 ++ docs/components/008-split-pane.md | 64 +++ docs/components/009-aspect-ratio.md | 41 ++ docs/components/010-divider.md | 43 ++ example_layout_test.go | 129 +++++ examples/gallery/pages_layout.go | 498 +++++++++++++++++- grid.go | 72 +++ grid_test.go | 93 ++++ messages_layout.go | 12 + scroll_area.go | 62 +++ scroll_area_test.go | 118 +++++ split_pane.go | 264 ++++++++++ split_pane_test.go | 190 +++++++ stack.go | 69 +++ stack_test.go | 114 ++++ status_bar.go | 237 +++++++++ status_bar_test.go | 130 +++++ testdata/snapshots/darwin/app_shell-dark.png | Bin 0 -> 17599 bytes testdata/snapshots/darwin/app_shell-light.png | Bin 0 -> 16677 bytes .../snapshots/darwin/aspect_ratio-dark.png | Bin 0 -> 3442 bytes .../snapshots/darwin/aspect_ratio-light.png | Bin 0 -> 3363 bytes testdata/snapshots/darwin/container-dark.png | Bin 0 -> 10207 bytes testdata/snapshots/darwin/container-light.png | Bin 0 -> 9861 bytes testdata/snapshots/darwin/divider-dark.png | Bin 0 -> 6227 bytes testdata/snapshots/darwin/divider-light.png | Bin 0 -> 5932 bytes testdata/snapshots/darwin/grid-dark.png | Bin 0 -> 6551 bytes testdata/snapshots/darwin/grid-light.png | Bin 0 -> 6367 bytes .../snapshots/darwin/scroll_area-dark.png | Bin 0 -> 4054 bytes .../snapshots/darwin/scroll_area-light.png | Bin 0 -> 3613 bytes testdata/snapshots/darwin/split_pane-dark.png | Bin 0 -> 3072 bytes .../snapshots/darwin/split_pane-light.png | Bin 0 -> 3049 bytes testdata/snapshots/darwin/stack-dark.png | Bin 0 -> 6786 bytes testdata/snapshots/darwin/stack-light.png | Bin 0 -> 6604 bytes testdata/snapshots/darwin/status_bar-dark.png | Bin 0 -> 6276 bytes .../snapshots/darwin/status_bar-light.png | Bin 0 -> 6200 bytes testdata/snapshots/darwin/title_bar-dark.png | Bin 0 -> 4559 bytes testdata/snapshots/darwin/title_bar-light.png | Bin 0 -> 4463 bytes title_bar.go | 38 ++ title_bar_test.go | 82 +++ 53 files changed, 3399 insertions(+), 1 deletion(-) create mode 100644 app_shell.go create mode 100644 app_shell_test.go create mode 100644 aspect_ratio.go create mode 100644 aspect_ratio_test.go create mode 100644 container.go create mode 100644 container_test.go create mode 100644 divider.go create mode 100644 divider_test.go create mode 100644 docs/components/001-app-shell.md create mode 100644 docs/components/002-title-bar.md create mode 100644 docs/components/003-status-bar.md create mode 100644 docs/components/004-container.md create mode 100644 docs/components/005-stack.md create mode 100644 docs/components/006-grid.md create mode 100644 docs/components/007-scroll-area.md create mode 100644 docs/components/008-split-pane.md create mode 100644 docs/components/009-aspect-ratio.md create mode 100644 docs/components/010-divider.md create mode 100644 example_layout_test.go create mode 100644 grid.go create mode 100644 grid_test.go create mode 100644 messages_layout.go create mode 100644 scroll_area.go create mode 100644 scroll_area_test.go create mode 100644 split_pane.go create mode 100644 split_pane_test.go create mode 100644 stack.go create mode 100644 stack_test.go create mode 100644 status_bar.go create mode 100644 status_bar_test.go create mode 100644 testdata/snapshots/darwin/app_shell-dark.png create mode 100644 testdata/snapshots/darwin/app_shell-light.png create mode 100644 testdata/snapshots/darwin/aspect_ratio-dark.png create mode 100644 testdata/snapshots/darwin/aspect_ratio-light.png create mode 100644 testdata/snapshots/darwin/container-dark.png create mode 100644 testdata/snapshots/darwin/container-light.png create mode 100644 testdata/snapshots/darwin/divider-dark.png create mode 100644 testdata/snapshots/darwin/divider-light.png create mode 100644 testdata/snapshots/darwin/grid-dark.png create mode 100644 testdata/snapshots/darwin/grid-light.png create mode 100644 testdata/snapshots/darwin/scroll_area-dark.png create mode 100644 testdata/snapshots/darwin/scroll_area-light.png create mode 100644 testdata/snapshots/darwin/split_pane-dark.png create mode 100644 testdata/snapshots/darwin/split_pane-light.png create mode 100644 testdata/snapshots/darwin/stack-dark.png create mode 100644 testdata/snapshots/darwin/stack-light.png create mode 100644 testdata/snapshots/darwin/status_bar-dark.png create mode 100644 testdata/snapshots/darwin/status_bar-light.png create mode 100644 testdata/snapshots/darwin/title_bar-dark.png create mode 100644 testdata/snapshots/darwin/title_bar-light.png create mode 100644 title_bar.go create mode 100644 title_bar_test.go diff --git a/app_shell.go b/app_shell.go new file mode 100644 index 0000000..ee6850e --- /dev/null +++ b/app_shell.go @@ -0,0 +1,120 @@ +package mujica + +import ( + "log/slog" + + "github.com/egoist/mygo/ui" + + "mujicaui/icons" + "mujicaui/internal" + "mujicaui/theme" +) + +// AppShellOptions are the slots of an AppShell. Content is required; the others are optional. +type AppShellOptions struct { + // TitleBar goes across the top, as a TitleBar. + TitleBar func() + // Sidebar scrolls on its own beside the content and collapses to a rail. + Sidebar func() + // Content scrolls on its own. + Content func() + // StatusBar goes across the bottom, as a StatusBar. + StatusBar func() + // SidebarWidth is the open sidebar's width in DIPs; zero is 240. + SidebarWidth float32 + // ContentScroll keeps the content's offset; nil keeps it by the shell's place. + ContentScroll *ui.ScrollState +} + +// AppShellResult is an AppShell and its event in this frame. +type AppShellResult struct { + *ui.Element + // Toggled reports the sidebar opened or collapsed. + Toggled bool +} + +// AppShell lays out a window: title bar, sidebar, content and status bar. +// *collapsed folds the sidebar to a rail; its toggle button and Cmd/Ctrl+B flip it. +func AppShell(c *ui.Context, collapsed *bool, opts AppShellOptions) AppShellResult { + if collapsed == nil { + panic("mujica: AppShell needs a collapsed state") + } + if opts.Content == nil { + panic("mujica: AppShell needs Content") + } + if opts.SidebarWidth < 0 { + panic("mujica: AppShell SidebarWidth must not be negative") + } + k := Tokens(c) + var res AppShellResult + toggle := func() { + *collapsed = !*collapsed + res.Toggled = true + slog.Debug("mujica: app_shell toggled", "collapsed", *collapsed) + } + if opts.Sidebar != nil && c.Shortcut(ui.Cmd, ui.KeyB) { + toggle() + } + e := ui.Column(c).Fill().AlignItems(ui.Stretch).Background(k.Background) + e.Children(func() { + if opts.TitleBar != nil { + opts.TitleBar() + } + ui.Row(c).Grow(1).MinHeight(0).AlignItems(ui.Stretch).Children(func() { + if opts.Sidebar != nil && appShellSidebar(c, *collapsed, opts) { + toggle() + } + ScrollArea(c, opts.ContentScroll, ScrollAreaOptions{}, opts.Content).Grow(1).MinWidth(0) + }) + if opts.StatusBar != nil { + opts.StatusBar() + } + }) + res.Element = e + return res +} + +// appShellSidebar builds the sidebar or its rail and reports whether its toggle was pressed. +func appShellSidebar(c *ui.Context, collapsed bool, opts AppShellOptions) bool { + k := Tokens(c) + u := Density(c).Unit() + open := opts.SidebarWidth + if open == 0 { + open = 240 + } + rail := theme.MinHitSize + 4*u + target := open + if collapsed { + target = rail + } + side := ui.Column(c).Shrink(0).Clip().AlignItems(ui.Stretch).Background(k.Surface). + BorderWidth(0, theme.BorderWidth, 0, 0).BorderColor(k.Border) + side.Width(side.AnimateWith("app_shell.width", target, Motion(c, theme.StateDuration), ui.EaseOut)) + var pressed bool + side.Children(func() { + head := ui.Row(c).Shrink(0).Padding(u * 2).Justify(ui.End) + if collapsed { + head.Justify(ui.Center) + } + head.Children(func() { + label := msg(c, "app_shell.collapse") + if collapsed { + label = msg(c, "app_shell.expand") + } + b := ui.ButtonBase(c).Size(theme.MinHitSize, theme.MinHitSize).Radius(theme.ControlRadius). + Label(label).Tooltip(label).TextColor(k.TextMuted) + internal.Ring(b, k.Focus, theme.ControlRadius) + if b.Pressed() { + b.Background(k.Selection).TextColor(k.Text) + } else if b.Hovered() { + b.Background(k.SurfaceHover).TextColor(k.Text) + } + b.Children(func() { ui.Icon(c, icons.Must("panel-left")).Size(16, 16) }) + pressed = b.Clicked() + }) + if !collapsed { + ScrollArea(c, nil, ScrollAreaOptions{}, opts.Sidebar).Grow(1).MinHeight(0) + } + }) + return pressed +} diff --git a/app_shell_test.go b/app_shell_test.go new file mode 100644 index 0000000..8521cbd --- /dev/null +++ b/app_shell_test.go @@ -0,0 +1,149 @@ +package mujica + +import ( + "fmt" + "testing" + + "github.com/egoist/mygo/ui" +) + +type appShellProbe struct { + collapsed bool + toggled int + content ui.ScrollState + side ui.Rect +} + +func (p *appShellProbe) view(c *ui.Context) { + Use(c, Settings{}) + r := AppShell(c, &p.collapsed, AppShellOptions{ + TitleBar: func() { TitleBar(c, "Court Records", TitleBarOptions{}) }, + Sidebar: func() { + for i := range 30 { + ui.Text(c, fmt.Sprintf("Nav %d", i)).Height(24) + } + }, + Content: func() { + for i := range 40 { + ui.Text(c, fmt.Sprintf("Entry %d", i)).Height(24) + } + }, + StatusBar: func() { StatusBar(c, StatusBarOptions{Left: []StatusItem{{ID: "s", Text: "Ready"}}}) }, + ContentScroll: &p.content, + }) + if r.Toggled { + p.toggled++ + } +} + +func appShellTester(p *appShellProbe) *ui.Tester { + tt := ui.NewTester(p.view, 640, 400) + tt.SetPreferences(ui.Preferences{ReduceMotion: true, TextScale: 1}) + tt.Frame() + return tt +} + +func TestAppShellSlots(t *testing.T) { + tt := appShellTester(&appShellProbe{}) + title, _ := tt.Find("Court Records") + nav, _ := tt.Find("Nav 0") + entry, _ := tt.Find("Entry 0") + ready, _ := tt.Find("Ready") + if title.Y > nav.Y || nav.X >= entry.X || ready.Y < 360 { + t.Fatalf("slots out of place: title %+v nav %+v entry %+v status %+v", title, nav, entry, ready) + } + if entry.X < 240 { + t.Fatalf("content at %v overlaps the 240 DIP sidebar", entry.X) + } +} + +func TestAppShellCollapseByButton(t *testing.T) { + p := &appShellProbe{} + tt := appShellTester(p) + if err := tt.Click("Collapse sidebar"); err != nil { + t.Fatal(err) + } + if !p.collapsed || p.toggled != 1 || tt.HasText("Nav 0") { + t.Fatalf("after collapse: collapsed=%v toggled=%d nav shown=%v", p.collapsed, p.toggled, tt.HasText("Nav 0")) + } + if entry, _ := tt.Find("Entry 0"); entry.X != 44 { + t.Fatalf("content at %v beside the rail, want 44", entry.X) + } + if err := tt.Click("Expand sidebar"); err != nil { + t.Fatal(err) + } + if p.collapsed || !tt.HasText("Nav 0") || p.toggled != 2 { + t.Fatal("Expand did not reopen the sidebar") + } +} + +func TestAppShellCollapseByShortcut(t *testing.T) { + p := &appShellProbe{} + tt := appShellTester(p) + tt.Key(ui.Cmd, ui.KeyB) + if !p.collapsed { + t.Fatal("Cmd+B did not collapse") + } + tt.Key(ui.Cmd, ui.KeyB) + if p.collapsed || p.toggled != 2 { + t.Fatalf("Cmd+B twice: collapsed=%v toggled=%d", p.collapsed, p.toggled) + } +} + +func TestAppShellContentScrollsAlone(t *testing.T) { + p := &appShellProbe{} + tt := appShellTester(p) + nav, _ := tt.Find("Nav 0") + tt.Scroll(400, 200, 0, 96) + if p.content.Y != 96 { + t.Fatalf("content offset %v, want 96", p.content.Y) + } + if r, _ := tt.Find("Nav 0"); r.Y != nav.Y { + t.Fatalf("sidebar moved from %v to %v with the content", nav.Y, r.Y) + } + if r, _ := tt.Find("Court Records"); r.Y > 20 { + t.Fatalf("title bar scrolled to %v", r.Y) + } + // Collapsing keeps the content's place. + tt.Key(ui.Cmd, ui.KeyB) + if p.content.Y != 96 { + t.Fatalf("collapse reset the content offset to %v", p.content.Y) + } +} + +func TestAppShellNeedsContent(t *testing.T) { + defer func() { + if recover() == nil { + t.Fatal("missing Content did not panic") + } + }() + var collapsed bool + ui.NewTester(func(c *ui.Context) { + Use(c, Settings{}) + AppShell(c, &collapsed, AppShellOptions{}) + }, 100, 100) +} + +func TestAppShellSnapshot(t *testing.T) { + snapshot(t, "app_shell", 560, 260, func(c *ui.Context) { + collapsed := ui.Local(c.Root(), "snap", func() bool { return false }) + AppShell(c, collapsed, AppShellOptions{ + TitleBar: func() { TitleBar(c, "Court Records", TitleBarOptions{}) }, + Sidebar: func() { + for _, s := range []string{"Chronicles", "Letters", "Decrees"} { + ui.Text(c, s).Padding(6, 12) + } + }, + Content: func() { + Container(c, ContainerOptions{}, func() { + Stack(c, StackOptions{Gap: SpaceM}, func() { + ui.Text(c, "The Grand Hall").FontSize(18) + Divider(c, DividerOptions{Ornament: true}) + ui.Text(c, "Content scrolls on its own, apart from the sidebar.") + }) + }) + }, + StatusBar: func() { StatusBar(c, StatusBarOptions{Left: []StatusItem{{ID: "s", Text: "Ready"}}}) }, + }) + }) +} diff --git a/aspect_ratio.go b/aspect_ratio.go new file mode 100644 index 0000000..d886a64 --- /dev/null +++ b/aspect_ratio.go @@ -0,0 +1,49 @@ +package mujica + +import "github.com/egoist/mygo/ui" + +// AspectRatioOptions configure an AspectRatio. +type AspectRatioOptions struct { + // Ratio is width over height; zero is 1. + Ratio float32 +} + +// AspectRatio fills its parent's box and centers a frame of Ratio inside, as large as fits. +// The child is centered in the frame without stretching and clipped to it. +func AspectRatio(c *ui.Context, opts AspectRatioOptions, child func()) *ui.Element { + if opts.Ratio < 0 { + panic("mujica: AspectRatio Ratio must be positive") + } + if child == nil { + panic("mujica: AspectRatio needs a child") + } + r := opts.Ratio + if r == 0 { + r = 1 + } + outer := ui.Box(c).Fill().Center() + room := outer.Bounds() + return outer.Children(func() { + frame := ui.Box(c).AspectRatio(r).Shrink(0).Center().Clip() + was := frame.Bounds() + w := room.W + // A room whose height differs from the frame's, or a frame narrower than it, has a set height. + if room.H-was.H > 0.5 || was.H-room.H > 0.5 || was.W < room.W-0.5 { + w = min(w, room.H*r) + } + if w <= 0 { + // Not laid out yet: lay out once, then size from the room. + frame.WidthPercent(100) + if first := ui.Local(outer, "aspect_ratio.first", func() bool { return true }); *first { + *first = false + c.Invalidate() + } + } else { + frame.Width(w) + if w-was.W > 0.5 || was.W-w > 0.5 { + c.Invalidate() + } + } + frame.Children(child) + }) +} diff --git a/aspect_ratio_test.go b/aspect_ratio_test.go new file mode 100644 index 0000000..e2f485f --- /dev/null +++ b/aspect_ratio_test.go @@ -0,0 +1,113 @@ +package mujica + +import ( + "testing" + + "github.com/egoist/mygo/ui" +) + +// aspectRatioBounds returns the frame (via a filling child) and a fixed 40x20 child. +func aspectRatioBounds(t *testing.T, ratio float32, w, h int) (box, child ui.Rect) { + t.Helper() + ui.NewTester(func(c *ui.Context) { + Use(c, Settings{}) + ui.Column(c).Size(float32(w), float32(h)).Children(func() { + AspectRatio(c, AspectRatioOptions{Ratio: ratio}, func() { + box = ui.Box(c).Fill().Absolute().Bounds() + child = ui.Box(c).Size(40, 20).Bounds() + }) + }) + }, 600, 600).Frame() + return box, child +} + +func TestAspectRatioWidthBound(t *testing.T) { + box, _ := aspectRatioBounds(t, 16.0/9, 320, 400) + if box.W != 320 || box.H != 180 { + t.Fatalf("box %vx%v, want 320x180", box.W, box.H) + } +} + +func TestAspectRatioHeightBound(t *testing.T) { + box, _ := aspectRatioBounds(t, 2, 400, 100) + if box.W != 200 || box.H != 100 { + t.Fatalf("box %vx%v, want 200x100", box.W, box.H) + } +} + +func TestAspectRatioDefaultSquareAndUnstretchedChild(t *testing.T) { + box, child := aspectRatioBounds(t, 0, 200, 400) + if box.W != 200 || box.H != 200 { + t.Fatalf("box %vx%v, want 200x200", box.W, box.H) + } + if child.W != 40 || child.H != 20 { + t.Fatalf("child stretched to %vx%v", child.W, child.H) + } + if child.X != box.X+80 || child.Y != box.Y+90 { + t.Fatalf("child at %v,%v not centered in %+v", child.X, child.Y, box) + } +} + +func TestAspectRatioFollowsWindowResize(t *testing.T) { + var box ui.Rect + tt := ui.NewTester(func(c *ui.Context) { + Use(c, Settings{}) + ui.Column(c).Fill().Children(func() { + AspectRatio(c, AspectRatioOptions{Ratio: 2}, func() { + box = ui.Box(c).Fill().Absolute().Bounds() + }) + }) + }, 400, 100) + if box.W != 200 || box.H != 100 { + t.Fatalf("short window: %vx%v, want 200x100", box.W, box.H) + } + tt.SetSize(400, 400) + tt.Frame() + if box.W != 400 || box.H != 200 { + t.Fatalf("tall window: %vx%v, want 400x200", box.W, box.H) + } + tt.SetSize(300, 400) + tt.Frame() + if box.W != 300 || box.H != 150 { + t.Fatalf("narrow window: %vx%v, want 300x150", box.W, box.H) + } +} + +func TestAspectRatioRejectsNegative(t *testing.T) { + defer func() { + if recover() == nil { + t.Fatal("negative ratio did not panic") + } + }() + ui.NewTester(func(c *ui.Context) { + Use(c, Settings{}) + AspectRatio(c, AspectRatioOptions{Ratio: -1}, func() {}) + }, 100, 100) +} + +func TestAspectRatioSnapshot(t *testing.T) { + snapshot(t, "aspect_ratio", 420, 160, func(c *ui.Context) { + k := Tokens(c) + ui.Row(c).Padding(16).Gap(16).Height(160).AlignItems(ui.Stretch).Children(func() { + for _, r := range []float32{1, 16.0 / 9, 3.0 / 4} { + ui.Box(c).Width(120).Children(func() { + AspectRatio(c, AspectRatioOptions{Ratio: r}, func() { + ui.Box(c).Fill().Center().Background(k.Surface).Border(1, k.Border).Radius(4).Children(func() { + ui.Text(c, aspectRatioLabel(r)).TextColor(k.TextMuted) + }) + }) + }) + } + }) + }) +} + +func aspectRatioLabel(r float32) string { + switch r { + case 1: + return "1 : 1" + case 3.0 / 4: + return "3 : 4" + } + return "16 : 9" +} diff --git a/container.go b/container.go new file mode 100644 index 0000000..7007853 --- /dev/null +++ b/container.go @@ -0,0 +1,85 @@ +package mujica + +import ( + "github.com/egoist/mygo/ui" + + "mujicaui/theme" +) + +// Space is a spacing step scaled by the window's density. Zero picks the component's default. +type Space int + +const ( + SpaceDefault Space = iota + SpaceNone + SpaceXS // 1 unit, 4 DIPs at default density + SpaceS // 2 units + SpaceM // 3 units + SpaceL // 4 units + SpaceXL // 6 units + SpaceXXL // 8 units +) + +// containerSpace returns s in DIPs for density d, using def when s is SpaceDefault. +func containerSpace(d theme.Density, s, def Space) float32 { + if s == SpaceDefault { + s = def + } + var u float32 + switch s { + case SpaceNone: + case SpaceXS: + u = 1 + case SpaceS: + u = 2 + case SpaceM: + u = 3 + case SpaceL: + u = 4 + case SpaceXL: + u = 6 + case SpaceXXL: + u = 8 + default: + panic("mujica: invalid Space value") + } + return u * d.Unit() +} + +// ContainerOptions configure a Container. +type ContainerOptions struct { + // Padding defaults to SpaceL. + Padding Space + // MaxWidth caps the width in DIPs; zero leaves it unbounded. + MaxWidth float32 + // Center places the container in the middle of its parent's width. + Center bool + // Fill takes the parent's whole content box. + Fill bool + // Clip cuts content that overflows the padding box. + Clip bool +} + +// Container pads its children, caps their width and can center, fill and clip. +func Container(c *ui.Context, opts ContainerOptions, children func()) *ui.Element { + if opts.MaxWidth < 0 { + panic("mujica: Container MaxWidth must not be negative") + } + if children == nil { + panic("mujica: Container needs children") + } + e := ui.Column(c).Padding(containerSpace(Density(c), opts.Padding, SpaceL)) + if opts.MaxWidth > 0 { + e.MaxWidth(opts.MaxWidth) + } + if opts.Fill { + e.Fill() + } + if opts.Center { + e.FillWidth().Margin(0, ui.Auto) + } + if opts.Clip { + e.Clip() + } + return e.Children(children) +} diff --git a/container_test.go b/container_test.go new file mode 100644 index 0000000..b3bceaa --- /dev/null +++ b/container_test.go @@ -0,0 +1,97 @@ +package mujica + +import ( + "image/color" + "testing" + + "github.com/egoist/mygo/ui" + + "mujicaui/theme" +) + +func TestContainerPaddingByDensity(t *testing.T) { + for _, tc := range []struct { + density theme.Density + pad Space + want float32 + }{{theme.Default, SpaceDefault, 16}, {theme.Compact, SpaceDefault, 12}, {theme.Comfortable, SpaceS, 10}, {theme.Default, SpaceNone, 0}} { + var outer, inner ui.Rect + ui.NewTester(func(c *ui.Context) { + Use(c, Settings{Density: tc.density}) + outer = Container(c, ContainerOptions{Padding: tc.pad}, func() { + inner = ui.Box(c).Size(10, 10).Bounds() + }).Bounds() + }, 400, 300).Frame() + if inner.X-outer.X != tc.want || inner.Y-outer.Y != tc.want { + t.Errorf("%v %v: inset %v,%v, want %v", tc.density, tc.pad, inner.X-outer.X, inner.Y-outer.Y, tc.want) + } + } +} + +func TestContainerMaxWidthCentered(t *testing.T) { + var r ui.Rect + ui.NewTester(func(c *ui.Context) { + Use(c, Settings{}) + ui.Column(c).Width(600).Children(func() { + r = Container(c, ContainerOptions{MaxWidth: 300, Center: true}, func() { ui.Text(c, "Body") }).Bounds() + }) + }, 600, 300).Frame() + if r.X != 150 || r.W != 300 { + t.Fatalf("container at x=%v w=%v, want 150 and 300", r.X, r.W) + } +} + +func TestContainerFill(t *testing.T) { + var r ui.Rect + ui.NewTester(func(c *ui.Context) { + Use(c, Settings{}) + ui.Row(c).Size(320, 200).Children(func() { + r = Container(c, ContainerOptions{Fill: true}, func() { ui.Text(c, "x") }).Bounds() + }) + }, 400, 300).Frame() + if r.W != 320 || r.H != 200 { + t.Fatalf("filled %vx%v, want 320x200", r.W, r.H) + } +} + +func TestContainerClip(t *testing.T) { + for _, clip := range []bool{false, true} { + var bg ui.Color + tt := ui.NewTester(func(c *ui.Context) { + Use(c, Settings{Mode: Light}) + bg = Tokens(c).Background + Container(c, ContainerOptions{Padding: SpaceNone, Clip: clip}, func() { + ui.Box(c).Size(200, 200).Background(ui.RGB(255, 0, 0)) + }).Size(100, 100) + }, 300, 300) + got := tt.Image().At(150, 150) + clipped := got == color.RGBA{bg.R, bg.G, bg.B, 255} + if clipped != clip { + t.Errorf("Clip %v: pixel outside %v", clip, got) + } + } +} + +func TestContainerInvalidSpacePanics(t *testing.T) { + defer func() { + if recover() == nil { + t.Fatal("invalid Space did not panic") + } + }() + ui.NewTester(func(c *ui.Context) { + Use(c, Settings{}) + Container(c, ContainerOptions{Padding: Space(99)}, func() {}) + }, 100, 100) +} + +func TestContainerSnapshot(t *testing.T) { + snapshot(t, "container", 420, 150, func(c *ui.Context) { + k := Tokens(c) + ui.Column(c).Padding(16).Children(func() { + Container(c, ContainerOptions{MaxWidth: 280, Center: true}, func() { + ui.Text(c, "Of the Court").Font(theme.SerifFont).FontSize(theme.H3Size) + ui.Text(c, "A centered container, 280 DIPs at most, padded by four units.").TextColor(k.TextMuted) + }).Background(k.Surface).Border(1, k.Border).Radius(theme.CardRadius) + }) + }) +} diff --git a/divider.go b/divider.go new file mode 100644 index 0000000..3e4bdda --- /dev/null +++ b/divider.go @@ -0,0 +1,65 @@ +package mujica + +import ( + "github.com/egoist/mygo/ui" + + "mujicaui/internal" + "mujicaui/theme" +) + +// dividerDiamond is the ornament's size in DIPs. +const dividerDiamond float32 = 7 + +// DividerOptions configure a Divider. +type DividerOptions struct { + // Vertical draws a line down a row instead of across a column. + Vertical bool + // Label sits in the middle of a horizontal divider. + Label string + // Ornament draws the line in the ornament color with a single diamond in the middle; a Label replaces the diamond. + Ornament bool +} + +// Divider is a thin separating line. It never takes focus and its ornament is not read out. +func Divider(c *ui.Context, opts DividerOptions) *ui.Element { + if opts.Vertical && opts.Label != "" { + panic("mujica: a vertical Divider cannot have a Label") + } + k := Tokens(c) + line := k.Border + if opts.Ornament { + line = k.Ornament + } + if opts.Label == "" && !opts.Ornament { + e := ui.Box(c).Background(line).Shrink(0).AlignSelf(ui.Stretch) + if opts.Vertical { + return e.Width(theme.BorderWidth) + } + return e.Height(theme.BorderWidth) + } + e := ui.Row(c).Gap(Density(c).Unit() * 2).Shrink(0).AlignSelf(ui.Stretch) + if opts.Vertical { + e.Column().AlignItems(ui.Center) + } + segment := func() { + s := ui.Box(c).Grow(1).Background(line) + if opts.Vertical { + s.Width(theme.BorderWidth) + } else { + s.Height(theme.BorderWidth) + } + } + return e.Children(func() { + segment() + if opts.Label != "" { + ui.Text(c, opts.Label).Font(theme.SerifFont).FontSize(theme.CaptionSize). + TextColor(k.TextMuted).SingleLine().Shrink(0) + } else { + ui.Box(c).Size(dividerDiamond, dividerDiamond).Shrink(0).Role(ui.RoleNone). + Draw(func(p *ui.Painter, r ui.Rect) { + p.FillPath(internal.Diamond(r.X+r.W/2, r.Y+r.H/2, r.W), k.Ornament) + }) + } + segment() + }) +} diff --git a/divider_test.go b/divider_test.go new file mode 100644 index 0000000..ab3db0c --- /dev/null +++ b/divider_test.go @@ -0,0 +1,107 @@ +package mujica + +import ( + "image/color" + "testing" + + "github.com/egoist/mygo/ui" +) + +func TestDividerSizes(t *testing.T) { + var h, v ui.Rect + ui.NewTester(func(c *ui.Context) { + Use(c, Settings{}) + ui.Column(c).Width(300).Children(func() { + h = Divider(c, DividerOptions{}).Bounds() + ui.Row(c).Height(80).Children(func() { + v = Divider(c, DividerOptions{Vertical: true}).Bounds() + }) + }) + }, 400, 300).Frame() + if h.W != 300 || h.H != 1 { + t.Fatalf("horizontal %vx%v, want 300x1", h.W, h.H) + } + if v.W != 1 || v.H != 80 { + t.Fatalf("vertical %vx%v, want 1x80", v.W, v.H) + } +} + +func TestDividerNeverFocused(t *testing.T) { + tt := ui.NewTester(func(c *ui.Context) { + Use(c, Settings{}) + ui.Column(c).Children(func() { + Divider(c, DividerOptions{Label: "Section", Ornament: true}) + Divider(c, DividerOptions{Ornament: true}) + Button(c, "OK", ButtonOptions{}) + }) + }, 400, 300) + tt.Key(0, ui.KeyTab) + if !tt.Focused("OK") { + t.Fatal("first Tab did not skip the dividers to the button") + } + if tt.Focused("Section") { + t.Fatal("divider label took the focus") + } +} + +func TestDividerLabelCentered(t *testing.T) { + tt := ui.NewTester(func(c *ui.Context) { + Use(c, Settings{}) + ui.Column(c).Width(400).Children(func() { + Divider(c, DividerOptions{Label: "Chapter II"}) + }) + }, 400, 100) + r, ok := tt.Find("Chapter II") + if !ok { + t.Fatal("label missing") + } + if mid := r.X + r.W/2; mid < 199 || mid > 201 { + t.Fatalf("label centered at %v, want 200", mid) + } +} + +func TestDividerOrnamentColor(t *testing.T) { + var want ui.Color + tt := ui.NewTester(func(c *ui.Context) { + Use(c, Settings{Mode: Light}) + want = Tokens(c).Ornament + ui.Column(c).Width(201).Padding(10, 0).Children(func() { + Divider(c, DividerOptions{Ornament: true}) + }) + }, 201, 40) + // The diamond's center sits on the line's middle. + got := tt.Image().At(100, 13) + if got != (color.RGBA{want.R, want.G, want.B, 255}) { + t.Fatalf("diamond center %v, want ornament %v", got, want) + } +} + +func TestDividerVerticalLabelPanics(t *testing.T) { + defer func() { + if recover() == nil { + t.Fatal("vertical label did not panic") + } + }() + ui.NewTester(func(c *ui.Context) { + Use(c, Settings{}) + Divider(c, DividerOptions{Vertical: true, Label: "x"}) + }, 100, 100) +} + +func TestDividerSnapshot(t *testing.T) { + snapshot(t, "divider", 420, 170, func(c *ui.Context) { + ui.Column(c).Padding(20).Gap(20).Children(func() { + Divider(c, DividerOptions{}) + Divider(c, DividerOptions{Ornament: true}) + Divider(c, DividerOptions{Label: "Chapter II"}) + Divider(c, DividerOptions{Label: "第二章", Ornament: true}) + ui.Row(c).Height(28).Gap(12).Children(func() { + ui.Text(c, "Left") + Divider(c, DividerOptions{Vertical: true}) + ui.Text(c, "Middle") + Divider(c, DividerOptions{Vertical: true, Ornament: true}) + ui.Text(c, "Right") + }) + }) + }) +} diff --git a/docs/components/001-app-shell.md b/docs/components/001-app-shell.md new file mode 100644 index 0000000..f3ccaca --- /dev/null +++ b/docs/components/001-app-shell.md @@ -0,0 +1,53 @@ +# 001 AppShell 应用外壳 + +## 用途 + +搭建桌面窗口的整体骨架:顶部标题栏、左侧侧栏、主内容区和底部状态栏四个插槽。侧栏可折叠为只含切换按钮的窄条;侧栏与主内容各自独立滚动。 + +## 最小示例 + +```go +var collapsed bool +mujica.AppShell(c, &collapsed, mujica.AppShellOptions{ + TitleBar: func() { mujica.TitleBar(c, "Court Records", mujica.TitleBarOptions{}) }, + Sidebar: func() { ui.Text(c, "Chronicles") }, + Content: func() { ui.Text(c, "The Grand Hall") }, +}) +``` + +签名:`func AppShell(c *ui.Context, collapsed *bool, opts AppShellOptions) AppShellResult` + +## 参数 + +| 字段 | 类型 | 默认值 | 含义 | +| --- | --- | --- | --- | +| `collapsed` | `*bool` | 必填 | 侧栏是否折叠,由应用持有;为 nil 时 panic。 | +| `TitleBar` | `func()` | nil | 顶部插槽,通常放 `TitleBar`。 | +| `Sidebar` | `func()` | nil | 侧栏内容,自带独立滚动;为 nil 时没有侧栏与折叠快捷键。 | +| `Content` | `func()` | 必填 | 主内容,自带独立滚动;为 nil 时 panic。 | +| `StatusBar` | `func()` | nil | 底部插槽,通常放 `StatusBar`。 | +| `SidebarWidth` | `float32` | 240 | 展开时侧栏宽度(DIP);负数 panic。 | +| `ContentScroll` | `*ui.ScrollState` | nil | 保存主内容滚动位置;nil 时按位置保留。 | + +返回 `AppShellResult`,内嵌 `*ui.Element`。 + +## 状态 + +- 展开:侧栏宽 `SidebarWidth`,顶部右侧为收起按钮。 +- 折叠:侧栏收为 `28 + 4 单位` 的窄条,只保留展开按钮。宽度变化 120 ms,减少动画时直接切换。 +- 切换按钮有悬停、按下和键盘焦点环;折叠不会重置主内容滚动位置。 + +## 事件 + +- `Toggled`:本帧侧栏被展开或折叠(按钮或快捷键),只触发一次。 + +## 键盘操作 + +- `Cmd+B`(macOS)/ `Ctrl+B`(其他平台):切换侧栏。焦点控件先处理该键,富文本等若占用此键则不会触发。 +- Tab 依次到达切换按钮、侧栏与内容中的控件;滚动区域内容溢出时可获得焦点,方向键、Page Up/Down、Home、End 滚动。 + +## 限制 + +- 窄窗口不会自动折叠,由应用根据窗口宽度设置 `*collapsed`。 +- 折叠动画期间侧栏内容立即隐藏,只有宽度过渡。 +- 快捷键固定为 Cmd/Ctrl+B,不可配置。 diff --git a/docs/components/002-title-bar.md b/docs/components/002-title-bar.md new file mode 100644 index 0000000..f7fc838 --- /dev/null +++ b/docs/components/002-title-bar.md @@ -0,0 +1,43 @@ +# 002 TitleBar 标题栏 + +## 用途 + +窗口顶部的标题行:衬线体应用标题、前后工具插槽。空白处可拖动窗口,并为系统窗口按钮留出空间。配合 `mygo.TitleBarHidden` 使用。 + +## 最小示例 + +```go +mujica.TitleBar(c, "Court Records", mujica.TitleBarOptions{ + Trailing: func() { mujica.Button(c, "Share", mujica.ButtonOptions{Variant: mujica.Ghost}) }, +}) +``` + +签名:`func TitleBar(c *ui.Context, title string, opts TitleBarOptions) *ui.Element` + +## 参数 + +| 字段 | 类型 | 默认值 | 含义 | +| --- | --- | --- | --- | +| `title` | `string` | 必填 | 标题文字,过长时单行省略。 | +| `Leading` | `func()` | nil | 标题前的工具,如侧栏开关。 | +| `Trailing` | `func()` | nil | 标题后的工具。 | + +## 状态 + +- 高度取 `c.TitleBar().Height` 与 `控件高度 + 2 单位` 中较大者(默认密度 44 DIP)。 +- 左右内边距在 `c.TitleBar().Left/Right` 之外再加 3 单位,不覆盖系统按钮;全屏时预留为 0。 +- Surface 底色,底部 1 DIP Border 分隔线。 + +## 事件 + +TitleBar 本身无事件;插槽中控件照常报告 `Clicked` 等事件。 + +## 键盘操作 + +插槽控件照常接受 Tab、Enter、Space。标题栏本身不获取焦点。 + +## 限制 + +- 拖动、双击缩放由 MyGo `DragWindow` 实现,只作用于非交互区域;需真机验证,无窗口测试无法覆盖。 +- 系统窗口按钮外观由系统决定,不换肤。 +- 普通有标题栏的窗口中 `c.TitleBar()` 为零,组件只是一行普通标题。 diff --git a/docs/components/003-status-bar.md b/docs/components/003-status-bar.md new file mode 100644 index 0000000..8e85639 --- /dev/null +++ b/docs/components/003-status-bar.md @@ -0,0 +1,63 @@ +# 003 StatusBar 状态栏 + +## 用途 + +窗口底部的状态条,分左右两组条目:状态文字、图标、进度与可点击操作。宽度不足时自动隐藏次要条目,并显示剩余数量。 + +## 最小示例 + +```go +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}}, +}) +if bar.Clicked == "bell" { + opened++ +} +``` + +签名:`func StatusBar(c *ui.Context, opts StatusBarOptions) StatusBarResult` + +## 参数 + +`StatusBarOptions` + +| 字段 | 类型 | 默认值 | 含义 | +| --- | --- | --- | --- | +| `Left` | `[]StatusItem` | nil | 左侧条目,从左到右。 | +| `Right` | `[]StatusItem` | nil | 右侧条目,从左到右。 | + +`StatusItem` + +| 字段 | 类型 | 默认值 | 含义 | +| --- | --- | --- | --- | +| `ID` | `string` | 必填 | 稳定键,也是 `Clicked` 的值;为空或重复 panic。 | +| `Text` | `string` | "" | 状态文字(12 DIP)。 | +| `Icon` | `*ui.SVG` | nil | 前置图标(14 DIP)。 | +| `Tone` | `StatusItemTone` | `StatusItemNeutral` | 语义色:Neutral、Success、Warning、Danger、Info。 | +| `ShowProgress` | `bool` | false | 显示 64 DIP 进度条。 | +| `Progress` | `float32` | 0 | 进度 0–1,超出 panic。 | +| `Action` | `bool` | false | 条目成为按钮。 | +| `Label` | `string` | "" | 无文字条目的无障碍名称;`Text` 与 `Label` 都为空时 panic。 | +| `Secondary` | `bool` | false | 空间不足时优先隐藏。 | + +## 状态 + +- 默认:Surface 底色,顶部 Border 线,组内条目以细线分隔。 +- 操作条目:悬停 SurfaceHover,按下 Selection,键盘焦点环。 +- 折叠:按“右组末项 → 左组首项”的顺序隐藏 `Secondary` 条目,直到放得下;显示 `+N`,悬停提示列出被隐藏条目,无障碍名称为“另有 N 项”。主要条目永不隐藏。 +- 进度:AccentText 填充,Border 轨道,描述为百分比。 + +## 事件 + +- `Clicked`:本帧被点击的操作条目 ID,没有则为空。一次点击只报告一次。 + +## 键盘操作 + +Tab 到达操作条目,Enter 或 Space 触发。纯文字条目不进入 Tab 序列。 + +## 限制 + +- 折叠依据上一帧的宽度和文字测量估算,窗口缩放后下一帧生效。 +- 被隐藏条目只在 `+N` 的提示中列出,提示仅悬停可见,键盘用户通过无障碍名称得知数量。 +- 不提供不确定进度动画;需要时在 `Text` 中说明。 diff --git a/docs/components/004-container.md b/docs/components/004-container.md new file mode 100644 index 0000000..abe01ee --- /dev/null +++ b/docs/components/004-container.md @@ -0,0 +1,45 @@ +# 004 Container 容器 + +## 用途 + +为一组内容提供内边距、最大宽度、水平居中、填充父级和裁剪,适合正文、设置页等需要可读行宽的区域。 + +## 最小示例 + +```go +mujica.Container(c, mujica.ContainerOptions{MaxWidth: 320, Center: true}, func() { + ui.Text(c, "A centered column of prose.") +}) +``` + +签名:`func Container(c *ui.Context, opts ContainerOptions, children func()) *ui.Element` + +## 参数 + +| 字段 | 类型 | 默认值 | 含义 | +| --- | --- | --- | --- | +| `Padding` | `Space` | `SpaceL`(4 单位,默认密度 16 DIP) | 四周内边距,随密度缩放。 | +| `MaxWidth` | `float32` | 0(不限) | 最大宽度 DIP;负数 panic。 | +| `Center` | `bool` | false | 占满父级宽度后受 `MaxWidth` 限制并水平居中。 | +| `Fill` | `bool` | false | 占满父级内容区(宽和高)。 | +| `Clip` | `bool` | false | 裁剪超出的内容。 | +| `children` | `func()` | 必填 | 子内容,纵向排列;nil 时 panic。 | + +`Space` 取值:`SpaceDefault`(组件默认)、`SpaceNone`、`SpaceXS`(1 单位)、`SpaceS`(2)、`SpaceM`(3)、`SpaceL`(4)、`SpaceXL`(6)、`SpaceXXL`(8)。单位为 Compact 3、Default 4、Comfortable 5 DIP。非法值 panic。 + +## 状态 + +布局组件,无交互状态。不绘制背景,可在返回的元素上调用 `Background`、`Border`。 + +## 事件 + +无。 + +## 键盘操作 + +不获取焦点。 + +## 限制 + +- 只做水平居中;垂直居中请用 `Stack` 的对齐或 MyGo `Center()`。 +- 子内容固定纵向排列,需要横向时在内部放 `Stack`。 diff --git a/docs/components/005-stack.md b/docs/components/005-stack.md new file mode 100644 index 0000000..bb2e7c9 --- /dev/null +++ b/docs/components/005-stack.md @@ -0,0 +1,49 @@ +# 005 Stack 堆叠 + +## 用途 + +按横向或纵向排列子元素,统一间距、对齐与换行。用 `StackItem` 包裹可隐藏的子项,隐藏时不占位置也不留间距。 + +## 最小示例 + +```go +mujica.Stack(c, mujica.StackOptions{Horizontal: true, Gap: mujica.SpaceM}, func() { + mujica.Button(c, "Save", mujica.ButtonOptions{}) + mujica.StackItem(!showDraft, func() { mujica.Button(c, "Draft", mujica.ButtonOptions{Variant: mujica.Secondary}) }) + mujica.Button(c, "Cancel", mujica.ButtonOptions{Variant: mujica.Ghost}) +}) +``` + +签名: + +- `func Stack(c *ui.Context, opts StackOptions, children func()) *ui.Element` +- `func StackItem(hidden bool, build func())` + +## 参数 + +| 字段 | 类型 | 默认值 | 含义 | +| --- | --- | --- | --- | +| `Horizontal` | `bool` | false | 横向排列;否则纵向。 | +| `Gap` | `Space` | `SpaceS`(2 单位) | 子项间距,随密度缩放。 | +| `Align` | `StackAlign` | `StackAlignAuto` | 交叉轴对齐:Auto(纵向拉伸、横向居中)、Start、Center、End、Stretch;非法值 panic。 | +| `Justify` | `ui.Align` | `ui.Start` | 主轴分布:Start、Center、End、SpaceBetween、SpaceAround、SpaceEvenly。 | +| `Wrap` | `bool` | false | 横向换行;纵向时设置会 panic。 | + +`StackItem`:`hidden` 为 true 时不构建 `build`,因此既不占空间也不产生间距。`build` 为 nil 时 panic。 + +## 状态 + +布局组件,无交互状态。 + +## 事件 + +无。 + +## 键盘操作 + +不获取焦点;Tab 顺序即子项构建顺序。 + +## 限制 + +- 隐藏项不会被构建,其内部界面状态(如未提交的输入)随之丢弃;需要保留时把状态放在应用中。 +- 只有横向支持换行。 diff --git a/docs/components/006-grid.md b/docs/components/006-grid.md new file mode 100644 index 0000000..c22bac8 --- /dev/null +++ b/docs/components/006-grid.md @@ -0,0 +1,57 @@ +# 006 Grid 网格 + +## 用途 + +按行列排布子元素:列宽可为固定尺寸、按内容或按比例分配剩余空间;子项可指定位置与跨行跨列。基于 MyGo 网格布局。 + +## 最小示例 + +```go +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") }) + ui.Text(c, "Name") + ui.Text(c, "Ada") +}) +``` + +签名: + +- `func Grid(c *ui.Context, opts GridOptions, children func()) *ui.Element` +- `func GridCell(c *ui.Context, opts GridCellOptions, build func()) *ui.Element` + +## 参数 + +`GridOptions` + +| 字段 | 类型 | 默认值 | 含义 | +| --- | --- | --- | --- | +| `Columns` | `[]ui.Track` | 一列 `Fr(1)` | 各列尺寸:`ui.Fixed(dip)`、`ui.Fr(n)`(分配剩余空间)、`ui.FitContent()`。 | +| `Rows` | `[]ui.Track` | nil | 前几行尺寸;之后自动添加的行按内容高度。 | +| `Gap` | `Space` | `SpaceM`(3 单位) | 列间距,也是默认行间距。 | +| `RowGap` | `Space` | 同 `Gap` | 行间距。 | + +`GridCellOptions` + +| 字段 | 类型 | 默认值 | 含义 | +| --- | --- | --- | --- | +| `Column`, `Row` | `int` | 0(自动放置) | 起始列、行,从 1 计;负数 panic。 | +| `ColumnSpan`, `RowSpan` | `int` | 1 | 跨越轨道数;-1 跨到最后一条;小于 -1 panic。 | + +`GridCell` 内的单个子元素会拉伸铺满整个单元格,与网格直接子项一致。 + +## 状态 + +布局组件,无交互状态。 + +## 事件 + +无。 + +## 键盘操作 + +不获取焦点;Tab 顺序为构建顺序。 + +## 限制 + +- 网格会构建全部子项;大量条目请使用虚拟化列表。 +- 不校验跨度是否超出列数,超出时由 MyGo 按其规则扩展。 diff --git a/docs/components/007-scroll-area.md b/docs/components/007-scroll-area.md new file mode 100644 index 0000000..947ab19 --- /dev/null +++ b/docs/components/007-scroll-area.md @@ -0,0 +1,49 @@ +# 007 ScrollArea 滚动区域 + +## 用途 + +纵向、横向或双向滚动内容。支持滚轮、触控板、滚动条拖动与键盘;可读取、设置并保留滚动位置。基于 MyGo `ui.Scroll`。 + +## 最小示例 + +```go +var offset ui.ScrollState +mujica.ScrollArea(c, &offset, mujica.ScrollAreaOptions{Label: "Log"}, func() { + for range 50 { + ui.Text(c, "A line of the log") + } +}).Height(200) +``` + +签名:`func ScrollArea(c *ui.Context, state *ui.ScrollState, opts ScrollAreaOptions, children func()) *ui.Element` + +## 参数 + +| 字段 | 类型 | 默认值 | 含义 | +| --- | --- | --- | --- | +| `state` | `*ui.ScrollState` | nil | 滚动位置。设置 `X`/`Y` 即滚动到该处,`math.MaxFloat32` 表示末尾;组件写回 `MaxX`/`MaxY`。nil 时按区域在视图中的位置保留。 | +| `Direction` | `ScrollDirection` | `ScrollVertical` | `ScrollVertical`、`ScrollHorizontal`、`ScrollBoth`;非法值 panic。 | +| `Label` | `string` | "" | 区域的无障碍名称。 | +| `children` | `func()` | 必填 | 内容;nil 时 panic。 | + +需给区域尺寸(`Height`、`Grow` 等)。 + +## 状态 + +- 内容溢出时区域可获得焦点,键盘焦点环画在区域内侧(2 DIP,Focus 色),避免被父级裁剪。 +- 不溢出时不进入 Tab 序列。 +- 滚动条为 MyGo 叠加式滑块,颜色取主题 Scrollbar(TextMuted 50%),指针悬停时显示,不占布局空间。 +- 切换主题、折叠周围布局都不重置位置。 + +## 事件 + +无独立事件;比较 `state.X`/`state.Y` 的变化即可得知滚动。 + +## 键盘操作 + +焦点在区域或其内容上时:方向键滚动 40 DIP,Page Up/Down 与 Space/Shift+Space 翻页,Home/End 到顶部/底部(macOS 另有 Cmd+↑/↓)。文本输入等自行处理这些键的控件优先。 + +## 限制 + +- 滚动条只在悬停时显示,不提供常驻滚动条(MyGo 滚动条不占空间的设计)。 +- 大量行请使用虚拟化列表,ScrollArea 会构建全部子项。 diff --git a/docs/components/008-split-pane.md b/docs/components/008-split-pane.md new file mode 100644 index 0000000..ea1e9b6 --- /dev/null +++ b/docs/components/008-split-pane.md @@ -0,0 +1,64 @@ +# 008 SplitPane 分栏 + +## 用途 + +将区域分为左右(或上下)两栏,中间分隔条可拖动、可用键盘调整;两栏有各自最小尺寸,可指定一栏可折叠。 + +## 最小示例 + +```go +split := mujica.SplitPaneState{Size: 160} +mujica.SplitPane(c, &split, mujica.SplitPaneOptions{MinFirst: 120, Collapsible: mujica.SplitPaneFirst}, + func() { ui.Text(c, "Files") }, + func() { ui.Text(c, "Editor") }, +).Fill() +``` + +签名:`func SplitPane(c *ui.Context, state *SplitPaneState, opts SplitPaneOptions, first, second func()) SplitPaneResult` + +## 参数 + +`SplitPaneState`(应用持有,nil 时 panic) + +| 字段 | 类型 | 默认值 | 含义 | +| --- | --- | --- | --- | +| `Size` | `float32` | 0(首次布局后取一半) | 第一栏宽度(纵向时为高度),DIP。每帧被限制在最小尺寸之间。 | +| `Collapsed` | `bool` | false | 可折叠栏是否折叠;折叠时保留 `Size` 以便恢复。未设置 `Collapsible` 却为 true 时 panic。 | + +`SplitPaneOptions` + +| 字段 | 类型 | 默认值 | 含义 | +| --- | --- | --- | --- | +| `Vertical` | `bool` | false | 第一栏在上。 | +| `MinFirst`, `MinSecond` | `float32` | 10 单位(默认 40 DIP) | 两栏最小尺寸;负数 panic。 | +| `Collapsible` | `SplitPaneSide` | `SplitPaneNeither` | 可折叠的栏:`SplitPaneFirst` 或 `SplitPaneSecond`。 | + +返回 `SplitPaneResult`,内嵌 `*ui.Element`。 + +## 状态 + +- 分隔条为 1 DIP ControlBorder 线,命中区域 1.5 单位宽;悬停或拖动时变为 AccentText;键盘焦点时在命中区域外画焦点环。 +- 折叠时该栏不构建,分隔条贴边并显示一段短标记,提示可从此处拉开。 +- 无障碍:角色为 splitter,名称“分隔条”,描述为第一栏所占百分比或“已折叠”。 + +## 事件 + +- `Resized`:本帧尺寸改变(拖动或键盘)。 +- `Toggled`:本帧折叠或展开。 + +## 键盘操作 + +Tab 聚焦分隔条后: + +- ←/→(纵向为 ↑/↓):移动 4 单位,到达最小尺寸后停止。 +- 可折叠栏已在最小尺寸时继续朝它按方向键:折叠;折叠后按反方向键:展开。 +- Home / End:移到最小 / 最大位置。 +- Enter:切换可折叠栏(未设置 `Collapsible` 时不占用 Enter)。 + +指针:拖动分隔条;拖到可折叠栏最小尺寸的一半以内即折叠,拖回超过一半即展开;双击切换折叠。 + +## 限制 + +- 未直接使用 MyGo `ui.Split`:它的最小栏宽固定为 10 个间距单位(`ui/split.go:20`),没有折叠能力,且分隔条元素不对外暴露,无法挂接 Enter、Home、End。本组件用 MyGo 公开的 `Focusable`、`Dragged`、`Shortcut`、`RoleSplitter` 重新组合,行为与 MyGo 分隔条一致并补齐上述功能。 +- MyGo 没有公开的无障碍数值范围接口,当前位置通过描述文字提供。 +- 键盘或拖动产生的尺寸在下一帧生效(组件会立即请求下一帧)。 diff --git a/docs/components/009-aspect-ratio.md b/docs/components/009-aspect-ratio.md new file mode 100644 index 0000000..5992bd9 --- /dev/null +++ b/docs/components/009-aspect-ratio.md @@ -0,0 +1,41 @@ +# 009 AspectRatio 宽高比 + +## 用途 + +在父容器的宽高约束内放置一个保持指定宽高比的框,尽可能大并居中;子内容在框内居中,不被拉伸,超出部分裁剪。适合预览、视频占位、封面图。 + +## 最小示例 + +```go +ui.Box(c).Size(320, 240).Children(func() { + mujica.AspectRatio(c, mujica.AspectRatioOptions{Ratio: 16.0 / 9}, func() { + ui.Text(c, "16 : 9") + }) +}) +``` + +签名:`func AspectRatio(c *ui.Context, opts AspectRatioOptions, child func()) *ui.Element` + +## 参数 + +| 字段 | 类型 | 默认值 | 含义 | +| --- | --- | --- | --- | +| `Ratio` | `float32` | 1 | 宽 / 高;负数 panic。 | +| `child` | `func()` | 必填 | 框内内容;nil 时 panic。需要铺满框时让子元素 `Fill()`。 | + +## 状态 + +布局组件,无交互状态。返回的元素占满父级内容区,比例框位于其中央。 + +## 事件 + +无。 + +## 键盘操作 + +不获取焦点。 + +## 限制 + +- 父级高度不确定(随内容增长)时只按宽度计算。 +- 框尺寸依据上一帧的父级尺寸计算,窗口缩放后组件请求下一帧完成调整(MyGo 布局的 `AspectRatio` 只能由宽推高,不能同时受宽高约束)。 diff --git a/docs/components/010-divider.md b/docs/components/010-divider.md new file mode 100644 index 0000000..bc938df --- /dev/null +++ b/docs/components/010-divider.md @@ -0,0 +1,43 @@ +# 010 Divider 分隔线 + +## 用途 + +横向或纵向的细分隔线;横向可在中间放文字;可选旧金色单个菱形装饰。永不获取焦点,装饰不进入无障碍树。 + +## 最小示例 + +```go +ui.Column(c).Padding(16).Gap(12).Children(func() { + mujica.Divider(c, mujica.DividerOptions{Ornament: true}) + mujica.Divider(c, mujica.DividerOptions{Label: "Chapter II"}) +}) +``` + +签名:`func Divider(c *ui.Context, opts DividerOptions) *ui.Element` + +## 参数 + +| 字段 | 类型 | 默认值 | 含义 | +| --- | --- | --- | --- | +| `Vertical` | `bool` | false | 纵向,放在横向行中,高度随行拉伸。 | +| `Label` | `string` | "" | 横向分隔线中间的文字(衬线 12 DIP,TextMuted)。纵向带文字时 panic。 | +| `Ornament` | `bool` | false | 线条改用 Ornament 色,并在中间画一个 7 DIP 菱形;有 `Label` 时文字取代菱形。 | + +## 状态 + +- 默认:1 DIP Border 色细线。 +- 装饰:Ornament 色线与菱形。 +- 无悬停、焦点等交互状态。 + +## 事件 + +无。 + +## 键盘操作 + +不获取焦点,Tab 直接跳过。 + +## 限制 + +- 方向不会根据父级自动判断,需要显式设置 `Vertical`。 +- 纵向不支持文字。 diff --git a/example_layout_test.go b/example_layout_test.go new file mode 100644 index 0000000..2926e55 --- /dev/null +++ b/example_layout_test.go @@ -0,0 +1,129 @@ +package mujica_test + +import ( + "github.com/egoist/mygo/ui" + + mujica "mujicaui" + "mujicaui/icons" +) + +func ExampleAppShell() { + var collapsed bool + view := func(c *ui.Context) { + mujica.Use(c, mujica.Settings{}) + mujica.AppShell(c, &collapsed, mujica.AppShellOptions{ + TitleBar: func() { mujica.TitleBar(c, "Court Records", mujica.TitleBarOptions{}) }, + Sidebar: func() { ui.Text(c, "Chronicles") }, + Content: func() { ui.Text(c, "The Grand Hall") }, + }) + } + ui.Render(view, 400, 300, 1) +} + +func ExampleTitleBar() { + view := func(c *ui.Context) { + mujica.Use(c, mujica.Settings{}) + mujica.TitleBar(c, "Court Records", mujica.TitleBarOptions{ + Trailing: func() { mujica.Button(c, "Share", mujica.ButtonOptions{Variant: mujica.Ghost}) }, + }) + } + ui.Render(view, 400, 300, 1) +} + +func ExampleStatusBar() { + opened := 0 + view := func(c *ui.Context) { + mujica.Use(c, mujica.Settings{}) + 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}}, + }) + if bar.Clicked == "bell" { + opened++ + } + } + ui.Render(view, 400, 300, 1) +} + +func ExampleContainer() { + view := func(c *ui.Context) { + mujica.Use(c, mujica.Settings{}) + mujica.Container(c, mujica.ContainerOptions{MaxWidth: 320, Center: true}, func() { + ui.Text(c, "A centered column of prose.") + }) + } + ui.Render(view, 400, 300, 1) +} + +func ExampleStack() { + showDraft := false + view := func(c *ui.Context) { + mujica.Use(c, mujica.Settings{}) + mujica.Stack(c, mujica.StackOptions{Horizontal: true, Gap: mujica.SpaceM}, func() { + mujica.Button(c, "Save", mujica.ButtonOptions{}) + mujica.StackItem(!showDraft, func() { mujica.Button(c, "Draft", mujica.ButtonOptions{Variant: mujica.Secondary}) }) + mujica.Button(c, "Cancel", mujica.ButtonOptions{Variant: mujica.Ghost}) + }) + } + ui.Render(view, 400, 300, 1) +} + +func ExampleGrid() { + view := func(c *ui.Context) { + mujica.Use(c, mujica.Settings{}) + 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") }) + ui.Text(c, "Name") + ui.Text(c, "Ada") + }) + } + ui.Render(view, 400, 300, 1) +} + +func ExampleScrollArea() { + var offset ui.ScrollState + view := func(c *ui.Context) { + mujica.Use(c, mujica.Settings{}) + mujica.ScrollArea(c, &offset, mujica.ScrollAreaOptions{Label: "Log"}, func() { + for range 50 { + ui.Text(c, "A line of the log") + } + }).Height(200) + } + ui.Render(view, 400, 300, 1) +} + +func ExampleSplitPane() { + split := mujica.SplitPaneState{Size: 160} + view := func(c *ui.Context) { + mujica.Use(c, mujica.Settings{}) + mujica.SplitPane(c, &split, mujica.SplitPaneOptions{MinFirst: 120, Collapsible: mujica.SplitPaneFirst}, + func() { ui.Text(c, "Files") }, + func() { ui.Text(c, "Editor") }, + ).Fill() + } + ui.Render(view, 400, 300, 1) +} + +func ExampleAspectRatio() { + view := func(c *ui.Context) { + mujica.Use(c, mujica.Settings{}) + ui.Box(c).Size(320, 240).Children(func() { + mujica.AspectRatio(c, mujica.AspectRatioOptions{Ratio: 16.0 / 9}, func() { + ui.Text(c, "16 : 9") + }) + }) + } + ui.Render(view, 400, 300, 1) +} + +func ExampleDivider() { + view := func(c *ui.Context) { + mujica.Use(c, mujica.Settings{}) + ui.Column(c).Padding(16).Gap(12).Children(func() { + mujica.Divider(c, mujica.DividerOptions{Ornament: true}) + mujica.Divider(c, mujica.DividerOptions{Label: "Chapter II"}) + }) + } + ui.Render(view, 400, 300, 1) +} diff --git a/examples/gallery/pages_layout.go b/examples/gallery/pages_layout.go index 9d315c6..cb295e1 100644 --- a/examples/gallery/pages_layout.go +++ b/examples/gallery/pages_layout.go @@ -1,3 +1,499 @@ package main -var layoutPages = []page{} +import ( + "fmt" + "math" + "slices" + + "github.com/egoist/mygo/ui" + + mujica "mujicaui" + "mujicaui/icons" +) + +var layoutPages = []page{ + {ID: "001", Name: "AppShell", ZhName: "应用外壳", Category: "Layout", Demo: layoutAppShellDemo, Code: layoutAppShellCode, + Keys: "Cmd/Ctrl+B or the toggle button folds the sidebar. Tab moves between the toggle, the sidebar and the content; arrows, Page Up/Down, Home and End scroll the focused region."}, + {ID: "002", Name: "TitleBar", ZhName: "标题栏", Category: "Layout", Demo: layoutTitleBarDemo, Code: layoutTitleBarCode, + Keys: "Tools in the slots take Tab and Enter/Space as usual. Dragging the empty bar moves the window; double-click zooms it."}, + {ID: "003", Name: "StatusBar", ZhName: "状态栏", Category: "Layout", Demo: layoutStatusBarDemo, Code: layoutStatusBarCode, + Keys: "Tab reaches action items; Enter or Space activates them. Plain status text is not a tab stop."}, + {ID: "004", Name: "Container", ZhName: "容器", Category: "Layout", Demo: layoutContainerDemo, Code: layoutContainerCode, + Keys: "None: Container never takes the focus."}, + {ID: "005", Name: "Stack", ZhName: "堆叠", Category: "Layout", Demo: layoutStackDemo, Code: layoutStackCode, + Keys: "None: Stack never takes the focus; Tab follows the children's order."}, + {ID: "006", Name: "Grid", ZhName: "网格", Category: "Layout", Demo: layoutGridDemo, Code: layoutGridCode, + Keys: "None: Grid never takes the focus; Tab follows the build order."}, + {ID: "007", Name: "ScrollArea", ZhName: "滚动区域", Category: "Layout", Demo: layoutScrollAreaDemo, Code: layoutScrollAreaCode, + Keys: "When the content overflows, Tab focuses the area; arrows scroll by a line, Page Up/Down and Space by a page, Home and End to the ends."}, + {ID: "008", Name: "SplitPane", ZhName: "分栏", Category: "Layout", Demo: layoutSplitPaneDemo, Code: layoutSplitPaneCode, + Keys: "Tab focuses the divider. Left/Right (Up/Down when vertical) move it by 4 units, Home and End to the limits, Enter folds the collapsible pane; an arrow past its minimum folds it, the opposite arrow opens it."}, + {ID: "009", Name: "AspectRatio", ZhName: "宽高比", Category: "Layout", Demo: layoutAspectRatioDemo, Code: layoutAspectRatioCode, + Keys: "None: AspectRatio never takes the focus."}, + {ID: "010", Name: "Divider", ZhName: "分隔线", Category: "Layout", Demo: layoutDividerDemo, Code: layoutDividerCode, + Keys: "None: Divider and its ornament never take the focus."}, +} + +// layoutCheck is a check box that logs its changes. +func layoutCheck(c *ui.Context, v *bool, label string, log func(string)) { + was := *v + ui.Checkbox(c, v, label) + if *v != was { + log(fmt.Sprintf("%s: %v", label, *v)) + } +} + +// layoutChoice is a labeled drop-down that logs its changes. +func layoutChoice(c *ui.Context, v *string, label string, options []string, log func(string)) { + was := *v + ui.Row(c).Gap(8).Children(func() { + ui.Text(c, label) + ui.Select(c, v, options) + }) + if *v != was { + log(fmt.Sprintf("%s: %s", label, *v)) + } +} + +// layoutSlider is a labeled slider that logs whole-number changes. +func layoutSlider(c *ui.Context, v *float64, label string, lo, hi float64, log func(string)) { + was := math.Round(*v) + ui.Row(c).Gap(8).Children(func() { + ui.Text(c, fmt.Sprintf("%s %.0f", label, *v)) + ui.Slider(c, v, lo, hi).Width(140) + }) + if math.Round(*v) != was { + log(fmt.Sprintf("%s: %.0f", label, *v)) + } +} + +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 { + k := mujica.Tokens(c) + return ui.Column(c).Height(h).Border(1, k.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)) } + +const layoutAppShellCode = `var collapsed bool +mujica.AppShell(c, &collapsed, mujica.AppShellOptions{ + TitleBar: func() { mujica.TitleBar(c, "Court Records", mujica.TitleBarOptions{}) }, + Sidebar: func() { ui.Text(c, "Chronicles") }, + Content: func() { ui.Text(c, "The Grand Hall") }, +})` + +func layoutAppShellDemo(c *ui.Context, log func(string)) { + type state struct { + collapsed, title, status bool + width float64 + scroll ui.ScrollState + } + s := ui.Local(c.Root(), "001", func() state { return state{title: true, status: true, width: 220} }) + layoutControls(c, func() { + layoutCheck(c, &s.title, "Title bar", log) + layoutCheck(c, &s.status, "Status bar", log) + layoutCheck(c, &s.collapsed, "Collapsed", log) + layoutSlider(c, &s.width, "Sidebar width", 160, 320, log) + }) + opts := mujica.AppShellOptions{ + SidebarWidth: float32(s.width), + ContentScroll: &s.scroll, + Sidebar: func() { + for _, n := range []string{"Chronicles", "Letters", "Decrees", "Portraits", "Ledgers"} { + ui.Text(c, n).Padding(6, 12) + } + }, + Content: func() { + mujica.Container(c, mujica.ContainerOptions{}, func() { + for i := range 30 { + ui.Text(c, fmt.Sprintf("Entry %d of the court record", i+1)).Padding(4, 0) + } + }) + }, + } + if s.title { + opts.TitleBar = func() { mujica.TitleBar(c, "Court Records", mujica.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)}}}) + } + } + ui.Column(c).Margin(16, 0, 0, 0).Children(func() { + layoutFrame(c, 360, func() { + if mujica.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}) }, +})` + +func layoutTitleBarDemo(c *ui.Context, log func(string)) { + type state struct { + leading, trailing bool + title string + } + s := ui.Local(c.Root(), "002", func() state { return state{trailing: true, title: "Court Records"} }) + layoutControls(c, func() { + layoutCheck(c, &s.leading, "Leading tool", log) + 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 + if s.leading { + opts.Leading = func() { + if mujica.Button(c, "", mujica.ButtonOptions{Variant: mujica.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() { + 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) }) + }) + 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) +} + +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}}, +}) +if bar.Clicked == "bell" { + opened++ +}` + +func layoutStatusBarDemo(c *ui.Context, log func(string)) { + type state struct { + width, progress float64 + showProgress bool + tone string + } + s := ui.Local(c.Root(), "003", func() state { return state{width: 640, progress: 40, showProgress: true, tone: "Warning"} }) + layoutControls(c, func() { + layoutSlider(c, &s.width, "Width", 240, 720, log) + layoutCheck(c, &s.showProgress, "Progress", log) + 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{ + {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{ + {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 != "" { + 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) +} + +const layoutContainerCode = `mujica.Container(c, mujica.ContainerOptions{MaxWidth: 320, Center: true}, func() { + ui.Text(c, "A centered column of prose.") +})` + +func layoutContainerDemo(c *ui.Context, log func(string)) { + type state struct { + maxWidth float64 + padding string + center, fill, clip, wide bool + } + s := ui.Local(c.Root(), "004", func() state { return state{maxWidth: 360, padding: "Default", center: true} }) + layoutControls(c, func() { + layoutSlider(c, &s.maxWidth, "Max width", 0, 600, log) + layoutChoice(c, &s.padding, "Padding", layoutSpaces, log) + layoutCheck(c, &s.center, "Center", log) + layoutCheck(c, &s.fill, "Fill", log) + layoutCheck(c, &s.clip, "Clip", log) + layoutCheck(c, &s.wide, "Overflowing child", log) + }) + k := mujica.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), + 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) + if s.wide { + ui.Box(c).Size(900, 24).Background(k.Selection).Shrink(0) + } + }).Background(k.Surface).Border(1, k.Border) + }) + }) +} + +const layoutStackCode = `mujica.Stack(c, mujica.StackOptions{Horizontal: true, Gap: mujica.SpaceM}, func() { + mujica.Button(c, "Save", mujica.ButtonOptions{}) + mujica.StackItem(!showDraft, func() { mujica.Button(c, "Draft", mujica.ButtonOptions{Variant: mujica.Secondary}) }) + mujica.Button(c, "Cancel", mujica.ButtonOptions{Variant: mujica.Ghost}) +})` + +func layoutStackDemo(c *ui.Context, log func(string)) { + type state struct { + horizontal, wrap, hide bool + gap, align, justify string + } + s := ui.Local(c.Root(), "005", func() state { + return state{horizontal: true, gap: "Default", align: "Auto", justify: "Start"} + }) + aligns := []string{"Auto", "Start", "Center", "End", "Stretch"} + justifies := map[string]ui.Align{"Start": ui.Start, "Center": ui.Center, "End": ui.End, "SpaceBetween": ui.SpaceBetween} + layoutControls(c, func() { + layoutCheck(c, &s.horizontal, "Horizontal", log) + s.wrap = s.wrap && s.horizontal + layoutCheck(c, &s.wrap, "Wrap", log) + layoutCheck(c, &s.hide, "Hide Draft", log) + layoutChoice(c, &s.gap, "Gap", layoutSpaces, log) + 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)) + 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), + Align: align, Justify: justifies[s.justify]}, func() { + for _, n := range []string{"Save", "Draft", "Publish", "Archive", "Cancel"} { + mujica.StackItem(s.hide && n == "Draft", func() { + if mujica.Button(c, n, mujica.ButtonOptions{Variant: mujica.Secondary}).Clicked() { + log(n + " clicked") + } + }) + } + }).Grow(1).Padding(12) + }) + }) +} + +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") }) + ui.Text(c, "Name") + ui.Text(c, "Ada") +})` + +func layoutGridDemo(c *ui.Context, log func(string)) { + type state struct { + first, gap, rowGap string + header, tall bool + } + s := ui.Local(c.Root(), "006", func() state { + return state{first: "Fixed 120", gap: "Default", rowGap: "Default", header: true, tall: true} + }) + tracks := map[string]ui.Track{"Fixed 120": ui.Fixed(120), "Fit content": ui.FitContent(), "Fr 1": ui.Fr(1), "Fr 2": ui.Fr(2)} + layoutControls(c, func() { + layoutChoice(c, &s.first, "First column", []string{"Fixed 120", "Fit content", "Fr 1", "Fr 2"}, log) + layoutChoice(c, &s.gap, "Gap", layoutSpaces, log) + layoutChoice(c, &s.rowGap, "Row gap", layoutSpaces, log) + layoutCheck(c, &s.header, "Header spans all", log) + layoutCheck(c, &s.tall, "First cell spans 2 rows", log) + }) + k := mujica.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)}, + Gap: layoutSpace(s.gap), RowGap: layoutSpace(s.rowGap)}, func() { + if s.header { + mujica.GridCell(c, mujica.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) }) + for i := range 4 { + cell(fmt.Sprintf("Cell %d", i+1)) + } + }) + }) +} + +const layoutScrollAreaCode = `var offset ui.ScrollState +mujica.ScrollArea(c, &offset, mujica.ScrollAreaOptions{Label: "Log"}, func() { + for range 50 { + ui.Text(c, "A line of the log") + } +}).Height(200)` + +func layoutScrollAreaDemo(c *ui.Context, log func(string)) { + type state struct { + direction string + offset ui.ScrollState + 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} + layoutControls(c, func() { + layoutChoice(c, &s.direction, "Direction", []string{"Vertical", "Horizontal", "Both"}, log) + if mujica.Button(c, "Top", mujica.ButtonOptions{Variant: mujica.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() { + 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() { + s.offset.X, s.offset.Y = math.MaxFloat32, math.MaxFloat32 + log("scroll to end") + } + }) + k := mujica.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() { + 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) + } + }).Height(220).Background(k.Surface).Border(1, k.Border).Radius(4) + }) + if s.offset.Y != s.lastY || s.offset.X != s.lastX { + s.lastX, s.lastY = s.offset.X, s.offset.Y + log(fmt.Sprintf("offset %.0f, %.0f", s.offset.X, s.offset.Y)) + } + ui.Text(c, fmt.Sprintf("Offset %.0f, %.0f of %.0f, %.0f", s.offset.X, s.offset.Y, s.offset.MaxX, s.offset.MaxY)). + 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}, + 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 + 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} + }) + sides := map[string]mujica.SplitPaneSide{"Neither": mujica.SplitPaneNeither, "First": mujica.SplitPaneFirst, "Second": mujica.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) + pane := func(title, body string, bg ui.Color) func() { + return func() { + ui.Column(c).Fill().Padding(12).Gap(6).Background(bg).Children(func() { + ui.Text(c, title).FontSize(16) + ui.Text(c, body).TextColor(k.TextMuted) + }) + } + } + 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), + 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)) + r.Fill() + if r.Resized { + log(fmt.Sprintf("resized to %.0f", s.split.Size)) + } + if r.Toggled { + log(fmt.Sprintf("collapsed: %v", s.split.Collapsed)) + } + }) + }) +} + +const layoutAspectRatioCode = `ui.Box(c).Size(320, 240).Children(func() { + mujica.AspectRatio(c, mujica.AspectRatioOptions{Ratio: 16.0 / 9}, func() { + ui.Text(c, "16 : 9") + }) +})` + +func layoutAspectRatioDemo(c *ui.Context, log func(string)) { + type state struct { + ratio string + width, height float64 + } + s := ui.Local(c.Root(), "009", func() state { return state{ratio: "16 : 9", width: 360, height: 220} }) + ratios := map[string]float32{"1 : 1": 1, "4 : 3": 4.0 / 3, "16 : 9": 16.0 / 9, "3 : 4": 3.0 / 4, "21 : 9": 21.0 / 9} + layoutControls(c, func() { + layoutChoice(c, &s.ratio, "Ratio", []string{"1 : 1", "4 : 3", "16 : 9", "3 : 4", "21 : 9"}, log) + layoutSlider(c, &s.width, "Parent width", 120, 560, log) + layoutSlider(c, &s.height, "Parent height", 80, 320, log) + }) + k := mujica.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() { + ui.Box(c).Fill().Center().Background(k.Surface).Border(1, k.Ornament).Children(func() { + ui.Text(c, s.ratio).TextColor(k.TextMuted) + }) + }) + }) +} + +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"}) +})` + +func layoutDividerDemo(c *ui.Context, log func(string)) { + type state struct { + vertical, ornament bool + label string + } + s := ui.Local(c.Root(), "010", func() state { return state{ornament: true, label: "None"} }) + layoutControls(c, func() { + layoutCheck(c, &s.vertical, "Vertical", log) + layoutCheck(c, &s.ornament, "Ornament", log) + if !s.vertical { + layoutChoice(c, &s.label, "Label", []string{"None", "Chapter II", "第二章"}, log) + } + }) + label := s.label + if label == "None" || s.vertical { + label = "" + } + opts := mujica.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) + ui.Text(c, "Aria") + }) + return + } + ui.Column(c).Gap(16).Children(func() { + ui.Text(c, "Overture") + mujica.Divider(c, opts) + ui.Text(c, "Aria") + }) + }) +} diff --git a/grid.go b/grid.go new file mode 100644 index 0000000..9de4fe2 --- /dev/null +++ b/grid.go @@ -0,0 +1,72 @@ +package mujica + +import "github.com/egoist/mygo/ui" + +// GridOptions configure a Grid. +type GridOptions struct { + // Columns size each column with ui.Fixed, ui.Fr or ui.FitContent; nil is one Fr(1) column. + Columns []ui.Track + // Rows size the first rows; rows added after fit their content. + Rows []ui.Track + // Gap defaults to SpaceM. + Gap Space + // RowGap defaults to Gap. + RowGap Space +} + +// Grid lays children out in columns and rows; GridCell places and spans them. +func Grid(c *ui.Context, opts GridOptions, children func()) *ui.Element { + if children == nil { + panic("mujica: Grid needs children") + } + d := Density(c) + gap := containerSpace(d, opts.Gap, SpaceM) + rowGap := gap + if opts.RowGap != SpaceDefault { + rowGap = containerSpace(d, opts.RowGap, SpaceM) + } + cols := opts.Columns + if len(cols) == 0 { + cols = []ui.Track{ui.Fr(1)} + } + e := ui.Grid(c).ColumnTracks(cols...).GapX(gap).GapY(rowGap) + if len(opts.Rows) > 0 { + e.RowTracks(opts.Rows...) + } + return e.Children(children) +} + +// GridCellOptions place one cell. Lines count from 1; zero places the cell automatically. +type GridCellOptions struct { + Column, Row int + // ColumnSpan and RowSpan default to 1; -1 spans to the last track. + ColumnSpan, RowSpan int +} + +// GridCell is one child of a Grid, placed and spanned by opts; build makes one element, stretched over the cell. +func GridCell(c *ui.Context, opts GridCellOptions, build func()) *ui.Element { + if build == nil { + panic("mujica: GridCell needs build") + } + if opts.Column < 0 || opts.Row < 0 { + panic("mujica: GridCell Column and Row count from 1") + } + if opts.ColumnSpan < -1 || opts.RowSpan < -1 { + panic("mujica: GridCell spans must be positive, or -1 for the rest") + } + // A one-track grid stretches its child over the whole cell, as the grid does its own children. + e := ui.Grid(c).Columns(1).Rows(1) + if opts.Column > 0 { + e.ColumnStart(opts.Column) + } + if opts.Row > 0 { + e.RowStart(opts.Row) + } + if opts.ColumnSpan != 0 { + e.ColumnSpan(opts.ColumnSpan) + } + if opts.RowSpan != 0 { + e.RowSpan(opts.RowSpan) + } + return e.Children(build) +} diff --git a/grid_test.go b/grid_test.go new file mode 100644 index 0000000..a04dce3 --- /dev/null +++ b/grid_test.go @@ -0,0 +1,93 @@ +package mujica + +import ( + "testing" + + "github.com/egoist/mygo/ui" +) + +func TestGridFixedAndFrTracks(t *testing.T) { + var a, b ui.Rect + ui.NewTester(func(c *ui.Context) { + Use(c, Settings{}) + Grid(c, GridOptions{Columns: []ui.Track{ui.Fixed(100), ui.Fr(1)}}, func() { + a = ui.Box(c).Height(20).Bounds() + b = ui.Box(c).Height(20).Bounds() + }).Width(400) + }, 400, 300).Frame() + if a.W != 100 || b.X != 112 || b.W != 288 { + t.Fatalf("tracks %v and %v at %v, want 100, 288 at 112", a.W, b.W, b.X) + } +} + +func TestGridSpans(t *testing.T) { + var head, tall, side ui.Rect + ui.NewTester(func(c *ui.Context) { + Use(c, Settings{}) + Grid(c, GridOptions{Columns: []ui.Track{ui.Fr(1), ui.Fr(1), ui.Fr(1)}, Gap: SpaceS, RowGap: SpaceL}, func() { + head = GridCell(c, GridCellOptions{ColumnSpan: -1}, func() { ui.Box(c).Height(20) }).Bounds() + tall = GridCell(c, GridCellOptions{RowSpan: 2}, func() { ui.Box(c).Height(10) }).Bounds() + side = GridCell(c, GridCellOptions{Column: 3, Row: 3}, func() { ui.Box(c).Height(30) }).Bounds() + GridCell(c, GridCellOptions{Column: 2, Row: 2}, func() { ui.Box(c).Height(30) }) + }).Width(316) + }, 400, 300).Frame() + if head.W != 316 { + t.Fatalf("header width %v, want 316", head.W) + } + // Rows of 30 with a 16 gap: the tall cell spans 30+16+30. + if tall.H != 76 { + t.Fatalf("row-spanning cell height %v, want 76", tall.H) + } + if side.X != 216 || side.Y != tall.Y+46 { + t.Fatalf("placed cell at %v,%v, want 216,%v", side.X, side.Y, tall.Y+46) + } +} + +func TestGridRowTracksShareHeight(t *testing.T) { + var a, b ui.Rect + ui.NewTester(func(c *ui.Context) { + Use(c, Settings{}) + Grid(c, GridOptions{Rows: []ui.Track{ui.Fixed(40), ui.Fr(1)}, Gap: SpaceNone}, func() { + a = ui.Box(c).Bounds() + b = ui.Box(c).Bounds() + }).Height(200) + }, 300, 300).Frame() + if a.H != 40 || b.H != 160 { + t.Fatalf("rows %v and %v, want 40 and 160", a.H, b.H) + } +} + +func TestGridCellRejectsBadPlacement(t *testing.T) { + for _, o := range []GridCellOptions{{Column: -1}, {RowSpan: -2}} { + func() { + defer func() { + if recover() == nil { + t.Errorf("%+v did not panic", o) + } + }() + ui.NewTester(func(c *ui.Context) { + Use(c, Settings{}) + Grid(c, GridOptions{}, func() { GridCell(c, o, func() {}) }) + }, 100, 100) + }() + } +} + +func TestGridSnapshot(t *testing.T) { + snapshot(t, "grid", 420, 200, func(c *ui.Context) { + k := Tokens(c) + cell := func(s string) { + ui.Box(c).Center().Padding(8).Background(k.Surface).Border(1, k.Border).Radius(4). + Children(func() { ui.Text(c, s).TextColor(k.TextMuted) }) + } + ui.Column(c).Padding(16).Children(func() { + Grid(c, GridOptions{Columns: []ui.Track{ui.Fixed(96), ui.Fr(1), ui.Fr(1)}}, func() { + GridCell(c, GridCellOptions{RowSpan: 2}, func() { cell("Fixed 96") }) + GridCell(c, GridCellOptions{ColumnSpan: 2}, func() { cell("Span 2") }) + cell("Fr 1") + cell("Fr 1") + GridCell(c, GridCellOptions{ColumnSpan: -1}, func() { cell("Across") }) + }) + }) + }) +} diff --git a/messages_layout.go b/messages_layout.go new file mode 100644 index 0000000..5563c39 --- /dev/null +++ b/messages_layout.go @@ -0,0 +1,12 @@ +package mujica + +func init() { + addMessages(map[string][2]string{ + "app_shell.collapse": {"Collapse sidebar", "收起侧栏"}, + "app_shell.expand": {"Expand sidebar", "展开侧栏"}, + "status_bar.label": {"Status bar", "状态栏"}, + "status_bar.hidden": {"%d more items", "另有 %d 项"}, + "split_pane.divider": {"Divider", "分隔条"}, + "split_pane.collapsed": {"Collapsed", "已折叠"}, + }) +} diff --git a/scroll_area.go b/scroll_area.go new file mode 100644 index 0000000..1802827 --- /dev/null +++ b/scroll_area.go @@ -0,0 +1,62 @@ +package mujica + +import ( + "github.com/egoist/mygo/ui" + + "mujicaui/theme" +) + +// ScrollDirection is the way a ScrollArea scrolls. The zero value is vertical. +type ScrollDirection int + +const ( + ScrollVertical ScrollDirection = iota + ScrollHorizontal + ScrollBoth +) + +// ScrollAreaOptions configure a ScrollArea. +type ScrollAreaOptions struct { + Direction ScrollDirection + // Label names the region for assistive technology. + Label string +} + +// ScrollArea scrolls its children with the wheel, its scroll bars and the keyboard. +// state keeps the offset across frames: set X or Y to scroll there, math.MaxFloat32 for the end. +// A nil state keeps the offset by the area's place in the view. +// The area takes the focus while its content overflows, so the keys reach it. +func ScrollArea(c *ui.Context, state *ui.ScrollState, opts ScrollAreaOptions, children func()) *ui.Element { + if children == nil { + panic("mujica: ScrollArea needs children") + } + var e *ui.Element + switch opts.Direction { + case ScrollVertical: + e = ui.Scroll(c) + case ScrollHorizontal: + e = ui.ScrollHorizontal(c) + case ScrollBoth: + e = ui.ScrollBoth(c) + default: + panic("mujica: invalid ScrollDirection") + } + if state == nil { + state = ui.Local(e, "scroll_area.state", func() ui.ScrollState { return ui.ScrollState{} }) + } + e.TrackScroll(state) + if state.MaxX > 0 || state.MaxY > 0 { + focus := Tokens(c).Focus + // The ring goes inside: areas often fill a clipped pane edge to edge. + e.Focusable().FocusRing(false).DrawOver(func(p *ui.Painter, r ui.Rect) { + if e.FocusVisible() { + in := theme.FocusWidth / 2 + p.Stroke(ui.Rect{X: r.X + in, Y: r.Y + in, W: r.W - 2*in, H: r.H - 2*in}, focus, theme.ControlRadius, theme.FocusWidth) + } + }) + } + if opts.Label != "" { + e.Label(opts.Label) + } + return e.Children(children) +} diff --git a/scroll_area_test.go b/scroll_area_test.go new file mode 100644 index 0000000..4beb683 --- /dev/null +++ b/scroll_area_test.go @@ -0,0 +1,118 @@ +package mujica + +import ( + "fmt" + "math" + "testing" + + "github.com/egoist/mygo/ui" +) + +func scrollAreaView(st *ui.ScrollState, opts ScrollAreaOptions, rows int) func(c *ui.Context) { + return func(c *ui.Context) { + Use(c, Settings{}) + ScrollArea(c, st, opts, func() { + for i := range rows { + ui.Text(c, fmt.Sprintf("Row %d", i)).Height(20).Width(float32(100 + 40*i)) + } + }).Size(200, 100) + } +} + +func TestScrollAreaScrollToEnd(t *testing.T) { + st := &ui.ScrollState{} + tt := ui.NewTester(scrollAreaView(st, ScrollAreaOptions{}, 20), 300, 300) + if st.MaxY != 300 { + t.Fatalf("MaxY %v, want 300", st.MaxY) + } + st.Y = math.MaxFloat32 + tt.Frame() + if st.Y != 300 { + t.Fatalf("Y %v after scrolling to the end, want 300", st.Y) + } + st.Y = 40 + tt.Frame() + if r, _ := tt.Find("Row 2"); r.Y != 0 { + t.Fatalf("Row 2 at %v after scrolling to 40, want 0", r.Y) + } +} + +func TestScrollAreaWheelAndKeys(t *testing.T) { + st := &ui.ScrollState{} + tt := ui.NewTester(scrollAreaView(st, ScrollAreaOptions{Label: "Log"}, 20), 300, 300) + tt.Scroll(50, 50, 0, 60) + if st.Y != 60 { + t.Fatalf("wheel moved to %v, want 60", st.Y) + } + tt.Key(0, ui.KeyTab) + if !tt.Focused("Log") { + t.Fatal("overflowing area did not take the focus") + } + tt.Key(0, ui.KeyDown) + if st.Y != 100 { + t.Fatalf("Down moved to %v, want 100", st.Y) + } + tt.Key(0, ui.KeyEnd) + if st.Y != 300 { + t.Fatalf("End moved to %v, want 300", st.Y) + } + tt.Key(0, ui.KeyHome) + if st.Y != 0 { + t.Fatalf("Home moved to %v, want 0", st.Y) + } +} + +func TestScrollAreaNoOverflowSkipsFocus(t *testing.T) { + tt := ui.NewTester(scrollAreaView(&ui.ScrollState{}, ScrollAreaOptions{Label: "Log"}, 2), 300, 300) + tt.Key(0, ui.KeyTab) + if tt.Focused("Log") { + t.Fatal("area without overflow took the focus") + } +} + +func TestScrollAreaHorizontal(t *testing.T) { + st := &ui.ScrollState{} + tt := ui.NewTester(func(c *ui.Context) { + Use(c, Settings{}) + ScrollArea(c, st, ScrollAreaOptions{Direction: ScrollHorizontal}, func() { + ui.Box(c).Size(500, 50) + }).Size(200, 60) + }, 300, 300) + tt.Scroll(50, 30, 120, 0) + if st.X != 120 || st.Y != 0 || st.MaxX != 300 { + t.Fatalf("offset %v,%v max %v, want 120,0 max 300", st.X, st.Y, st.MaxX) + } +} + +func TestScrollAreaKeepsPlaceWithoutState(t *testing.T) { + tt := ui.NewTester(scrollAreaView(nil, ScrollAreaOptions{Direction: ScrollBoth}, 20), 300, 300) + tt.Scroll(50, 50, 0, 80) + tt.Frame() + tt.Frame() + if r, _ := tt.Find("Row 4"); r.Y != 0 { + t.Fatalf("Row 4 at %v after frames, want 0", r.Y) + } +} + +func TestScrollAreaInvalidDirectionPanics(t *testing.T) { + defer func() { + if recover() == nil { + t.Fatal("invalid direction did not panic") + } + }() + ui.NewTester(scrollAreaView(nil, ScrollAreaOptions{Direction: 7}, 1), 100, 100) +} + +func TestScrollAreaSnapshot(t *testing.T) { + snapshot(t, "scroll_area", 300, 160, func(c *ui.Context) { + k := Tokens(c) + st := ui.Local(c.Root(), "snap", func() ui.ScrollState { return ui.ScrollState{Y: 30} }) + ui.Column(c).Padding(16).Children(func() { + ScrollArea(c, st, ScrollAreaOptions{}, func() { + for i := range 12 { + ui.Text(c, fmt.Sprintf("Verse %d", i+1)).Padding(4, 12) + } + }).Height(128).Background(k.Surface).Border(1, k.Border).Radius(4) + }) + }) +} diff --git a/split_pane.go b/split_pane.go new file mode 100644 index 0000000..5d0dc32 --- /dev/null +++ b/split_pane.go @@ -0,0 +1,264 @@ +package mujica + +import ( + "fmt" + "log/slog" + + "github.com/egoist/mygo/ui" + + "mujicaui/internal" + "mujicaui/theme" +) + +// SplitPaneSide names a pane of a SplitPane. +type SplitPaneSide int + +const ( + SplitPaneNeither SplitPaneSide = iota + SplitPaneFirst + SplitPaneSecond +) + +// SplitPaneState is the app-held layout of a SplitPane. +type SplitPaneState struct { + // Size is the first pane's width, or height when vertical, in DIPs; zero starts at half. + Size float32 + // Collapsed hides the Collapsible pane; Size is kept for when it opens again. + Collapsed bool +} + +// SplitPaneOptions configure a SplitPane. +type SplitPaneOptions struct { + // Vertical puts the first pane above the second. + Vertical bool + // MinFirst and MinSecond bound the panes in DIPs; zero is 10 spacing units. + MinFirst, MinSecond float32 + // Collapsible lets that pane collapse: Enter on the divider, a double click, + // dragging it under half its minimum, or an arrow key past its minimum. + Collapsible SplitPaneSide +} + +// SplitPaneResult is a SplitPane and its events in this frame. +type SplitPaneResult struct { + *ui.Element + Resized bool + Toggled bool +} + +type splitPaneDrag struct { + held bool + // want is where the pointer asks the divider to be, before clamping. + want float32 +} + +// SplitPane lays two panes side by side with a divider the pointer drags and the arrow keys move. +func SplitPane(c *ui.Context, state *SplitPaneState, opts SplitPaneOptions, first, second func()) SplitPaneResult { + if state == nil || first == nil || second == nil { + panic("mujica: SplitPane needs a state and two panes") + } + if opts.MinFirst < 0 || opts.MinSecond < 0 { + panic("mujica: SplitPane minimums must not be negative") + } + if opts.Collapsible < SplitPaneNeither || opts.Collapsible > SplitPaneSecond { + panic("mujica: invalid SplitPane Collapsible") + } + if state.Collapsed && opts.Collapsible == SplitPaneNeither { + panic("mujica: SplitPane collapsed without a Collapsible pane") + } + k := Tokens(c) + u := Density(c).Unit() + lo, minSecond := opts.MinFirst, opts.MinSecond + if lo == 0 { + lo = 10 * u + } + if minSecond == 0 { + minSecond = 10 * u + } + e := ui.Row(c).AlignItems(ui.Stretch) + if opts.Vertical { + e.Column() + } + total := e.Bounds().W + if opts.Vertical { + total = e.Bounds().H + } + hi := max(lo, total-theme.BorderWidth-minSecond) + clamp := func(v float32) float32 { return max(lo, min(v, hi)) } + if state.Size <= 0 && total > 0 { + state.Size = clamp((total - theme.BorderWidth) / 2) + } else if total > 0 { + state.Size = clamp(state.Size) + } else if first := ui.Local(e, "split_pane.first", func() bool { return true }); *first { + // Not laid out yet: measure, then size in the next frame. + *first = false + c.Invalidate() + } + var res SplitPaneResult + resize := func(v float32) { + if v = clamp(v); v != state.Size { + state.Size = v + res.Resized = true + } + } + toggle := func() { + state.Collapsed = !state.Collapsed + res.Toggled = true + slog.Debug("mujica: split_pane toggled", "collapsed", state.Collapsed) + } + firstShown := !(state.Collapsed && opts.Collapsible == SplitPaneFirst) + secondShown := !(state.Collapsed && opts.Collapsible == SplitPaneSecond) + along := func(el *ui.Element, v float32) { + if opts.Vertical { + el.Height(v) + } else { + el.Width(v) + } + } + e.Children(func() { + if firstShown { + a := ui.Box(c).Key("first").Shrink(0).Clip() + switch { + case !secondShown: + a.Grow(1).Shrink(1) + case state.Size <= 0 && opts.Vertical: + a.HeightPercent(50) + case state.Size <= 0: + a.WidthPercent(50) + default: + along(a, state.Size) + } + a.Children(first) + } + div := ui.Box(c).Key("divider").Shrink(0).Focusable().FocusRing(false).Role(ui.RoleSplitter).Label(msg(c, "split_pane.divider")) + along(div, theme.BorderWidth) + if state.Collapsed { + div.Description(msg(c, "split_pane.collapsed")) + } else if total > 0 { + div.Description(fmt.Sprintf("%.0f%%", 100*state.Size/total)) + } + splitPaneKeys(c, div, state, opts, lo, hi, u, resize, toggle) + if secondShown { + b := ui.Box(c).Key("second").Grow(1).Shrink(1).Clip() + if opts.Vertical { + b.MinHeight(0) + } else { + b.MinWidth(0) + } + b.Children(second) + } + at := state.Size + switch { + case !firstShown: + at = 0 + case !secondShown: + at = total - theme.BorderWidth + } + grip := 1.5 * u + handle := ui.Box(c).Key("handle").Absolute() + if opts.Vertical { + handle.Left(0).Right(0).Top(at - (grip-1)/2).Height(grip).Cursor(ui.CursorResizeNS) + } else { + handle.Top(0).Bottom(0).Left(at - (grip-1)/2).Width(grip).Cursor(ui.CursorResizeEW) + } + drag := ui.Local(handle, "split_pane.drag", func() splitPaneDrag { return splitPaneDrag{} }) + dx, dy, held := handle.Dragged() + if held { + div.Focus() + if !drag.held { + drag.want = at + } + delta := dx + if opts.Vertical { + delta = dy + } + if delta != 0 { + drag.want += delta + splitPaneFollow(state, opts, drag.want, lo, minSecond, total, resize, toggle) + } + } + drag.held = held + if opts.Collapsible != SplitPaneNeither && handle.DoubleClicked() { + toggle() + } + active := held || handle.Hovered() + div.Draw(func(p *ui.Painter, r ui.Rect) { + line := k.ControlBorder + if active { + line = k.AccentText + } + p.Fill(r, line, 0) + if state.Collapsed { + // A short tab marks the edge a collapsed pane opens from. + if opts.Vertical { + p.Fill(ui.Rect{X: r.X + r.W/2 - 12, Y: r.Y - 1, W: 24, H: 3}, line, 1.5) + } else { + p.Fill(ui.Rect{X: r.X - 1, Y: r.Y + r.H/2 - 12, W: 3, H: 24}, line, 1.5) + } + } + }) + handle.Draw(func(p *ui.Painter, r ui.Rect) { + if div.FocusVisible() { + internal.FocusRing(p, r, 0, k.Focus) + } + }) + }) + if res.Resized || res.Toggled { + c.Invalidate() + } + res.Element = e + return res +} + +// splitPaneFollow moves the divider where the pointer wants it; past half its minimum, a collapsible pane closes. +func splitPaneFollow(state *SplitPaneState, opts SplitPaneOptions, want, lo, minSecond, total float32, resize func(float32), toggle func()) { + var past bool + switch opts.Collapsible { + case SplitPaneFirst: + past = want < lo/2 + case SplitPaneSecond: + past = total-theme.BorderWidth-want < minSecond/2 + } + if past != state.Collapsed { + toggle() + } + if !state.Collapsed { + resize(want) + } +} + +// splitPaneKeys moves the divider with the arrows, Home and End, and toggles the collapsible pane with Enter. +func splitPaneKeys(c *ui.Context, div *ui.Element, state *SplitPaneState, opts SplitPaneOptions, lo, hi, u float32, resize func(float32), toggle func()) { + back, forth := ui.KeyLeft, ui.KeyRight + if opts.Vertical { + back, forth = ui.KeyUp, ui.KeyDown + } + step := 4 * u + switch { + case div.Shortcut(0, back): + switch { + case state.Collapsed && opts.Collapsible == SplitPaneSecond: + toggle() + case state.Collapsed: + case opts.Collapsible == SplitPaneFirst && state.Size <= lo: + toggle() + default: + resize(state.Size - step) + } + case div.Shortcut(0, forth): + switch { + case state.Collapsed && opts.Collapsible == SplitPaneFirst: + toggle() + case state.Collapsed: + case opts.Collapsible == SplitPaneSecond && state.Size >= hi: + toggle() + default: + resize(state.Size + step) + } + case div.Shortcut(0, ui.KeyHome) && !state.Collapsed: + resize(lo) + case div.Shortcut(0, ui.KeyEnd) && !state.Collapsed: + resize(hi) + case opts.Collapsible != SplitPaneNeither && div.Shortcut(0, ui.KeyEnter): + toggle() + } +} diff --git a/split_pane_test.go b/split_pane_test.go new file mode 100644 index 0000000..463c883 --- /dev/null +++ b/split_pane_test.go @@ -0,0 +1,190 @@ +package mujica + +import ( + "testing" + + "github.com/egoist/mygo/ui" +) + +type splitPaneProbe struct { + resized, toggled int +} + +func splitPaneTester(st *SplitPaneState, opts SplitPaneOptions, p *splitPaneProbe) *ui.Tester { + return ui.NewTester(func(c *ui.Context) { + Use(c, Settings{}) + r := SplitPane(c, st, opts, func() { ui.Text(c, "Files") }, func() { ui.Text(c, "Editor") }) + r.Fill() + if r.Resized { + p.resized++ + } + if r.Toggled { + p.toggled++ + } + }, 401, 200) +} + +func TestSplitPaneStartsAtHalf(t *testing.T) { + st := &SplitPaneState{} + tt := splitPaneTester(st, SplitPaneOptions{}, &splitPaneProbe{}) + if st.Size != 200 { + t.Fatalf("size %v, want 200", st.Size) + } + if r, _ := tt.Find("Editor"); r.X != 201 { + t.Fatalf("second pane at %v, want 201", r.X) + } +} + +func TestSplitPaneKeyboard(t *testing.T) { + st := &SplitPaneState{Size: 150} + p := &splitPaneProbe{} + tt := splitPaneTester(st, SplitPaneOptions{MinFirst: 100, MinSecond: 120}, p) + tt.Key(0, ui.KeyTab) + if !tt.Focused("Divider") { + t.Fatal("Tab did not focus the divider") + } + tt.Key(0, ui.KeyRight) + if st.Size != 166 { + t.Fatalf("Right moved to %v, want 166", st.Size) + } + tt.Key(0, ui.KeyHome) + if st.Size != 100 { + t.Fatalf("Home moved to %v, want the minimum 100", st.Size) + } + tt.Key(0, ui.KeyLeft) + if st.Size != 100 || st.Collapsed { + t.Fatalf("Left past the minimum gave %v collapsed=%v", st.Size, st.Collapsed) + } + tt.Key(0, ui.KeyEnd) + if st.Size != 401-1-120 { + t.Fatalf("End moved to %v, want 280", st.Size) + } + tt.Key(0, ui.KeyEnter) + if st.Collapsed || p.toggled != 0 { + t.Fatal("Enter collapsed a pane that is not collapsible") + } + if p.resized != 3 { + t.Fatalf("resized %d times, want 3", p.resized) + } +} + +func TestSplitPaneDragClampsToMinimum(t *testing.T) { + st := &SplitPaneState{Size: 200} + tt := splitPaneTester(st, SplitPaneOptions{MinFirst: 80}, &splitPaneProbe{}) + tt.Press(200, 100) + tt.Move(240, 100) + if st.Size != 240 { + t.Fatalf("dragged to %v, want 240", st.Size) + } + tt.Move(10, 100) + tt.Release(10, 100) + if st.Size != 80 || st.Collapsed { + t.Fatalf("dragged past the minimum to %v collapsed=%v, want 80", st.Size, st.Collapsed) + } + if !tt.Focused("Divider") { + t.Fatal("drag did not focus the divider") + } +} + +func TestSplitPaneCollapseFirst(t *testing.T) { + st := &SplitPaneState{Size: 200} + p := &splitPaneProbe{} + tt := splitPaneTester(st, SplitPaneOptions{MinFirst: 100, Collapsible: SplitPaneFirst}, p) + // Dragging under half the minimum collapses; back over it opens again. + tt.Press(200, 100) + tt.Move(120, 100) + if st.Collapsed || st.Size != 120 { + t.Fatalf("at 120: size %v collapsed=%v", st.Size, st.Collapsed) + } + tt.Move(40, 100) + if !st.Collapsed || tt.HasText("Files") { + t.Fatalf("under half the minimum: collapsed=%v files shown=%v", st.Collapsed, tt.HasText("Files")) + } + if r, _ := tt.Find("Editor"); r.X != 1 { + t.Fatalf("second pane at %v, want the whole width from 1", r.X) + } + tt.Move(160, 100) + tt.Release(160, 100) + if st.Collapsed || st.Size != 160 { + t.Fatalf("dragged back: size %v collapsed=%v", st.Size, st.Collapsed) + } + // Keyboard: Left at the minimum collapses, Right opens, Enter toggles. + tt.Key(0, ui.KeyHome) + tt.Key(0, ui.KeyLeft) + if !st.Collapsed { + t.Fatal("Left at the minimum did not collapse") + } + tt.Key(0, ui.KeyRight) + if st.Collapsed || st.Size != 100 { + t.Fatalf("Right did not reopen at 100: size %v collapsed=%v", st.Size, st.Collapsed) + } + tt.Key(0, ui.KeyEnter) + if !st.Collapsed { + t.Fatal("Enter did not collapse") + } + tt.Key(0, ui.KeyEnter) + if st.Collapsed || !tt.HasText("Files") { + t.Fatal("Enter did not reopen") + } + if p.toggled != 6 { + t.Fatalf("toggled %d times, want 6", p.toggled) + } +} + +func TestSplitPaneCollapseSecondVertical(t *testing.T) { + st := &SplitPaneState{Size: 100} + tt := ui.NewTester(func(c *ui.Context) { + Use(c, Settings{}) + SplitPane(c, st, SplitPaneOptions{Vertical: true, MinSecond: 60, Collapsible: SplitPaneSecond}, + func() { ui.Text(c, "Editor") }, func() { ui.Text(c, "Console") }).Fill() + }, 300, 201) + tt.Key(0, ui.KeyTab) + tt.Key(0, ui.KeyDown) + if st.Size != 116 { + t.Fatalf("Down moved to %v, want 116", st.Size) + } + tt.Key(0, ui.KeyEnd) + tt.Key(0, ui.KeyDown) + if !st.Collapsed || tt.HasText("Console") { + t.Fatal("Down past the second minimum did not collapse it") + } + tt.Key(0, ui.KeyUp) + if st.Collapsed || st.Size != 140 { + t.Fatalf("Up did not reopen at 140: size %v collapsed=%v", st.Size, st.Collapsed) + } +} + +func TestSplitPaneDoubleClickToggles(t *testing.T) { + st := &SplitPaneState{Size: 200} + tt := splitPaneTester(st, SplitPaneOptions{Collapsible: SplitPaneFirst}, &splitPaneProbe{}) + tt.ClickAt(200, 100) + tt.ClickAt(200, 100) + if !st.Collapsed || st.Size != 200 { + t.Fatalf("double click: collapsed=%v size %v, want collapsed keeping 200", st.Collapsed, st.Size) + } +} + +func TestSplitPaneRejectsCollapsedWithoutSide(t *testing.T) { + defer func() { + if recover() == nil { + t.Fatal("collapsed state without a Collapsible pane did not panic") + } + }() + splitPaneTester(&SplitPaneState{Collapsed: true}, SplitPaneOptions{}, &splitPaneProbe{}) +} + +func TestSplitPaneSnapshot(t *testing.T) { + snapshot(t, "split_pane", 420, 160, func(c *ui.Context) { + k := Tokens(c) + st := ui.Local(c.Root(), "snap", func() SplitPaneState { return SplitPaneState{Size: 140} }) + pane := func(s string, bg ui.Color) func() { + return func() { + ui.Box(c).Fill().Padding(12).Background(bg).Children(func() { ui.Text(c, s) }) + } + } + ui.Column(c).Padding(16).Children(func() { + SplitPane(c, st, SplitPaneOptions{}, pane("Files", k.Surface), pane("Editor", k.Background)). + Height(128).Border(1, k.Border).Radius(4).Clip() + }) + }) +} diff --git a/stack.go b/stack.go new file mode 100644 index 0000000..1914350 --- /dev/null +++ b/stack.go @@ -0,0 +1,69 @@ +package mujica + +import "github.com/egoist/mygo/ui" + +// StackAlign places children across a Stack's direction. Zero stretches a vertical stack and centers a horizontal one. +type StackAlign int + +const ( + StackAlignAuto StackAlign = iota + StackAlignStart + StackAlignCenter + StackAlignEnd + StackAlignStretch +) + +// StackOptions configure a Stack. +type StackOptions struct { + // Horizontal lays children left to right; otherwise top to bottom. + Horizontal bool + // Gap defaults to SpaceS. + Gap Space + Align StackAlign + // Justify places children along the direction; zero is ui.Start. + Justify ui.Align + // Wrap moves children of a horizontal stack onto more lines. + Wrap bool +} + +// Stack arranges children in a row or column. Build children with StackItem to hide them without leaving a gap. +func Stack(c *ui.Context, opts StackOptions, children func()) *ui.Element { + if children == nil { + panic("mujica: Stack needs children") + } + if opts.Wrap && !opts.Horizontal { + panic("mujica: Stack Wrap needs Horizontal") + } + e := ui.Column(c) + if opts.Horizontal { + e.Row() + } + e.Gap(containerSpace(Density(c), opts.Gap, SpaceS)).Justify(opts.Justify) + switch opts.Align { + case StackAlignAuto: + case StackAlignStart: + e.AlignItems(ui.Start) + case StackAlignCenter: + e.AlignItems(ui.Center) + case StackAlignEnd: + e.AlignItems(ui.End) + case StackAlignStretch: + e.AlignItems(ui.Stretch) + default: + panic("mujica: invalid StackAlign") + } + if opts.Wrap { + e.Wrap() + } + return e.Children(children) +} + +// StackItem builds one child of a Stack unless hidden; a hidden item takes no room and no gap. +func StackItem(hidden bool, build func()) { + if build == nil { + panic("mujica: StackItem needs build") + } + if !hidden { + build() + } +} diff --git a/stack_test.go b/stack_test.go new file mode 100644 index 0000000..d9c2053 --- /dev/null +++ b/stack_test.go @@ -0,0 +1,114 @@ +package mujica + +import ( + "testing" + + "github.com/egoist/mygo/ui" +) + +func TestStackVerticalGap(t *testing.T) { + var a, b ui.Rect + ui.NewTester(func(c *ui.Context) { + Use(c, Settings{}) + Stack(c, StackOptions{}, func() { + a = ui.Box(c).Height(20).Bounds() + b = ui.Box(c).Height(20).Bounds() + }) + }, 300, 300).Frame() + if b.Y-(a.Y+a.H) != 8 { + t.Fatalf("gap %v, want 8", b.Y-(a.Y+a.H)) + } + if a.W != 300 { + t.Fatalf("vertical stack did not stretch: width %v", a.W) + } +} + +func TestStackHiddenItemTakesNoGap(t *testing.T) { + for _, hide := range []bool{false, true} { + var a, last ui.Rect + ui.NewTester(func(c *ui.Context) { + Use(c, Settings{}) + Stack(c, StackOptions{Horizontal: true, Gap: SpaceL}, func() { + a = ui.Box(c).Size(30, 10).Bounds() + StackItem(hide, func() { ui.Box(c).Size(30, 10) }) + last = ui.Box(c).Size(30, 10).Bounds() + }) + }, 300, 300).Frame() + want := a.X + 30 + 16 + if !hide { + want += 30 + 16 + } + if last.X != want { + t.Errorf("hidden=%v: last at %v, want %v", hide, last.X, want) + } + } +} + +func TestStackAlignEnd(t *testing.T) { + var r ui.Rect + ui.NewTester(func(c *ui.Context) { + Use(c, Settings{}) + ui.Column(c).Width(200).Children(func() { + Stack(c, StackOptions{Align: StackAlignEnd}, func() { + r = ui.Box(c).Size(50, 10).Bounds() + }) + }) + }, 300, 300).Frame() + if r.X != 150 { + t.Fatalf("end-aligned child at %v, want 150", r.X) + } +} + +func TestStackJustifyAndWrap(t *testing.T) { + var first, third ui.Rect + ui.NewTester(func(c *ui.Context) { + Use(c, Settings{}) + ui.Column(c).Width(250).Children(func() { + Stack(c, StackOptions{Horizontal: true, Wrap: true, Gap: SpaceNone, Justify: ui.End}, func() { + first = ui.Box(c).Size(100, 20).Bounds() + ui.Box(c).Size(100, 20) + third = ui.Box(c).Size(100, 20).Bounds() + }) + }) + }, 300, 300).Frame() + if third.Y != first.Y+20 { + t.Fatalf("third at y=%v, want the next line at %v", third.Y, first.Y+20) + } + if first.X != 50 || third.X != 150 { + t.Fatalf("end-justified lines start at %v and %v, want 50 and 150", first.X, third.X) + } +} + +func TestStackWrapNeedsHorizontal(t *testing.T) { + defer func() { + if recover() == nil { + t.Fatal("vertical Wrap did not panic") + } + }() + ui.NewTester(func(c *ui.Context) { + Use(c, Settings{}) + Stack(c, StackOptions{Wrap: true}, func() {}) + }, 100, 100) +} + +func TestStackSnapshot(t *testing.T) { + snapshot(t, "stack", 420, 150, func(c *ui.Context) { + k := Tokens(c) + chip := func(s string) { + ui.Box(c).Padding(6, 12).Background(k.Surface).Border(1, k.Border).Radius(2).Children(func() { ui.Text(c, s) }) + } + ui.Column(c).Padding(16).Gap(16).Children(func() { + Stack(c, StackOptions{Horizontal: true}, func() { + for _, s := range []string{"Overture", "Aria", "Recitative"} { + chip(s) + } + }) + Stack(c, StackOptions{Horizontal: true, Justify: ui.SpaceBetween}, func() { + chip("Start") + StackItem(true, func() { chip("Hidden") }) + chip("Between") + chip("End") + }) + }) + }) +} diff --git a/status_bar.go b/status_bar.go new file mode 100644 index 0000000..c12f496 --- /dev/null +++ b/status_bar.go @@ -0,0 +1,237 @@ +package mujica + +import ( + "fmt" + "log/slog" + "strings" + + "github.com/egoist/mygo/ui" + + "mujicaui/internal" + "mujicaui/theme" +) + +// StatusItemTone colors a status item's text and icon. The zero value is muted text. +type StatusItemTone int + +const ( + StatusItemNeutral StatusItemTone = iota + StatusItemSuccess + StatusItemWarning + StatusItemDanger + StatusItemInfo +) + +// StatusItem is one entry of a StatusBar. +type StatusItem struct { + // ID is the item's stable key and the value StatusBarResult.Clicked reports. + ID string + Text string + Icon *ui.SVG + Tone StatusItemTone + // ShowProgress draws a short bar filled to Progress, from 0 to 1. + ShowProgress bool + Progress float32 + // Action makes the item a button. + Action bool + // Label names an item without Text for assistive technology. + Label string + // Secondary items hide first, last first, when the bar is too narrow. + Secondary bool +} + +// StatusBarOptions configure a StatusBar. +type StatusBarOptions struct { + Left, Right []StatusItem +} + +// StatusBarResult is a StatusBar and its event in this frame. +type StatusBarResult struct { + *ui.Element + // Clicked is the ID of the action item clicked, or empty. + Clicked string +} + +const statusBarProgressWidth float32 = 64 + +// StatusBar is a window's bottom strip with left and right groups of status items. +func StatusBar(c *ui.Context, opts StatusBarOptions) StatusBarResult { + k := Tokens(c) + d := Density(c) + statusBarCheck(opts) + h := max(theme.MinHitSize, d.ControlHeight()-8) + gap := d.Unit() * 2 + e := ui.Row(c).Height(h).Shrink(0).PaddingX(d.Unit()*2).Gap(gap). + Background(k.Surface).BorderWidth(theme.BorderWidth, 0, 0, 0).BorderColor(k.Border). + Label(msg(c, "status_bar.label")) + hidden := statusBarHide(c, e, opts, e.Bounds().W-d.Unit()*4, gap) + var res StatusBarResult + group := func(items []StatusItem) { + ui.Row(c).Gap(gap).Shrink(0).Dividers(theme.BorderWidth, k.Border).Children(func() { + for _, it := range items { + if hidden[it.ID] { + continue + } + if statusBarItem(c, it) { + res.Clicked = it.ID + slog.Debug("mujica: status_bar clicked", "id", it.ID) + } + } + }) + } + e.Children(func() { + group(opts.Left) + ui.Spacer(c) + if len(hidden) > 0 { + var names []string + for _, it := range append(append([]StatusItem{}, opts.Left...), opts.Right...) { + if hidden[it.ID] { + names = append(names, statusBarName(it)) + } + } + label := fmt.Sprintf(msg(c, "status_bar.hidden"), len(hidden)) + ui.Text(c, fmt.Sprintf("+%d", len(hidden))).FontSize(theme.CaptionSize).TextColor(k.TextMuted). + Label(label).Tooltip(strings.Join(names, "\n")).Shrink(0) + } + group(opts.Right) + }) + res.Element = e + return res +} + +func statusBarCheck(opts StatusBarOptions) { + seen := map[string]bool{} + for _, it := range append(append([]StatusItem{}, opts.Left...), opts.Right...) { + switch { + case it.ID == "": + panic("mujica: StatusItem needs an ID") + case seen[it.ID]: + panic("mujica: duplicate StatusItem ID " + it.ID) + case it.Progress < 0 || it.Progress > 1: + panic("mujica: StatusItem Progress must be within 0 and 1") + case it.Text == "" && it.Label == "": + panic("mujica: StatusItem " + it.ID + " needs Text or Label") + } + seen[it.ID] = true + } +} + +func statusBarName(it StatusItem) string { + if it.Text != "" { + return it.Text + } + return it.Label +} + +// statusBarHide returns the secondary items to hide so the rest fit in room DIPs. +func statusBarHide(c *ui.Context, bar *ui.Element, opts StatusBarOptions, room, gap float32) map[string]bool { + hidden := map[string]bool{} + if room <= 0 { + // Not laid out yet: show all, and measure in the next frame. + if first := ui.Local(bar, "status_bar.first", func() bool { return true }); *first { + *first = false + c.Invalidate() + } + return hidden + } + all := append(append([]StatusItem{}, opts.Left...), opts.Right...) + need := func() float32 { + var w float32 + for _, it := range all { + if !hidden[it.ID] { + w += statusBarWidth(c, it) + gap + } + } + if len(hidden) > 0 { + mw, _ := c.MeasureText(0, ui.Span{Text: fmt.Sprintf("+%d", len(hidden)), Size: theme.CaptionSize}) + w += mw + gap + } + return w + } + for i := len(all) - 1; i >= 0 && need() > room; i-- { + if all[i].Secondary { + hidden[all[i].ID] = true + } + } + return hidden +} + +// statusBarWidth estimates the width an item takes. +func statusBarWidth(c *ui.Context, it StatusItem) float32 { + d := Density(c) + w := d.Unit() * 2 + if it.Icon != nil { + w += 14 + d.Unit() + } + if it.Text != "" { + tw, _ := c.MeasureText(0, ui.Span{Text: it.Text, Size: theme.CaptionSize}) + w += tw + d.Unit() + } + if it.ShowProgress { + w += statusBarProgressWidth + d.Unit() + } + return w +} + +// statusBarItem builds one item and reports whether its action was clicked. +func statusBarItem(c *ui.Context, it StatusItem) bool { + k := Tokens(c) + d := Density(c) + fg := k.TextMuted + if it.Action { + fg = k.Text + } + switch it.Tone { + case StatusItemNeutral: + case StatusItemSuccess: + fg = k.Success + case StatusItemWarning: + fg = k.Warning + case StatusItemDanger: + fg = k.Danger + case StatusItemInfo: + fg = k.Info + default: + panic("mujica: invalid StatusItemTone") + } + var e *ui.Element + if it.Action { + e = ui.ButtonBase(c).Key(it.ID).MinHeight(theme.MinHitSize).MinWidth(theme.MinHitSize).Radius(theme.ControlRadius) + internal.Ring(e, k.Focus, theme.ControlRadius) + if e.Pressed() { + e.Background(k.Selection) + } else if e.Hovered() { + e.Background(k.SurfaceHover) + } + } else { + e = ui.Row(c).Key(it.ID).MinHeight(theme.MinHitSize) + } + e.PaddingX(d.Unit()).Gap(d.Unit()).TextColor(fg).Shrink(0) + if it.Label != "" { + e.Label(it.Label) + } + e.Children(func() { + if it.Icon != nil { + ui.Icon(c, it.Icon).Size(14, 14) + } + if it.Text != "" { + ui.Text(c, it.Text).FontSize(theme.CaptionSize).SingleLine() + } + if it.ShowProgress { + statusBarProgress(c, it.Progress) + } + }) + return it.Action && e.Clicked() +} + +func statusBarProgress(c *ui.Context, v float32) { + k := Tokens(c) + ui.Box(c).Size(statusBarProgressWidth, 4).Shrink(0).Role(ui.RoleProgress). + Description(fmt.Sprintf("%.0f%%", v*100)). + Draw(func(p *ui.Painter, r ui.Rect) { + p.Fill(r, k.Border, 2) + if v > 0 { + p.Fill(ui.Rect{X: r.X, Y: r.Y, W: max(r.W*v, r.H), H: r.H}, k.AccentText, 2) + } + }) +} diff --git a/status_bar_test.go b/status_bar_test.go new file mode 100644 index 0000000..7c5f1e3 --- /dev/null +++ b/status_bar_test.go @@ -0,0 +1,130 @@ +package mujica + +import ( + "testing" + + "github.com/egoist/mygo/ui" + + "mujicaui/icons" +) + +func statusBarSample() StatusBarOptions { + return StatusBarOptions{ + Left: []StatusItem{ + {ID: "branch", Text: "main", Icon: icons.Must("folder")}, + {ID: "sync", Text: "Syncing", ShowProgress: true, Progress: 0.4}, + {ID: "lint", Text: "2 warnings", Tone: StatusItemWarning, Secondary: true}, + }, + Right: []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}, + }, + } +} + +func TestStatusBarActionClick(t *testing.T) { + var clicked []string + tt := ui.NewTester(func(c *ui.Context) { + Use(c, Settings{}) + if r := StatusBar(c, statusBarSample()); r.Clicked != "" { + clicked = append(clicked, r.Clicked) + } + }, 800, 100) + if err := tt.Click("Notifications"); err != nil { + t.Fatal(err) + } + _ = tt.Click("main") + if len(clicked) != 1 || clicked[0] != "bell" { + t.Fatalf("clicked %v, want [bell]", clicked) + } +} + +func TestStatusBarActionKeyboard(t *testing.T) { + n := 0 + tt := ui.NewTester(func(c *ui.Context) { + Use(c, Settings{}) + if StatusBar(c, statusBarSample()).Clicked == "bell" { + n++ + } + }, 800, 100) + tt.Key(0, ui.KeyTab) + if !tt.Focused("Notifications") { + t.Fatal("Tab did not reach the action") + } + tt.Key(0, ui.KeyEnter) + tt.Key(0, ui.KeySpace) + if n != 2 { + t.Fatalf("keyboard activations %d, want 2", n) + } +} + +func TestStatusBarCollapsesSecondary(t *testing.T) { + tt := ui.NewTester(func(c *ui.Context) { + Use(c, Settings{}) + StatusBar(c, statusBarSample()) + }, 800, 100) + for _, s := range []string{"main", "Syncing", "2 warnings", "UTF-8", "Ln 12, Col 4"} { + if !tt.HasText(s) { + t.Fatalf("wide bar lost %q", s) + } + } + tt.SetSize(330, 100) + tt.Frame() + for _, s := range []string{"Ln 12, Col 4", "UTF-8", "2 warnings"} { + if tt.HasText(s) { + t.Fatalf("narrow bar still shows secondary %q; texts %q", s, tt.Texts()) + } + } + if !tt.HasText("main") || !tt.HasText("Syncing") { + t.Fatalf("narrow bar hid primary items: %q", tt.Texts()) + } + if !tt.HasText("3 more items") { + t.Fatalf("no hidden count named: %q", tt.Texts()) + } + // Medium width drops only the last secondary items. + tt.SetSize(460, 100) + tt.Frame() + if !tt.HasText("2 warnings") || tt.HasText("Ln 12, Col 4") { + t.Fatalf("medium bar hid the wrong items: %q", tt.Texts()) + } + if _, ok := tt.Find("Notifications"); !ok { + t.Fatal("primary action hidden") + } + tt.SetSize(800, 100) + tt.Frame() + if !tt.HasText("UTF-8") { + t.Fatal("secondary items did not come back when wide") + } +} + +func TestStatusBarRejectsBadItems(t *testing.T) { + for _, o := range []StatusBarOptions{ + {Left: []StatusItem{{Text: "no id"}}}, + {Left: []StatusItem{{ID: "a", Text: "a"}}, Right: []StatusItem{{ID: "a", Text: "b"}}}, + {Left: []StatusItem{{ID: "p", Text: "p", ShowProgress: true, Progress: 1.5}}}, + {Left: []StatusItem{{ID: "n", Icon: icons.Must("bell")}}}, + } { + func() { + defer func() { + if recover() == nil { + t.Errorf("%+v did not panic", o) + } + }() + ui.NewTester(func(c *ui.Context) { + Use(c, Settings{}) + StatusBar(c, o) + }, 400, 100) + }() + } +} + +func TestStatusBarSnapshot(t *testing.T) { + snapshot(t, "status_bar", 560, 60, func(c *ui.Context) { + ui.Column(c).Fill().Justify(ui.End).Children(func() { + o := statusBarSample() + o.Left = append(o.Left, StatusItem{ID: "ok", Text: "Saved", Tone: StatusItemSuccess, Icon: icons.Must("check")}) + StatusBar(c, o) + }) + }) +} diff --git a/testdata/snapshots/darwin/app_shell-dark.png b/testdata/snapshots/darwin/app_shell-dark.png new file mode 100644 index 0000000000000000000000000000000000000000..d646dbecfd813afc6b18d1547ad8e2a48e3a8ed6 GIT binary patch literal 17599 zcmeIaWmFyA)+V~K;O_2j3GVI$2yVgMgF6Jb;O@a4vT-N4LvVL@ciFwq_n!Bh^P|VT zeS38G=pT1gjRN+7s#SAMdFEWtB;t#rG%^AK0ssI&mX(oE0RSLB0ss)uaFF0nTH?}4 z004CthbqUFuE#UwRjR~W+OU1)Jjy3u zUT$4~RWF}2}Fu00*5$)!~F_Y;LkiigvYJ?AwrQZnQ3d5iIksZGq~`)(cIT-8R$HY5BWm3dGjPyFlA_IOS!!?CjONrmH1bE zN=iybMr%}Rwf$OSNwdT_3dq@rgP@7FWPmlR>I*blyv**{#81<(1Y$Wix}`kR2N`*{ zc=U2#t6Y@Bjgyj|{P=*bWMfJJj(_+ii`J%XTH-wf81r$zLW4OAyNr;J zB1C-ygCHYh3Yfe6-njTbdrxVT*o{R() zdQ_vmXH!cVuixke^Rv}PDWFb4?iw=Av(2YUc?k}uzPLlHouI%yACe1w<>4l2rSoOqpA8Ln$*!Ys zZ>AU#T2yX|3W7Y~DNSVa`)1X@jZzM(m&XMc-L!^;WqdAvenCDZ*3J+i3r`m^+WfAM zj$Yo{`oh;C#ND*na7X&oLpQiG#IHY2wz3Z^4DGbY>|%J*tE6H$pP!vZ1+7K zOPMdr=Sra{_}^~Kw|2{J2nbwEq)?TV-e&tH6I{2POoQd{nNo+_Gcz+|iG_))5!647 zwhj4&A1=o>Wo11wsE2oT*-lhiDGO3kV_f~pgR0hlyXp?Yk)JT)FgG`U`^J^oVa`vE zdYj4igm07D`TX3rzi&>V!hl`YmXL!t`>=cDOzKy!&hlV@CG35kbtJ)IHq9PE+JQdD z?sSuqbegpq0Oi$HYv1yPE{&UezlvKwGW-j*ot*xCPb-<^?*vKUh5c7~35&_S;Lr~t z!E~)dJ~;i8DDcW29?gRiN~Z+HtH86~!2*iW5eAKF{hFDhrOa9%YNqTqIzKlLkHf6y zy*9Uthm-A-Qzp}Y*`zBGRmiNi^?-d*|*zpC(j zI~ggN-_TxS8n0G=48yWDO5Xk8Ac$tv=;+)!lc*h-?guI=DoP{)K|@m}B|`)S(#Y

~vkZWVEr~;Y9>uVL#Z$exaqU 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 Q1{Xk9Qc%rNl30Zsi4)Bpeg literal 0 HcmV?d00001 diff --git a/testdata/snapshots/darwin/app_shell-light.png b/testdata/snapshots/darwin/app_shell-light.png new file mode 100644 index 0000000000000000000000000000000000000000..e6834ac799385377db4efa8e12ceab7ac0b3d87c GIT binary patch literal 16677 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

0UR|nzK*IE8^Km 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 literal 0 HcmV?d00001 diff --git a/testdata/snapshots/darwin/aspect_ratio-light.png b/testdata/snapshots/darwin/aspect_ratio-light.png new file mode 100644 index 0000000000000000000000000000000000000000..97bf95886d99e07587593d64f1b03aff0ab1b6db GIT binary patch literal 3363 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 literal 0 HcmV?d00001 diff --git a/testdata/snapshots/darwin/container-light.png b/testdata/snapshots/darwin/container-light.png new file mode 100644 index 0000000000000000000000000000000000000000..604f644cde40bd238fda91c7545b3d0818861b60 GIT binary patch literal 9861 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 Dwdz~J literal 0 HcmV?d00001 diff --git a/testdata/snapshots/darwin/divider-dark.png b/testdata/snapshots/darwin/divider-dark.png new file mode 100644 index 0000000000000000000000000000000000000000..52e118718f67d9c1d88a81ea8bd9fa4e9c72fb46 GIT binary patch literal 6227 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 literal 0 HcmV?d00001 diff --git a/testdata/snapshots/darwin/divider-light.png b/testdata/snapshots/darwin/divider-light.png new file mode 100644 index 0000000000000000000000000000000000000000..edf9be8be0fe653d210ee81cf146a7c787f3c9dd GIT binary patch literal 5932 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 literal 0 HcmV?d00001 diff --git a/testdata/snapshots/darwin/grid-dark.png b/testdata/snapshots/darwin/grid-dark.png new file mode 100644 index 0000000000000000000000000000000000000000..122e08b8746236fb09717b2efeb673a2181ff3ee GIT binary patch literal 6551 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 s3}*lTNWcHTU550wsG%>W$qzXuHA_@r| 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 literal 0 HcmV?d00001 diff --git a/testdata/snapshots/darwin/scroll_area-dark.png b/testdata/snapshots/darwin/scroll_area-dark.png new file mode 100644 index 0000000000000000000000000000000000000000..7dce19f2a56f8ac0219b9e59e8447d18f7ac2102 GIT binary patch literal 4054 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 literal 0 HcmV?d00001 diff --git a/testdata/snapshots/darwin/scroll_area-light.png b/testdata/snapshots/darwin/scroll_area-light.png new file mode 100644 index 0000000000000000000000000000000000000000..551db14adf34774845571ad3b853a7d205550c02 GIT binary patch literal 3613 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 YdHf)IV#NLvga6!bWd=Uz%$7U z@e%|cftvYXZ^iy&Mu`wa7^eB8{YU5zBZ53x?;I^sUk`#X@*$4 z_hQI2@?e@ru}y$?5ZV8TlFsPr1@#vjI%B((H(dL(upyy(=5G`&C?I}pSQ|AXJ!c785 zH#f=RFh+XH;4^D{WF!pbB6;CiY}PNQMMlDo*&^AbK^Zavm*9mad9kG95M?3ZQcT2HgkH5(VZzrhUnEZI(0Y4I zkd-PQ~j{LBry@~UXOzu0WWnmtO*VnGZG^DX<2q-8HZLEKshl&dx^RRU`@!wN?0F zNQ@0!?Kj5qjs&)giaK>+cWBZLbwBkkYkVY!y5r&^uV=;Xb>&Q;(XE8ig4Nnq)QO9+TCropd#SxSIo9*qw9d~}_PUYsn4o#r{J{1b zdVV7x&_167(H#H5!`qAjQLXzpS$TO!_?LMQBu0)L*DGv>s|&(8|JN7?xa zPfhPOg%Swb(OJ(ul5S8dtB4l8mXq0;;tb=>(LW?r`z04jm5VjbDAzxA8ttE;ZWGMR zlOs(gA#~aWb>Nb+q3hZk>oFYai}+oV;j#Rh8Vk?;B#a&sOeh+5DBRui1MCk~py^zp=5+%p2Y+Wk4M2G%Zv`98G0!pHoept4dEtprW8U#&7jiF@HAg z7ldE6uRnoMI#_Hf#~_J`vkMqTh~9%(yk{f+$7!j|ULWm_z}11{8LG!DEG@0hl-Jad zOm=|Da$|wzaa2Z5e4$7BjiS=h;65BR#Q!9ytPJYw=hrL40f`%g#Y8!CH#W!9=5wdZ z;xX6Z@(@1Y5$#&f%9S|-o4L*Q#>OmnLP>VLBYAv0?V@IIklldq$e}~KSS(gja&~*k zORE_y2xIF7&G9Q6vWK42%Um5setXtXQU(;~#(ykaU}oG&cxYYyd5TIsZWoPyOqzcf zYhxQ9lZ=;9RQr&gX}Of1sTh}X5l(pdGK#yOyt!!-x3>DVLu4V4h{YK@e5v%k^CCfA|%XUI@ry+M3OLxXF1p3m;(T5LLY zrxP5Y*4WSxZRlZZ%R>sc`WVD*cw?K;>a&PId>cFp?A4@xs?IwV1LNCgFL~f1`{5F3 zW&4_Yl%eUJFb+&kZW)b2CNQLl@vF#bdr3*Ui)Nw{DV$C(2%R40G`-iPeR*=;#KkV5 z_3?#I_w~A4e0t|z56>K6O@t(!h9O*5k6L@YiRWMW$^wTO#bY%a-gsDEyue5_*jem8X- zmNm3UrBYi2qPW_ao)0}|+o;3|I-Rn(&Lt7gDt`w?U0hsRPtm%@!h(Z|i|f=wX0KQU zb2IRdQhIVLWi<1JrJwu|+(@0>RuoH3wt)W-FJu?B=V++his|N_?F>jqz@9RVAtDD9 zDgMvTDJ{>Ntq5E8I`{&3lK$w9eySNJqXKFDKmT_Sz;*%}*p>kO#(F+pdCLHeQ#yw& z-eeKD$(y{^Ou?%;vP`KIYHbqKdWh9sq9q)Y{-ik>1b7KTCf|v{{%6%aS->Zo@r%{d zAbzL{9q^&tVW?Y07DtMC#{vz%a|YhboC#V9+e2H|&zvKM*`c;r7a8ZQA}2so7I-yG z?qDaE##LB1gTj`rCiQDkNa4yH4V!oXFBb@t? zX@enjbK@wCj59Zf_Y^anYuf9a$o_8n%WJ-^`dA4w+H1?W!V)SUTl`Yr6|snKDuVwq z{42M-7s-^;GJVK>7#+Y-AGWRg{{sisWY3>%%yZW?SFl=F(){n?wX(2*Hko-n{tK2# B$3_4E literal 0 HcmV?d00001 diff --git a/testdata/snapshots/darwin/stack-dark.png b/testdata/snapshots/darwin/stack-dark.png new file mode 100644 index 0000000000000000000000000000000000000000..fbe69a34ca48f6eeac842f5997319bf171220ab2 GIT binary patch literal 6786 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 N$Vn?pRX|LF{tJqE;z9rb literal 0 HcmV?d00001 diff --git a/testdata/snapshots/darwin/stack-light.png b/testdata/snapshots/darwin/stack-light.png new file mode 100644 index 0000000000000000000000000000000000000000..01bc2506484786531f266b1a5beaefab19f8ff9c GIT binary patch literal 6604 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 DMF4&p literal 0 HcmV?d00001 diff --git a/testdata/snapshots/darwin/status_bar-dark.png b/testdata/snapshots/darwin/status_bar-dark.png new file mode 100644 index 0000000000000000000000000000000000000000..4c7aceec6677b700ebe1d9064586edd252424c6e GIT binary patch literal 6276 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 YpF?Di6S*UX`dt9zq~TH(5+*_a1z?cAM*si- literal 0 HcmV?d00001 diff --git a/testdata/snapshots/darwin/status_bar-light.png b/testdata/snapshots/darwin/status_bar-light.png new file mode 100644 index 0000000000000000000000000000000000000000..169e081fc748873639aac6a17425a95fe30f549c GIT binary patch literal 6200 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 JYLyqU{{;`{&&mJ* literal 0 HcmV?d00001 diff --git a/testdata/snapshots/darwin/title_bar-dark.png b/testdata/snapshots/darwin/title_bar-dark.png new file mode 100644 index 0000000000000000000000000000000000000000..cfae176c3fb6e50bf213f58f845727691ece53c5 GIT binary patch literal 4559 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 literal 0 HcmV?d00001 diff --git a/testdata/snapshots/darwin/title_bar-light.png b/testdata/snapshots/darwin/title_bar-light.png new file mode 100644 index 0000000000000000000000000000000000000000..e516140413f1279a7f07049d801de9896e2f792a GIT binary patch literal 4463 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 literal 0 HcmV?d00001 diff --git a/title_bar.go b/title_bar.go new file mode 100644 index 0000000..af6e1e5 --- /dev/null +++ b/title_bar.go @@ -0,0 +1,38 @@ +package mujica + +import ( + "github.com/egoist/mygo/ui" + + "mujicaui/theme" +) + +// TitleBarOptions configure a TitleBar. +type TitleBarOptions struct { + // Leading holds tools before the title, such as a sidebar toggle. + Leading func() + // Trailing holds tools after the title. + Trailing func() +} + +// TitleBar is a window's title row. Its empty parts drag the window, and it keeps clear of the system window buttons +// reported by c.TitleBar() in a window with a hidden title bar. Controls in the slots stay clickable. +func TitleBar(c *ui.Context, title string, opts TitleBarOptions) *ui.Element { + k := Tokens(c) + d := Density(c) + bar := c.TitleBar() + pad := d.Unit() * 3 + e := ui.Row(c).Height(max(bar.Height, d.ControlHeight()+d.Unit()*2)).Shrink(0). + Padding(0, bar.Right+pad, 0, bar.Left+pad).Gap(d.Unit()*2). + Background(k.Surface).BorderWidth(0, 0, theme.BorderWidth, 0).BorderColor(k.Border). + DragWindow() + return e.Children(func() { + if opts.Leading != nil { + ui.Row(c).Gap(d.Unit()).Shrink(0).Children(opts.Leading) + } + ui.Text(c, title).Font(theme.SerifFont).FontSize(theme.BodySize + 1).FontWeight(600). + SingleLine().Grow(1).Shrink(1).MinWidth(0) + if opts.Trailing != nil { + ui.Row(c).Gap(d.Unit()).Shrink(0).Children(opts.Trailing) + } + }) +} diff --git a/title_bar_test.go b/title_bar_test.go new file mode 100644 index 0000000..18202bb --- /dev/null +++ b/title_bar_test.go @@ -0,0 +1,82 @@ +package mujica + +import ( + "testing" + + "github.com/egoist/mygo/ui" +) + +func TestTitleBarDefaultHeight(t *testing.T) { + var r ui.Rect + ui.NewTester(func(c *ui.Context) { + Use(c, Settings{}) + r = TitleBar(c, "Archive", TitleBarOptions{}).Bounds() + }, 400, 300).Frame() + if r.H != 44 || r.W != 400 { + t.Fatalf("bar %vx%v, want 400x44", r.W, r.H) + } +} + +func TestTitleBarClearsWindowControls(t *testing.T) { + var r ui.Rect + tt := ui.NewTester(func(c *ui.Context) { + Use(c, Settings{}) + r = TitleBar(c, "Archive", TitleBarOptions{ + Trailing: func() { Button(c, "Share", ButtonOptions{Variant: Ghost}) }, + }).Bounds() + }, 400, 300) + tt.SetTitleBar(ui.TitleBar{Height: 52, Left: 78, Right: 40}) + tt.Frame() + if r.H != 52 { + t.Fatalf("bar height %v, want the controls' 52", r.H) + } + title, _ := tt.Find("Archive") + if title.X != 78+12 { + t.Fatalf("title at %v, want 90 past the controls", title.X) + } + share, _ := tt.Find("Share") + if share.X+share.W > 400-40-12+0.5 { + t.Fatalf("trailing tool ends at %v, inside the right controls", share.X+share.W) + } +} + +func TestTitleBarSlotsStayClickable(t *testing.T) { + n := 0 + tt := ui.NewTester(func(c *ui.Context) { + Use(c, Settings{}) + TitleBar(c, "Archive", TitleBarOptions{ + Leading: func() { + if Button(c, "Menu", ButtonOptions{Variant: Ghost}).Clicked() { + n++ + } + }, + }) + }, 400, 300) + if err := tt.Click("Menu"); err != nil { + t.Fatal(err) + } + if n != 1 { + t.Fatalf("leading tool clicked %d times, want 1", n) + } +} + +func TestTitleBarLongTitleKeepsTools(t *testing.T) { + tt := ui.NewTester(func(c *ui.Context) { + Use(c, Settings{}) + TitleBar(c, "A very long window title that cannot possibly fit in the bar", TitleBarOptions{ + Trailing: func() { Button(c, "Share", ButtonOptions{Variant: Ghost}) }, + }) + }, 240, 100) + share, ok := tt.Find("Share") + if !ok || share.X+share.W > 240-12+0.5 { + t.Fatalf("tool pushed out: %+v", share) + } +} + +func TestTitleBarSnapshot(t *testing.T) { + snapshot(t, "title_bar", 420, 60, func(c *ui.Context) { + TitleBar(c, "Chronicle of the Court", TitleBarOptions{ + Trailing: func() { Button(c, "Share", ButtonOptions{Variant: Ghost}) }, + }) + }) +}