From e82a1ae6c2fd3859dc467c1f4d6acf0ab8c81d0c 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:24:13 -0400 Subject: [PATCH] Forms: components 041-050 --- color_picker.go | 396 +++++++++++++++++ color_picker_test.go | 126 ++++++ docs/components/041-form.md | 59 +++ docs/components/042-form-field.md | 49 +++ docs/components/043-input-group.md | 54 +++ docs/components/044-pin-input.md | 48 ++ docs/components/045-tags-input.md | 48 ++ docs/components/046-rating.md | 46 ++ docs/components/047-color-picker.md | 45 ++ docs/components/048-file-picker.md | 52 +++ docs/components/049-file-drop-zone.md | 49 +++ docs/components/050-transfer.md | 46 ++ example_form_test.go | 124 ++++++ examples/gallery/pages_form.go | 416 +++++++++++++++++- file_drop_zone.go | 292 ++++++++++++ file_drop_zone_test.go | 120 +++++ file_picker.go | 137 ++++++ file_picker_test.go | 131 ++++++ form.go | 309 +++++++++++++ form_field.go | 38 ++ form_field_test.go | 79 ++++ form_test.go | 202 +++++++++ input_group.go | 172 ++++++++ input_group_test.go | 110 +++++ messages_form.go | 53 +++ pin_input.go | 165 +++++++ pin_input_test.go | 104 +++++ rating.go | 144 ++++++ rating_test.go | 102 +++++ tags_input.go | 178 ++++++++ tags_input_test.go | 117 +++++ .../snapshots/darwin/color_picker-dark.png | Bin 0 -> 26924 bytes .../snapshots/darwin/color_picker-light.png | Bin 0 -> 26953 bytes .../snapshots/darwin/file_drop_zone-dark.png | Bin 0 -> 18554 bytes .../snapshots/darwin/file_drop_zone-light.png | Bin 0 -> 18214 bytes .../snapshots/darwin/file_picker-dark.png | Bin 0 -> 16388 bytes .../snapshots/darwin/file_picker-light.png | Bin 0 -> 15682 bytes testdata/snapshots/darwin/form-dark.png | Bin 0 -> 17134 bytes testdata/snapshots/darwin/form-light.png | Bin 0 -> 17089 bytes testdata/snapshots/darwin/form_field-dark.png | Bin 0 -> 12502 bytes .../snapshots/darwin/form_field-light.png | Bin 0 -> 11877 bytes .../snapshots/darwin/input_group-dark.png | Bin 0 -> 9904 bytes .../snapshots/darwin/input_group-light.png | Bin 0 -> 9588 bytes testdata/snapshots/darwin/pin_input-dark.png | Bin 0 -> 3390 bytes testdata/snapshots/darwin/pin_input-light.png | Bin 0 -> 3362 bytes testdata/snapshots/darwin/rating-dark.png | Bin 0 -> 11275 bytes testdata/snapshots/darwin/rating-light.png | Bin 0 -> 11156 bytes testdata/snapshots/darwin/tags_input-dark.png | Bin 0 -> 10757 bytes .../snapshots/darwin/tags_input-light.png | Bin 0 -> 10519 bytes testdata/snapshots/darwin/transfer-dark.png | Bin 0 -> 15519 bytes testdata/snapshots/darwin/transfer-light.png | Bin 0 -> 15126 bytes transfer.go | 225 ++++++++++ transfer_test.go | 106 +++++ 53 files changed, 4341 insertions(+), 1 deletion(-) create mode 100644 color_picker.go create mode 100644 color_picker_test.go create mode 100644 docs/components/041-form.md create mode 100644 docs/components/042-form-field.md create mode 100644 docs/components/043-input-group.md create mode 100644 docs/components/044-pin-input.md create mode 100644 docs/components/045-tags-input.md create mode 100644 docs/components/046-rating.md create mode 100644 docs/components/047-color-picker.md create mode 100644 docs/components/048-file-picker.md create mode 100644 docs/components/049-file-drop-zone.md create mode 100644 docs/components/050-transfer.md create mode 100644 example_form_test.go create mode 100644 file_drop_zone.go create mode 100644 file_drop_zone_test.go create mode 100644 file_picker.go create mode 100644 file_picker_test.go create mode 100644 form.go create mode 100644 form_field.go create mode 100644 form_field_test.go create mode 100644 form_test.go create mode 100644 input_group.go create mode 100644 input_group_test.go create mode 100644 messages_form.go create mode 100644 pin_input.go create mode 100644 pin_input_test.go create mode 100644 rating.go create mode 100644 rating_test.go create mode 100644 tags_input.go create mode 100644 tags_input_test.go create mode 100644 testdata/snapshots/darwin/color_picker-dark.png create mode 100644 testdata/snapshots/darwin/color_picker-light.png create mode 100644 testdata/snapshots/darwin/file_drop_zone-dark.png create mode 100644 testdata/snapshots/darwin/file_drop_zone-light.png create mode 100644 testdata/snapshots/darwin/file_picker-dark.png create mode 100644 testdata/snapshots/darwin/file_picker-light.png create mode 100644 testdata/snapshots/darwin/form-dark.png create mode 100644 testdata/snapshots/darwin/form-light.png create mode 100644 testdata/snapshots/darwin/form_field-dark.png create mode 100644 testdata/snapshots/darwin/form_field-light.png create mode 100644 testdata/snapshots/darwin/input_group-dark.png create mode 100644 testdata/snapshots/darwin/input_group-light.png create mode 100644 testdata/snapshots/darwin/pin_input-dark.png create mode 100644 testdata/snapshots/darwin/pin_input-light.png create mode 100644 testdata/snapshots/darwin/rating-dark.png create mode 100644 testdata/snapshots/darwin/rating-light.png create mode 100644 testdata/snapshots/darwin/tags_input-dark.png create mode 100644 testdata/snapshots/darwin/tags_input-light.png create mode 100644 testdata/snapshots/darwin/transfer-dark.png create mode 100644 testdata/snapshots/darwin/transfer-light.png create mode 100644 transfer.go create mode 100644 transfer_test.go diff --git a/color_picker.go b/color_picker.go new file mode 100644 index 0000000..a4f6724 --- /dev/null +++ b/color_picker.go @@ -0,0 +1,396 @@ +package mujica + +import ( + "fmt" + "log/slog" + "math" + "strconv" + "strings" + + "github.com/egoist/mygo/ui" + + "mujicaui/icons" + "mujicaui/internal" + "mujicaui/theme" +) + +// ColorPickerOptions configure a ColorPicker. +type ColorPickerOptions struct { + // Presets are offered as swatches; nil uses the built-in court palette. + Presets []ui.Color + // Label names the trigger; empty uses the built-in "Color". + Label string + Disabled bool +} + +// ColorPickerParts are the parts of a ColorPicker. +type ColorPickerParts struct { + Trigger *ui.Element + changed bool +} + +// Changed reports whether the user picked a new color this frame. +func (p ColorPickerParts) Changed() bool { return p.changed } + +// colorPickerPresets are the default swatches: court tones and neutrals. +var colorPickerPresets = []ui.Color{ + ui.Hex("#7D2034"), ui.Hex("#A12635"), ui.Hex("#AD9164"), ui.Hex("#805714"), + ui.Hex("#326247"), ui.Hex("#355E80"), ui.Hex("#4B3A5A"), ui.Hex("#211C1D"), + ui.Hex("#6A5B55"), ui.Hex("#D2C3AF"), ui.Hex("#F2EBDD"), ui.Hex("#FFFFFF"), +} + +// colorPickerHSVA keeps hue for grays, which RGB loses. +type colorPickerHSVA struct{ h, s, v, a float64 } + +type colorPickerState struct { + open, wasOpen bool + hsv colorPickerHSVA + last ui.Color + hex, r, g, b string + alpha string + bad string +} + +// ColorPicker is a swatch button opening a panel to pick *color: a saturation and +// brightness square, hue and opacity sliders, HEX, RGB and opacity fields, and presets. +func ColorPicker(c *ui.Context, color *ui.Color, opts ColorPickerOptions) ColorPickerParts { + k := Tokens(c) + d := Density(c) + label := opts.Label + if label == "" { + label = msg(c, "color_picker.label") + } + presets := opts.Presets + if presets == nil { + presets = colorPickerPresets + } + parts := ColorPickerParts{} + trigger := ui.ButtonBase(c).MinHeight(d.ControlHeight()).Padding(0, d.Unit()*3, 0, d.Unit()*1.5).Gap(d.Unit() * 2). + Radius(theme.ControlRadius).Background(k.Surface).Disabled(opts.Disabled).Label(label) + parts.Trigger = trigger + internal.Ring(trigger, k.Focus, theme.ControlRadius) + st := ui.Local(trigger, "picker", func() colorPickerState { + s := colorPickerState{last: *color, hsv: colorPickerToHSVA(*color)} + s.sync(*color) + return s + }) + if *color != st.last { + st.hsv, st.last = colorPickerToHSVA(*color), *color + st.sync(*color) + } + set := func(x colorPickerHSVA, fields bool) { + x.s, x.v, x.a = colorPickerClamp(x.s), colorPickerClamp(x.v), colorPickerClamp(x.a) + st.hsv = x + next := x.color() + if next != *color { + slog.Debug("mujica: color picker changed", "color", colorPickerHex(next)) + *color = next + parts.changed = true + } + st.last = *color + if fields { + st.sync(*color) + } + } + if trigger.Clicked() { + st.open = !st.open + } + border := k.ControlBorder + if trigger.Hovered() && !opts.Disabled { + border = k.ControlBorder.Mix(k.Text, 0.25) + trigger.Background(k.SurfaceHover) + } + trigger.Border(theme.BorderWidth, border).Description(colorPickerHex(*color)) + now := *color + trigger.Children(func() { + ui.Box(c).Size(d.Unit()*6, d.Unit()*6).Radius(theme.ControlRadius).Border(theme.BorderWidth, k.Border).Clip().Shrink(0). + Role(ui.RoleNone).Draw(func(p *ui.Painter, r ui.Rect) { + colorPickerCheckers(p, r, 4) + p.Fill(r, now, 0) + }) + ui.Text(c, strings.ToUpper(colorPickerHex(now))).Font(theme.MonoFont).SingleLine() + ui.Icon(c, icons.Must("chevron-down")).Size(16, 16).TextColor(k.TextMuted) + }) + opened := st.open && !st.wasOpen + ui.PopoverBase(c, trigger, &st.open, func(panel *ui.Element) { + panel.Margin(d.Unit(), 0, 0, 0).Padding(d.Unit()*3).Gap(d.Unit()*3).Width(260).Radius(theme.PopupRadius). + Background(k.Surface).Border(theme.BorderWidth, k.Border).Shadow(0, 4, 12, 0, ui.RGBA(0, 0, 0, 0.18)) + colorPickerSquare(c, st, set, opened) + x := st.hsv + h := x.h + if colorPickerSlider(c, &h, 360, msg(c, "color_picker.hue"), func(p *ui.Painter, r ui.Rect) { + hues := []ui.Color{ui.RGB(255, 0, 0), ui.RGB(255, 255, 0), ui.RGB(0, 255, 0), ui.RGB(0, 255, 255), ui.RGB(0, 0, 255), ui.RGB(255, 0, 255), ui.RGB(255, 0, 0)} + w := r.W / 6 + for i := range 6 { + p.FillGradient(ui.Rect{X: r.X + w*float32(i), Y: r.Y, W: w + 0.5, H: r.H}, ui.LinearGradient{From: hues[i], To: hues[i+1], Angle: 90}, 0) + } + }).Changed() { + set(colorPickerHSVA{h, x.s, x.v, x.a}, true) + } + a := x.a + opaque := colorPickerHSVA{x.h, x.s, x.v, 1}.color() + if colorPickerSlider(c, &a, 1, msg(c, "color_picker.alpha"), func(p *ui.Painter, r ui.Rect) { + colorPickerCheckers(p, r, 4) + p.FillGradient(r, ui.LinearGradient{From: opaque.Alpha(0), To: opaque, Angle: 90}, 0) + }).Changed() { + set(colorPickerHSVA{x.h, x.s, x.v, a}, true) + } + colorPickerFields(c, st, color, set) + ui.Grid(c).ColumnTracks(ui.Fr(1), ui.Fr(1), ui.Fr(1), ui.Fr(1), ui.Fr(1), ui.Fr(1)).Gap(d.Unit() * 1.5).Children(func() { + for _, sw := range presets { + hex := strings.ToUpper(colorPickerHex(sw)) + b := ui.ButtonBase(c).Height(d.Unit()*6).Radius(theme.ControlRadius).Border(theme.BorderWidth, k.Border).Label(hex).Clip() + internal.Ring(b, k.Focus, theme.ControlRadius) + chosen := *color == sw + b.Selected(chosen).Draw(func(p *ui.Painter, r ui.Rect) { + colorPickerCheckers(p, r, 4) + p.Fill(r, sw, 0) + }) + if chosen { + mark := ui.RGB(255, 255, 255) + if colorPickerLuma(sw) > 0.55 { + mark = ui.RGB(0, 0, 0) + } + b.Children(func() { ui.Icon(c, icons.Must("check")).Size(14, 14).TextColor(mark) }) + } + if b.Clicked() { + set(colorPickerToHSVA(sw), true) + } + } + }) + }) + if st.wasOpen && !st.open { + trigger.Focus() + } + st.wasOpen = st.open + return parts +} + +// colorPickerSquare picks saturation across and brightness up. +func colorPickerSquare(c *ui.Context, st *colorPickerState, set func(colorPickerHSVA, bool), opened bool) { + k := Tokens(c) + x := st.hsv + sq := ui.Box(c).Height(150).Radius(theme.ControlRadius).Focusable().Role(ui.RoleSlider).Cursor(ui.CursorCrosshair). + Label(msg(c, "color_picker.square")) + sq.FocusRing(false) + if opened { + sq.Focus() + } + if _, _, ok := sq.Dragged(); ok { + px, py, _ := sq.PointerPosition() + if b := sq.Bounds(); b.W > 0 && b.H > 0 { + set(colorPickerHSVA{x.h, float64(px / b.W), 1 - float64(py/b.H), x.a}, true) + } + } + switch { + case sq.Shortcut(0, ui.KeyRight): + set(colorPickerHSVA{x.h, x.s + 0.01, x.v, x.a}, true) + case sq.Shortcut(0, ui.KeyLeft): + set(colorPickerHSVA{x.h, x.s - 0.01, x.v, x.a}, true) + case sq.Shortcut(0, ui.KeyUp): + set(colorPickerHSVA{x.h, x.s, x.v + 0.01, x.a}, true) + case sq.Shortcut(0, ui.KeyDown): + set(colorPickerHSVA{x.h, x.s, x.v - 0.01, x.a}, true) + } + x = st.hsv + sq.Description(fmt.Sprintf(msg(c, "color_picker.sv"), x.s*100, x.v*100)) + hue := colorPickerHSVA{x.h, 1, 1, 1}.color() + sq.Draw(func(p *ui.Painter, r ui.Rect) { + p.FillGradient(r, ui.LinearGradient{From: ui.RGB(255, 255, 255), To: hue, Angle: 90}, theme.ControlRadius) + p.FillGradient(r, ui.LinearGradient{From: ui.RGBA(0, 0, 0, 0), To: ui.RGB(0, 0, 0), Angle: 180}, theme.ControlRadius) + cx, cy := internal.Snap(p, r.X+r.W*float32(x.s)), internal.Snap(p, r.Y+r.H*float32(1-x.v)) + p.FillPath(internal.Diamond(cx, cy, 14), ui.RGB(0, 0, 0).Alpha(0.5)) + p.FillPath(internal.Diamond(cx, cy, 11), ui.RGB(255, 255, 255)) + p.FillPath(internal.Diamond(cx, cy, 6), x.color().Alpha(1)) + if sq.FocusVisible() { + internal.FocusRing(p, r, theme.ControlRadius, k.Focus) + } + }) +} + +// colorPickerSlider is a channel slider from 0 to hi over the track track paints. +func colorPickerSlider(c *ui.Context, v *float64, hi float64, name string, track func(p *ui.Painter, r ui.Rect)) *ui.Element { + k := Tokens(c) + const knob = 10 + s := ui.SliderBase(c, v, 0, hi).Height(18).PaddingX(knob / 2).FocusRing(false).Label(name) + frac := float32(*v / hi) + s.Draw(func(p *ui.Painter, r ui.Rect) { + bar := ui.Rect{X: r.X, Y: r.Y + 3, W: r.W, H: r.H - 6} + p.Clip(bar, theme.ControlRadius, func() { track(p, bar) }) + p.Stroke(bar, k.Border, theme.ControlRadius, theme.BorderWidth) + x := r.X + knob/2 + (r.W-knob)*frac + thumb := ui.Rect{X: x - knob/2, Y: r.Y, W: knob, H: r.H} + p.Fill(thumb, k.Surface, theme.ControlRadius) + p.Stroke(thumb, k.Text, theme.ControlRadius, 1.5) + if s.FocusVisible() { + internal.FocusRing(p, thumb, theme.ControlRadius, k.Focus) + } + }) + return s +} + +// sync shows col in every field. +func (st *colorPickerState) sync(col ui.Color) { + st.hex = strings.TrimPrefix(strings.ToUpper(colorPickerHex(col)), "#") + st.r, st.g, st.b = strconv.Itoa(int(col.R)), strconv.Itoa(int(col.G)), strconv.Itoa(int(col.B)) + st.alpha = strconv.Itoa(int(math.Round(float64(col.A) / 255 * 100))) + st.bad = "" +} + +// colorPickerFields edits the color as HEX, RGB and opacity text. +func colorPickerFields(c *ui.Context, st *colorPickerState, color *ui.Color, set func(colorPickerHSVA, bool)) { + k := Tokens(c) + d := Density(c) + apply := func(field string, text *string, col ui.Color, ok bool) { + if !ok { + st.bad = field + return + } + kept := *text + set(colorPickerToHSVA(col), true) + *text = kept + } + editing := false + channel := func(s string) (uint8, bool) { + n, err := strconv.Atoi(strings.TrimSpace(s)) + return uint8(n), err == nil && n >= 0 && n <= 255 + } + ui.Column(c).Gap(d.Unit() * 2).Children(func() { + hex := InputGroup(c, &st.hex, InputGroupOptions{Prefix: "#", Label: "HEX", Invalid: st.bad == "hex"}) + editing = hex.Input.Focused() + if hex.Changed() { + col, err := colorPickerParseHex(st.hex) + apply("hex", &st.hex, col, err == nil) + } + ui.Grid(c).ColumnTracks(ui.Fr(1), ui.Fr(1), ui.Fr(1), ui.Fr(1)).Gap(d.Unit() * 1.5).Children(func() { + for _, f := range []struct { + name, label string + text *string + }{{"R", msg(c, "color_picker.red"), &st.r}, {"G", msg(c, "color_picker.green"), &st.g}, + {"B", msg(c, "color_picker.blue"), &st.b}, {"A %", msg(c, "color_picker.percent"), &st.alpha}} { + ui.Column(c).Gap(d.Unit()).Children(func() { + ui.Text(c, f.name).FontSize(theme.CaptionSize).TextColor(k.TextMuted) + in := InputGroup(c, f.text, InputGroupOptions{Label: f.label, Invalid: st.bad == f.name}) + editing = editing || in.Input.Focused() + if !in.Changed() { + return + } + col := *color + v, ok := channel(*f.text) + switch f.name { + case "R": + col.R = v + case "G": + col.G = v + case "B": + col.B = v + default: + ok = ok && v <= 100 + col.A = uint8(math.Round(float64(v) / 100 * 255)) + } + apply(f.name, f.text, col, ok) + }) + } + }) + if !editing { + st.sync(*color) + } + if st.bad != "" { + ui.Text(c, msg(c, "color_picker.invalid")).FontSize(theme.CaptionSize).TextColor(k.Danger).Role(ui.RoleStatus) + } + }) +} + +// colorPickerHex returns col as #rrggbb, or #rrggbbaa when translucent. +func colorPickerHex(col ui.Color) string { + if col.A == 255 { + return fmt.Sprintf("#%02x%02x%02x", col.R, col.G, col.B) + } + return fmt.Sprintf("#%02x%02x%02x%02x", col.R, col.G, col.B, col.A) +} + +// colorPickerParseHex parses rgb, rrggbb or rrggbbaa, with or without "#". +func colorPickerParseHex(s string) (ui.Color, error) { + h := strings.TrimPrefix(strings.TrimSpace(s), "#") + if len(h) == 3 { + h = string([]byte{h[0], h[0], h[1], h[1], h[2], h[2]}) + } + if len(h) == 6 { + h += "ff" + } + if len(h) != 8 { + return ui.Color{}, fmt.Errorf("mujica: color %q is not hex", s) + } + v, err := strconv.ParseUint(h, 16, 32) + if err != nil { + return ui.Color{}, fmt.Errorf("mujica: color %q: %w", s, err) + } + return ui.Color{R: uint8(v >> 24), G: uint8(v >> 16), B: uint8(v >> 8), A: uint8(v)}, nil +} + +func colorPickerToHSVA(col ui.Color) colorPickerHSVA { + r, g, b := float64(col.R)/255, float64(col.G)/255, float64(col.B)/255 + hi, lo := math.Max(r, math.Max(g, b)), math.Min(r, math.Min(g, b)) + d := hi - lo + var h float64 + switch { + case d == 0: + case hi == r: + h = 60 * math.Mod((g-b)/d, 6) + case hi == g: + h = 60 * ((b-r)/d + 2) + default: + h = 60 * ((r-g)/d + 4) + } + if h < 0 { + h += 360 + } + s := 0.0 + if hi > 0 { + s = d / hi + } + return colorPickerHSVA{h, s, hi, float64(col.A) / 255} +} + +func (x colorPickerHSVA) color() ui.Color { + ch := x.v * x.s + h := math.Mod(x.h, 360) / 60 + y := ch * (1 - math.Abs(math.Mod(h, 2)-1)) + var r, g, b float64 + switch { + case h < 1: + r, g = ch, y + case h < 2: + r, g = y, ch + case h < 3: + g, b = ch, y + case h < 4: + g, b = y, ch + case h < 5: + r, b = y, ch + default: + r, b = ch, y + } + m := x.v - ch + byte8 := func(v float64) uint8 { return uint8(math.Round(colorPickerClamp(v) * 255)) } + return ui.Color{R: byte8(r + m), G: byte8(g + m), B: byte8(b + m), A: byte8(x.a)} +} + +func colorPickerClamp(v float64) float64 { return math.Max(0, math.Min(1, v)) } + +// colorPickerLuma is the relative brightness of col, 0 to 1. +func colorPickerLuma(col ui.Color) float64 { + return (0.2126*float64(col.R) + 0.7152*float64(col.G) + 0.0722*float64(col.B)) / 255 +} + +// colorPickerCheckers paints the grid that shows transparency. +func colorPickerCheckers(p *ui.Painter, r ui.Rect, size float32) { + p.Fill(r, ui.RGB(255, 255, 255), 0) + for y, row := r.Y, 0; y < r.Y+r.H; y, row = y+size, row+1 { + for x, col := r.X, 0; x < r.X+r.W; x, col = x+size, col+1 { + if (row+col)%2 == 0 { + p.Fill(ui.Rect{X: x, Y: y, W: min(size, r.X+r.W-x), H: min(size, r.Y+r.H-y)}, ui.RGB(204, 204, 204), 0) + } + } + } +} diff --git a/color_picker_test.go b/color_picker_test.go new file mode 100644 index 0000000..b5dc1a5 --- /dev/null +++ b/color_picker_test.go @@ -0,0 +1,126 @@ +package mujica + +import ( + "testing" + + "github.com/egoist/mygo/ui" +) + +func colorPickerTester(col *ui.Color, opts ColorPickerOptions, changes *int) *ui.Tester { + return ui.NewTester(func(c *ui.Context) { + Use(c, Settings{}) + ui.Column(c).Padding(10).AlignItems(ui.Start).Children(func() { + if ColorPicker(c, col, opts).Changed() { + *changes++ + } + }) + }, 400, 560) +} + +func TestColorPickerPresetAndHex(t *testing.T) { + col, n := ui.Hex("#7D2034"), 0 + tt := colorPickerTester(&col, ColorPickerOptions{Label: "Accent"}, &n) + if err := tt.Click("Accent"); err != nil { + t.Fatal(err) + } + if err := tt.Click("#355E80"); err != nil { + t.Fatal(err) + } + if col != ui.Hex("#355E80") || n != 1 { + t.Fatalf("preset: %v changes %d", col, n) + } + if err := tt.Click("HEX"); err != nil { + t.Fatal(err) + } + tt.Key(ui.Cmd, ui.KeyA) + tt.Type("ff000080") + if col != (ui.Color{R: 255, A: 128}) { + t.Fatalf("hex: %v", col) + } + tt.Key(ui.Cmd, ui.KeyA) + tt.Type("zz") + if col != (ui.Color{R: 255, A: 128}) || !tt.HasText(colorPickerInvalidEN) { + t.Fatalf("invalid hex changed the color or showed no error: %v", col) + } +} + +const colorPickerInvalidEN = "Use HEX RGB, RRGGBB or RRGGBBAA; channels 0–255; opacity 0–100." + +func TestColorPickerRGBAndAlphaFields(t *testing.T) { + col, n := ui.RGB(0, 0, 0), 0 + tt := colorPickerTester(&col, ColorPickerOptions{Label: "Ink"}, &n) + _ = tt.Click("Ink") + _ = tt.Click("Green") + tt.Key(ui.Cmd, ui.KeyA) + tt.Type("200") + _ = tt.Click("Opacity percent") + tt.Key(ui.Cmd, ui.KeyA) + tt.Type("50") + if col != (ui.Color{G: 200, A: 128}) { + t.Fatalf("rgb: %v", col) + } + tt.Key(ui.Cmd, ui.KeyA) + tt.Type("300") + if col.A != 128 || !tt.HasText(colorPickerInvalidEN) { + t.Fatalf("opacity 300 accepted: %v", col) + } +} + +func TestColorPickerKeyboardAndEscape(t *testing.T) { + col, n := ui.Hex("#808080"), 0 + tt := colorPickerTester(&col, ColorPickerOptions{Label: "Shade"}, &n) + tt.Key(0, ui.KeyTab) + tt.Key(0, ui.KeyEnter) + if !tt.Focused("Saturation and brightness") { + t.Fatal("square not focused on open") + } + tt.Key(0, ui.KeyUp) + if col == ui.Hex("#808080") || col.R != col.G { + t.Fatalf("up arrow: %v", col) + } + tt.Key(0, ui.KeyTab) + if !tt.Focused("Hue") { + t.Fatal("hue slider not next") + } + tt.Key(0, ui.KeyEnd) + tt.Key(0, ui.KeyEscape) + if tt.HasText("HEX") || !tt.Focused("Shade") { + t.Fatal("escape did not close and return focus") + } +} + +func TestColorPickerDisabledAndParse(t *testing.T) { + col, n := ui.Hex("#7D2034"), 0 + tt := colorPickerTester(&col, ColorPickerOptions{Label: "Accent", Disabled: true}, &n) + _ = tt.Click("Accent") + if tt.HasText("HEX") { + t.Fatal("disabled picker opened") + } + for in, want := range map[string]ui.Color{"#abc": ui.Hex("#aabbcc"), "7D2034": ui.Hex("#7D2034"), "#00000000": {}} { + got, err := colorPickerParseHex(in) + if err != nil || got != want { + t.Errorf("%q: %v %v", in, got, err) + } + } + for _, in := range []string{"", "#12", "#12345g", "#1234567"} { + if _, err := colorPickerParseHex(in); err == nil { + t.Errorf("%q parsed", in) + } + } +} + +func TestColorPickerSnapshot(t *testing.T) { + a, b := ui.Hex("#7D2034"), ui.Hex("#355E8099") + snapshot(t, "color_picker", 340, 520, func(c *ui.Context) { + ui.Column(c).Padding(20).Gap(12).AlignItems(ui.Start).Children(func() { + ColorPicker(c, &b, ColorPickerOptions{Disabled: true}) + p := ColorPicker(c, &a, ColorPickerOptions{}) + st := ui.Local(p.Trigger, "snap", func() bool { return false }) + if !*st { + *st = true + ui.Local(p.Trigger, "picker", func() colorPickerState { return colorPickerState{} }).open = true + c.Invalidate() + } + }) + }) +} diff --git a/docs/components/041-form.md b/docs/components/041-form.md new file mode 100644 index 0000000..fa36aa6 --- /dev/null +++ b/docs/components/041-form.md @@ -0,0 +1,59 @@ +# 041 Form + +表单。 + +## 用途 + +组合多个字段:注册字段、同步与调用方提供的异步校验、提交与重置、提交中防重入、提交失败时定位第一个错误字段。Form 不保存业务模型,不调用远程接口,也不做持久化;值始终由应用持有。 + +## 最小示例 + +```go +var state mujica.FormState +name := "" +view := func(c *ui.Context) { + mujica.Use(c, mujica.Settings{}) + res := mujica.Form(c, &state, mujica.FormOptions{}, func(f *mujica.FormScope) { + mujica.FormItem(f, "name", &name, mujica.FormItemOptions[string]{Label: "Name", Required: true}, func() *ui.Element { + return mujica.InputGroup(c, &name, mujica.InputGroupOptions{Invalid: state.Error("name") != ""}).Input + }) + }) + if res.Submitted() { + state.EndSubmit("") // after saving name + } +} +ui.Render(view, 400, 300, 1) +``` + +## 参数 + +| 字段 | 类型 | 默认值 | 含义 | +| --- | --- | --- | --- | +| `FormOptions.Horizontal` | `bool` | false | 标签放在控件左侧一列,右对齐(MyGo `ui.Form` 布局)。 | +| `FormOptions.SubmitLabel` | `string` | 内置“Submit/提交” | 提交按钮文字。 | +| `FormOptions.ResetLabel` | `string` | 内置“Reset/重置” | 重置按钮文字。 | +| `FormItemOptions[T].Label` | `string` | 必填 | 字段标签;空值 panic。 | +| `FormItemOptions[T].Required` | `bool` | false | 标记必填;零值、空字符串、空切片或空映射视为缺失。 | +| `FormItemOptions[T].Description` | `string` | "" | 说明文字;异步校验进行中显示“Checking…”。 | +| `FormItemOptions[T].Initial` | `T` | 零值 | 重置时写回的初始值。 | +| `FormItemOptions[T].Validate` | `func(T) string` | nil | 同步校验,返回错误文字或 "";跨字段规则在此读取其他值。 | +| `FormItemOptions[T].Async` | `func(v T, version uint64)` | nil | 启动异步校验;完成后在界面线程调用 `FormState.Resolve(name, version, msg)`。 | + +## 状态 + +默认;字段错误(触碰后或首次提交后才显示);异步校验中(说明显示 Checking…);提交中(提交按钮显示加载并阻止重复提交,重置禁用);提交失败(表单级错误显示在按钮上方,保留全部值)。 + +## 事件 + +`FormParts.Submitted()`:全部校验通过的一次提交,处理完毕后调用 `FormState.EndSubmit(err)`。`FormParts.Reset()`:已恢复初始值并清除校验与提交状态。`FormState.Submit()` 可在 Enter 等处请求提交。`FormState.Error(name)` 返回当前应显示的错误,用于给控件设置 Invalid。 + +## 键盘操作 + +Tab / Shift+Tab 在字段间移动,离开字段即校验。提交失败后焦点移到第一个错误字段并滚动到可见处,同时向屏幕阅读器播报。点击标签聚焦控件。 + +## 限制 + +- 控件由调用方构建,`FormItem` 的 control 回调必须返回可获取焦点的元素,用于错误定位。 +- 校验在失焦与提交时触发;已触碰字段在值变化时重新同步校验,异步校验只在下一次失焦或提交时重跑。 +- 异步结果以版本号对齐:值变化后旧版本的 `Resolve` 返回 false 并被丢弃。`FormState` 的方法只能在界面线程调用,后台任务需经 `Window.Update` 回到界面线程。 +- 必填标记以“ *”附在标签文字后,屏幕阅读器会一起读出;MyGo 的 Field 标签不支持富文本,无法单独着色。 diff --git a/docs/components/042-form-field.md b/docs/components/042-form-field.md new file mode 100644 index 0000000..9e3f6e0 --- /dev/null +++ b/docs/components/042-form-field.md @@ -0,0 +1,49 @@ +# 042 FormField + +表单字段。 + +## 用途 + +为单个控件提供标签、必填标记、说明与错误文字。标签通过 MyGo `ui.Field` 关联控件:屏幕阅读器以标签命名控件,并读出说明与错误;点击标签聚焦控件。 + +## 最小示例 + +```go +email := "" +view := func(c *ui.Context) { + mujica.Use(c, mujica.Settings{}) + mujica.FormField(c, "Email", mujica.FormFieldOptions{Required: true, Description: "For receipts."}, func() { + mujica.InputGroup(c, &email, mujica.InputGroupOptions{}) + }) +} +ui.Render(view, 400, 300, 1) +``` + +## 参数 + +| 字段 | 类型 | 默认值 | 含义 | +| --- | --- | --- | --- | +| `label` | `string` | 必填 | 标签文字;空值 panic。 | +| `FormFieldOptions.Required` | `bool` | false | 在标签后加“ *”。 | +| `FormFieldOptions.Description` | `string` | "" | 控件下方的说明。 | +| `FormFieldOptions.Error` | `string` | "" | 控件下方的错误,并把控件标记为无效。 | +| `FormFieldOptions.Horizontal` | `bool` | false | 标签在左、控件在右。 | +| `control` | `func()` | 必填 | 构建控件;其中第一个可获取焦点的元素即被关联的控件。 | + +## 状态 + +默认、必填、带说明、错误;禁用由控件自身决定,标签随之变淡。 + +## 事件 + +无自身事件;事件来自内部控件。 + +## 键盘操作 + +Tab 直接到达控件;点击标签聚焦控件,或切换复选框、开关。 + +## 限制 + +- 标签、说明与错误文字由 MyGo `ui.Field` 绘制,使用主题颜色(文字、TextMuted、Danger);字号与间距跟随 MyGo。 +- 错误只把边框染红的效果对 MyGo 自带输入框生效;MujicaUI 的 InputGroup 等需同时传入 `Invalid: true`。 +- 左右布局时多个字段需放在同一个 `ui.Form` 或 MujicaUI `Form(Horizontal)` 中才能对齐标签宽度;单独使用时只按自身标签宽度排列。 diff --git a/docs/components/043-input-group.md b/docs/components/043-input-group.md new file mode 100644 index 0000000..a9a86ba --- /dev/null +++ b/docs/components/043-input-group.md @@ -0,0 +1,54 @@ +# 043 InputGroup + +输入组合。 + +## 用途 + +把单行输入框与前后缀文字、按钮放进同一个边框,例如网址前缀、单位、清空或搜索按钮。每个按钮保留独立焦点。 + +## 最小示例 + +```go +site := "" +view := func(c *ui.Context) { + mujica.Use(c, mujica.Settings{}) + g := mujica.InputGroup(c, &site, mujica.InputGroupOptions{ + Prefix: "https://", Label: "Site", + Buttons: []mujica.InputGroupButton{{Label: "Open"}}, + }) + if g.Clicked == 0 { + c.OpenURL("https://" + site) + } +} +ui.Render(view, 400, 300, 1) +``` + +## 参数 + +| 字段 | 类型 | 默认值 | 含义 | +| --- | --- | --- | --- | +| `value` | `*string` | 必填 | 输入框的值。 | +| `Prefix / Suffix` | `string` | "" | 输入框前后的固定文字。 | +| `Buttons` | `[]InputGroupButton` | nil | 按钮:Label(必填,文字或无障碍名称)、Icon、IconOnly、Leading(放在输入框前)、Disabled。 | +| `Placeholder` | `string` | "" | 占位文字。 | +| `Label` | `string` | "" | 不在 FormField 内时为输入框命名。 | +| `Disabled` | `bool` | false | 禁用整个组合。 | +| `ReadOnly` | `bool` | false | 值显示为可选择、可复制、可聚焦的文字,不能修改;按钮仍可用。 | +| `Invalid` | `bool` | false | 边框显示危险色。 | + +## 状态 + +默认、悬停(边框加深)、聚焦(强调色边框与焦点环)、禁用、只读、错误。 + +## 事件 + +`InputGroupParts.Changed()` 值被编辑;`Submitted()` 在输入框中按 Enter;`Clicked` 为被点击按钮在 Buttons 中的下标,未点击为 -1。`Input` 为可聚焦的输入元素。 + +## 键盘操作 + +输入框与每个按钮都是独立的 Tab 停靠点;按钮响应 Enter 和 Space;输入框保留选择、剪贴板、撤销与输入法。 + +## 限制 + +- MyGo 没有公开的只读输入框,ReadOnly 改用可选择文字实现,宽度与输入框一致。 +- IconOnly 按钮必须提供 Icon,Label 作为无障碍名称;缺失时 panic。 diff --git a/docs/components/044-pin-input.md b/docs/components/044-pin-input.md new file mode 100644 index 0000000..0954adb --- /dev/null +++ b/docs/components/044-pin-input.md @@ -0,0 +1,48 @@ +# 044 PinInput + +验证码输入。 + +## 用途 + +输入固定位数的数字验证码。值是字符串,保留前导零。 + +## 最小示例 + +```go +code := "" +view := func(c *ui.Context) { + mujica.Use(c, mujica.Settings{}) + if mujica.PinInput(c, &code, mujica.PinInputOptions{Length: 6}).Completed() { + fmt.Println("verify", code) + } +} +ui.Render(view, 400, 300, 1) +``` + +## 参数 + +| 字段 | 类型 | 默认值 | 含义 | +| --- | --- | --- | --- | +| `value` | `*string` | 必填 | 当前数字串;含非数字或超出位数时 panic。 | +| `Length` | `int` | 6 | 位数。 | +| `Label` | `string` | 内置“Verification code/验证码” | 无障碍名称。 | +| `Disabled` | `bool` | false | 禁用。 | +| `Invalid` | `bool` | false | 格子边框显示危险色。 | + +## 状态 + +默认、聚焦(当前格强调边框、焦点环与光标)、输入法组合中(当前格显示带下划线的组合文字)、禁用、错误。 + +## 事件 + +`PinInputParts.Changed()` 值改变;`Completed()` 在用户填满最后一格的那一帧为 true。 + +## 键盘操作 + +数字键依次填格;非数字被忽略;Backspace 清除最后一位;Cmd/Ctrl+V 或编辑菜单粘贴整段,取其中的数字填满各格。 + +## 限制 + +- 只支持数字。编辑按顺序进行:只能在末尾追加或删除,不能单独改中间某一格。 +- 输入法组合期间不提交、不跳格;组合结束后才写入值。 +- 整个组件是一个焦点停靠点,屏幕阅读器以文本框读出名称与“已输入 n / N 位”的说明。 diff --git a/docs/components/045-tags-input.md b/docs/components/045-tags-input.md new file mode 100644 index 0000000..61ff072 --- /dev/null +++ b/docs/components/045-tags-input.md @@ -0,0 +1,48 @@ +# 045 TagsInput + +标签输入。 + +## 用途 + +编辑一组不重复的标签,对应 MyGo 的 Token field。支持 Enter 或分隔符添加、整段粘贴拆分、去重、数量上限与添加前校验。 + +## 最小示例 + +```go +tags := []string{"gothic"} +view := func(c *ui.Context) { + mujica.Use(c, mujica.Settings{}) + mujica.TagsInput(c, &tags, mujica.TagsInputOptions{Max: 5, Placeholder: "Add a tag", Label: "Tags"}) +} +ui.Render(view, 400, 300, 1) +``` + +## 参数 + +| 字段 | 类型 | 默认值 | 含义 | +| --- | --- | --- | --- | +| `tags` | `*[]string` | 必填 | 标签列表。 | +| `Max` | `int` | 0(不限) | 最多标签数;负数 panic。 | +| `Separators` | `string` | ",;,;" 与换行、制表符 | 输入或粘贴时用于拆分的字符。 | +| `Validate` | `func(string) string` | nil | 添加前校验,返回错误文字或 ""。 | +| `Placeholder` | `string` | "" | 占位文字。 | +| `Label` | `string` | "" | 不在 FormField 内时为输入框命名。 | +| `Disabled` | `bool` | false | 禁用。 | +| `Invalid` | `bool` | false | 边框显示危险色。 | + +## 状态 + +默认、聚焦、错误(重复、超出上限、校验失败时显示具体原因并保留输入文字)、禁用。 + +## 事件 + +`TagsInputParts.Changed()` 添加或移除了标签;`Input` 为输入框元素。 + +## 键盘操作 + +Enter 或分隔符添加;输入框为空时 Backspace 移除最后一个标签;Tab 可到达每个标签的移除按钮,Enter/Space 移除。 + +## 限制 + +- 粘贴含分隔符的文本时,未通过检查的片段保留在输入框中,供用户修改。 +- MyGo 单行输入框会处理粘贴文本中的换行,换行能否作为分隔符取决于平台粘贴行为;逗号、分号总是有效。 diff --git a/docs/components/046-rating.md b/docs/components/046-rating.md new file mode 100644 index 0000000..6e7dd1c --- /dev/null +++ b/docs/components/046-rating.md @@ -0,0 +1,46 @@ +# 046 Rating + +评分。 + +## 用途 + +以星形显示和设置评分,支持半星等步长、只读展示和数值说明。 + +## 最小示例 + +```go +stars := 3.5 +view := func(c *ui.Context) { + mujica.Use(c, mujica.Settings{}) + mujica.Rating(c, &stars, mujica.RatingOptions{Step: 0.5, ShowValue: true}) +} +ui.Render(view, 400, 300, 1) +``` + +## 参数 + +| 字段 | 类型 | 默认值 | 含义 | +| --- | --- | --- | --- | +| `value` | `*float64` | 必填 | 评分,0 到 Max。 | +| `Max` | `int` | 5 | 星数;负数 panic。 | +| `Step` | `float64` | 1 | 最小变化量,须整除 1(如 0.5、0.25);否则 panic。 | +| `ReadOnly` | `bool` | false | 只显示,可聚焦以便读出数值。 | +| `Disabled` | `bool` | false | 禁用。 | +| `Label` | `string` | 内置“Rating/评分” | 无障碍名称。 | +| `ShowValue` | `bool` | false | 在星形右侧显示“3.5 / 5”。 | + +## 状态 + +默认、悬停(预览指针处的数值)、按下拖动、聚焦(焦点环)、只读、禁用。 + +## 事件 + +`RatingParts.Changed()` 用户设置了新值;同值点击不触发。 + +## 键盘操作 + +→/↑ 增加一个步长,←/↓ 减少,Home 清零,End 满分。 + +## 限制 + +- 屏幕阅读器以滑块角色读出名称,数值通过说明“3.5 of 5”提供;MyGo 未公开为自定义元素设置数值范围的接口。 diff --git a/docs/components/047-color-picker.md b/docs/components/047-color-picker.md new file mode 100644 index 0000000..1929278 --- /dev/null +++ b/docs/components/047-color-picker.md @@ -0,0 +1,45 @@ +# 047 ColorPicker + +颜色选择器。 + +## 用途 + +选择带透明度的颜色:色板按钮弹出面板,包含饱和度/亮度方块、色相与透明度滑块、HEX 与 RGB、透明度输入以及预设色。透明部分显示棋盘格。 + +## 最小示例 + +```go +tint := ui.Hex("#7D2034") +view := func(c *ui.Context) { + mujica.Use(c, mujica.Settings{}) + mujica.ColorPicker(c, &tint, mujica.ColorPickerOptions{Label: "Tint"}) +} +ui.Render(view, 400, 300, 1) +``` + +## 参数 + +| 字段 | 类型 | 默认值 | 含义 | +| --- | --- | --- | --- | +| `color` | `*ui.Color` | 必填 | 当前颜色。 | +| `Presets` | `[]ui.Color` | 内置 12 色宫廷色板 | 预设色。 | +| `Label` | `string` | 内置“Color/颜色” | 按钮的无障碍名称。 | +| `Disabled` | `bool` | false | 禁用。 | + +## 状态 + +默认、悬停、聚焦、展开、输入无效(对应输入框边框变红并显示格式说明,颜色不变)、预设选中(边框加勾选标记)、禁用。 + +## 事件 + +`ColorPickerParts.Changed()` 用户选择了新颜色;`Trigger` 为按钮元素。 + +## 键盘操作 + +Enter/Space 打开;打开后焦点在方块上,方向键调整饱和度与亮度;Tab 依次到达色相、透明度滑块(方向键、Home、End)、HEX、R、G、B、A 输入框与预设色;Escape 或点击外部关闭并把焦点还给按钮。 + +## 限制 + +- HEX 接受 RGB、RRGGBB、RRGGBBAA,可带或不带 #;RGB 为 0–255 整数,透明度为 0–100。无效输入保留在输入框中直到失焦,不修改颜色。 +- 色谱渐变是 PRD 允许的例外。 +- 弹层位置由 MyGo PopoverBase 决定,空间不足时显示在按钮上方。 diff --git a/docs/components/048-file-picker.md b/docs/components/048-file-picker.md new file mode 100644 index 0000000..f974065 --- /dev/null +++ b/docs/components/048-file-picker.md @@ -0,0 +1,52 @@ +# 048 FilePicker + +文件选择。 + +## 用途 + +调用 MyGo 的系统文件或文件夹选择对话框(`mygo.Dialog.Open`),支持单选、多选与扩展名筛选,只返回所选路径。 + +## 最小示例 + +```go +var paths []string +view := func(c *ui.Context) { + mujica.Use(c, mujica.Settings{}) + mujica.FilePicker(c, &paths, mujica.FilePickerOptions{ + Multiple: true, + Filters: []mygo.FileFilter{{Name: "Scores", Extensions: []string{"pdf", "mid"}}}, + }) +} +ui.Render(view, 400, 300, 1) +``` + +## 参数 + +| 字段 | 类型 | 默认值 | 含义 | +| --- | --- | --- | --- | +| `paths` | `*[]string` | 必填 | 所选路径;取消时不变。 | +| `Directory` | `bool` | false | 选择文件夹。 | +| `Multiple` | `bool` | false | 允许多选。 | +| `Filters` | `[]mygo.FileFilter` | nil | 扩展名筛选,扩展名不带点;带点时 panic。 | +| `Title` | `string` | "" | 对话框标题。 | +| `Label` | `string` | 内置“Choose file…/选择文件…” | 按钮文字。 | +| `Parent` | `*mygo.Window` | nil | 附着的窗口(macOS 显示为表单)。 | +| `Disabled` | `bool` | false | 禁用。 | + +## 状态 + +默认、对话框打开中(按钮显示加载并阻止重复打开)、已选择(列出文件名与所在目录)、对话框失败(危险色说明)、禁用。 + +## 事件 + +`FilePickerParts.Changed()` 选中了新路径;`Canceled()` 用户取消,不视为错误。 + +## 键盘操作 + +Enter/Space 打开系统对话框;对话框内的键盘操作由系统提供。 + +## 限制 + +- 对话框为系统样式,不换肤。 +- 对话框在后台 goroutine 中等待,结果经 `Context.Invalidate` 回到界面线程;需要应用已通过 `mygo.App.Run` 启动,无窗口测试中无法打开真实对话框。 +- 不读取文件内容。 diff --git a/docs/components/049-file-drop-zone.md b/docs/components/049-file-drop-zone.md new file mode 100644 index 0000000..f285161 --- /dev/null +++ b/docs/components/049-file-drop-zone.md @@ -0,0 +1,49 @@ +# 049 FileDropZone + +文件拖放区。 + +## 用途 + +接收从系统拖入或通过“浏览…”选择的文件,检查类型、数量和大小,显示文件列表与错误,可逐项移除。不上传,不读取内容。 + +## 最小示例 + +```go +var files []mujica.DroppedFile +view := func(c *ui.Context) { + mujica.Use(c, mujica.Settings{}) + mujica.FileDropZone(c, &files, mujica.FileDropZoneOptions{ + Extensions: []string{"png", "jpg"}, MaxFiles: 3, MaxSize: 5 << 20, + }) +} +ui.Render(view, 400, 300, 1) +``` + +## 参数 + +| 字段 | 类型 | 默认值 | 含义 | +| --- | --- | --- | --- | +| `files` | `*[]DroppedFile` | 必填 | 已接受的文件(Path、Size)。 | +| `Extensions` | `[]string` | nil(不限) | 允许的扩展名,不带点,不区分大小写。 | +| `MaxFiles` | `int` | 0(不限) | 列表最多文件数。 | +| `MaxSize` | `int64` | 0(不限) | 单个文件的最大字节数。 | +| `Hint` | `string` | 内置“Drop files here/将文件拖到此处” | 提示文字。 | +| `Disabled` | `bool` | false | 禁用拖放、浏览与移除。 | + +## 状态 + +默认、文件拖到上方(选中底色与强调色虚线边框)、检查中(列表显示“Checking…”)、已接受、被拒绝(危险色图标与具体原因)、禁用。 + +## 事件 + +`FileDropZoneParts.Changed()` 添加或移除了文件。被拒绝的条目只显示在组件内,可单独关闭。 + +## 键盘操作 + +Tab 到达“浏览…”与每个移除按钮;拖放需要指针,键盘用户通过“浏览…”添加文件。 + +## 限制 + +- 文件大小通过后台 goroutine 中的 `os.Stat` 获取,不阻塞界面线程;文件夹、无法读取的路径会被拒绝。 +- 重复路径静默忽略。 +- MyGo 的无窗口测试器不能模拟系统拖放,自动测试通过“浏览…”路径覆盖同一检查流程。 diff --git a/docs/components/050-transfer.md b/docs/components/050-transfer.md new file mode 100644 index 0000000..7ace8e6 --- /dev/null +++ b/docs/components/050-transfer.md @@ -0,0 +1,46 @@ +# 050 Transfer + +穿梭框。 + +## 用途 + +在左右两个列表间移动条目,选出一个子集。两侧各有搜索、复选与全选,中间按钮批量移动。以稳定 ID 保存状态。 + +## 最小示例 + +```go +chosen := []string{"uika"} +items := []mujica.TransferItem{{ID: "uika", Label: "Uika"}, {ID: "mutsumi", Label: "Mutsumi"}, {ID: "nyamu", Label: "Nyamu", Disabled: true}} +view := func(c *ui.Context) { + mujica.Use(c, mujica.Settings{}) + mujica.Transfer(c, &chosen, items, mujica.TransferOptions{}) +} +ui.Render(view, 400, 300, 1) +``` + +## 参数 + +| 字段 | 类型 | 默认值 | 含义 | +| --- | --- | --- | --- | +| `target` | `*[]string` | 必填 | 右侧已选条目的 ID,按加入顺序;含未知 ID 时 panic。 | +| `items` | `[]TransferItem` | 必填 | 全部条目:ID(唯一、非空)、Label、Disabled。 | +| `SourceTitle / TargetTitle` | `string` | 内置“Available/可选”“Chosen/已选” | 两侧标题。 | +| `Height` | `float32` | 240 | 列表高度(DIP)。 | +| `Disabled` | `bool` | false | 禁用整个组件。 | + +## 状态 + +默认、悬停、勾选(底色加勾选标记)、半选(全选框显示减号)、禁用条目(不可勾选、不随全选移动)、空列表、无搜索结果、禁用。 + +## 事件 + +`TransferParts.Changed()` 有条目移动。 + +## 键盘操作 + +Tab 进入列表,↑/↓ 在条目间移动,Space 勾选;Tab 到达全选框、搜索框与移动按钮。 + +## 限制 + +- 条目逐项创建,适合数百条以内;大数据量请使用虚拟化列表组合。 +- 全选只作用于当前搜索结果中的可用条目。 diff --git a/example_form_test.go b/example_form_test.go new file mode 100644 index 0000000..0873135 --- /dev/null +++ b/example_form_test.go @@ -0,0 +1,124 @@ +package mujica_test + +import ( + "fmt" + + "github.com/egoist/mygo" + "github.com/egoist/mygo/ui" + + mujica "mujicaui" +) + +func ExampleForm() { + var state mujica.FormState + name := "" + view := func(c *ui.Context) { + mujica.Use(c, mujica.Settings{}) + res := mujica.Form(c, &state, mujica.FormOptions{}, func(f *mujica.FormScope) { + mujica.FormItem(f, "name", &name, mujica.FormItemOptions[string]{Label: "Name", Required: true}, func() *ui.Element { + return mujica.InputGroup(c, &name, mujica.InputGroupOptions{Invalid: state.Error("name") != ""}).Input + }) + }) + if res.Submitted() { + state.EndSubmit("") // after saving name + } + } + ui.Render(view, 400, 300, 1) +} + +func ExampleFormField() { + email := "" + view := func(c *ui.Context) { + mujica.Use(c, mujica.Settings{}) + mujica.FormField(c, "Email", mujica.FormFieldOptions{Required: true, Description: "For receipts."}, func() { + mujica.InputGroup(c, &email, mujica.InputGroupOptions{}) + }) + } + ui.Render(view, 400, 300, 1) +} + +func ExampleInputGroup() { + site := "" + view := func(c *ui.Context) { + mujica.Use(c, mujica.Settings{}) + g := mujica.InputGroup(c, &site, mujica.InputGroupOptions{ + Prefix: "https://", Label: "Site", + Buttons: []mujica.InputGroupButton{{Label: "Open"}}, + }) + if g.Clicked == 0 { + c.OpenURL("https://" + site) + } + } + ui.Render(view, 400, 300, 1) +} + +func ExamplePinInput() { + code := "" + view := func(c *ui.Context) { + mujica.Use(c, mujica.Settings{}) + if mujica.PinInput(c, &code, mujica.PinInputOptions{Length: 6}).Completed() { + fmt.Println("verify", code) + } + } + ui.Render(view, 400, 300, 1) +} + +func ExampleTagsInput() { + tags := []string{"gothic"} + view := func(c *ui.Context) { + mujica.Use(c, mujica.Settings{}) + mujica.TagsInput(c, &tags, mujica.TagsInputOptions{Max: 5, Placeholder: "Add a tag", Label: "Tags"}) + } + ui.Render(view, 400, 300, 1) +} + +func ExampleRating() { + stars := 3.5 + view := func(c *ui.Context) { + mujica.Use(c, mujica.Settings{}) + mujica.Rating(c, &stars, mujica.RatingOptions{Step: 0.5, ShowValue: true}) + } + ui.Render(view, 400, 300, 1) +} + +func ExampleColorPicker() { + tint := ui.Hex("#7D2034") + view := func(c *ui.Context) { + mujica.Use(c, mujica.Settings{}) + mujica.ColorPicker(c, &tint, mujica.ColorPickerOptions{Label: "Tint"}) + } + ui.Render(view, 400, 300, 1) +} + +func ExampleFilePicker() { + var paths []string + view := func(c *ui.Context) { + mujica.Use(c, mujica.Settings{}) + mujica.FilePicker(c, &paths, mujica.FilePickerOptions{ + Multiple: true, + Filters: []mygo.FileFilter{{Name: "Scores", Extensions: []string{"pdf", "mid"}}}, + }) + } + ui.Render(view, 400, 300, 1) +} + +func ExampleFileDropZone() { + var files []mujica.DroppedFile + view := func(c *ui.Context) { + mujica.Use(c, mujica.Settings{}) + mujica.FileDropZone(c, &files, mujica.FileDropZoneOptions{ + Extensions: []string{"png", "jpg"}, MaxFiles: 3, MaxSize: 5 << 20, + }) + } + ui.Render(view, 400, 300, 1) +} + +func ExampleTransfer() { + chosen := []string{"uika"} + items := []mujica.TransferItem{{ID: "uika", Label: "Uika"}, {ID: "mutsumi", Label: "Mutsumi"}, {ID: "nyamu", Label: "Nyamu", Disabled: true}} + view := func(c *ui.Context) { + mujica.Use(c, mujica.Settings{}) + mujica.Transfer(c, &chosen, items, mujica.TransferOptions{}) + } + ui.Render(view, 400, 300, 1) +} diff --git a/examples/gallery/pages_form.go b/examples/gallery/pages_form.go index 4c1b18b..f446d3f 100644 --- a/examples/gallery/pages_form.go +++ b/examples/gallery/pages_form.go @@ -1,3 +1,417 @@ package main -var formPages = []page{} +import ( + "fmt" + "strings" + "time" + + "github.com/egoist/mygo" + "github.com/egoist/mygo/ui" + + mujica "mujicaui" + "mujicaui/icons" +) + +// formStates shows a component's states side by side. +func formStates(c *ui.Context, fn func()) { + ui.Column(c).Gap(8).Children(func() { + ui.Text(c, "States").FontWeight(600) + ui.Row(c).Gap(24).Wrap().AlignItems(ui.Start).Children(fn) + }) +} + +// formToggles shows parameter check boxes in one row. +func formToggles(c *ui.Context, names []string, values ...*bool) { + ui.Row(c).Gap(16).Wrap().Children(func() { + for i, n := range names { + ui.Checkbox(c, values[i], n) + } + }) +} + +type formDemo struct { + st mujica.FormState + name, email, password string + tags []string + horizontal, slowServer bool + pending int +} + +type fieldDemo struct { + value string + required, horizontal bool + withError bool +} + +type groupDemo struct { + value string + disabled, readOnly, invalid bool +} + +type pinDemo struct { + code string + length4, disabled bool + invalid bool +} + +type tagsDemo struct { + tags []string + limit, disabled bool +} + +type ratingDemo struct { + value float64 + half, readOnly, disabled bool +} + +type colorDemo struct { + color ui.Color + disabled bool +} + +type pickerDemo struct { + paths []string + directory, multiple, filter bool + disabled bool +} + +type dropDemo struct { + files []mujica.DroppedFile + images bool + disabled bool +} + +type transferDemo struct { + chosen []string + disabled bool +} + +var transferDemoItems = []mujica.TransferItem{ + {ID: "sakiko", Label: "Sakiko Togawa"}, {ID: "uika", Label: "Uika Misumi"}, {ID: "mutsumi", Label: "Mutsumi Wakaba"}, + {ID: "umiri", Label: "Umiri Yahata"}, {ID: "nyamu", Label: "Nyamu Yutenji"}, {ID: "tomori", Label: "Tomori Takamatsu", Disabled: true}, + {ID: "anon", Label: "Anon Chihaya"}, {ID: "rana", Label: "Rana Kaname"}, {ID: "soyo", Label: "Soyo Nagasaki"}, +} + +type formAsync struct { + version uint64 + err string +} + +var formPages = []page{ + {ID: "041", Name: "Form", ZhName: "表单", Category: "Forms", Code: formCode["Form"], + Keys: "Tab moves between fields; leaving a field validates it. Enter in a field submits. A failed submit focuses the first error.", + Demo: func(c *ui.Context, log func(string)) { + d := ui.Local(c.Root(), "041", func() formDemo { + return formDemo{name: "Sakiko", tags: []string{"keyboard"}} + }) + async := *ui.Local(c.Root(), "041.async", func() chan formAsync { return make(chan formAsync, 8) }) + saved := *ui.Local(c.Root(), "041.save", func() chan string { return make(chan string, 1) }) + for drained := false; !drained; { + select { + case r := <-async: + log(fmt.Sprintf("async name check v%d: %q, kept %v", r.version, r.err, d.st.Resolve("name", r.version, r.err))) + case e := <-saved: + d.st.EndSubmit(e) + log(fmt.Sprintf("submit ended, error %q", e)) + default: + drained = true + } + } + formToggles(c, []string{"Labels beside fields", "Server rejects the submit"}, &d.horizontal, &d.slowServer) + res := mujica.Form(c, &d.st, mujica.FormOptions{Horizontal: d.horizontal}, func(f *mujica.FormScope) { + mujica.FormItem(f, "name", &d.name, mujica.FormItemOptions[string]{Label: "Stage name", Required: true, Initial: "Sakiko", + Description: "Checked against the archive; \"Oblivionis\" is taken.", + Async: func(v string, version uint64) { + log(fmt.Sprintf("async check started v%d", version)) + go func() { + time.Sleep(700 * time.Millisecond) + msg := "" + if strings.EqualFold(v, "Oblivionis") { + msg = "That name is taken." + } + async <- formAsync{version, msg} + c.Invalidate() + }() + }}, func() *ui.Element { + return mujica.InputGroup(c, &d.name, mujica.InputGroupOptions{Invalid: d.st.Error("name") != ""}).Input + }) + mujica.FormItem(f, "email", &d.email, mujica.FormItemOptions[string]{Label: "Email", Required: true, + Validate: func(v string) string { + if !strings.Contains(v, "@") { + return "Enter an email address." + } + return "" + }}, func() *ui.Element { + g := mujica.InputGroup(c, &d.email, mujica.InputGroupOptions{Invalid: d.st.Error("email") != ""}) + if g.Submitted() { + d.st.Submit() + } + return g.Input + }) + mujica.FormItem(f, "tags", &d.tags, mujica.FormItemOptions[[]string]{Label: "Roles", Required: true, Initial: []string{"keyboard"}}, func() *ui.Element { + return mujica.TagsInput(c, &d.tags, mujica.TagsInputOptions{Max: 4, Invalid: d.st.Error("tags") != ""}).Input + }) + }) + if res.Submitted() { + log(fmt.Sprintf("submitted %q %q %q", d.name, d.email, d.tags)) + fail := "" + if d.slowServer { + fail = "The archive refused the entry. Your values are kept." + } + go func() { + time.Sleep(900 * time.Millisecond) + saved <- fail + c.Invalidate() + }() + } + if res.Reset() { + log("reset to initial values") + } + }}, + {ID: "042", Name: "FormField", ZhName: "表单字段", Category: "Forms", Code: formCode["FormField"], + Keys: "Clicking the label focuses the control; Tab reaches the control directly.", + Demo: func(c *ui.Context, log func(string)) { + d := ui.Local(c.Root(), "042", func() fieldDemo { return fieldDemo{} }) + formToggles(c, []string{"Required", "Label beside", "Show error"}, &d.required, &d.horizontal, &d.withError) + e := "" + if d.withError { + e = "This name is already in the guest list." + } + mujica.FormField(c, "Guest name", mujica.FormFieldOptions{Required: d.required, Horizontal: d.horizontal, + Description: "As written on the invitation.", Error: e}, func() { + if mujica.InputGroup(c, &d.value, mujica.InputGroupOptions{Invalid: d.withError}).Changed() { + log(fmt.Sprintf("value %q", d.value)) + } + }) + }}, + {ID: "043", Name: "InputGroup", ZhName: "输入组合", Category: "Forms", Code: formCode["InputGroup"], + Keys: "Each button and the input are separate Tab stops. Enter in the input submits.", + Demo: func(c *ui.Context, log func(string)) { + d := ui.Local(c.Root(), "043", func() groupDemo { return groupDemo{value: "mujica"} }) + formToggles(c, []string{"Disabled", "Read-only", "Invalid"}, &d.disabled, &d.readOnly, &d.invalid) + opts := mujica.InputGroupOptions{Prefix: "https://", Suffix: ".org", Label: "Site", Disabled: d.disabled, ReadOnly: d.readOnly, Invalid: d.invalid, + Buttons: []mujica.InputGroupButton{{Label: "Clear", Icon: icons.Must("x"), IconOnly: true}, {Label: "Copy", Icon: icons.Must("copy")}}} + g := mujica.InputGroup(c, &d.value, opts) + switch g.Clicked { + case 0: + d.value = "" + log("clear") + case 1: + c.WriteClipboard("https://" + d.value + ".org") + log("copied") + } + if g.Changed() { + log(fmt.Sprintf("value %q", d.value)) + } + if g.Submitted() { + log("submitted") + } + formStates(c, func() { + for _, o := range []mujica.InputGroupOptions{{Label: "Disabled", Disabled: true}, {Label: "Read-only", ReadOnly: true}, {Label: "Invalid", Invalid: true}} { + v := o.Label + mujica.InputGroup(c, &v, o) + } + }) + }}, + {ID: "044", Name: "PinInput", ZhName: "验证码输入", Category: "Forms", Code: formCode["PinInput"], + Keys: "Type digits to fill cells, Backspace clears the last, Cmd/Ctrl+V pastes the whole code.", + Demo: func(c *ui.Context, log func(string)) { + d := ui.Local(c.Root(), "044", func() pinDemo { return pinDemo{} }) + formToggles(c, []string{"4 digits", "Disabled", "Invalid"}, &d.length4, &d.disabled, &d.invalid) + n := 6 + if d.length4 { + n = 4 + } + if len(d.code) > n { + d.code = d.code[:n] + } + p := mujica.PinInput(c, &d.code, mujica.PinInputOptions{Length: n, Disabled: d.disabled, Invalid: d.invalid}) + if p.Changed() { + log(fmt.Sprintf("code %q", d.code)) + } + if p.Completed() { + log(fmt.Sprintf("completed %q", d.code)) + } + formStates(c, func() { + a, b := "12", "0420" + mujica.PinInput(c, &a, mujica.PinInputOptions{Length: 4, Disabled: true, Label: "Disabled code"}) + mujica.PinInput(c, &b, mujica.PinInputOptions{Length: 4, Invalid: true, Label: "Invalid code"}) + }) + }}, + {ID: "045", Name: "TagsInput", ZhName: "标签输入", Category: "Forms", Code: formCode["TagsInput"], + Keys: "Enter or a comma adds a tag; Backspace in the empty input removes the last; Tab reaches each remove button.", + Demo: func(c *ui.Context, log func(string)) { + d := ui.Local(c.Root(), "045", func() tagsDemo { return tagsDemo{tags: []string{"rose", "thorn"}} }) + formToggles(c, []string{"At most 5", "Disabled"}, &d.limit, &d.disabled) + opts := mujica.TagsInputOptions{Placeholder: "Add a flower", Label: "Flowers", Disabled: d.disabled, + Validate: func(s string) string { + if len([]rune(s)) > 16 { + return "Keep tags under 16 characters." + } + return "" + }} + if d.limit { + opts.Max = 5 + } + if mujica.TagsInput(c, &d.tags, opts).Changed() { + log(fmt.Sprintf("tags %q", d.tags)) + } + }}, + {ID: "046", Name: "Rating", ZhName: "评分", Category: "Forms", Code: formCode["Rating"], + Keys: "Arrows change by one step, Home clears, End fills.", + Demo: func(c *ui.Context, log func(string)) { + d := ui.Local(c.Root(), "046", func() ratingDemo { return ratingDemo{value: 3, half: true} }) + formToggles(c, []string{"Half stars", "Read-only", "Disabled"}, &d.half, &d.readOnly, &d.disabled) + step := 1.0 + if d.half { + step = 0.5 + } + if !d.half { + d.value = float64(int(d.value + 0.5)) + } + if mujica.Rating(c, &d.value, mujica.RatingOptions{Step: step, ReadOnly: d.readOnly, Disabled: d.disabled, ShowValue: true}).Changed() { + log(fmt.Sprintf("rating %v", d.value)) + } + formStates(c, func() { + a, b := 4.5, 2.0 + mujica.Rating(c, &a, mujica.RatingOptions{Step: 0.5, ReadOnly: true, Label: "Read-only rating"}) + mujica.Rating(c, &b, mujica.RatingOptions{Disabled: true, Label: "Disabled rating"}) + }) + }}, + {ID: "047", Name: "ColorPicker", ZhName: "颜色选择器", Category: "Forms", Code: formCode["ColorPicker"], + Keys: "Enter or Space opens; arrows move in the square and sliders; Escape closes and returns focus.", + Demo: func(c *ui.Context, log func(string)) { + d := ui.Local(c.Root(), "047", func() colorDemo { return colorDemo{color: ui.Hex("#7D2034")} }) + formToggles(c, []string{"Disabled"}, &d.disabled) + if mujica.ColorPicker(c, &d.color, mujica.ColorPickerOptions{Label: "Banner color", Disabled: d.disabled}).Changed() { + log(fmt.Sprintf("color %v", d.color)) + } + }}, + {ID: "048", Name: "FilePicker", ZhName: "文件选择", Category: "Forms", Code: formCode["FilePicker"], + Keys: "Enter or Space opens the system dialog; the dialog's own keys apply.", + Demo: func(c *ui.Context, log func(string)) { + d := ui.Local(c.Root(), "048", func() pickerDemo { return pickerDemo{multiple: true} }) + formToggles(c, []string{"Folders", "Multiple", "PDF and MIDI only", "Disabled"}, &d.directory, &d.multiple, &d.filter, &d.disabled) + opts := mujica.FilePickerOptions{Directory: d.directory, Multiple: d.multiple, Disabled: d.disabled, Title: "Choose scores"} + if d.filter { + opts.Filters = []mygo.FileFilter{{Name: "Scores", Extensions: []string{"pdf", "mid"}}} + } + p := mujica.FilePicker(c, &d.paths, opts) + if p.Changed() { + log(fmt.Sprintf("picked %d: %q", len(d.paths), d.paths)) + } + if p.Canceled() { + log("canceled (not an error)") + } + }}, + {ID: "049", Name: "FileDropZone", ZhName: "文件拖放区", Category: "Forms", Code: formCode["FileDropZone"], + Keys: "Tab reaches Browse… and each remove button; dropping needs a pointer, Browse is the keyboard path.", + Demo: func(c *ui.Context, log func(string)) { + d := ui.Local(c.Root(), "049", func() dropDemo { return dropDemo{} }) + formToggles(c, []string{"Images only", "Disabled"}, &d.images, &d.disabled) + opts := mujica.FileDropZoneOptions{MaxFiles: 4, MaxSize: 10 << 20, Disabled: d.disabled} + if d.images { + opts.Extensions = []string{"png", "jpg", "jpeg", "gif"} + } + if mujica.FileDropZone(c, &d.files, opts).Changed() { + log(fmt.Sprintf("files %d", len(d.files))) + } + }}, + {ID: "050", Name: "Transfer", ZhName: "穿梭框", Category: "Forms", Code: formCode["Transfer"], + Keys: "Up and Down move within a list, Space checks; Tab reaches the move buttons.", + Demo: func(c *ui.Context, log func(string)) { + d := ui.Local(c.Root(), "050", func() transferDemo { return transferDemo{chosen: []string{"sakiko"}} }) + formToggles(c, []string{"Disabled"}, &d.disabled) + if mujica.Transfer(c, &d.chosen, transferDemoItems, mujica.TransferOptions{SourceTitle: "Auditions", TargetTitle: "Band", Disabled: d.disabled}).Changed() { + log(fmt.Sprintf("band %q", d.chosen)) + } + }}, +} + +// formCode holds each page's copyable snippet, the same as its doc and Example. +var formCode = map[string]string{ + "Form": `var state mujica.FormState +name := "" +view := func(c *ui.Context) { + mujica.Use(c, mujica.Settings{}) + res := mujica.Form(c, &state, mujica.FormOptions{}, func(f *mujica.FormScope) { + mujica.FormItem(f, "name", &name, mujica.FormItemOptions[string]{Label: "Name", Required: true}, func() *ui.Element { + return mujica.InputGroup(c, &name, mujica.InputGroupOptions{Invalid: state.Error("name") != ""}).Input + }) + }) + if res.Submitted() { + state.EndSubmit("") // after saving name + } +} +ui.Render(view, 400, 300, 1)`, + "FormField": `email := "" +view := func(c *ui.Context) { + mujica.Use(c, mujica.Settings{}) + mujica.FormField(c, "Email", mujica.FormFieldOptions{Required: true, Description: "For receipts."}, func() { + mujica.InputGroup(c, &email, mujica.InputGroupOptions{}) + }) +} +ui.Render(view, 400, 300, 1)`, + "InputGroup": `site := "" +view := func(c *ui.Context) { + mujica.Use(c, mujica.Settings{}) + g := mujica.InputGroup(c, &site, mujica.InputGroupOptions{ + Prefix: "https://", Label: "Site", + Buttons: []mujica.InputGroupButton{{Label: "Open"}}, + }) + if g.Clicked == 0 { + c.OpenURL("https://" + site) + } +} +ui.Render(view, 400, 300, 1)`, + "PinInput": `code := "" +view := func(c *ui.Context) { + mujica.Use(c, mujica.Settings{}) + if mujica.PinInput(c, &code, mujica.PinInputOptions{Length: 6}).Completed() { + fmt.Println("verify", code) + } +} +ui.Render(view, 400, 300, 1)`, + "TagsInput": `tags := []string{"gothic"} +view := func(c *ui.Context) { + mujica.Use(c, mujica.Settings{}) + mujica.TagsInput(c, &tags, mujica.TagsInputOptions{Max: 5, Placeholder: "Add a tag", Label: "Tags"}) +} +ui.Render(view, 400, 300, 1)`, + "Rating": `stars := 3.5 +view := func(c *ui.Context) { + mujica.Use(c, mujica.Settings{}) + mujica.Rating(c, &stars, mujica.RatingOptions{Step: 0.5, ShowValue: true}) +} +ui.Render(view, 400, 300, 1)`, + "ColorPicker": `tint := ui.Hex("#7D2034") +view := func(c *ui.Context) { + mujica.Use(c, mujica.Settings{}) + mujica.ColorPicker(c, &tint, mujica.ColorPickerOptions{Label: "Tint"}) +} +ui.Render(view, 400, 300, 1)`, + "FilePicker": `var paths []string +view := func(c *ui.Context) { + mujica.Use(c, mujica.Settings{}) + mujica.FilePicker(c, &paths, mujica.FilePickerOptions{ + Multiple: true, + Filters: []mygo.FileFilter{{Name: "Scores", Extensions: []string{"pdf", "mid"}}}, + }) +} +ui.Render(view, 400, 300, 1)`, + "FileDropZone": `var files []mujica.DroppedFile +view := func(c *ui.Context) { + mujica.Use(c, mujica.Settings{}) + mujica.FileDropZone(c, &files, mujica.FileDropZoneOptions{ + Extensions: []string{"png", "jpg"}, MaxFiles: 3, MaxSize: 5 << 20, + }) +} +ui.Render(view, 400, 300, 1)`, + "Transfer": `chosen := []string{"uika"} +items := []mujica.TransferItem{{ID: "uika", Label: "Uika"}, {ID: "mutsumi", Label: "Mutsumi"}, {ID: "nyamu", Label: "Nyamu", Disabled: true}} +view := func(c *ui.Context) { + mujica.Use(c, mujica.Settings{}) + mujica.Transfer(c, &chosen, items, mujica.TransferOptions{}) +} +ui.Render(view, 400, 300, 1)`, +} diff --git a/file_drop_zone.go b/file_drop_zone.go new file mode 100644 index 0000000..8b1a588 --- /dev/null +++ b/file_drop_zone.go @@ -0,0 +1,292 @@ +package mujica + +import ( + "errors" + "fmt" + "io/fs" + "log/slog" + "os" + "path/filepath" + "slices" + "strings" + + "github.com/egoist/mygo" + "github.com/egoist/mygo/ui" + + "mujicaui/icons" + "mujicaui/internal" + "mujicaui/theme" +) + +// DroppedFile is a file a FileDropZone accepted. +type DroppedFile struct { + Path string + Size int64 +} + +// FileDropZoneOptions configure a FileDropZone. Zero limits accept anything. +type FileDropZoneOptions struct { + // Extensions lists the accepted extensions without the dot, matched case-insensitively. + Extensions []string + // MaxFiles caps how many files the list holds. + MaxFiles int + // MaxSize caps each file's size in bytes. + MaxSize int64 + // Hint replaces the built-in "Drop files here". + Hint string + Disabled bool +} + +// FileDropZoneParts are the parts of a FileDropZone. +type FileDropZoneParts struct { + Element *ui.Element + changed bool +} + +// Changed reports whether files were added or removed this frame. +func (p FileDropZoneParts) Changed() bool { return p.changed } + +// fileDropZoneReject is a file that failed a check, shown until dismissed. +type fileDropZoneReject struct { + path, reason string +} + +type fileDropZoneStat struct { + path string + size int64 + dir bool + err error +} + +type fileDropZoneState struct { + pending []string + stats chan []fileDropZoneStat + browse chan filePickerResult + rejected []fileDropZoneReject +} + +// FileDropZone takes files dropped from the system or chosen with its Browse button, +// checks type, count and size, and lists them in *files. It never reads or uploads them. +func FileDropZone(c *ui.Context, files *[]DroppedFile, opts FileDropZoneOptions) FileDropZoneParts { + if opts.MaxFiles < 0 || opts.MaxSize < 0 { + panic("mujica: FileDropZone limits must not be negative") + } + for _, ext := range opts.Extensions { + if ext == "" || strings.HasPrefix(ext, ".") { + panic(fmt.Sprintf("mujica: FileDropZone extension %q: give it without the dot", ext)) + } + } + k := Tokens(c) + d := Density(c) + parts := FileDropZoneParts{} + col := ui.Column(c).Gap(d.Unit() * 2) + parts.Element = col + st := ui.Local(col, "zone", func() fileDropZoneState { return fileDropZoneState{} }) + reject := func(path, key string, args ...any) { + st.rejected = append(st.rejected, fileDropZoneReject{path, fmt.Sprintf(msg(c, key), args...)}) + slog.Debug("mujica: file drop zone rejected", "reason", key) + } + // take checks types now and sizes off the UI thread. + take := func(paths []string) { + var ok []string + for _, p := range paths { + ext := strings.TrimPrefix(strings.ToLower(filepath.Ext(p)), ".") + switch { + case slices.ContainsFunc(*files, func(f DroppedFile) bool { return f.Path == p }), slices.Contains(st.pending, p), slices.Contains(ok, p): + case len(opts.Extensions) > 0 && !slices.ContainsFunc(opts.Extensions, func(e string) bool { return strings.EqualFold(e, ext) }): + reject(p, "file_drop_zone.type", strings.Join(opts.Extensions, ", ")) + default: + ok = append(ok, p) + } + } + if len(ok) == 0 { + return + } + st.pending = append(st.pending, ok...) + if st.stats != nil { + return // the running check picks them up next + } + fileDropZoneCheck(c, st) + } + if st.stats != nil { + select { + case res := <-st.stats: + st.stats = nil + st.pending = st.pending[len(res):] + for _, s := range res { + switch { + case s.err != nil: + err := s.err + if pe, ok := errors.AsType[*fs.PathError](err); ok { + err = pe.Err + } + reject(s.path, "file_drop_zone.unreadable", err) + case s.dir: + reject(s.path, "file_drop_zone.folder") + case opts.MaxSize > 0 && s.size > opts.MaxSize: + reject(s.path, "file_drop_zone.size", fileDropZoneSize(opts.MaxSize)) + case opts.MaxFiles > 0 && len(*files) >= opts.MaxFiles: + reject(s.path, "file_drop_zone.count", opts.MaxFiles) + default: + *files = append(slices.Clone(*files), DroppedFile{s.path, s.size}) + parts.changed = true + } + } + if len(st.pending) > 0 { + fileDropZoneCheck(c, st) + } + default: + } + } + if st.browse != nil { + select { + case r := <-st.browse: + st.browse = nil + if r.err != nil { + reject("", "file_picker.failed", r.err) + } else { + take(r.paths) + } + default: + } + } + col.Children(func() { + zone := ui.Column(c).AlignItems(ui.Center).Justify(ui.Center).Gap(d.Unit() * 2).MinHeight(140).Padding(d.Unit() * 5). + Radius(theme.CardRadius).Disabled(opts.Disabled) + if dropped := zone.DroppedFiles(); dropped != nil && !opts.Disabled { + slog.Debug("mujica: file drop zone dropped", "count", len(dropped)) + take(dropped) + } + over := zone.FileDragOver() && !opts.Disabled + bg, border := k.Surface, k.ControlBorder + if over { + bg, border = k.Selection, k.AccentText + } + zone.Background(bg).Draw(func(p *ui.Painter, r ui.Rect) { + p.StrokeDashed(r, border, theme.CardRadius, theme.BorderWidth*1.5) + }) + hint := opts.Hint + if hint == "" { + hint = msg(c, "file_drop_zone.hint") + } + zone.Children(func() { + ui.Icon(c, icons.Must("upload")).Size(24, 24).TextColor(k.AccentText) + ui.Text(c, hint).TextAlign(ui.Center) + ui.Text(c, fileDropZoneRules(c, opts)).TextColor(k.TextMuted).FontSize(theme.CaptionSize).TextAlign(ui.Center) + b := Button(c, msg(c, "file_drop_zone.browse"), ButtonOptions{Variant: Outline, Loading: st.browse != nil, Disabled: opts.Disabled}) + if b.Clicked() { + ch := make(chan filePickerResult, 1) + st.browse = ch + dlg := mygo.OpenDialogOptions{Multiple: opts.MaxFiles != 1} + if len(opts.Extensions) > 0 { + dlg.Filters = []mygo.FileFilter{{Name: hint, Extensions: opts.Extensions}} + } + go func() { + p, err := filePickerOpen(dlg) + ch <- filePickerResult{p, err} + c.Invalidate() + }() + } + }) + fileDropZoneList(c, st, files, &parts, opts.Disabled) + }) + return parts +} + +// fileDropZoneCheck stats the pending files on a goroutine. +func fileDropZoneCheck(c *ui.Context, st *fileDropZoneState) { + ch := make(chan []fileDropZoneStat, 1) + st.stats = ch + paths := slices.Clone(st.pending) + go func() { + res := make([]fileDropZoneStat, len(paths)) + for i, p := range paths { + info, err := os.Stat(p) + res[i] = fileDropZoneStat{path: p, err: err} + if err == nil { + res[i].size, res[i].dir = info.Size(), info.IsDir() + } + } + ch <- res + c.Invalidate() + }() +} + +// fileDropZoneList shows accepted, checking and rejected files. +func fileDropZoneList(c *ui.Context, st *fileDropZoneState, files *[]DroppedFile, parts *FileDropZoneParts, disabled bool) { + k := Tokens(c) + d := Density(c) + row := func(key string, icon string, color ui.Color, name, detail string, remove func()) { + ui.Row(c).Key(key).Gap(d.Unit()*2).AlignItems(ui.Center).MinHeight(d.ControlHeight()).Padding(0, d.Unit()*2). + BorderWidth(0, 0, theme.BorderWidth, 0).BorderColor(k.Border).Children(func() { + ui.Icon(c, icons.Must(icon)).Size(16, 16).TextColor(color) + ui.Text(c, name).SingleLine().Ellipsis("…").MinWidth(0).Shrink(1) + ui.Text(c, detail).TextColor(color).FontSize(theme.CaptionSize).SingleLine().Grow(1) + if remove != nil { + x := ui.ButtonBase(c).Size(28, 28).Radius(theme.ControlRadius).TextColor(k.TextMuted). + Label(fmt.Sprintf(msg(c, "file_drop_zone.remove"), name)).Disabled(disabled) + internal.Ring(x, k.Focus, theme.ControlRadius) + if x.Hovered() { + x.Background(k.SurfaceHover).TextColor(k.Text) + } + x.Children(func() { ui.Icon(c, icons.Must("x")).Size(14, 14) }) + if x.Clicked() { + remove() + } + } + }) + } + ui.Column(c).Children(func() { + for i, f := range *files { + row("ok"+f.Path, "file", k.TextMuted, filepath.Base(f.Path), fileDropZoneSize(f.Size), func() { + *files = slices.Delete(slices.Clone(*files), i, i+1) + parts.changed = true + slog.Debug("mujica: file drop zone removed", "count", len(*files)) + }) + } + for _, p := range st.pending { + row("pending"+p, "loader-circle", k.TextMuted, filepath.Base(p), msg(c, "file_drop_zone.checking"), nil) + } + for i, r := range st.rejected { + name := filepath.Base(r.path) + if r.path == "" { + name = msg(c, "file_drop_zone.dialog") + } + row(fmt.Sprint("bad", i, r.path), "circle-alert", k.Danger, name, r.reason, func() { + st.rejected = slices.Delete(slices.Clone(st.rejected), i, i+1) + }) + } + }) +} + +// fileDropZoneRules describes the limits, such as "PDF, PNG · up to 3 files · 2 MB each". +func fileDropZoneRules(c *ui.Context, opts FileDropZoneOptions) string { + var rules []string + if len(opts.Extensions) > 0 { + rules = append(rules, strings.ToUpper(strings.Join(opts.Extensions, ", "))) + } + if opts.MaxFiles > 0 { + rules = append(rules, fmt.Sprintf(msg(c, "file_drop_zone.max_files"), opts.MaxFiles)) + } + if opts.MaxSize > 0 { + rules = append(rules, fmt.Sprintf(msg(c, "file_drop_zone.max_size"), fileDropZoneSize(opts.MaxSize))) + } + if len(rules) == 0 { + return msg(c, "file_drop_zone.any") + } + return strings.Join(rules, " · ") +} + +// fileDropZoneSize formats n bytes in binary units. +func fileDropZoneSize(n int64) string { + const unit = 1024 + if n < unit { + return fmt.Sprintf("%d B", n) + } + v, i := float64(n)/unit, 0 + for v >= unit && i < 3 { + v /= unit + i++ + } + return fmt.Sprintf("%.1f %s", v, []string{"KB", "MB", "GB", "TB"}[i]) +} diff --git a/file_drop_zone_test.go b/file_drop_zone_test.go new file mode 100644 index 0000000..ccd4527 --- /dev/null +++ b/file_drop_zone_test.go @@ -0,0 +1,120 @@ +package mujica + +import ( + "os" + "path/filepath" + "testing" + + "github.com/egoist/mygo" + "github.com/egoist/mygo/ui" +) + +// fileDropZoneFiles writes files of the given sizes into a temp dir. +func fileDropZoneFiles(t *testing.T, sizes map[string]int) string { + t.Helper() + dir := t.TempDir() + for name, n := range sizes { + if err := os.WriteFile(filepath.Join(dir, name), make([]byte, n), 0o644); err != nil { + t.Fatal(err) + } + } + return dir +} + +func fileDropZoneTester(files *[]DroppedFile, opts FileDropZoneOptions, changes *int) *ui.Tester { + return ui.NewTester(func(c *ui.Context) { + Use(c, Settings{}) + if FileDropZone(c, files, opts).Changed() { + *changes++ + } + }, 560, 600) +} + +// fileDropZoneBrowse answers the Browse dialog with paths. +func fileDropZoneBrowse(t *testing.T, paths ...string) *mygo.OpenDialogOptions { + saved := filePickerOpen + t.Cleanup(func() { filePickerOpen = saved }) + got := &mygo.OpenDialogOptions{} + filePickerOpen = func(o mygo.OpenDialogOptions) ([]string, error) { + *got = o + return paths, nil + } + return got +} + +func TestFileDropZoneChecksTypeSizeAndCount(t *testing.T) { + dir := fileDropZoneFiles(t, map[string]int{"a.pdf": 10, "b.PDF": 3000, "c.pdf": 20, "d.txt": 5, "e.pdf": 30}) + p := func(n string) string { return filepath.Join(dir, n) } + got := fileDropZoneBrowse(t, p("a.pdf"), p("b.PDF"), p("c.pdf"), p("d.txt"), p("e.pdf"), p("missing.pdf"), dir+"/sub.pdf") + if err := os.Mkdir(p("sub.pdf"), 0o755); err != nil { + t.Fatal(err) + } + var files []DroppedFile + n := 0 + tt := fileDropZoneTester(&files, FileDropZoneOptions{Extensions: []string{"pdf"}, MaxFiles: 2, MaxSize: 1024}, &n) + if err := tt.Click("Browse…"); err != nil { + t.Fatal(err) + } + filePickerWait(t, tt, func() bool { return len(files) == 2 && tt.HasText("More than 2 files") }) + if files[0] != (DroppedFile{p("a.pdf"), 10}) || files[1] != (DroppedFile{p("c.pdf"), 20}) || n != 1 { + t.Fatalf("files %+v changes %d", files, n) + } + for _, want := range []string{"Only pdf files", "Larger than 1.0 KB", "Folders are not accepted", "Cannot read: no such file or directory"} { + if !tt.HasText(want) { + t.Errorf("missing %q in %q", want, tt.Texts()) + } + } + if !got.Multiple || got.Filters[0].Extensions[0] != "pdf" { + t.Fatalf("dialog options %+v", got) + } +} + +func TestFileDropZoneRemoveAndDedupe(t *testing.T) { + dir := fileDropZoneFiles(t, map[string]int{"score.mid": 2048}) + path := filepath.Join(dir, "score.mid") + fileDropZoneBrowse(t, path) + files := []DroppedFile{{path, 2048}} + n := 0 + tt := fileDropZoneTester(&files, FileDropZoneOptions{}, &n) + _ = tt.Click("Browse…") + for range 20 { + tt.Frame() + } + if len(files) != 1 || n != 0 || !tt.HasText("2.0 KB") { + t.Fatalf("duplicate added: %+v", files) + } + if err := tt.Click("Remove score.mid"); err != nil { + t.Fatal(err) + } + if len(files) != 0 || n != 1 { + t.Fatalf("remove: %+v changes %d", files, n) + } +} + +func TestFileDropZoneDisabled(t *testing.T) { + called := false + saved := filePickerOpen + t.Cleanup(func() { filePickerOpen = saved }) + filePickerOpen = func(mygo.OpenDialogOptions) ([]string, error) { called = true; return nil, nil } + files := []DroppedFile{{"/x/kept.pdf", 1}} + n := 0 + tt := fileDropZoneTester(&files, FileDropZoneOptions{Disabled: true}, &n) + _ = tt.Click("Browse…") + _ = tt.Click("Remove kept.pdf") + tt.Frame() + if called || len(files) != 1 { + t.Fatal("disabled zone acted") + } + if fileDropZoneSize(1536) != "1.5 KB" || fileDropZoneSize(3<<20) != "3.0 MB" || fileDropZoneSize(12) != "12 B" { + t.Fatal("size format") + } +} + +func TestFileDropZoneSnapshot(t *testing.T) { + files := []DroppedFile{{"/scores/Imprisoned XII.pdf", 482133}, {"/scores/cover.png", 1 << 20}} + snapshot(t, "file_drop_zone", 520, 330, func(c *ui.Context) { + ui.Column(c).Padding(20).Children(func() { + FileDropZone(c, &files, FileDropZoneOptions{Extensions: []string{"pdf", "png"}, MaxFiles: 3, MaxSize: 2 << 20}) + }) + }) +} diff --git a/file_picker.go b/file_picker.go new file mode 100644 index 0000000..44942e8 --- /dev/null +++ b/file_picker.go @@ -0,0 +1,137 @@ +package mujica + +import ( + "fmt" + "log/slog" + "path/filepath" + "strings" + + "github.com/egoist/mygo" + "github.com/egoist/mygo/ui" + + "mujicaui/icons" + "mujicaui/theme" +) + +// FilePickerOptions configure a FilePicker. +type FilePickerOptions struct { + // Directory picks folders instead of files. + Directory bool + Multiple bool + // Filters limit the files offered by extension, without the dot. + Filters []mygo.FileFilter + // Title is the system dialog's title. + Title string + // Label is the button text; empty uses the built-in "Choose file…" or "Choose folder…". + Label string + // Parent attaches the dialog to a window, as a sheet on macOS. + Parent *mygo.Window + Disabled bool +} + +// FilePickerParts are the parts of a FilePicker. +type FilePickerParts struct { + Element *ui.Element + changed bool + canceled bool +} + +// Changed reports whether the user picked new paths this frame. +func (p FilePickerParts) Changed() bool { return p.changed } + +// Canceled reports whether the user closed the dialog without picking, which is not an error. +func (p FilePickerParts) Canceled() bool { return p.canceled } + +// filePickerOpen shows the system dialog; tests replace it. +var filePickerOpen = mygo.Dialog.Open + +type filePickerResult struct { + paths []string + err error +} + +type filePickerState struct { + result chan filePickerResult + err string +} + +// FilePicker opens the system file or folder dialog and stores the chosen paths in *paths. +// The dialog runs off the UI thread; the button shows loading until it closes. +func FilePicker(c *ui.Context, paths *[]string, opts FilePickerOptions) FilePickerParts { + for _, f := range opts.Filters { + for _, ext := range f.Extensions { + if ext == "" || strings.HasPrefix(ext, ".") { + panic(fmt.Sprintf("mujica: FilePicker extension %q: give it without the dot", ext)) + } + } + } + k := Tokens(c) + d := Density(c) + parts := FilePickerParts{} + col := ui.Column(c).Gap(d.Unit() * 2).AlignItems(ui.Start) + parts.Element = col + st := ui.Local(col, "picker", func() filePickerState { return filePickerState{} }) + if st.result != nil { + select { + case r := <-st.result: + st.result = nil + switch { + case r.err != nil: + st.err = fmt.Sprintf(msg(c, "file_picker.failed"), r.err) + slog.Debug("mujica: file picker failed", "err", r.err) + case r.paths == nil: + parts.canceled = true + slog.Debug("mujica: file picker canceled") + default: + *paths = r.paths + parts.changed = true + slog.Debug("mujica: file picker picked", "count", len(r.paths)) + } + default: + } + } + label := opts.Label + if label == "" { + label = msg(c, "file_picker.choose_file") + if opts.Directory { + label = msg(c, "file_picker.choose_folder") + } + } + icon := icons.Must("file") + if opts.Directory { + icon = icons.Must("folder") + } + col.Children(func() { + b := Button(c, label, ButtonOptions{Variant: Secondary, Icon: icon, Loading: st.result != nil, Disabled: opts.Disabled}) + if b.Clicked() { + st.err = "" + ch := make(chan filePickerResult, 1) + st.result = ch + dlg := mygo.OpenDialogOptions{Parent: opts.Parent, Title: opts.Title, Filters: opts.Filters, + Directory: opts.Directory, Multiple: opts.Multiple} + slog.Debug("mujica: file picker opened", "directory", opts.Directory, "multiple", opts.Multiple) + go func() { + p, err := filePickerOpen(dlg) + ch <- filePickerResult{p, err} + c.Invalidate() + }() + } + switch { + case st.err != "": + ui.Text(c, st.err).TextColor(k.Danger).FontSize(theme.CaptionSize).Role(ui.RoleStatus) + case len(*paths) == 0: + ui.Text(c, msg(c, "file_picker.none")).TextColor(k.TextMuted).FontSize(theme.CaptionSize) + default: + ui.Column(c).Gap(d.Unit()).Children(func() { + for _, p := range *paths { + ui.Row(c).Key(p).Gap(d.Unit() * 2).AlignItems(ui.Center).Children(func() { + ui.Icon(c, icon).Size(14, 14).TextColor(k.TextMuted) + ui.Text(c, filepath.Base(p)).SingleLine() + ui.Text(c, filepath.Dir(p)).TextColor(k.TextMuted).FontSize(theme.CaptionSize).SingleLine().Ellipsis("…").MinWidth(0).Shrink(1) + }) + } + }) + } + }) + return parts +} diff --git a/file_picker_test.go b/file_picker_test.go new file mode 100644 index 0000000..459c847 --- /dev/null +++ b/file_picker_test.go @@ -0,0 +1,131 @@ +package mujica + +import ( + "errors" + "slices" + "testing" + "time" + + "github.com/egoist/mygo" + "github.com/egoist/mygo/ui" +) + +type filePickerProbe struct { + paths []string + changes, cancels int + got mygo.OpenDialogOptions + reply []string + err error + release chan struct{} +} + +func (p *filePickerProbe) stub(t *testing.T) { + saved := filePickerOpen + t.Cleanup(func() { filePickerOpen = saved }) + filePickerOpen = func(o mygo.OpenDialogOptions) ([]string, error) { + p.got = o + if p.release != nil { + <-p.release + } + return p.reply, p.err + } +} + +func (p *filePickerProbe) tester(opts FilePickerOptions) *ui.Tester { + return ui.NewTester(func(c *ui.Context) { + Use(c, Settings{}) + r := FilePicker(c, &p.paths, opts) + if r.Changed() { + p.changes++ + } + if r.Canceled() { + p.cancels++ + } + }, 500, 200) +} + +// filePickerWait draws frames until done holds. +func filePickerWait(t *testing.T, tt *ui.Tester, done func() bool) { + t.Helper() + for range 200 { + tt.Frame() + if done() { + return + } + time.Sleep(5 * time.Millisecond) + } + t.Fatal("dialog result never arrived") +} + +func TestFilePickerPicksFiles(t *testing.T) { + p := &filePickerProbe{reply: []string{"/court/score.pdf", "/court/cast.png"}, release: make(chan struct{})} + p.stub(t) + filters := []mygo.FileFilter{{Name: "Docs", Extensions: []string{"pdf", "png"}}} + tt := p.tester(FilePickerOptions{Multiple: true, Filters: filters, Title: "Score"}) + if err := tt.Click("Choose file…"); err != nil { + t.Fatal(err) + } + _ = tt.Click("Choose file…") + close(p.release) + filePickerWait(t, tt, func() bool { return p.changes > 0 }) + if !slices.Equal(p.paths, p.reply) || p.changes != 1 || !tt.HasText("score.pdf") { + t.Fatalf("paths %q changes %d", p.paths, p.changes) + } + if !p.got.Multiple || p.got.Directory || p.got.Title != "Score" || p.got.Filters[0].Extensions[1] != "png" { + t.Fatalf("dialog options %+v", p.got) + } +} + +func TestFilePickerCancelIsNotAnError(t *testing.T) { + p := &filePickerProbe{paths: []string{"/kept"}} + p.stub(t) + tt := p.tester(FilePickerOptions{Directory: true}) + if err := tt.Click("Choose folder…"); err != nil { + t.Fatal(err) + } + filePickerWait(t, tt, func() bool { return p.cancels > 0 }) + if p.changes != 0 || p.paths[0] != "/kept" || !p.got.Directory { + t.Fatalf("cancel changed paths: %q", p.paths) + } +} + +func TestFilePickerReportsDialogError(t *testing.T) { + p := &filePickerProbe{err: errors.New("no display")} + p.stub(t) + tt := p.tester(FilePickerOptions{}) + _ = tt.Click("Choose file…") + want := "Could not open the file dialog: no display" + filePickerWait(t, tt, func() bool { return tt.HasText(want) }) + if p.changes != 0 || p.cancels != 0 { + t.Fatalf("error counted as result") + } +} + +func TestFilePickerDisabledAndBadFilter(t *testing.T) { + p := &filePickerProbe{} + p.stub(t) + called := false + filePickerOpen = func(mygo.OpenDialogOptions) ([]string, error) { called = true; return nil, nil } + tt := p.tester(FilePickerOptions{Disabled: true}) + _ = tt.Click("Choose file…") + tt.Frame() + if called { + t.Fatal("disabled picker opened the dialog") + } + defer func() { + if recover() == nil { + t.Fatal("dotted extension accepted") + } + }() + p.tester(FilePickerOptions{Filters: []mygo.FileFilter{{Name: "PDF", Extensions: []string{".pdf"}}}}) +} + +func TestFilePickerSnapshot(t *testing.T) { + a, b := []string{"/Users/sakiko/scores/Imprisoned XII.pdf", "/Users/sakiko/scores/Ave Mujica.mid"}, []string{} + snapshot(t, "file_picker", 460, 190, func(c *ui.Context) { + ui.Column(c).Padding(20).Gap(20).Children(func() { + FilePicker(c, &a, FilePickerOptions{Multiple: true}) + FilePicker(c, &b, FilePickerOptions{Directory: true, Disabled: true}) + }) + }) +} diff --git a/form.go b/form.go new file mode 100644 index 0000000..278e916 --- /dev/null +++ b/form.go @@ -0,0 +1,309 @@ +package mujica + +import ( + "fmt" + "log/slog" + "reflect" + "slices" + + "github.com/egoist/mygo/ui" +) + +// FormState is a form's validation and submit state; keep it beside the form's values. +// Its methods run on the UI thread: deliver async results through Window.Update. +type FormState struct { + fields map[string]*formFieldState + order []string + resets []func() + seq uint64 + tried bool + submitting bool + awaiting bool + requested bool + focus string + formError string +} + +type formFieldState struct { + snapshot string + version uint64 + touched bool + focused bool + err string + asyncFor uint64 + asyncErr string + pending bool + check func() string + startAsync func() +} + +// Version returns the version of name's value, which changes whenever the value does. +func (s *FormState) Version(name string) uint64 { + if f := s.fields[name]; f != nil { + return f.version + } + return 0 +} + +// Resolve delivers the async check of name's value at version; "" means valid. +// It reports false and drops the result when the value has changed since. +func (s *FormState) Resolve(name string, version uint64, err string) bool { + f := s.fields[name] + if f == nil || f.version != version { + slog.Debug("mujica: form async result dropped", "field", name, "version", version) + return false + } + f.pending, f.asyncFor, f.asyncErr = false, version, err + slog.Debug("mujica: form async result", "field", name, "valid", err == "") + return true +} + +// Submit asks the form to submit in its next frame, as the submit button does. +func (s *FormState) Submit() { s.requested = true } + +// Submitting reports whether a submit is in progress. +func (s *FormState) Submitting() bool { return s.submitting } + +// EndSubmit finishes a submit; formError, if any, shows above the buttons and keeps all values. +func (s *FormState) EndSubmit(formError string) { + if !s.submitting { + panic("mujica: FormState.EndSubmit without a submit in progress") + } + s.submitting, s.formError = false, formError + slog.Debug("mujica: form submit ended", "failed", formError != "") +} + +// Error returns the error name shows now, "" when valid, untouched or pending. +func (s *FormState) Error(name string) string { + f := s.fields[name] + if f == nil || !(f.touched || s.tried) { + return "" + } + if f.err != "" { + return f.err + } + if f.asyncFor == f.version { + return f.asyncErr + } + return "" +} + +// FormOptions configure a Form. +type FormOptions struct { + // Horizontal puts labels in a column beside the controls. + Horizontal bool + // SubmitLabel and ResetLabel replace the built-in "Submit" and "Reset". + SubmitLabel, ResetLabel string +} + +// FormScope registers a Form's fields with FormItem. +type FormScope struct { + c *ui.Context + st *FormState +} + +// FormParts are the parts of a Form. +type FormParts struct { + Element *ui.Element + submitted, reset bool +} + +// Submitted reports a submit that passed every check; call EndSubmit when it finishes. +func (p FormParts) Submitted() bool { return p.submitted } + +// Reset reports that the form restored its initial values this frame. +func (p FormParts) Reset() bool { return p.reset } + +// FormItemOptions configure one field of a Form. +type FormItemOptions[T any] struct { + Label string + Required bool + Description string + // Initial is the value Reset restores. + Initial T + // Validate returns an error message for v, or ""; cross-field rules read other values here. + Validate func(v T) string + // Async starts a check of v off the UI thread, finished with FormState.Resolve(name, version, msg). + Async func(v T, version uint64) +} + +// Form lays out the fields that fields registers, validates them on blur and submit, +// and shows submit and reset buttons. It never stores or sends the values itself. +func Form(c *ui.Context, st *FormState, opts FormOptions, fields func(f *FormScope)) FormParts { + if st.fields == nil { + st.fields = map[string]*formFieldState{} + } + st.order, st.resets = st.order[:0], nil + d := Density(c) + k := Tokens(c) + parts := FormParts{} + col := ui.Column(c).Gap(d.Unit() * 5) + parts.Element = col + scope := &FormScope{c: c, st: st} + col.Children(func() { + if opts.Horizontal { + ui.Form(c, func() { fields(scope) }) + } else { + ui.Column(c).Gap(d.Unit() * 4).Children(func() { fields(scope) }) + } + if st.formError != "" { + ui.Text(c, st.formError).TextColor(k.Danger).Role(ui.RoleStatus) + } + ui.Row(c).Gap(d.Unit() * 3).Children(func() { + submit, reset := opts.SubmitLabel, opts.ResetLabel + if submit == "" { + submit = msg(c, "form.submit") + } + if reset == "" { + reset = msg(c, "form.reset") + } + if Button(c, submit, ButtonOptions{Loading: st.submitting || st.awaiting}).Clicked() { + st.requested = true + } + if Button(c, reset, ButtonOptions{Variant: Secondary, Disabled: st.submitting}).Clicked() { + formReset(st) + parts.reset = true + } + }) + }) + if st.requested { + st.requested = false + if !st.submitting && !st.awaiting { + formStartSubmit(st) + } + } + if st.awaiting { + parts.submitted = formSettle(c, st) + } + return parts +} + +// formStartSubmit validates every field and starts the async checks still owed. +func formStartSubmit(st *FormState) { + st.tried, st.awaiting, st.formError = true, true, "" + for _, name := range st.order { + f := st.fields[name] + f.err = f.check() + if f.err == "" { + f.startAsync() + } + } + slog.Debug("mujica: form submit requested", "fields", len(st.order)) +} + +// formSettle finishes a waiting submit once no check is pending; it reports success. +func formSettle(c *ui.Context, st *FormState) bool { + first, pending := "", false + for _, name := range st.order { + if st.Error(name) != "" && first == "" { + first = name + } + pending = pending || st.fields[name].pending + } + switch { + case first != "": + st.awaiting, st.focus = false, first + c.Announce(msg(c, "form.failed")) + c.Invalidate() + slog.Debug("mujica: form submit failed", "first", first) + case pending: + default: + st.awaiting, st.submitting = false, true + slog.Debug("mujica: form submitted") + return true + } + return false +} + +// formReset restores the initial values and forgets every check. +func formReset(st *FormState) { + for _, r := range st.resets { + r() + } + st.fields = map[string]*formFieldState{} + st.tried, st.awaiting, st.requested, st.focus, st.formError = false, false, false, "", "" + slog.Debug("mujica: form reset") +} + +// FormItem registers field name, labels the control that control returns, and shows its error. +// control must return the element to focus when the field has the first error. +func FormItem[T any](f *FormScope, name string, value *T, opts FormItemOptions[T], control func() *ui.Element) *ui.Element { + c, st := f.c, f.st + if name == "" || opts.Label == "" { + panic("mujica: FormItem needs a name and a Label") + } + if slices.Contains(st.order, name) { + panic("mujica: FormItem " + name + " registered twice") + } + st.order = append(st.order, name) + snap := fmt.Sprintf("%#v", *value) + fs := st.fields[name] + if fs == nil { + st.seq++ + fs = &formFieldState{snapshot: snap, version: st.seq} + st.fields[name] = fs + } + fs.check = func() string { + if opts.Required && formEmpty(*value) { + return msg(c, "form.required") + } + if opts.Validate != nil { + return opts.Validate(*value) + } + return "" + } + fs.startAsync = func() { + if opts.Async != nil && fs.asyncFor != fs.version && !fs.pending { + fs.pending = true + slog.Debug("mujica: form async check started", "field", name, "version", fs.version) + opts.Async(*value, fs.version) + } + } + if snap != fs.snapshot { + st.seq++ + fs.snapshot, fs.version, fs.pending, fs.asyncErr = snap, st.seq, false, "" + if fs.touched || st.tried { + fs.err = fs.check() + } + } + initial := opts.Initial + st.resets = append(st.resets, func() { *value = initial }) + desc := opts.Description + if fs.pending { + desc = msg(c, "form.checking") + } + var ctrl *ui.Element + field := formFieldBuild(c, opts.Label, FormFieldOptions{Required: opts.Required, Description: desc, Error: st.Error(name)}, func() { + ctrl = control() + }) + if ctrl == nil { + panic("mujica: FormItem " + name + ": control returned nil") + } + focused := ctrl.FocusWithin() + if fs.focused && !focused { + fs.touched = true + fs.err = fs.check() + if fs.err == "" { + fs.startAsync() + } + slog.Debug("mujica: form field blurred", "field", name, "valid", fs.err == "") + } + fs.focused = focused + if st.focus == name { + st.focus = "" + ctrl.Focus().ScrollIntoView() + } + return field +} + +// formEmpty reports whether a required value is missing: zero, or an empty string, slice or map. +func formEmpty(v any) bool { + r := reflect.ValueOf(v) + switch r.Kind() { + case reflect.Invalid: + return true + case reflect.String, reflect.Slice, reflect.Map, reflect.Array: + return r.Len() == 0 + } + return r.IsZero() +} diff --git a/form_field.go b/form_field.go new file mode 100644 index 0000000..517840f --- /dev/null +++ b/form_field.go @@ -0,0 +1,38 @@ +package mujica + +import ( + "github.com/egoist/mygo/ui" +) + +// FormFieldOptions configure a FormField. +type FormFieldOptions struct { + // Required marks the label; the check itself belongs to the caller or Form. + Required bool + Description string + // Error shows below the control and marks it invalid for assistive technology. + Error string + // Horizontal puts the label beside the control instead of above it. + Horizontal bool +} + +// FormField labels the control that control builds and adds help and error text. +// The label names the control and a click on it focuses the control. +func FormField(c *ui.Context, label string, opts FormFieldOptions, control func()) *ui.Element { + if label == "" { + panic("mujica: FormField needs a label") + } + if opts.Horizontal { + var f *ui.Element + ui.Form(c, func() { f = formFieldBuild(c, label, opts, control) }) + return f + } + return formFieldBuild(c, label, opts, control) +} + +// formFieldBuild builds the field in the layout the caller set up. +func formFieldBuild(c *ui.Context, label string, opts FormFieldOptions, control func()) *ui.Element { + if opts.Required { + label += " *" + } + return ui.Field(c, label, control).Description(opts.Description).Error(opts.Error) +} diff --git a/form_field_test.go b/form_field_test.go new file mode 100644 index 0000000..c6ca7f6 --- /dev/null +++ b/form_field_test.go @@ -0,0 +1,79 @@ +package mujica + +import ( + "testing" + + "github.com/egoist/mygo/ui" +) + +func TestFormFieldLabelFocusesControl(t *testing.T) { + name := "" + tt := ui.NewTester(func(c *ui.Context) { + Use(c, Settings{}) + FormField(c, "Name", FormFieldOptions{Required: true, Description: "As on your seal."}, func() { + InputGroup(c, &name, InputGroupOptions{}) + }) + }, 400, 200) + if err := tt.Click("Name *"); err != nil { + t.Fatal(err) + } + tt.Type("Sakiko") + if name != "Sakiko" { + t.Fatalf("label click did not focus the control: %q", name) + } + if !tt.Focused("Name *") || !tt.HasText("As on your seal.") { + t.Fatalf("texts %q", tt.Texts()) + } +} + +func TestFormFieldErrorAndLayout(t *testing.T) { + v := "" + horizontal := false + tt := ui.NewTester(func(c *ui.Context) { + Use(c, Settings{}) + FormField(c, "Email", FormFieldOptions{Error: "Enter an email address.", Horizontal: horizontal}, func() { + InputGroup(c, &v, InputGroupOptions{Prefix: "mailto:"}) + }) + }, 500, 200) + if !tt.HasText("Enter an email address.") { + t.Fatal("error not shown") + } + label, _ := tt.Find("Email") + input, _ := tt.Find("mailto:") + if !(input.Y > label.Y+label.H/2) { + t.Fatalf("vertical: label %v input %v", label, input) + } + horizontal = true + tt.Frame() + label, _ = tt.Find("Email") + input, _ = tt.Find("mailto:") + if !(input.X > label.X+label.W) || input.Y > label.Y+label.H { + t.Fatalf("horizontal: label %v input %v", label, input) + } +} + +func TestFormFieldNeedsLabel(t *testing.T) { + defer func() { + if recover() == nil { + t.Fatal("no panic for an empty label") + } + }() + ui.Render(func(c *ui.Context) { + Use(c, Settings{}) + FormField(c, "", FormFieldOptions{}, func() {}) + }, 100, 100, 1) +} + +func TestFormFieldSnapshot(t *testing.T) { + a, b := "Sakiko Togawa", "uika@" + snapshot(t, "form_field", 520, 260, func(c *ui.Context) { + ui.Column(c).Padding(20).Gap(16).Children(func() { + FormField(c, "Name", FormFieldOptions{Required: true, Description: "As written on the invitation."}, func() { + InputGroup(c, &a, InputGroupOptions{}) + }) + FormField(c, "Email", FormFieldOptions{Horizontal: true, Error: "Enter a full email address."}, func() { + InputGroup(c, &b, InputGroupOptions{Invalid: true}) + }) + }) + }) +} diff --git a/form_test.go b/form_test.go new file mode 100644 index 0000000..2d00634 --- /dev/null +++ b/form_test.go @@ -0,0 +1,202 @@ +package mujica + +import ( + "strings" + "testing" + + "github.com/egoist/mygo/ui" +) + +type formProbe struct { + st FormState + name, email string + tags []string + asyncCalls []uint64 + asyncValues []string + submits, resets int + horizontal bool + useAsync bool +} + +func (p *formProbe) run(c *ui.Context) { + Use(c, Settings{}) + p.view(c) +} + +func (p *formProbe) view(c *ui.Context) { + res := Form(c, &p.st, FormOptions{Horizontal: p.horizontal}, func(f *FormScope) { + FormItem(f, "name", &p.name, FormItemOptions[string]{Label: "Name", Required: true, Initial: "Sakiko"}, func() *ui.Element { + return InputGroup(c, &p.name, InputGroupOptions{Invalid: p.st.Error("name") != ""}).Input + }) + opts := FormItemOptions[string]{Label: "Email", Validate: func(v string) string { + if v != "" && !strings.Contains(v, "@") { + return "Enter an email address." + } + return "" + }} + if p.useAsync { + opts.Async = func(v string, version uint64) { + p.asyncCalls = append(p.asyncCalls, version) + p.asyncValues = append(p.asyncValues, v) + } + } + FormItem(f, "email", &p.email, opts, func() *ui.Element { + return InputGroup(c, &p.email, InputGroupOptions{Invalid: p.st.Error("email") != ""}).Input + }) + FormItem(f, "tags", &p.tags, FormItemOptions[[]string]{Label: "Tags", Required: true, Initial: []string{"rose"}}, func() *ui.Element { + return TagsInput(c, &p.tags, TagsInputOptions{}).Input + }) + }) + if res.Submitted() { + p.submits++ + } + if res.Reset() { + p.resets++ + } +} + +func TestFormBlurValidationAndUntouchedFields(t *testing.T) { + p := &formProbe{name: "Sakiko", tags: []string{"rose"}} + tt := ui.NewTester(p.run, 600, 500) + _ = tt.Click("Email") + tt.Type("mutsumi") + if tt.HasText("Enter an email address.") { + t.Fatal("error before blur") + } + tt.Key(0, ui.KeyTab) + if !tt.HasText("Enter an email address.") { + t.Fatal("no error after blur") + } + if tt.HasText("Required") { + t.Fatal("untouched field shows an error") + } + _ = tt.Click("Email") + tt.Type("@court") + if tt.HasText("Enter an email address.") || p.email != "mutsumi@court" { + t.Fatalf("touched field did not revalidate: %q", p.email) + } +} + +func TestFormSubmitFailureFocusesFirstErrorAndKeepsValues(t *testing.T) { + p := &formProbe{email: "bad"} + tt := ui.NewTester(p.run, 600, 500) + if err := tt.Click("Submit"); err != nil { + t.Fatal(err) + } + if p.submits != 0 || !tt.HasText("Required") || !tt.HasText("Enter an email address.") { + t.Fatalf("submits %d texts %q", p.submits, tt.Texts()) + } + if !tt.Focused("Name *") || p.email != "bad" { + t.Fatal("first error not focused or value lost") + } + if got := tt.Announcements(); len(got) == 0 || got[len(got)-1] != "Some fields need attention" { + t.Fatalf("announcements %q", got) + } +} + +func TestFormSubmitOnceAndEndSubmit(t *testing.T) { + p := &formProbe{name: "Uika", tags: []string{"x"}} + tt := ui.NewTester(p.run, 600, 500) + _ = tt.Click("Submit") + if p.submits != 1 || !p.st.Submitting() { + t.Fatalf("submits %d", p.submits) + } + _ = tt.Click("Submit") + p.st.Submit() + tt.Frame() + if p.submits != 1 { + t.Fatalf("re-entered: %d", p.submits) + } + p.st.EndSubmit("The server is away.") + tt.Frame() + if !tt.HasText("The server is away.") || p.name != "Uika" { + t.Fatal("form error not shown") + } + _ = tt.Click("Submit") + if p.submits != 2 || tt.HasText("The server is away.") { + t.Fatalf("second submit: %d", p.submits) + } +} + +func TestFormAsyncStaleResultsAndPendingSubmit(t *testing.T) { + p := &formProbe{name: "Uika", tags: []string{"x"}, useAsync: true} + tt := ui.NewTester(p.run, 600, 500) + _ = tt.Click("Email") + tt.Type("a@b") + tt.Key(0, ui.KeyTab) + if len(p.asyncCalls) != 1 || p.asyncValues[0] != "a@b" || !tt.HasText("Checking…") { + t.Fatalf("async calls %v", p.asyncCalls) + } + old := p.asyncCalls[0] + _ = tt.Click("Email") + tt.Type("c") + if p.st.Resolve("email", old, "Taken") { + t.Fatal("stale result accepted") + } + tt.Frame() + if tt.HasText("Taken") { + t.Fatal("stale result shown") + } + _ = tt.Click("Submit") + if len(p.asyncCalls) != 2 || p.asyncValues[1] != "a@bc" || p.submits != 0 { + t.Fatalf("submit did not wait for the new check: %v %d", p.asyncValues, p.submits) + } + if !p.st.Resolve("email", p.st.Version("email"), "") { + t.Fatal("current result refused") + } + tt.Frame() + if p.submits != 1 { + t.Fatalf("submit did not finish after the check: %d", p.submits) + } +} + +func TestFormAsyncErrorBlocksSubmit(t *testing.T) { + p := &formProbe{name: "Uika", tags: []string{"x"}, email: "a@b", useAsync: true} + tt := ui.NewTester(p.run, 600, 500) + _ = tt.Click("Submit") + p.st.Resolve("email", p.st.Version("email"), "Already registered") + tt.Frame() + tt.Frame() + if p.submits != 0 || !tt.HasText("Already registered") || !tt.Focused("Email") { + t.Fatalf("submits %d texts %q", p.submits, tt.Texts()) + } +} + +func TestFormResetRestoresInitialValues(t *testing.T) { + p := &formProbe{name: "", email: "bad", tags: nil} + tt := ui.NewTester(p.run, 600, 500) + _ = tt.Click("Submit") + if err := tt.Click("Reset"); err != nil { + t.Fatal(err) + } + tt.Frame() + if p.name != "Sakiko" || p.email != "" || len(p.tags) != 1 || p.tags[0] != "rose" || p.resets != 1 { + t.Fatalf("reset values %q %q %q", p.name, p.email, p.tags) + } + if tt.HasText("Required") || p.st.Error("email") != "" { + t.Fatal("errors survive reset") + } +} + +func TestFormHorizontalLayout(t *testing.T) { + p := &formProbe{horizontal: true} + tt := ui.NewTester(p.run, 600, 500) + name, _ := tt.Find("Name *") + email, _ := tt.Find("Email") + if name.X+name.W != email.X+email.W { + t.Fatalf("labels not right-aligned: %v %v", name, email) + } +} + +func TestFormSnapshot(t *testing.T) { + p := &formProbe{name: "Sakiko Togawa", email: "oblivionis", tags: []string{"Ave Mujica"}} + snapshot(t, "form", 560, 420, func(c *ui.Context) { + ui.Column(c).Padding(24).Children(func() { + p.view(c) + if !p.st.tried { + p.st.Submit() + c.Invalidate() + } + }) + }) +} diff --git a/input_group.go b/input_group.go new file mode 100644 index 0000000..2db0177 --- /dev/null +++ b/input_group.go @@ -0,0 +1,172 @@ +package mujica + +import ( + "github.com/egoist/mygo/ui" + + "mujicaui/internal" + "mujicaui/theme" +) + +// InputGroupButton is a button joined to an InputGroup's border. +type InputGroupButton struct { + // Label is shown, or names an icon-only button for assistive technology. + Label string + Icon *ui.SVG + // IconOnly hides Label on screen; Icon is then required. + IconOnly bool + // Leading puts the button before the input. + Leading bool + Disabled bool +} + +// InputGroupOptions configure an InputGroup. +type InputGroupOptions struct { + // Prefix and Suffix are fixed text beside the input, such as "https://". + Prefix, Suffix string + Buttons []InputGroupButton + Placeholder string + // Label names the input when it is not inside a FormField. + Label string + Disabled bool + // ReadOnly shows the value as selectable text that cannot change. + ReadOnly bool + // Invalid draws the border in the danger color. + Invalid bool +} + +// InputGroupParts are the parts of an InputGroup. +type InputGroupParts struct { + Group *ui.Element + // Input takes the focus; it is selectable text when ReadOnly. + Input *ui.Element + // Clicked is the index in Buttons of the button clicked, or -1. + Clicked int +} + +// Changed reports whether the user edited the value this frame. +func (p InputGroupParts) Changed() bool { return p.Input.Changed() } + +// Submitted reports whether Enter was pressed in the input. +func (p InputGroupParts) Submitted() bool { return p.Input.Submitted() } + +// InputGroup joins a single-line input with text and buttons inside one border. +// Every button keeps its own place in the Tab order. +func InputGroup(c *ui.Context, value *string, opts InputGroupOptions) InputGroupParts { + for _, b := range opts.Buttons { + if b.IconOnly && b.Icon == nil { + panic("mujica: InputGroupButton IconOnly needs an Icon") + } + if b.Label == "" { + panic("mujica: InputGroupButton needs a Label") + } + } + k := Tokens(c) + d := Density(c) + parts := InputGroupParts{Clicked: -1} + g := ui.Row(c).MinHeight(d.ControlHeight()).AlignItems(ui.Stretch).Radius(theme.ControlRadius). + Padding(theme.BorderWidth).Background(k.Surface).Disabled(opts.Disabled) + parts.Group = g + r := theme.ControlRadius - theme.BorderWidth + // Index of each segment, for its corners and separators. + n := len(opts.Buttons) + 1 + if opts.Prefix != "" { + n++ + } + if opts.Suffix != "" { + n++ + } + seg := 0 + segment := func(e *ui.Element) { + var left, right float32 + if seg == 0 { + left = r + } + if seg == n-1 { + right = r + } + e.Radius(left, right, right, left) + if seg > 0 { + e.BorderWidth(0, 0, 0, theme.BorderWidth).BorderColor(k.ControlBorder) + } + seg++ + } + button := func(i int) { + b := opts.Buttons[i] + bb := ui.ButtonBase(c).Padding(0, d.Unit()*3).Gap(d.Unit() * 2).MinWidth(d.ControlHeight()). + Disabled(b.Disabled).TextColor(k.AccentText).Label(b.Label) + segment(bb) + internal.Ring(bb, k.Focus, theme.ControlRadius) + if bb.Pressed() { + bb.Background(k.Selection) + } else if bb.Hovered() { + bb.Background(k.SurfaceHover) + } + bb.Children(func() { + if b.Icon != nil { + ui.Icon(c, b.Icon).Size(16, 16) + } + if !b.IconOnly { + ui.Text(c, b.Label).SingleLine() + } + }) + if bb.Clicked() { + parts.Clicked = i + } + } + addon := func(s string) { + a := ui.Row(c).Padding(0, d.Unit()*3).Background(k.Background).Shrink(0) + segment(a) + a.Children(func() { ui.Text(c, s).TextColor(k.TextMuted).SingleLine() }) + } + g.Children(func() { + for i, b := range opts.Buttons { + if b.Leading { + button(i) + } + } + if opts.Prefix != "" { + addon(opts.Prefix) + } + box := ui.Row(c).Grow(1).MinWidth(d.Unit()*12).Padding(0, d.Unit()*3) + segment(box) + box.Children(func() { + if opts.ReadOnly { + // As wide as MyGo makes a text input. + parts.Input = ui.Text(c, *value).SingleLine().Selectable().Focusable().FocusRing(false).Grow(1).Width(200).MinWidth(0) + } else { + parts.Input = ui.TextInputBase(c, value).FocusRing(false).Grow(1).MinWidth(0).Placeholder(opts.Placeholder) + } + if opts.Label != "" { + parts.Input.Label(opts.Label) + } + }) + if box.Clicked() { + parts.Input.Focus() + } + if opts.Suffix != "" { + addon(opts.Suffix) + } + for i, b := range opts.Buttons { + if !b.Leading { + button(i) + } + } + }) + in := parts.Input + border := k.ControlBorder + switch { + case opts.Invalid: + border = k.Danger + case in.Focused(): + border = k.Focus + case g.Hovered() && !opts.Disabled: + border = k.ControlBorder.Mix(k.Text, 0.25) + } + g.Border(theme.BorderWidth, border) + g.DrawOver(func(p *ui.Painter, r ui.Rect) { + if in.Focused() { + internal.FocusRing(p, r, theme.ControlRadius, k.Focus) + } + }) + return parts +} diff --git a/input_group_test.go b/input_group_test.go new file mode 100644 index 0000000..bcdd4e1 --- /dev/null +++ b/input_group_test.go @@ -0,0 +1,110 @@ +package mujica + +import ( + "testing" + + "github.com/egoist/mygo/ui" + + "mujicaui/icons" +) + +func TestInputGroupTypingAndButtons(t *testing.T) { + value, clicked, submitted := "", -1, 0 + opts := InputGroupOptions{ + Prefix: "https://", Suffix: ".com", Label: "Site", + Buttons: []InputGroupButton{{Label: "Clear", Icon: icons.Must("x"), IconOnly: true}, {Label: "Go"}}, + } + tt := ui.NewTester(func(c *ui.Context) { + Use(c, Settings{}) + p := InputGroup(c, &value, opts) + if p.Clicked >= 0 { + clicked = p.Clicked + if p.Clicked == 0 { + value = "" + } + } + if p.Submitted() { + submitted++ + } + }, 500, 120) + if err := tt.Click("Site"); err != nil { + t.Fatal(err) + } + tt.Type("mujica") + tt.Key(0, ui.KeyEnter) + if value != "mujica" || submitted != 1 { + t.Fatalf("value %q submitted %d", value, submitted) + } + if err := tt.Click("Go"); err != nil { + t.Fatal(err) + } + if clicked != 1 { + t.Fatalf("clicked %d, want 1", clicked) + } + if err := tt.Click("Clear"); err != nil { + t.Fatal(err) + } + if clicked != 0 || value != "" { + t.Fatalf("clear: clicked %d value %q", clicked, value) + } + if !tt.HasText("https://") || !tt.HasText(".com") { + t.Fatal("addons missing") + } +} + +func TestInputGroupTabOrder(t *testing.T) { + value := "" + tt := ui.NewTester(func(c *ui.Context) { + Use(c, Settings{}) + InputGroup(c, &value, InputGroupOptions{Label: "Query", Buttons: []InputGroupButton{{Label: "Back", Leading: true}, {Label: "Search"}}}) + }, 500, 120) + tt.Key(0, ui.KeyTab) + if !tt.Focused("Back") { + t.Fatal("leading button is not first") + } + tt.Key(0, ui.KeyTab) + if !tt.Focused("Query") { + t.Fatal("input is not second") + } + tt.Key(0, ui.KeyTab) + if !tt.Focused("Search") { + t.Fatal("trailing button is not third") + } +} + +func TestInputGroupBlockedStates(t *testing.T) { + for _, opts := range []InputGroupOptions{{Label: "Code", Disabled: true}, {Label: "Code", ReadOnly: true}} { + value, clicked := "abc", -1 + tt := ui.NewTester(func(c *ui.Context) { + Use(c, Settings{}) + opts.Buttons = []InputGroupButton{{Label: "Copy"}} + if p := InputGroup(c, &value, opts); p.Clicked >= 0 { + clicked = p.Clicked + } + }, 400, 100) + _ = tt.Click("Code") + tt.Type("zz") + _ = tt.Click("Copy") + if value != "abc" { + t.Fatalf("%+v: value changed to %q", opts, value) + } + if opts.Disabled && clicked != -1 { + t.Fatal("disabled button clicked") + } + if opts.ReadOnly && clicked != 0 { + t.Fatal("read-only group blocked its button") + } + } +} + +func TestInputGroupSnapshot(t *testing.T) { + a, b, e := "mujica", "", "bad value" + snapshot(t, "input_group", 520, 200, func(c *ui.Context) { + ui.Column(c).Padding(20).Gap(12).Children(func() { + InputGroup(c, &a, InputGroupOptions{Prefix: "https://", Suffix: ".com", Label: "Site"}) + InputGroup(c, &b, InputGroupOptions{Placeholder: "Search the archive", Label: "Search", + Buttons: []InputGroupButton{{Label: "Search", Icon: icons.Must("search"), IconOnly: true, Leading: true}, {Label: "Go"}}}) + InputGroup(c, &e, InputGroupOptions{Invalid: true, Suffix: "kg", Label: "Weight"}) + }) + }) +} diff --git a/messages_form.go b/messages_form.go new file mode 100644 index 0000000..96ca640 --- /dev/null +++ b/messages_form.go @@ -0,0 +1,53 @@ +package mujica + +func init() { + addMessages(map[string][2]string{ + "rating.label": {"Rating", "评分"}, + "pin_input.label": {"Verification code", "验证码"}, + "pin_input.filled": {"%d of %d digits entered", "已输入 %d / %d 位"}, + "rating.value": {"%s of %d", "%s / %d 分"}, + "tags_input.duplicate": {"“%s” is already added", "“%s”已添加"}, + "tags_input.max": {"At most %d tags", "最多 %d 个标签"}, + "tags_input.remove": {"Remove %s", "移除 %s"}, + "color_picker.label": {"Color", "颜色"}, + "color_picker.red": {"Red", "红"}, + "color_picker.green": {"Green", "绿"}, + "color_picker.blue": {"Blue", "蓝"}, + "color_picker.percent": {"Opacity percent", "不透明度百分比"}, + "color_picker.hue": {"Hue", "色相"}, + "color_picker.alpha": {"Opacity", "不透明度"}, + "color_picker.square": {"Saturation and brightness", "饱和度与亮度"}, + "color_picker.sv": {"%.0f%% saturation, %.0f%% brightness", "饱和度 %.0f%%,亮度 %.0f%%"}, + "color_picker.invalid": {"Use HEX RGB, RRGGBB or RRGGBBAA; channels 0–255; opacity 0–100.", "HEX 须为 RGB、RRGGBB 或 RRGGBBAA;通道 0–255;不透明度 0–100。"}, + "file_picker.failed": {"Could not open the file dialog: %v", "无法打开文件对话框:%v"}, + "file_picker.choose_file": {"Choose file…", "选择文件…"}, + "file_picker.choose_folder": {"Choose folder…", "选择文件夹…"}, + "file_picker.none": {"Nothing chosen", "未选择"}, + "file_drop_zone.hint": {"Drop files here", "将文件拖到此处"}, + "file_drop_zone.browse": {"Browse…", "浏览…"}, + "file_drop_zone.any": {"Any file", "任意文件"}, + "file_drop_zone.max_files": {"up to %d files", "最多 %d 个文件"}, + "file_drop_zone.max_size": {"%s each", "每个不超过 %s"}, + "file_drop_zone.type": {"Only %s files", "仅接受 %s 文件"}, + "file_drop_zone.size": {"Larger than %s", "超过 %s"}, + "file_drop_zone.count": {"More than %d files", "超过 %d 个文件"}, + "file_drop_zone.folder": {"Folders are not accepted", "不接受文件夹"}, + "file_drop_zone.unreadable": {"Cannot read: %v", "无法读取:%v"}, + "file_drop_zone.checking": {"Checking…", "检查中…"}, + "file_drop_zone.remove": {"Remove %s", "移除 %s"}, + "file_drop_zone.dialog": {"File dialog", "文件对话框"}, + "transfer.source": {"Available", "可选"}, + "transfer.target": {"Chosen", "已选"}, + "transfer.to": {"Move to %s", "移到%s"}, + "transfer.all": {"Select all in %s", "全选%s"}, + "transfer.search": {"Search", "搜索"}, + "transfer.search_in": {"Search %s", "搜索%s"}, + "transfer.empty": {"No items", "暂无条目"}, + "transfer.no_match": {"No matches", "无匹配结果"}, + "form.submit": {"Submit", "提交"}, + "form.reset": {"Reset", "重置"}, + "form.required": {"Required", "必填"}, + "form.checking": {"Checking…", "校验中…"}, + "form.failed": {"Some fields need attention", "部分字段需要修改"}, + }) +} diff --git a/pin_input.go b/pin_input.go new file mode 100644 index 0000000..99f05e0 --- /dev/null +++ b/pin_input.go @@ -0,0 +1,165 @@ +package mujica + +import ( + "fmt" + "log/slog" + "strings" + + "github.com/egoist/mygo/ui" + + "mujicaui/internal" + "mujicaui/theme" +) + +// PinInputOptions configure a PinInput. Length is 6 when zero. +type PinInputOptions struct { + Length int + // Label names the input; empty uses the built-in "Verification code". + Label string + Disabled bool + // Invalid draws the cells in the danger color. + Invalid bool +} + +// PinInputParts are the parts of a PinInput. +type PinInputParts struct { + Element *ui.Element + changed, completed bool +} + +// Changed reports whether the user changed the code this frame. +func (p PinInputParts) Changed() bool { return p.changed } + +// Completed reports whether the user filled the last cell this frame. +func (p PinInputParts) Completed() bool { return p.completed } + +// pinInputEvent is input taken between frames, applied in the next one. +type pinInputEvent struct { + text string + backspace bool + paste bool +} + +type pinInputState struct { + queue []pinInputEvent + compose string +} + +// PinInput edits a fixed-length code of digits, one per cell, keeping leading zeros. +// Typing fills the next cell, Backspace clears the last, and paste fills them all. +func PinInput(c *ui.Context, value *string, opts PinInputOptions) PinInputParts { + n := opts.Length + if n == 0 { + n = 6 + } + if n < 0 || len(*value) > n || strings.Trim(*value, "0123456789") != "" { + panic(fmt.Sprintf("mujica: PinInput value %q does not fit %d digits", *value, n)) + } + k := Tokens(c) + d := Density(c) + label := opts.Label + if label == "" { + label = msg(c, "pin_input.label") + } + parts := PinInputParts{} + e := ui.Row(c).Gap(d.Unit() * 2).Shrink(0).Role(ui.RoleTextField).Label(label).Disabled(opts.Disabled) + parts.Element = e + st := ui.Local(e, "pin", func() pinInputState { return pinInputState{} }) + put := func(s string) { + var b strings.Builder + for _, r := range s { + if r >= '0' && r <= '9' { + b.WriteRune(r) + } + } + next := b.String() + if len(next) > n { + next = next[:n] + } + if next == *value { + return + } + was := len(*value) + *value = next + parts.changed = true + if len(next) == n && was < n { + parts.completed = true + slog.Debug("mujica: pin input completed", "length", n) + } + } + for _, ev := range st.queue { + switch { + case ev.paste: + put(c.ReadClipboard()) + case ev.backspace && *value != "": + put((*value)[:len(*value)-1]) + case ev.text != "": + put(*value + ev.text) + } + } + st.queue = st.queue[:0] + if opts.Disabled { + st.compose = "" + } else { + e.Focusable().FocusRing(false).HandleInput(func(ev ui.InputEvent) bool { + switch { + case ev.Kind == ui.InputText: + st.queue = append(st.queue, pinInputEvent{text: ev.Text}) + st.compose = "" + case ev.Kind == ui.InputCompose: + st.compose = ev.Text + case ev.Kind == ui.InputCommand && ev.Text == "paste", + ev.Kind == ui.InputKeyDown && ev.Mods == ui.Cmd && ev.Key == ui.KeyV: + st.queue = append(st.queue, pinInputEvent{paste: true}) + case ev.Kind == ui.InputKeyDown && ev.Mods == 0 && ev.Key == ui.KeyBackspace: + st.queue = append(st.queue, pinInputEvent{backspace: true}) + default: + return false + } + return true + }) + } + size := d.ControlHeight() + d.Unit()*2 + active := min(len(*value), n-1) + focused := e.Focused() + compose := st.compose + e.Description(fmt.Sprintf(msg(c, "pin_input.filled"), len(*value), n)) + e.TextCaret(ui.Rect{X: float32(active) * (size + d.Unit()*2), Y: 0, W: size, H: size}) + e.Children(func() { + for i := range n { + cell := ui.Box(c).Size(size, size).Center().Radius(theme.ControlRadius).Background(k.Surface).Role(ui.RoleNone) + border := k.ControlBorder + switch { + case opts.Invalid: + border = k.Danger + case focused && i == active: + border = k.Focus + } + cell.Border(theme.BorderWidth, border) + ch := "" + if i < len(*value) { + ch = (*value)[i : i+1] + } + showCompose := focused && i == active && compose != "" + cell.Children(func() { + switch { + case showCompose: + ui.Text(c, compose).FontSize(theme.H3Size).Underline().SingleLine() + case ch != "": + ui.Text(c, ch).FontSize(theme.H3Size).FontFeatures("tnum").SingleLine() + } + }) + caret := focused && i == active && len(*value) < n && !showCompose + cell.DrawOver(func(p *ui.Painter, r ui.Rect) { + if focused && i == active { + internal.FocusRing(p, r, theme.ControlRadius, k.Focus) + } + if caret { + h := r.H * 0.4 + p.Fill(ui.Rect{X: r.X + r.W/2 - 0.5, Y: r.Y + (r.H-h)/2, W: 1, H: h}, k.Text, 0) + } + }) + } + }) + return parts +} diff --git a/pin_input_test.go b/pin_input_test.go new file mode 100644 index 0000000..07bcf9f --- /dev/null +++ b/pin_input_test.go @@ -0,0 +1,104 @@ +package mujica + +import ( + "testing" + + "github.com/egoist/mygo/ui" +) + +type pinInputProbe struct { + value string + changes, completes int +} + +func (p *pinInputProbe) tester(opts PinInputOptions) *ui.Tester { + return ui.NewTester(func(c *ui.Context) { + Use(c, Settings{}) + r := PinInput(c, &p.value, opts) + if r.Changed() { + p.changes++ + } + if r.Completed() { + p.completes++ + } + }, 480, 120) +} + +func TestPinInputTypingAndBackspace(t *testing.T) { + p := &pinInputProbe{} + tt := p.tester(PinInputOptions{Length: 4, Label: "Code"}) + tt.Key(0, ui.KeyTab) + if !tt.Focused("Code") { + t.Fatal("not focusable") + } + tt.TypeKey(0, ui.Key0, "0") + tt.Type("a") + tt.Type("07") + if p.value != "007" || p.completes != 0 { + t.Fatalf("value %q completes %d", p.value, p.completes) + } + tt.Type("9") + tt.Type("5") + if p.value != "0079" || p.completes != 1 { + t.Fatalf("full: value %q completes %d", p.value, p.completes) + } + tt.Key(0, ui.KeyBackspace) + tt.Key(0, ui.KeyBackspace) + if p.value != "00" { + t.Fatalf("backspace: %q", p.value) + } + tt.Type("12") + if p.value != "0012" || p.completes != 2 { + t.Fatalf("refill: %q completes %d", p.value, p.completes) + } +} + +func TestPinInputPasteAndCompose(t *testing.T) { + p := &pinInputProbe{value: "1"} + tt := p.tester(PinInputOptions{Label: "Code"}) + tt.Key(0, ui.KeyTab) + tt.SetClipboard(" 012-345 9") + tt.Command("paste") + if p.value != "012345" || p.completes != 1 || p.changes != 1 { + t.Fatalf("paste: %q completes %d changes %d", p.value, p.completes, p.changes) + } + tt.Key(0, ui.KeyBackspace) + tt.Compose("4", 1) + if p.value != "01234" || !tt.HasText("4") { + t.Fatalf("compose committed early: %q", p.value) + } + tt.Type("8") + if p.value != "012348" { + t.Fatalf("commit: %q", p.value) + } +} + +func TestPinInputDisabled(t *testing.T) { + p := &pinInputProbe{value: "12"} + tt := p.tester(PinInputOptions{Label: "Code", Disabled: true}) + tt.Key(0, ui.KeyTab) + tt.Type("3") + if tt.Focused("Code") || p.value != "12" { + t.Fatalf("disabled took input: %q", p.value) + } +} + +func TestPinInputRejectsBadValue(t *testing.T) { + defer func() { + if recover() == nil { + t.Fatal("letters accepted") + } + }() + p := &pinInputProbe{value: "12a"} + p.tester(PinInputOptions{}) +} + +func TestPinInputSnapshot(t *testing.T) { + a, b := "0420", "13" + snapshot(t, "pin_input", 420, 150, func(c *ui.Context) { + ui.Column(c).Padding(20).Gap(16).Children(func() { + PinInput(c, &a, PinInputOptions{}).Element.Focus() + PinInput(c, &b, PinInputOptions{Length: 4, Invalid: true}) + }) + }) +} diff --git a/rating.go b/rating.go new file mode 100644 index 0000000..ccf7942 --- /dev/null +++ b/rating.go @@ -0,0 +1,144 @@ +package mujica + +import ( + "fmt" + "log/slog" + "math" + "strconv" + + "github.com/egoist/mygo/ui" + + "mujicaui/internal" + "mujicaui/theme" +) + +// RatingOptions configure a Rating. Max is 5 and Step 1 when zero. +type RatingOptions struct { + Max int + // Step is the smallest change, such as 0.5 for half stars; it must divide 1. + Step float64 + ReadOnly bool + Disabled bool + // Label names the rating; empty uses the built-in "Rating". + Label string + // ShowValue prints "3.5 / 5" beside the stars. + ShowValue bool +} + +// RatingParts are the parts of a Rating. +type RatingParts struct { + Element *ui.Element + changed bool +} + +// Changed reports whether the user set a new value this frame. +func (p RatingParts) Changed() bool { return p.changed } + +// Rating shows *value as stars and lets the pointer and arrows set it in steps. +func Rating(c *ui.Context, value *float64, opts RatingOptions) RatingParts { + n, step := opts.Max, opts.Step + if n == 0 { + n = 5 + } + if step == 0 { + step = 1 + } + if n < 0 || step < 0 || step > 1 || math.Abs(1/step-math.Round(1/step)) > 1e-9 { + panic(fmt.Sprintf("mujica: Rating Max %d, Step %v: Max must be positive and Step divide 1", opts.Max, opts.Step)) + } + k := Tokens(c) + size := Density(c).ControlHeight() * 0.6 + label := opts.Label + if label == "" { + label = msg(c, "rating.label") + } + parts := RatingParts{} + set := func(v float64) { + v = math.Max(0, math.Min(float64(n), math.Round(v/step)*step)) + if v != *value { + slog.Debug("mujica: rating changed", "from", *value, "to", v) + *value = v + parts.changed = true + } + } + e := ui.Row(c).AlignItems(ui.Center).Gap(Density(c).Unit() * 2).Shrink(0) + parts.Element = e + e.Label(label) + interactive := !opts.ReadOnly && !opts.Disabled + if opts.Disabled { + e.Disabled(true) + } else { + e.Focusable() + internal.Ring(e, k.Focus, theme.ControlRadius) + } + if interactive { + e.Role(ui.RoleSlider).Cursor(ui.CursorPointer) + switch { + case e.Shortcut(0, ui.KeyRight), e.Shortcut(0, ui.KeyUp): + set(*value + step) + case e.Shortcut(0, ui.KeyLeft), e.Shortcut(0, ui.KeyDown): + set(*value - step) + case e.Shortcut(0, ui.KeyHome): + set(0) + case e.Shortcut(0, ui.KeyEnd): + set(float64(n)) + } + } + e.Children(func() { + stars := ui.Box(c).Size(size*float32(n), size).Shrink(0) + shown, fill := *value, k.AccentText + if interactive { + at := func(x float32) float64 { return math.Ceil(float64(x/size)/step-1e-9) * step } + if _, _, ok := stars.Dragged(); ok { + x, _, _ := stars.PointerPosition() + set(at(x)) + shown = *value + } else if x, _, over := stars.PointerPosition(); over { + shown, fill = math.Max(step, math.Min(float64(n), at(x))), k.AccentText.Alpha(0.6) + } + } + stars.Draw(func(p *ui.Painter, r ui.Rect) { + for i := range n { + cell := ui.Rect{X: r.X + float32(i)*size, Y: r.Y, W: size, H: size} + path := ratingStar(cell) + p.StrokePath(path, 1.25, k.ControlBorder) + part := float32(math.Max(0, math.Min(1, shown-float64(i)))) + if part > 0 { + p.Clip(ui.Rect{X: cell.X, Y: cell.Y, W: cell.W * part, H: cell.H}, 0, func() { + p.FillPath(path, fill) + p.StrokePath(path, 1.25, fill) + }) + } + } + }) + if opts.ShowValue { + ui.Text(c, fmt.Sprintf("%s / %d", strconv.FormatFloat(*value, 'f', -1, 64), n)). + TextColor(k.TextMuted).FontFeatures("tnum").SingleLine() + } + }) + e.Description(fmt.Sprintf(msg(c, "rating.value"), strconv.FormatFloat(*value, 'f', -1, 64), n)) + return parts +} + +// ratingStar returns a five-pointed star inset in r. +func ratingStar(r ui.Rect) *ui.Path { + var p ui.Path + cx, cy := r.X+r.W/2, r.Y+r.H*0.54 + outer := r.W * 0.42 + inner := outer * 0.45 + for i := range 10 { + rad := outer + if i%2 == 1 { + rad = inner + } + a := -math.Pi/2 + float64(i)*math.Pi/5 + x, y := cx+rad*float32(math.Cos(a)), cy+rad*float32(math.Sin(a)) + if i == 0 { + p.MoveTo(x, y) + } else { + p.LineTo(x, y) + } + } + p.Close() + return &p +} diff --git a/rating_test.go b/rating_test.go new file mode 100644 index 0000000..b814fc2 --- /dev/null +++ b/rating_test.go @@ -0,0 +1,102 @@ +package mujica + +import ( + "testing" + + "github.com/egoist/mygo/ui" +) + +func ratingTester(v *float64, opts RatingOptions, changes *int) *ui.Tester { + return ui.NewTester(func(c *ui.Context) { + Use(c, Settings{}) + ui.Column(c).Padding(10).AlignItems(ui.Start).Children(func() { + if Rating(c, v, opts).Changed() { + *changes++ + } + }) + }, 300, 100) +} + +func TestRatingKeyboardSteps(t *testing.T) { + v, n := 2.0, 0 + tt := ratingTester(&v, RatingOptions{Step: 0.5, Label: "Taste"}, &n) + tt.Key(0, ui.KeyTab) + if !tt.Focused("Taste") { + t.Fatal("rating not focusable") + } + tt.Key(0, ui.KeyRight) + if v != 2.5 { + t.Fatalf("right: %v", v) + } + tt.Key(0, ui.KeyDown) + tt.Key(0, ui.KeyDown) + if v != 1.5 { + t.Fatalf("down twice: %v", v) + } + tt.Key(0, ui.KeyEnd) + tt.Key(0, ui.KeyRight) + if v != 5 { + t.Fatalf("end then right: %v", v) + } + tt.Key(0, ui.KeyHome) + tt.Key(0, ui.KeyLeft) + if v != 0 || n != 5 { + t.Fatalf("home then left: %v, %d changes", v, n) + } +} + +func TestRatingPointer(t *testing.T) { + v, n := 0.0, 0 + tt := ratingTester(&v, RatingOptions{Step: 0.5, Label: "Taste"}, &n) + size := float32(36) * 0.6 + // The left half of the third star. + tt.ClickAt(10+2*size+size*0.25, 10+size/2) + if v != 2.5 || n != 1 { + t.Fatalf("click: %v, %d changes", v, n) + } + tt.ClickAt(10+2*size+size*0.25, 10+size/2) + if n != 1 { + t.Fatalf("same value reported a change") + } +} + +func TestRatingReadOnlyAndDisabled(t *testing.T) { + for _, opts := range []RatingOptions{{ReadOnly: true, Label: "Score"}, {Disabled: true, Label: "Score"}} { + v, n := 3.0, 0 + tt := ratingTester(&v, opts, &n) + tt.ClickAt(12, 20) + tt.Key(0, ui.KeyTab) + tt.Key(0, ui.KeyRight) + if v != 3 || n != 0 { + t.Fatalf("%+v: value %v changes %d", opts, v, n) + } + if opts.ReadOnly != tt.Focused("Score") { + t.Fatalf("%+v: focus %v", opts, tt.Focused("Score")) + } + } +} + +func TestRatingShowsValueAndPanics(t *testing.T) { + v, n := 3.5, 0 + tt := ratingTester(&v, RatingOptions{Step: 0.5, ShowValue: true}, &n) + if !tt.HasText("3.5 / 5") { + t.Fatalf("texts %q", tt.Texts()) + } + defer func() { + if recover() == nil { + t.Fatal("Step 0.3 accepted") + } + }() + ratingTester(&v, RatingOptions{Step: 0.3}, &n) +} + +func TestRatingSnapshot(t *testing.T) { + a, b, d := 3.5, 4.0, 2.0 + snapshot(t, "rating", 360, 170, func(c *ui.Context) { + ui.Column(c).Padding(20).Gap(14).AlignItems(ui.Start).Children(func() { + Rating(c, &a, RatingOptions{Step: 0.5, ShowValue: true}) + Rating(c, &b, RatingOptions{ReadOnly: true, Max: 7, ShowValue: true}) + Rating(c, &d, RatingOptions{Disabled: true}) + }) + }) +} diff --git a/tags_input.go b/tags_input.go new file mode 100644 index 0000000..3669bf0 --- /dev/null +++ b/tags_input.go @@ -0,0 +1,178 @@ +package mujica + +import ( + "fmt" + "log/slog" + "slices" + "strings" + + "github.com/egoist/mygo/ui" + + "mujicaui/icons" + "mujicaui/internal" + "mujicaui/theme" +) + +// TagsInputOptions configure a TagsInput. +type TagsInputOptions struct { + // Max caps the number of tags; zero allows any number. + Max int + // Separators split typed and pasted text into tags; empty uses ",;,;" and line breaks. + Separators string + // Validate checks a tag before it is added and returns an error message, or "". + Validate func(tag string) string + Placeholder string + // Label names the input when it is not inside a FormField. + Label string + Disabled bool + // Invalid draws the border in the danger color. + Invalid bool +} + +// TagsInputParts are the parts of a TagsInput. +type TagsInputParts struct { + Element *ui.Element + // Input takes the focus. + Input *ui.Element + changed bool +} + +// Changed reports whether tags were added or removed this frame. +func (p TagsInputParts) Changed() bool { return p.changed } + +type tagsInputState struct { + query string + err string + backspace bool +} + +// TagsInput edits a list of unique tags shown as chips before a text input. +// Enter or a separator adds the text; Backspace in the empty input removes the last tag. +func TagsInput(c *ui.Context, tags *[]string, opts TagsInputOptions) TagsInputParts { + if opts.Max < 0 { + panic("mujica: TagsInput Max must not be negative") + } + k := Tokens(c) + d := Density(c) + seps := opts.Separators + if seps == "" { + seps = ",;,;\n\r\t" + } + parts := TagsInputParts{} + outer := ui.Column(c).Gap(d.Unit()) + parts.Element = outer + st := ui.Local(outer, "tags", func() tagsInputState { return tagsInputState{} }) + add := func(s string) bool { + s = strings.TrimSpace(s) + switch { + case s == "": + return true + case slices.Contains(*tags, s): + st.err = fmt.Sprintf(msg(c, "tags_input.duplicate"), s) + return false + case opts.Max > 0 && len(*tags) >= opts.Max: + st.err = fmt.Sprintf(msg(c, "tags_input.max"), opts.Max) + return false + } + if opts.Validate != nil { + if e := opts.Validate(s); e != "" { + st.err = e + return false + } + } + *tags = append(*tags, s) + parts.changed = true + slog.Debug("mujica: tags input added", "count", len(*tags)) + return true + } + remove := func(i int) { + *tags = slices.Delete(slices.Clone(*tags), i, i+1) + parts.changed, st.err = true, "" + slog.Debug("mujica: tags input removed", "count", len(*tags)) + } + if st.backspace { + st.backspace = false + if len(*tags) > 0 && st.query == "" { + remove(len(*tags) - 1) + } + } + var box *ui.Element + outer.Children(func() { + box = ui.Row(c).Wrap().AlignItems(ui.Center).Gap(d.Unit()*1.5).MinHeight(d.ControlHeight()). + Padding(d.Unit(), d.Unit()*2).Radius(theme.ControlRadius).Background(k.Surface).Disabled(opts.Disabled) + box.Children(func() { + for i, tag := range *tags { + chip := ui.Row(c).Key(tag).AlignItems(ui.Center).Gap(d.Unit()).Padding(0, d.Unit(), 0, d.Unit()*2). + MinHeight(d.ControlHeight() - d.Unit()*3).Radius(theme.ControlRadius).Background(k.Selection).Shrink(0) + chip.Children(func() { + ui.Text(c, tag).SingleLine() + x := ui.ButtonBase(c).Size(20, 20).Radius(theme.ControlRadius).TextColor(k.TextMuted). + Label(fmt.Sprintf(msg(c, "tags_input.remove"), tag)) + internal.Ring(x, k.Focus, theme.ControlRadius) + if x.Hovered() { + x.Background(k.SurfaceHover).TextColor(k.Text) + } + x.Children(func() { ui.Icon(c, icons.Must("x")).Size(14, 14) }) + if x.Clicked() { + remove(i) + } + }) + } + // The input keeps its identity whatever chips come before it. + ui.Box(c).Key("input").Grow(1).MinWidth(d.Unit() * 24).Children(func() { + in := ui.TextInputBase(c, &st.query).FocusRing(false).FillWidth().Placeholder(opts.Placeholder) + parts.Input = in + if opts.Label != "" { + in.Label(opts.Label) + } + in.HandleInput(func(ev ui.InputEvent) bool { + if ev.Kind == ui.InputKeyDown && ev.Mods == 0 && ev.Key == ui.KeyBackspace && st.query == "" { + st.backspace = true + return true + } + return false + }) + }) + }) + if st.err != "" { + ui.Text(c, st.err).TextColor(k.Danger).FontSize(theme.CaptionSize).Role(ui.RoleStatus) + } + }) + in := parts.Input + if in.Changed() { + st.err = "" + } + if strings.ContainsAny(st.query, seps) { + pieces := strings.FieldsFunc(st.query+"\x00", func(r rune) bool { return strings.ContainsRune(seps, r) }) + last := strings.TrimSuffix(pieces[len(pieces)-1], "\x00") + var kept []string + for _, p := range pieces[:len(pieces)-1] { + if !add(p) { + kept = append(kept, strings.TrimSpace(p)) + } + } + st.query = strings.Join(append(kept, last), string([]rune(seps)[0])) + } + if in.Submitted() && add(st.query) { + st.query = "" + } + if box.Clicked() { + in.Focus() + } + border := k.ControlBorder + switch { + case opts.Invalid || st.err != "": + border = k.Danger + case in.Focused(): + border = k.Focus + case box.Hovered() && !opts.Disabled: + border = k.ControlBorder.Mix(k.Text, 0.25) + } + box.Border(theme.BorderWidth, border) + box.DrawOver(func(p *ui.Painter, r ui.Rect) { + if in.Focused() { + internal.FocusRing(p, r, theme.ControlRadius, k.Focus) + } + }) + return parts +} diff --git a/tags_input_test.go b/tags_input_test.go new file mode 100644 index 0000000..2d2dae2 --- /dev/null +++ b/tags_input_test.go @@ -0,0 +1,117 @@ +package mujica + +import ( + "slices" + "strings" + "testing" + + "github.com/egoist/mygo/ui" +) + +func tagsInputTester(tags *[]string, opts TagsInputOptions, changes *int) *ui.Tester { + return ui.NewTester(func(c *ui.Context) { + Use(c, Settings{}) + ui.Column(c).Padding(10).Children(func() { + if TagsInput(c, tags, opts).Changed() { + *changes++ + } + }) + }, 520, 200) +} + +func TestTagsInputEnterSeparatorsAndBackspace(t *testing.T) { + tags, n := []string{"rose"}, 0 + tt := tagsInputTester(&tags, TagsInputOptions{Label: "Tags"}, &n) + tt.Key(0, ui.KeyTab) + tt.Key(0, ui.KeyTab) + if !tt.Focused("Tags") { + t.Fatal("input not after the chip's remove button") + } + tt.Type("thorn") + tt.Key(0, ui.KeyEnter) + tt.Type("lily, ivy;moss") + if !slices.Equal(tags, []string{"rose", "thorn", "lily", "ivy"}) { + t.Fatalf("tags %q", tags) + } + tt.Key(0, ui.KeyEnter) + if tags[4] != "moss" || n != 3 { + t.Fatalf("tags %q changes %d", tags, n) + } + tt.Key(0, ui.KeyBackspace) + if !slices.Equal(tags, []string{"rose", "thorn", "lily", "ivy"}) { + t.Fatalf("backspace: %q", tags) + } +} + +func TestTagsInputPasteDedupeAndMax(t *testing.T) { + tags, n := []string{"a"}, 0 + tt := tagsInputTester(&tags, TagsInputOptions{Label: "Tags", Max: 3}, &n) + _ = tt.Click("Tags") + tt.SetClipboard("b,a,c,d,") + tt.Command("paste") + if !slices.Equal(tags, []string{"a", "b", "c"}) { + t.Fatalf("tags %q", tags) + } + if !tt.HasText("“a” is already added") && !tt.HasText("At most 3 tags") { + t.Fatalf("no error shown: %q", tt.Texts()) + } + tt.Key(0, ui.KeyEnter) + if len(tags) != 3 { + t.Fatalf("max exceeded: %q", tags) + } +} + +func TestTagsInputValidateKeepsText(t *testing.T) { + tags, n, seen := []string{}, 0, "" + opts := TagsInputOptions{Label: "Tags", Validate: func(s string) string { + seen = s + if strings.ContainsAny(s, " ") { + return "No spaces" + } + return "" + }} + tt := tagsInputTester(&tags, opts, &n) + _ = tt.Click("Tags") + tt.Type("two words") + tt.Key(0, ui.KeyEnter) + if len(tags) != 0 || !tt.HasText("No spaces") { + t.Fatalf("tags %q texts %q", tags, tt.Texts()) + } + tt.Type("x") + if tt.HasText("No spaces") { + t.Fatal("error stays after editing") + } + tt.Key(0, ui.KeyEnter) + if seen != "two wordsx" { + t.Fatalf("typed text lost: %q", seen) + } +} + +func TestTagsInputRemoveButtonAndDisabled(t *testing.T) { + tags, n := []string{"rose", "ivy"}, 0 + tt := tagsInputTester(&tags, TagsInputOptions{}, &n) + if err := tt.Click("Remove rose"); err != nil { + t.Fatal(err) + } + if !slices.Equal(tags, []string{"ivy"}) || n != 1 { + t.Fatalf("tags %q", tags) + } + tags = []string{"rose"} + tt = tagsInputTester(&tags, TagsInputOptions{Disabled: true}, &n) + _ = tt.Click("Remove rose") + tt.Key(0, ui.KeyTab) + tt.Key(0, ui.KeyBackspace) + if len(tags) != 1 { + t.Fatal("disabled input removed a tag") + } +} + +func TestTagsInputSnapshot(t *testing.T) { + a, b := []string{"Ave Mujica", "Oblivionis", "Doloris"}, []string{"rose"} + snapshot(t, "tags_input", 520, 190, func(c *ui.Context) { + ui.Column(c).Padding(20).Gap(16).Children(func() { + TagsInput(c, &a, TagsInputOptions{Placeholder: "Add a member"}) + TagsInput(c, &b, TagsInputOptions{Invalid: true, Placeholder: "Add a flower"}) + }) + }) +} diff --git a/testdata/snapshots/darwin/color_picker-dark.png b/testdata/snapshots/darwin/color_picker-dark.png new file mode 100644 index 0000000000000000000000000000000000000000..494495e2609c0f96a3535c03b87b84d9c3ef0794 GIT binary patch literal 26924 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 PAQ7p))>N!fu#Wg2(IYN? literal 0 HcmV?d00001 diff --git a/testdata/snapshots/darwin/color_picker-light.png b/testdata/snapshots/darwin/color_picker-light.png new file mode 100644 index 0000000000000000000000000000000000000000..233bb64b80288840b857a6c1d5d31c0fc1820b21 GIT binary patch literal 26953 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 w%x(b8O`}4tDGnKw3Gx45{>vkv6`lul%# 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 f)Bg1S0SYf&ox*VVL=W{P#+c9|8Xdm55qr literal 0 HcmV?d00001 diff --git a/testdata/snapshots/darwin/form-dark.png b/testdata/snapshots/darwin/form-dark.png new file mode 100644 index 0000000000000000000000000000000000000000..64b96f994bf299a7de7da434026bdfa0130f7509 GIT binary patch literal 17134 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 Q{z)SlNkxfju}^{j7Z**NlK=n! literal 0 HcmV?d00001 diff --git a/testdata/snapshots/darwin/form-light.png b/testdata/snapshots/darwin/form-light.png new file mode 100644 index 0000000000000000000000000000000000000000..7690b2ad47f122ded2ef4375ea0a39ec23e140cf GIT binary patch literal 17089 zcmeIZ1yo#5w=bB45Ind$X&}MfT^o0|;O_28aCZnE+&#D@IKhIuJ9OjFXfyr)zJI=Z zXWra3^VYj}&Ahel>FPRl&N_8YZK>K-zr8C$Sy37dnF#sCix+4zKnc|sFJ7^{c=56j z@fGaOx9IS*7ca!ZWh6w^y|O?{f$}r%B!lP8B%V2Bqde~6_6lk(Wq}7c)3r%+9GLw@ ziv5kDRgGrUI`NMe_{dS5#tEu$5~7+UPdrc{DvpY3>Ci0Tvss@0H(OI`GSWV0;iZsV zE`OwP9b%2>yKPDw4Jogf?`4^WsZYb>6E-HEXs+%EAL_jrHxt(_vw&Pi9L9C;-(~DCVvq`La z5`BjxDyAZ@0Dq+m;dqI@O@h&((K`rzLN6;pyMyofc_cKzwUOB;P<`u`?<+M^k&MUZS_U<`?VOvR)9 z$;9;XyY)aNoUd-TH8Xv4i@}LTMZOvYgp7Drb|(YAi>oZ*=WoYjU=h5$nky|WI{LWI zMnH4A{9xnq>t))=HSEnD?{sX!LM6oWDJSEtfB}o~7GnYL9iB3_64hXhnE@FhuM4TD zoCkRIWuZBqp-c2jDm+3ST%=``5ab?ZzPVZ;{{6iAp2L?3$Jo$9yGVG_0iCCIV}HC3}} zCatm*E$t0@>k3-;22+hrK}&7m)|=Szg~p0szvyEq(X$vBSqM;&6<{X({(VSg68V*$ioI9Ne;zwD+pwm2d->Fj z54`r&S}>2zL8S2TgeD`=(<2t6CSG2(t6Sx|)O>um7QWuK>^RG7=u$k7AdWT0%kwB@RD%kB*7Q_I3x?E*i72Y1i9Vu0rl_n6U^5 z2!uSFOr74p^M@S3hY@qz^HphrvBePYgaIGr6>%qzuNT9nrJpboWRhqIQInRJs^_zK zwXDmScn$M}9`zz(4D1`O3yVo@TAc32zU0SbV$m^bHp(DUnhES`-JA&0m6xBw-R8U$ zeML$~*-Gm|EQKJ~ivAl6-~VzHCn*y}VqF#({6f4a8AqlsC5<6LM4JMSCgesL_@z>m zjS0__p%WbR-=)-YVF`^E{^W zHy>cZvG%oe!So@2ljIEqGHAc-|LB#9KfiqBM zu2q6@;tg7-Mqw(Oy~Su{(0{OttMi|ooZ;+=1=`DTul zYcdV5+sfV`!K#Q#q4-}2!(<5f^g8|pmdGpM3#9+-i^5?9>#2ZcPgBzFJ(mz|K=N>* zI{6n0L^k_^{QUB#K5URBxN?z8eO(x?Fk=MwWUV4*KYevRjso$E{K(MmMU$;?4)ahZ zLp)8D87&@C9wx^Zso#i1f>cBw0`Tc#EKFbJ0h+DE> zC!Kd@QTdlQuC%bcz5P=JhRe>G^tw@Xq0~U;=qO3*^;UNPE`FMF+Qv1u*VAi5;m`9* zu;O#mypsI;%}qy{ws!W0*v#}XN9)$aTHDjiO%+~R_R@1_^TtzI%h-a?pM_>wH@$p( zYPIFXfa2oQIjg7SJZD`eYJ9;MC@Aa#l$%IMNBK(R#GqQ*zQiCsBT642hL z_tfGdD>$hjkN8VUNd4pED<(*aWv=tZ`OTAUdMt*^mPFITv$L)3aYeb!E4Vp10G5r! zi)Cep zlt0A_im1(*1-d~;vzohL=IgV&`{T>)R-HebN5=%<;^JmXqNv3cSz6{|A>#^*u(A9O z+=S;QiqSNh$H4B4={FhZzHI7(u9OZ{2|uq$jj4T~F1duz&^WG<3|KI?xbCBW{$@az zq7oE&dX1S`Kuny8jqPb|H5$F15TE&wo|d*xoC)o&%k$n2tzB+*RGW`YLBW?bHEo*6 zeo)uf|DKwvDIpAJ=iXRXl-NXVBa}lrV9m+<(q{ilFE9#U(7Ru{Fd|h@IhwNIDvnCwbOQ= zjAnYuG9)cGx2CPF?L@5&CVo8TfKaId!RL;OpeI+D>SAMW|CA}Lt+g7wG4M^oN_9!!@^X48aV(04c@%Fa)3C@r@n1^qP@%n7@oT;_FTVjlWQzxAk z3`zx@DglDM9-Oo~>$zwjW)i6$5fkjl|mYbir4VAtm7_65Lkz+43}hzm}Q7l_Z$1 zWODPfDUz_Wv8fA$b2P%bp=Ip!21PpeskOgDa#Atz^ynui_se0W#k=cZSxd88(6`!Y z0Co26QsK}I_ehrtYqkD+{m{g7eOX!hQ9CVcEQe9=a)IFQ?;jkWlVd0r-quFMsW>w; zlbmfT&6EbYxhX5FP$P{*KLmq|CarXJGZNb)1F(jFpIeQ1?D1%7%q6F3Eijny^DR}B zmtWku&&~?-@bX5pqWnG7PvLxa+x-={vP*w_OcZML#n#eM4v~<6U}!?v*4WrkIUaH{ z4*o0}00##*1L(0}9vWBV=3;$|9(8omW%(}Xf*t@k*vNZg9Mf8p15yREq^AaB2zAF^ z$j{7qtS&9t+}=+T#Yi=n=%T_DWai>H1zU&Vn7Ny$WJPLn4LWjmayFR?-q}&s(=*al z-P_udD4EXYjcuhBK0dl%9Ucyd*7;+ViV*?q>}nA^9(JIc`uYo_fV=B0NyA^zpyJ|7 zRMabXx15dugR@o7b#6}1!2!GW2jSAGLpQXT^e}Yt+b7RWfAL=Yg$0J_sHpL=CkEl} zkKX-$yx>}}+x;kiUOCHhL*_rM=kL8#N8$mRy#UJaM zK^1@C<|8%0dY#TbAJ%b`l_`C2Ki}9$Xq`(!9USa{$i>QvjH=yzv8z>8$-v3UX&ub< zxFLqEY= z@NVfY%wk=tPZ18Bn#ySW_?eS3xYs#R)_gqZx~A}4B^n;WtS>UE|)e0ZZh%j&Vv>k zq#pC$2C-)HWd)BCpt^I_tyE!BO00`83kl`b)(*|M8M$_Do8J8f$J(8ouOh`lg#e`uH!Jtq^gG@A1k0XmP z=~I`6%JzNg`}ydBIA#&e)TkE?%^f;n{nj_01Bfq~2Xz0(=yupT^z6kH*_UKp69aEa zgD3wZ2?>dI&GD`$HcZUPz<(~i{vY!t_IQr5(Gf?lJ2d9*&0X%WN7x@MOOupgG0qnc zQY?XvO#Jm?gbp*dvWc;UY$o@2w$QhSDPN?G|;W? za_qH(S~^a%FHBkby2OTRJOVT z|3GN#+VCK+xLTW14vY)$m^>d}m|ws0CsAG>d>ip^BC~H6mqkhLQngHQ;=uyl0kCu! z=sN>ExE?>6%4tD-xl`7y#u{Jy-oFWxk}z|*7?jRj-a2nt_gqeRm-f9II=63x8Z}hY z{UjtzzPM%iHU8gzOl*{gK39ImA$-Gf%t9K@t`Xt-se$?J$L0#tCUpxNzkv_76Dg_s zkm;++2_x?7Ubmuo(>iTKFX-@A{-sCz*lGKdW{uPf8Es}s6Pk9MsWF{j6gQv5C2{(UZ^Ic|49X&lY zBe*}<$9M7g3p;P(_FOmOXJ<>c^qH8M#=GP;vAoH>xf0U1dd8cLgK~e7X{p-*&>s3wBy zr*^qVCno08;NvMN#l=E{QIQC-*b4O~;!j9gS8_F+Tj`jz`ghA)^l{TFwp?bHJN0p4 z@L!8P1ppCA>`VOryVI}%&~r06d-oAPCWW(@&-7t2^`t4PBinPq8)2d$0QSPfo~Z4P z4&L!)Uv=e;uSj{Ov*l+kR(GAUbEkQbczO4M4B^gIvr@~K=L%X_ujgPjC?va}B|`EA zv4-Wu6`FNFzF7iOFO6F4I9NSG>12rco6$@`+AKW%PSCgmdt`xI;=det%Ke8 zJ_U_XpECU@TB1;PimrFy>6Bbrh|#~K{rXF9?CLw%k^_R+F1;X_xOXzy=yi7+O+?Ig zc)oMqCao^B^sG6bc@#~Ct@^Ntch2uSO%BLcl3P!2pcMA<035%D(PREP%qg#_%i}T! zRzsi4N#-Z}U|0Jx6IMcC?U(R2J?0-$wU~CViGNCo_epK#@h~rTP9B}4Uiwp6wI2vs z{SNM1_D&q`$XcfvlqJ73lun{auNwRBR-IO%QZuZq97;ziaiI ze4t|pI9-M4z}o)f+m=6mzyQU?Z4U!@W#Qoj#r(4#nNtB428Qx=4J1W2M2`ff_5d^B z5R3>JUh(>6@Pu$PBQ>vUHJ6ZZVB@e`>~(x=gX?ajWxj~P9 znR|LR%K-!ZcHP4^v>_w2kk{)C4&XOIn*_c<1}?ogSX=i=Cnp<-i4^Cb_JawieIU7E zIFYQ#{a@E!K8;ST*e%_(uiCipC7^j1=1DR*yPmt zXJ;}YA^g1KawT$l@z=fe-5q(8eYMgLU-{gB!J!AIkXL8n7Bww{H8h^bNA!5lblfhd zFD|trj6OlqYIH6unwpxveWSQ}&&fehqM%k(M2)SYJ|oZmM>Le;iDsc@966Y;MftV1 z?I!you*Jm16|>p+^a>~YoU(59oJsJpiT&^8-}#PbM@P@N_`_prXpuKB`lzbzqxq#C zVovuxhm%D{W4z95ynf}Y$RHzwMDF(yC^xoL_Y^X&DA#vo?PkyYXPgC~Q5v*eQDffS z-POOiyT5PyGTuUt5@p(DcJ7#%nzOQE$LHF1?&MvhNnD^DO?b;7)Fo7{!*jw%?yB3y z(QLL(pTJJW=d|&0?-4}Ge^X2LFX4)bsg}`TPex$V*5sJ5WC}lbcpuGtFbOK{f*v)+ z^AtmOc7``AcgI_tO=pjQon2dJo9_+@%NPW{>D)7ulJ2FmTjJn6?g3}>?$Qq*p7jTO zYQusB0U%0abR=IO){?j?9WE=YD>9cOsgT{m!H>yyPh9xdL!22_mbCu2#_pRroe>e| zKK>y;o$N;D6hN)V@d*iSuKQ%|t2yx)gC=kpMbC*zod60E!?P7f*t%(M$t9qt@4OP> z-}RLD;#B!P`Pp={$r=?o!-luPi*^C$Mfq#&@KLU=SG%}}hjA&%$wJpj<~lljmS4@- z(wDm>lCu)c8m{~Lg#}Fd!X4kXPcn4ILFUZoXCka%4Z)MsvXQm$(~q3BS-p6a;v#`{ z-{z3fsT;zIb+%;-OYF&p#z5-)Zd+Hp?B5MJFP(RPrHrI8$C2Dy7lmW7@h*@Ztr*Rk3W{3|#{6bA0^u8gJT8(ihQh zY&M(TIa%Tt0T4Z=z?JpZQwWWxOECxrby8S;eF1oZB=Rc$e>hoph=@tT(Ge+6eu{jf z&VgEC^&-9+J>DrSm>;l0x%n2!6Uco+nke#~WEYVj)Y$9SEnS|Iy9gE%Qg5v$F`5Vr z=2cS>KqQc&XbTX*ij)Z628m#W_mhlF;{EmAizrjxpFi*gy)m3f&_j;l0qL@$h?52f zfZ(CW*`+PjrB^VZSi!}aq2#Hq@12Ms;;R&sThVz?8d0)nK=w6b#syJ7>h-8^fL@27 zbd9z68+1Yev$Oc0;%^-3%+6k$)JcEhbK`tw;8Wmz2JxB4liPRfiCid!^LduG9DU4F zG6R0Rum5pn{v93$3TMzAmD-o{Xafd4>rg3-g{VnGYbm{qWeF%HjdLu(Ppa*(ne_VRkRN7it-e6eZ zcv_G4F{#m*7KvYGL8eC$%OjCyUolY-n+7Msj4p%?hLBLw2~eNYS#z+8z?%Q%TSeN( zR|7BpM-O7=g#SvZUYn0y^ATEs|aI)-;2f!Jq~8{&L5ZKkGk)2_-a*>|6^zxE*U zJ|(zPS5a@EJm0du_f$zsB4p~vd*?rDWL+u3*D9FSP)EhB#UBrFS;YOM-T=X_$P>O# z@U`C1q77w1r7UbOHrzSkD>S#8!b^c)^G12%@FDr5hD7x7h?J~4grBWV?;~criZ!DY zVbIz=PBU*(R|t$;8j<|xky2r`k>)|$%Wn07U{1e)1&t!q&i&XQ-_5D{Jvi}kuwwb0 zAg05PLhmv+=RH7X*WD1v<>y=RA=B}%KP$gOfiUh7R?0S zWJ{LBX*Y}IC+7}744E82x;Z}t90x^<{PqB;o3DlW#}IKhdhUd#yd1&D7L)Y=xm>4{ zrF(Oe`i5tDY}z%|g9I~rC4-HrPK-q3+{wBhub7Plo|8_)BF91F^UYR*ubxbDwM#Pi zqZ+m8J-gQRm9024I`;ef9Q}d$$`HR+{ zNR2tecjN>dAh(z8#XG-JuwBx=U^;m{Sr=wi$dtk_Yy~Z5ftH0~8{LV!isp@(d1S>l z+}2Kews}P<8a;|B{x>m&Aq-ZjeCL457K#1iE@HX3wt&&?07+zRZYw#2{!2k4` z;wA9Kl=>^x-u*De&ike(jfZu(SlVnk{Vgs>WrNCX6iWWebkX9`H;ZNNAp_*_Jbzbmj$i1jTTSwmt1 zRA#?X5PZv+ExaAuKfPTbrnR2O$TwEU0RH)a+#n&?qKk>kce$pX*GKK9P)_Az@dG^U z&{f`5%vCqacs<>5AbsMlR&i?w7`)DL%opdI2O8+U#g(+EzR=@axAoY`5`KbCq3_1x zJl`Gi8+6QCnN)GPI_4_VMJ#%)gYS5wKh+&P@TRgGfSe%^gA9l&2>bZpBXE(@JS>@1 z)kE!rIevpPco$}_i?T2;lz>>W)J!hvV^;Z$Z?ES#J?Cdg^&5s?$|MZ^UJ58<7PL_w zTYpq;e0bMcY{1~!K&U__%AN31M#Iodq0QUL~$0*#7?h>+DCjbGLnVI}im+)-P-PITP0JO}=j)_;a9S-> zm^1h-mkT(3AzG)4w7G-?bpibfAGi@}S{iFq)E>9=Di^mpDVT1~AHp{O9wV$}Usz{( z=r5W>yFe>CGLCiy&TDf4@BH~CxEgk6==pourg~20@MWpjg(Wg?+-7&-atLcgi3rKl_uWb2wGQr5_bqqNeSYMkMSnSjD8awwo zl>+>PXgP&+^OFaKua#F)5!&d+vjbd}hHcyNx~~NUNj~rwg5vANe>war1l=#U``8kL znGO6=(D)N(Y;`@|v#UjW>nfF0dqTD%@1NGc`npv~EBQ52RaA6Xc!kNC=)~^w#4e1c z76+|+rfN=xs&I7alxh=?l>8+_?Kz=Pun!C#i)bbu3C0S7mnVRWF^&+VTSsg1WMdj`WVb=HNL%vu_9p>ZLpmmh|2K_lt z`39DtQv{P}KJVDmv2*EqLo))bHkuSi^Ea7?2|=ewl<(ZrJFPqGDjuxna(PoxMu5lf z`A6co#QYXM@E8aTx*YAa4(0}AH|iP)m?J3(Q{6bB%XT*wd_SsaCpRUaLPyDgJW6$a zD=2Te2G(6ixSODRw#olR;mG{mOaID#It+BN$&~_Z-ywpZ@yG3Iu3VW#6B`Q;ge8MQ zBh)$%F^_MO)WzVsJ5bPdL+jRCqN!mVY+C=LSym)m1j5wwLTme1ni`(i?dK+CnR8tg05T?sMNv*(J5Z)%XXeXAnl&V=|vF%~OU%wi4%V?33y(vZ2U8B<9~-d z$34DdCTwu5(-(5G)>%ntWv||2=%AZ8p_N*{!;GPP(RfaS!G`f7^hn z({9lko}8QtyV7^;V)Z)v#+!clVK`|vt!1zeX`3EE|&)otYZ)HF0+WcvMsgG8urCW0y!oLSe_)}qpO zqDk55xaKkh>{Wr`I*fZ{un?h*9~&UM%y0sj*U-KRv`}w463fW{SHbrnbu}`jbtE;= z3Hm76Y3PVd^xI#_4^?QDjdul~%0SUlODoOINJ3I_$rHDDOy0o(YJZukWHCBgTJfO5 zctbJp;{}JP%KydbCG*?H=5@RT zgZ9YN?%tWHi<0i{^#@)#bn>OA$uM40Qqn+AsWMR&-z#=2Rk&#hgV0T+vXnhUc z(h_tHTDI!1)n`U@cJn3Leic zz5Y883n`wA5D>FpGVr8VFE!m1|d_x=dU2Szjf#;mw2i> zOsW7$b&ya%yasYJb=YLzoR*w?2f1{#vm?SGVxwo29E(azOIswVtPcKHPva;wv;qni zwcUfz)2ew$Vry%SWg^a{OuuDN24hU^Uj%-UK%@*_iMz4(gT)a2@sj+{6{g;OzC!oi zC!5(xfu1LIFpD}QN-#EN=Ou4Kj+1xm%}Z5fP!Mfui9~-h=`!;ihxk>;|1XpEt;Z+j*`oH=;~Pq4frv9BVvW7vE7;^V=E02`nn=Bjqmba}{2N!;YO zrxmg80l^>8^a!1j(DmKf&PLlcxb1Ho_L>f`Bc&8iyxAuB{i#%F{0^WY z83Ism+nxx_$M&~`BCx7az0+;fH2SRn47$j&6B`mv3|x8c zrEHrLKH$HK<3;)-3$(BCQ*67hK2xyMN|#JewsuX=tYqaW|4v|~qoZfW!<$l>h{lTw zyno31dLS7bPnv80a6IpOAS?Xs^!MpfXF<#Na76-GZZ&^G>V%c`n)auRQ{u1;95VoJ zu*lL}aw5HA{`hh{i!%5zKAA({@?(XL0T1}^G7)HItB~5>sT5$YR z*jq64Mz3MCyqv3lFl;=Blip`@-naYsz^>n6)&{%l-e%yzl~#IGpRj#XQvuDtEXSDe zX0@z-C%KS`7B>x#x&+Y4uQ9+jvE4BI59bd|f>Q+{S2a=iM@L6ys)x&tohdZJGKlBr zH%w}DQL?h~Vu|}Z;lW}F%3e1;cM~3d=NEF!Rr(z*d3DCCsf_N|*KU~)uVnz{vgYTr z6@^KfRa^>8=vR=_i}k7uBF3u2#zVK+#zr51e#*kO;^NkZVy&9Vnb~!<&VLj?nrzjC#n%=b7lMMPp+*Jci2Z&dv`)IbB{gUM;?%!&LH$Y>V&Z z@isS4WaeSo+QNS3I5E9*vF_s?L&j;N`tkSRx%0KnwXK5#)A)Gn-Q5})nNO$CGib30 zr2vUC3=8|o&%e3SNC^??ENy6Se3)@4m)D+ebvU|Wq{7i_j<%@Ss{`8hp^VKbivs{) z=S~yaaULF<=yrvi2t!WEkFTgG`wro0i)1Ac)7ell>5BCRmB*cJT+JJxfSzzv#8S{eA_^NVpa^r{74ZS@nbhBS43kZdT#q+`=!{gr| zloXfL=(b%w?v7OHf(w6$hyl}8d1V=5pB^WtYziH>a9~zk9Ps@PkK*zi z*VLNf!F!IUA+Zu4U$5WU42X$95Ev9|UYPdi(kNdR{gtVJ-hwCg_(m=+&(;wivGU+R z=p2b(`0mlQNS4}-Kv!3;LpbMK#8!Rv zD`&qf)gl*H_iH!L2_b*J8j8UFMCxLG(WmZ;V>#wngIQjVoU78=SD!zfn7tgSXBR6W z6Z8PJl|P~>F>B$cRWtF^rl){S-!zS5*&ixvL-85T#w$;pkN$O3sMkBlsvlq=xJQ3m@yFQdKg%qfnyw_C$W zuu`U`fnH`#3L#|!FpeP9+1W!V;e)W}rkJtJuqOgkO3UX*AS^z&ap@uqTx4!uGLBQZ z-nJ`i9U(&DRNKIBN@Q%K|jUy4}*H?{tDt^yXGNLk`rC#Uh0~>iD`2D z?vL)b>=`O%Y;KM#;%{0n=_Df3MisT)vhnGMX4MTC3ER$Y6!JQQ^PSyh7B509iMON5 z6?yj}yW@@Tbdl=4I=cW_&85*6hedSXgc1W5ybNH#dB4M&)8je=HdC)!A!zq8t;{{m z+#|-ABprvpKx-*{P`%AC%LrF zBqeiJ!D5uTbZ0sgF5aJ^unj0d3CzL<&5s3RZdbyYl-Im{RFi5;;TTj{$nAFODjJO& zK?JGhup#2G4gT%T4f@j1SB!J7f#-pS@0c%MpweqcO*-{k^o5s(1E0^g?d|O!x={W& zcwSQq7R6`5V)E~!F=2L(;te22ZuqTvRaZ)*)9Ss)>faX42OBHb_@HJ3X``XV!N1kv z?@_oD**dtO>fDij?Nk{0;7_)x$NElcX$uv}638RoLx?pVe}9QOy!3MbmQ4QINmCpA z{Ah^$$149v{`129_kd7i@&I}SSQT;9*lmJPQa3TI$o`aFSw7hI8N~cgFDZW^%NYa#Y!}8s=`ld1; zw<3F^FK27-oJ4D1MJ2YMP3Z#=bF%!~CzT)g@8=>ozhJ?A?gMc10|fZ>(0#tQnMSXl zp62@IJ^;A2o^P|(cX=VN{tIKw|Lb4vvnGO7A2X=nZ)|;ivc)uJKW=Ais)*aAym9m) zvrU9ohchW=l{;pCVpbz}%9@||WDz=#ff($7HC*^R7hVC`*z}(#KYT`;!frR8FKQj* z%I4)$Kv?!uc3V=U{T&|eC6Mzd`P{yw$OUsc{Ppl#^u&g>vIpz5yUO)4*OL(##sBRk5|?zQ5xGNO2l+z!=VVVkV5BSJ?PHf?$0-5l)Zp= zYR}O9+Ib^mxVQMR+wu%}D1)KrP8tIGxaeB_y&C6Ev(kPoM@priZqEv{G=jp=8^J0y z)ek=|uE%J{>$int7c8%W7wc#}@NAYj>_EovTnO2?1a@v9_7WVwl*rO@R2xm7$BOYh z9uH}S10Uu(9-+^;c`Pheeo_Ndx3}6JkIrgFH4Y9ADrH!UFZcj%z=QbTlzHOy?m)kn z&yClo>Cpp%r`5v|N*{*wEq7b`(CBYpz0AA-6xdJi(BUQ9-O^*mkg(h2t5V+Fm?4l- zo763LeKB}I_Skh;JJ{aN?&7%Y36b(Rb5`^Iel%URyg>GEA2A_KtSGT-QP=l8JC532 z$5QDXEBEbulN&Gc8A!kvsCkt*-#q3!xUecN(V*FN$j)me{p+GD4ikPnJfqopH(X`v zct3-#8QiK{Kqla4b^r4*HObxZ=gSk*&Oj@>JD+Kf{&p09aoQ^@_Y$az+r?#2K4lPo zVAtVHHB~{^K7O~-yL=-H^F9WdwVX%(o2Z-s2`Uw<1G9Q+N@I(K5fyXu-Jd^2EgE=f zX=}DJWs`<+NJ(3df^eCbm?Yfprc&aBmYmMFs3RlG z87%@hDjDfDpo5s?7JTJSYP9Dj?kc}ZC%HYIm}~L&MwFn6IQ?2SESp`t?PA_2z~D8U z!-%y$X^HMfO~m%zcviKpnbzn>+ON(~nN_~7c8^9~1&Q_C*5BH?{av%0Q%gP?CC7;) z{t@qeR|ouk(MuDt@Qb=sR8?=F*B&EO)8jLfv;qpZ0+RMeRiq?> z;|Mm}OeFWd4>@^Y{m~Vlaejbam|3GPN~$^SzCS@)4pv&u)T;CT<7UC2C+O6sySErM zD>Ts*exyF4bl1#Y^#lRiqJPd`WvThW9-QM@e}6I*ljg%+#+E1oiSm+oP;;^5ndZa3&-h*omL z;^LpZJ&Ph#`KO9=_K|n9Jd%BK33)#oOiLIu@G;19ggrm-3pve%(ciEC()Gz|&=wf% zwpuQ*ZA5$OoJR+nI4V6vMS(`hU+0&2r=#aqSD#c?3faa;6_k`nPv$+br9Y&zH)>Wi zJ_02b6xLnhP8hB8YG{B(i3su*w3G}sKl0!Fo!#b2 zqTR3&pK>OXJh)t^X5GS%4Nd@e4Gpp3?gI(CpPSDYLOU5~Tm$3!owGt7(7B$Uim576 z>;=^lhF2!alycQ_4U_MsqzCQ0z&*Y`J`unprj&)PGV#oA-7d zAI%ItZtzfLu!h5?0)xRPC#nsLb}R&Y6M+gvvK_7^t|wWAJw5rmr{<_Ut*3Gk z@-*1|a9=1CzXmpV2G%L07?TXPXE%H}0xj~|Pti@~ncA2DvnoTWq!RiHscFP;*brO| zw5cw8%*O3LhX?lHVt7)%OL*-V9G>CCn*Q_aH!T|?ssRb=L^K;5T6+T&e!Zp398(*} zE7{L{5w^{zr>%$H4@}_2F%I2k~HCc%8<$JeB!#E_F!e6XFPjScFXw9Y2~L1IU`fNsZi{)e!zxBzJXq&i!p z#aLT)^}&EbmF0M!!nB<>$(fI!B)6}CRPUAuRRReSkzpNd2{bIGTjT5}={yM=A>Zvts?3Ine??+-&7D-ign*TJr@6i7$Qw}?DCABpZATVIOPW)n5 zqrq}NHqB_AvBp8ZezDRzBcpgY;2G*dE%(1Z;ljxY*T?T{fDsw`n6I7o@JCgSl(^qywhh#F<$5!Y4(e-4GF-nw{}<49AjqI2kma|0<~CS^g)`_y6a8>nUhcxuE%3C*?6i$9 T-~I|zejy{NC{Zo;@yq`Ka+V8$ literal 0 HcmV?d00001 diff --git a/testdata/snapshots/darwin/form_field-dark.png b/testdata/snapshots/darwin/form_field-dark.png new file mode 100644 index 0000000000000000000000000000000000000000..d6b2a7e3d7c863a2fb6fc01922a2a230245ad886 GIT binary patch literal 12502 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 literal 0 HcmV?d00001 diff --git a/testdata/snapshots/darwin/pin_input-light.png b/testdata/snapshots/darwin/pin_input-light.png new file mode 100644 index 0000000000000000000000000000000000000000..32a9913561606a30aef786cdb203b6e39d4b0f3f GIT binary patch literal 3362 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 N%LZ%@!kNPp{|=mxXgmM_ literal 0 HcmV?d00001 diff --git a/testdata/snapshots/darwin/rating-dark.png b/testdata/snapshots/darwin/rating-dark.png new file mode 100644 index 0000000000000000000000000000000000000000..7c28431c5ade9f0e722ee201099c683b4fa9eddf GIT binary patch literal 11275 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 U#Gf1h|Jh4kT1Bcx!Zh^%0Cx;A)c^nh literal 0 HcmV?d00001 diff --git a/testdata/snapshots/darwin/rating-light.png b/testdata/snapshots/darwin/rating-light.png new file mode 100644 index 0000000000000000000000000000000000000000..cf87bd9ead2df62cdd90df92f3570cb6eb17e0a8 GIT binary patch literal 11156 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

nCx`p4M!5{bLecjs`ymsOJFx|gFVf1f# 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

-yUu% zI77aVnvF(fyzX{~5QGv&_lt;c+#C}l1yg;IwnY->jf&ZJY0eTR)QNG17n(0YdM5;%KwV{CuVZ`D zh6;yRAA2fp&&hh=dftn6N0j!uaN ze9y&50RsH)TWWb6te$+H%PtRlQ@?(?rtVqkaNBl||NNpv@)YMM@MnBfQ zhM7)?-{>&Q2vTtY08Ys3pFEEVPLHE7b0>?CW6As-7vE;=>UoGpYx*3eQ4v((zPL&L zrV5*$nYkWL<04)&7UE)kPr1GSw`t>Q{P0k_Mwi*;@0VB|>dT88%j!kb?;OZ?r_(m| z3cgR>##iY?#Ov$l9|yXEyquOS0G|VYz3lA!JmOYluWwp9niJqL5+qdFa0#es>)6lz zaSy!Ob>YC+LPiGa)M`Oj7i~7f-Ny%3qg!5a zdSOnpC;YcTg>jwxIWv-hBo{uc#{SzJ}5LE z{*G+8rF%u-N^ma`NsD`5Uk@#Cl=!o^T9V%AW|cF^_~=*~zdImFPh_R9FBeXnp5o^^ zszj0=DHo7vPE zGf80A_?gx(kWyjn<*nABMTJ8}dU`r;sW#f(F;|)bTG~kLfCS2FMUhF3&cJ)JL*WY# zOPcDCpBZ67MKFxt(8g=gB#_`k&G87X%YB%bm}-b8mm6=&d!ti@up5kImB$DFEV1C6 z;-Puc*aIDGDA5q#XLM0m9WFB8296s4?X*hxdPf$Cw)*ualD$NLgM*#jQD5RrBt!AG zxZ50biWo>#(b81LA=Y&82(eT%234 zt{x~>y+i7G`J8TLEz=h}&J+g#hY3JsyM2d}qe9P^uf~wj*YVo2Xzu_7)>|$taisMM zpg_1C$}+-y2eOfl?x+6)rJwWJ-FS`3PxDK)Gcq!O_CG3{+chtVW3~*~9P=g@rlu|d z_QxYlO};lbk=o3k!ReP{@%bpfE>tI>$eT<^tOzWJ50iL5Cm#juuicApEj79Ld)#ah z*zxUkEj2O0y(fD-aO;GoSLrEn>=)#zbjDWOv+;Sw0Ai$EIQ>x^%mW*0%Ie_2m>*99 zlQfLGZKd6tOALKMZvUK`x-)+;mMtPf<}JyN;dx0FPfMCGy8#7dQ z_hH+dV0T~(3MUgb?(MLzCttn!1qDX4K99R3E0yMZOd~pIhb(JucxW~my3;#qM`NKl zVLVw3r}X9L6Q-pZlk>B`=F4Bgsb^aMszQ!ug$!4vhJ-OAJ7|_X5|L3nKT(MWwle8pY#-? z%YJWrSnhCabcvFIeXBxH5xmWup#wE--2s;eSjF5LVr3FuKGC^q#Ub9_uYP8yP_KZV z&)DLanojN#LkU6a-?j*^Flh2BY|(SOS)zM;`_eUP;3A8HaM>0n_%q?v;7C+=}~+3^SAcuTVICLC+KZ3ww{ln3Ku zy%gf!5ah|F^4Wn6@Urz?`=x(Oa`79k1|KhTHnT$h)# zGzD$Hu2)0n= zQSjLfxlpvHLl~NdLH)c(9W9t=IJv1!)Ri2<0h*?tyKPW5p@*L1@#f{yi5zz;cn7wb zKAZlBDd$WVvZvR-r-uKLA^z8!br@{n6hjOqO1?FH*pFQUm_tMWUlf6?HnM~!= zFHlYvKg@a4&bGHXL<|OFI-V=2PoEDq$31zqG&7=r7Uch&oC;;;kM6&Ax=6#H&wHzU z!V>up0skfCH=fX^xqoE(SC;xe=eB{`b54GHWC8=df>Jx&W7!=DsF2Jk{(t*#(LiVu z0c&Wr_P{?!1Yx?L%cu7V`Z{5d*uTMIn83gj{7-tsCm@1pWgb=bm7070M;bXPCCLhL Hlkfiqylx?b literal 0 HcmV?d00001 diff --git a/testdata/snapshots/darwin/transfer-dark.png b/testdata/snapshots/darwin/transfer-dark.png new file mode 100644 index 0000000000000000000000000000000000000000..c9c50e080fe3deb5da009f924e328f449b433732 GIT binary patch literal 15519 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 @Yllf+` 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 0 + left := transferCount(st, chosen) > 0 + if Button(c, "", ButtonOptions{Variant: Secondary, Icon: icons.Must("chevron-right"), Label: fmt.Sprintf(msg(c, "transfer.to"), dstTitle), Disabled: !right}).Clicked() { + move(source, true) + } + if Button(c, "", ButtonOptions{Variant: Secondary, Icon: icons.Must("chevron-left"), Label: fmt.Sprintf(msg(c, "transfer.to"), srcTitle), Disabled: !left}).Clicked() { + move(chosen, false) + } + }) + transferPanel(c, st, 1, dstTitle, chosen, height) + }) + return parts +} + +// transferCount counts the checked items that can move. +func transferCount(st *transferState, items []TransferItem) int { + n := 0 + for _, it := range items { + if st.checked[it.ID] && !it.Disabled { + n++ + } + } + return n +} + +// transferPanel is one side: header with select-all, search, and the list. +func transferPanel(c *ui.Context, st *transferState, side int, title string, items []TransferItem, height float32) { + k := Tokens(c) + d := Density(c) + q := strings.ToLower(strings.TrimSpace(st.query[side])) + var shown []TransferItem + enabled := 0 + for _, it := range items { + if q == "" || strings.Contains(strings.ToLower(it.Label), q) { + shown = append(shown, it) + if !it.Disabled { + enabled++ + } + } + } + checked := transferCount(st, shown) + ui.Column(c).Width(240).Radius(theme.CardRadius).Background(k.Surface).Border(theme.BorderWidth, k.Border).Children(func() { + head := ui.Row(c).Gap(d.Unit()*2).AlignItems(ui.Center).MinHeight(d.ControlHeight()).Padding(0, d.Unit()*3). + BorderWidth(0, 0, theme.BorderWidth, 0).BorderColor(k.Border) + head.Children(func() { + all := checked > 0 && checked == enabled + box := ui.CheckboxBase(c, &all).Label(fmt.Sprintf(msg(c, "transfer.all"), title)).Disabled(enabled == 0) + transferMark(c, box, all, checked > 0 && !all) + if box.Changed() { + for _, it := range shown { + if !it.Disabled { + st.checked[it.ID] = all + } + } + } + ui.Text(c, title).Font(theme.SerifFont).FontWeight(600).TextColor(k.Text).Grow(1) + ui.Text(c, fmt.Sprintf("%d / %d", checked, len(items))).TextColor(k.TextMuted).FontSize(theme.CaptionSize).FontFeatures("tnum") + }) + ui.Box(c).Padding(d.Unit() * 2).Children(func() { + InputGroup(c, &st.query[side], InputGroupOptions{Placeholder: msg(c, "transfer.search"), Label: fmt.Sprintf(msg(c, "transfer.search_in"), title)}) + }) + list := ui.Scroll(c).Height(height).Padding(0, d.Unit(), d.Unit(), d.Unit()).FocusGroup(ui.Vertical).Role(ui.RoleList).Label(title) + list.Children(func() { + if len(shown) == 0 { + empty := msg(c, "transfer.empty") + if q != "" { + empty = msg(c, "transfer.no_match") + } + ui.Text(c, empty).TextColor(k.TextMuted).Padding(d.Unit() * 3).TextAlign(ui.Center).FillWidth() + return + } + for _, it := range shown { + on := st.checked[it.ID] + ui.Box(c).Key(it.ID).Children(func() { + cb := ui.CheckboxBase(c, &on).Gap(d.Unit()*2).MinHeight(d.ControlHeight()-d.Unit()).Padding(0, d.Unit()*2). + Radius(theme.ControlRadius).FillWidth().Disabled(it.Disabled) + if cb.Changed() { + st.checked[it.ID] = on + } + if on { + cb.Background(k.Selection) + } else if cb.Hovered() { + cb.Background(k.SurfaceHover) + } + transferMark(c, cb, on, false) + cb.Children(func() { ui.Text(c, it.Label).SingleLine().Ellipsis("…").MinWidth(0) }) + }) + } + }) + }) +} + +// transferMark draws a check box's square inside box, which takes the focus ring. +func transferMark(c *ui.Context, box *ui.Element, on, mixed bool) { + k := Tokens(c) + internal.Ring(box, k.Focus, theme.ControlRadius) + box.Children(func() { + sq := ui.Box(c).Size(16, 16).Shrink(0).Center().Radius(theme.ControlRadius).Role(ui.RoleNone) + if on || mixed { + sq.Background(k.Accent).TextColor(k.OnAccent) + } else { + sq.Background(k.Surface).Border(theme.BorderWidth, k.ControlBorder) + } + sq.Children(func() { + switch { + case on: + ui.Icon(c, icons.Must("check")).Size(12, 12) + case mixed: + ui.Icon(c, icons.Must("minus")).Size(12, 12) + } + }) + }) +} diff --git a/transfer_test.go b/transfer_test.go new file mode 100644 index 0000000..fb366b0 --- /dev/null +++ b/transfer_test.go @@ -0,0 +1,106 @@ +package mujica + +import ( + "slices" + "testing" + + "github.com/egoist/mygo/ui" +) + +var transferCast = []TransferItem{ + {ID: "uika", Label: "Uika"}, {ID: "mutsumi", Label: "Mutsumi"}, {ID: "umiri", Label: "Umiri"}, + {ID: "nyamu", Label: "Nyamu", Disabled: true}, {ID: "sakiko", Label: "Sakiko"}, +} + +func transferTester(target *[]string, items *[]TransferItem, opts TransferOptions, changes *int) *ui.Tester { + return ui.NewTester(func(c *ui.Context) { + Use(c, Settings{}) + if Transfer(c, target, *items, opts).Changed() { + *changes++ + } + }, 640, 420) +} + +func TestTransferMoveSelected(t *testing.T) { + target, items, n := []string{"sakiko"}, slices.Clone(transferCast), 0 + tt := transferTester(&target, &items, TransferOptions{}, &n) + _ = tt.Click("Mutsumi") + _ = tt.Click("Uika") + if err := tt.Click("Move to Chosen"); err != nil { + t.Fatal(err) + } + if !slices.Equal(target, []string{"sakiko", "uika", "mutsumi"}) || n != 1 { + t.Fatalf("target %q changes %d", target, n) + } + _ = tt.Click("Sakiko") + _ = tt.Click("Move to Available") + if !slices.Equal(target, []string{"uika", "mutsumi"}) || n != 2 { + t.Fatalf("back: %q", target) + } +} + +func TestTransferSelectAllSkipsDisabledAndFollowsSearch(t *testing.T) { + target, items, n := []string{}, slices.Clone(transferCast), 0 + tt := transferTester(&target, &items, TransferOptions{}, &n) + _ = tt.Click("Search Available") + tt.Type("u") + if tt.HasText("Sakiko") { + t.Fatal("search did not filter") + } + _ = tt.Click("Select all in Available") + _ = tt.Click("Move to Chosen") + if !slices.Equal(target, []string{"uika", "mutsumi", "umiri"}) { + t.Fatalf("select all moved %q", target) + } + _ = tt.Click("Search Available") + tt.Type("zzz") + if !tt.HasText("No matches") { + t.Fatal("no empty search state") + } +} + +func TestTransferKeyboardAndStableIDs(t *testing.T) { + target, items, n := []string{}, slices.Clone(transferCast), 0 + tt := transferTester(&target, &items, TransferOptions{}, &n) + _ = tt.Click("Umiri") + // Items reorder and relabel; the check stays on the same ID. + items = []TransferItem{{ID: "umiri", Label: "Yahata Umiri"}, {ID: "uika", Label: "Uika"}} + tt.Frame() + tt.Key(0, ui.KeyDown) + tt.Key(0, ui.KeySpace) + _ = tt.Click("Move to Chosen") + if !slices.Equal(target, []string{"umiri", "uika"}) { + t.Fatalf("target %q", target) + } +} + +func TestTransferDisabledAndBadIDs(t *testing.T) { + target, items, n := []string{}, slices.Clone(transferCast), 0 + tt := transferTester(&target, &items, TransferOptions{Disabled: true}, &n) + _ = tt.Click("Uika") + _ = tt.Click("Move to Chosen") + if len(target) != 0 { + t.Fatal("disabled transfer moved") + } + defer func() { + if recover() == nil { + t.Fatal("unknown target ID accepted") + } + }() + bad := []string{"ghost"} + transferTester(&bad, &items, TransferOptions{}, &n) +} + +func TestTransferSnapshot(t *testing.T) { + target := []string{"sakiko", "mutsumi"} + snapshot(t, "transfer", 620, 380, func(c *ui.Context) { + ui.Column(c).Padding(20).Children(func() { + p := Transfer(c, &target, transferCast, TransferOptions{Height: 200}) + st := ui.Local(p.Element, "transfer", func() transferState { return transferState{} }) + if !st.checked["uika"] { + st.checked["uika"] = true + c.Invalidate() + } + }) + }) +}