From e682ed4d399ed6169e82d3a27f3e8a44f87d0bc2 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 14:11:52 -0400 Subject: [PATCH] WIP essentials 4 --- choice_chips.go | 84 ++++++++++++++ choice_chips_test.go | 85 ++++++++++++++ color_palette.go | 109 ++++++++++++++++++ color_palette_test.go | 78 +++++++++++++ messages_essentials.go | 2 + .../snapshots/darwin/choice_chips-dark.png | Bin 0 -> 6174 bytes .../snapshots/darwin/choice_chips-light.png | Bin 0 -> 6140 bytes .../snapshots/darwin/color_palette-dark.png | Bin 0 -> 4352 bytes .../snapshots/darwin/color_palette-light.png | Bin 0 -> 4264 bytes 9 files changed, 358 insertions(+) create mode 100644 choice_chips.go create mode 100644 choice_chips_test.go create mode 100644 color_palette.go create mode 100644 color_palette_test.go create mode 100644 testdata/snapshots/darwin/choice_chips-dark.png create mode 100644 testdata/snapshots/darwin/choice_chips-light.png create mode 100644 testdata/snapshots/darwin/color_palette-dark.png create mode 100644 testdata/snapshots/darwin/color_palette-light.png diff --git a/choice_chips.go b/choice_chips.go new file mode 100644 index 0000000..c979143 --- /dev/null +++ b/choice_chips.go @@ -0,0 +1,84 @@ +package mujica + +import ( + "log/slog" + "slices" + + "github.com/egoist/mygo/ui" + + "mujicaui/icons" + "mujicaui/internal" + "mujicaui/theme" +) + +// ChoiceChipsOptions configure ChoiceChips. +type ChoiceChipsOptions struct { + // Multiple lets several chips be on; otherwise one or none. + Multiple bool + Disabled bool + // Label names the chip set for assistive technology. + Label string +} + +// ChoiceChips are wrapping filter chips toggling values in *value; arrows move between them. +func ChoiceChips[T comparable](c *ui.Context, value *[]T, items []ToggleItem[T], opts ChoiceChipsOptions) ChoiceResult { + if value == nil { + panic("mujica: ChoiceChips needs a value") + } + if !opts.Multiple && len(*value) > 1 { + panic("mujica: single ChoiceChips holds more than one value") + } + k := Tokens(c) + d := Density(c) + g := ui.Row(c).Wrap().Gap(d.Unit()*2).Disabled(opts.Disabled).Role(ui.RoleGroup).FocusGroup(ui.Horizontal) + if opts.Label != "" { + g.Label(opts.Label) + } + changed := false + g.Children(func() { + for _, it := range items { + on := slices.Contains(*value, it.Value) + pressed := on + b := ui.ToggleBase(c, &pressed).Disabled(it.Disabled) + // The base took input before Disabled applied: drop a disabled chip's press. + if b.Changed() && !b.IsDisabled() && toggleGroupApply(value, it.Value, pressed, ToggleGroupOptions{Multiple: opts.Multiple, AllowEmpty: true}) { + changed, on = true, pressed + } + choiceChipLook(c, k, d, b, on, it) + } + }) + if changed { + slog.Debug("mujica: choice_chips changed", "label", opts.Label, "value", *value) + } + return ChoiceResult{g, changed} +} + +func choiceChipLook[T comparable](c *ui.Context, k theme.Tokens, d theme.Density, b *ui.Element, on bool, it ToggleItem[T]) { + b.MinHeight(max(theme.MinHitSize, d.ControlHeight()-4)).MinWidth(theme.MinHitSize).Padding(0, d.Unit()*3).Gap(d.Unit()*1.5). + Radius(theme.PopupRadius) + internal.Ring(b, k.Focus, theme.PopupRadius) + bg, fg, line := k.Surface, k.Text, k.ControlBorder + switch { + case on: + bg, fg, line = k.Selection, k.AccentText, k.AccentText + case b.Pressed(): + bg = k.Selection + case b.Hovered(): + bg = k.SurfaceHover + } + b.Background(bg).TextColor(fg).Border(theme.BorderWidth, line).Transition(colorFade) + if it.Name != "" { + b.Label(it.Name) + } + b.Children(func() { + switch { + case on: + ui.Icon(c, icons.Must("check")).Size(14, 14) + case it.Icon != nil: + ui.Icon(c, it.Icon).Size(14, 14) + } + if it.Label != "" { + ui.Text(c, it.Label).SingleLine() + } + }) +} diff --git a/choice_chips_test.go b/choice_chips_test.go new file mode 100644 index 0000000..531a86a --- /dev/null +++ b/choice_chips_test.go @@ -0,0 +1,85 @@ +package mujica + +import ( + "slices" + "testing" + + "github.com/egoist/mygo/ui" +) + +var chipItems = []ToggleItem[string]{{Value: "open", Label: "Open"}, {Value: "draft", Label: "Draft"}, {Value: "closed", Label: "Closed", Disabled: true}, {Value: "merged", Label: "Merged"}} + +func chipTester(v *[]string, opts *ChoiceChipsOptions, n *int) *ui.Tester { + return ui.NewTester(func(c *ui.Context) { + Use(c, Settings{}) + if ChoiceChips(c, v, chipItems, *opts).Changed() { + *n++ + } + }, 600, 200) +} + +func TestChoiceChipsSingleAndMultiple(t *testing.T) { + var v []string + opts := ChoiceChipsOptions{} + n := 0 + tt := chipTester(&v, &opts, &n) + _ = tt.Click("Open") + _ = tt.Click("Draft") + if !slices.Equal(v, []string{"draft"}) || n != 2 { + t.Fatalf("single: %v %d", v, n) + } + _ = tt.Click("Draft") + if len(v) != 0 { + t.Fatalf("single chip did not turn off: %v", v) + } + _ = tt.Click("Closed") + if len(v) != 0 || n != 3 { + t.Fatalf("disabled chip: %v %d", v, n) + } + opts.Multiple = true + _ = tt.Click("Open") + _ = tt.Click("Merged") + if !slices.Equal(v, []string{"open", "merged"}) { + t.Fatalf("multiple: %v", v) + } +} + +func TestChoiceChipsKeyboard(t *testing.T) { + var v []string + opts := ChoiceChipsOptions{Multiple: true} + n := 0 + tt := chipTester(&v, &opts, &n) + tt.Key(0, ui.KeyTab) + tt.Key(0, ui.KeyRight) + tt.Key(0, ui.KeySpace) + tt.Key(0, ui.KeyRight) // skips the disabled chip + tt.Key(0, ui.KeySpace) + if !slices.Equal(v, []string{"draft", "merged"}) { + t.Fatalf("keyboard: %v", v) + } + opts.Disabled = true + tt.Frame() + _ = tt.Click("Open") + if len(v) != 2 { + t.Fatalf("disabled set changed: %v", v) + } +} + +func TestChoiceChipsRejectsTwoInSingle(t *testing.T) { + defer func() { + if recover() == nil { + t.Fatal("no panic") + } + }() + v := []string{"open", "draft"} + ui.NewTester(func(c *ui.Context) { Use(c, Settings{}); ChoiceChips(c, &v, chipItems, ChoiceChipsOptions{}) }, 300, 100) +} + +func TestChoiceChipsSnapshot(t *testing.T) { + v := []string{"open", "merged"} + snapshot(t, "choice_chips", 480, 80, func(c *ui.Context) { + ui.Row(c).Padding(16).Children(func() { + ChoiceChips(c, &v, chipItems, ChoiceChipsOptions{Multiple: true}) + }) + }) +} diff --git a/color_palette.go b/color_palette.go new file mode 100644 index 0000000..89b0df0 --- /dev/null +++ b/color_palette.go @@ -0,0 +1,109 @@ +package mujica + +import ( + "fmt" + "log/slog" + + "github.com/egoist/mygo/ui" + + "mujicaui/internal" + "mujicaui/theme" +) + +// ColorSwatch is one preset of a ColorPalette; Name is read out. +type ColorSwatch struct { + Color ui.Color + Name string +} + +// ColorPaletteOptions configure a ColorPalette. +type ColorPaletteOptions struct { + // Columns is the grid width in swatches; zero is 8. + Columns int + // Custom adds a ColorPicker after the swatches for any other color. + Custom bool + Disabled bool + // Label names the palette for assistive technology. + Label string +} + +// ColorPalette picks *value from preset swatches in a grid; arrows move in two dimensions. +func ColorPalette(c *ui.Context, value *ui.Color, swatches []ColorSwatch, opts ColorPaletteOptions) ChoiceResult { + if value == nil || len(swatches) == 0 { + panic("mujica: ColorPalette needs a value and swatches") + } + if opts.Columns < 0 { + panic(fmt.Sprintf("mujica: ColorPalette Columns %d is negative", opts.Columns)) + } + cols := opts.Columns + if cols == 0 { + cols = 8 + } + for _, s := range swatches { + if s.Name == "" { + panic("mujica: ColorPalette swatch needs a Name") + } + } + k := Tokens(c) + u := Density(c).Unit() + g := ui.Column(c).Gap(u * 2).Disabled(opts.Disabled).Role(ui.RoleRadioGroup).Label(opts.Label) + disabled := g.IsDisabled() + changed := false + g.Children(func() { + grid := ui.Box(c).Grid().Columns(cols).Gap(u * 1.5).FocusGroup(ui.Horizontal) + var cells []*ui.Element + grid.Children(func() { + for i, s := range swatches { + b := colorPaletteSwatch(c, k, s, *value == s.Color).Key(i) + cells = append(cells, b) + if b.Clicked() && !disabled && *value != s.Color { + *value, changed = s.Color, true + } + } + }) + // Up and Down move a whole row; FocusGroup moves by one. + for i, b := range cells { + switch { + case b.Shortcut(0, ui.KeyDown) && i+cols < len(cells): + cells[i+cols].Focus() + case b.Shortcut(0, ui.KeyUp) && i-cols >= 0: + cells[i-cols].Focus() + } + } + if opts.Custom { + ui.Row(c).Children(func() { + if ColorPicker(c, value, ColorPickerOptions{Label: msg(c, "color_palette.custom"), Disabled: disabled}).Changed() { + changed = true + } + }) + } + }) + if changed { + slog.Debug("mujica: color_palette changed", "color", colorPickerHex(*value)) + } + return ChoiceResult{g, changed} +} + +// colorPaletteSwatch is one square swatch; the chosen one has an inset frame and a check. +func colorPaletteSwatch(c *ui.Context, k theme.Tokens, s ColorSwatch, on bool) *ui.Element { + b := ui.ButtonBase(c).Size(theme.MinHitSize, theme.MinHitSize).Radius(theme.ControlRadius).Role(ui.RoleRadio). + Label(s.Name).Tooltip(s.Name).Selected(on) + internal.Ring(b, k.Focus, theme.ControlRadius) + edge := k.ControlBorder + if b.Hovered() { + edge = k.Text + } + b.Background(s.Color).Border(theme.BorderWidth, edge) + mark := theme.Light().Text + if theme.Contrast(s.Color, mark) < theme.Contrast(s.Color, theme.Light().OnAccent) { + mark = theme.Light().OnAccent + } + b.DrawOver(func(p *ui.Painter, r ui.Rect) { + if !on { + return + } + p.Stroke(ui.Rect{X: r.X + 3, Y: r.Y + 3, W: r.W - 6, H: r.H - 6}, mark, 1, 1.5) + internal.Mark(p, ui.Rect{X: r.X + 7, Y: r.Y + 7, W: r.W - 14, H: r.H - 14}, "check", mark) + }) + return b +} diff --git a/color_palette_test.go b/color_palette_test.go new file mode 100644 index 0000000..93eb283 --- /dev/null +++ b/color_palette_test.go @@ -0,0 +1,78 @@ +package mujica + +import ( + "testing" + + "github.com/egoist/mygo/ui" +) + +func paletteSwatches() []ColorSwatch { + var s []ColorSwatch + for i, c := range colorPickerPresets { + s = append(s, ColorSwatch{Color: c, Name: "Tone " + string(rune('A'+i))}) + } + return s +} + +func TestColorPaletteChooseAndKeyboard(t *testing.T) { + v := colorPickerPresets[0] + dis := false + n := 0 + sw := paletteSwatches() + tt := ui.NewTester(func(c *ui.Context) { + Use(c, Settings{}) + if ColorPalette(c, &v, sw, ColorPaletteOptions{Columns: 4, Custom: true, Disabled: dis, Label: "Tag color"}).Changed() { + n++ + } + }, 500, 400) + if err := tt.Click("Tone C"); err != nil || v != sw[2].Color || n != 1 { + t.Fatalf("click: %v %v %d", err, v, n) + } + tt.Key(0, ui.KeyDown) // focus stays on C: down a row is G + tt.Key(0, ui.KeyEnter) + if v != sw[6].Color { + t.Fatalf("Down then Enter chose %v, want %v", v, sw[6].Color) + } + tt.Key(0, ui.KeyRight) + tt.Key(0, ui.KeyUp) + tt.Key(0, ui.KeySpace) + if v != sw[3].Color { + t.Fatalf("Right, Up, Space chose %v", v) + } + if _, ok := tt.Find("Custom color"); !ok { + t.Fatal("no custom color entry") + } + dis = true + tt.Frame() + _ = tt.Click("Tone A") + if v != sw[3].Color || n != 3 { + t.Fatalf("disabled palette changed: %v %d", v, n) + } +} + +func TestColorPaletteRejects(t *testing.T) { + v := ui.Hex("#000000") + for name, f := range map[string]func(c *ui.Context){ + "no name": func(c *ui.Context) { ColorPalette(c, &v, []ColorSwatch{{Color: v}}, ColorPaletteOptions{}) }, + "columns": func(c *ui.Context) { ColorPalette(c, &v, paletteSwatches(), ColorPaletteOptions{Columns: -1}) }, + "no items": func(c *ui.Context) { ColorPalette(c, &v, nil, ColorPaletteOptions{}) }, + } { + func() { + defer func() { + if recover() == nil { + t.Errorf("%s: no panic", name) + } + }() + ui.NewTester(func(c *ui.Context) { Use(c, Settings{}); f(c) }, 300, 200) + }() + } +} + +func TestColorPaletteSnapshot(t *testing.T) { + v := colorPickerPresets[2] + snapshot(t, "color_palette", 300, 150, func(c *ui.Context) { + ui.Column(c).Padding(16).Children(func() { + ColorPalette(c, &v, paletteSwatches(), ColorPaletteOptions{Columns: 6, Custom: true}) + }) + }) +} diff --git a/messages_essentials.go b/messages_essentials.go index 3943040..fa7fc68 100644 --- a/messages_essentials.go +++ b/messages_essentials.go @@ -14,6 +14,8 @@ func init() { "bulk_action_bar.selected": {"%d selected", "已选 %d 项"}, "bulk_action_bar.clear": {"Clear selection", "清除选择"}, + "color_palette.custom": {"Custom color", "自定义颜色"}, + "qr_code.label": {"QR code", "二维码"}, "qr_code.too_long": {"Text too long for a QR code", "文字过长,无法生成二维码"}, }) diff --git a/testdata/snapshots/darwin/choice_chips-dark.png b/testdata/snapshots/darwin/choice_chips-dark.png new file mode 100644 index 0000000000000000000000000000000000000000..3a10fcf85fab681b092aa0edfc680c3550c41670 GIT binary patch literal 6174 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 ahX7F(UpW8Gq7Rn$2WYA3s#XJSBL4#}C6^Zf literal 0 HcmV?d00001 diff --git a/testdata/snapshots/darwin/choice_chips-light.png b/testdata/snapshots/darwin/choice_chips-light.png new file mode 100644 index 0000000000000000000000000000000000000000..473b5a3fb2dbded889f322fb5cb527e83d95da22 GIT binary patch literal 6140 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 literal 0 HcmV?d00001 diff --git a/testdata/snapshots/darwin/color_palette-dark.png b/testdata/snapshots/darwin/color_palette-dark.png new file mode 100644 index 0000000000000000000000000000000000000000..8c4106ed32f972b0a8df2b9f0f5b9a2b2a7881f6 GIT binary patch literal 4352 zcmcgwXHXMNw?;$_pb$WM4P8M{K&k`?MT$b`B28Kl2^|5cCWH!D2NlyQj-?D^GN=Oiba=~2V6n*4_j_!2OM?knA%o-6dsc8%CaFt(qM zb{t^4!gpWEtZ{IV&+W`i|I%7ro}f(ka}FhB%rxYE;sTU;mG04TkwO0Nq&Y$P<(fcG zA0ja^o5B27O?b?HitR^-Y;H>Bt2PRAbzP36CtoU6UB>fFl>}NPOhy4z=+8Kj=`M^n z{s8-mKaejel(t>n7w-RO6lY;U*XcLoA|WZ^Kt+u)+9`~-(~DSlbzRUG+E;g&+i$7YltgD*do<7tz4QR~Jz1F}Lt@6G=foT(WAXi+--ki-K zKP>+KQ_9LUO3diS(L=AX9?)p&>xl%@H`HUE@9+HDV3(zNoNH{zh_|8gi)0k$OCI0E zV#(hrO->HpPCeyeru>CWfQAv~a=m*%@R2QF7zb)e*CLLZuQh#VCT~K63!YDX0Cv}A zrreVNMuz+v39BA8C4})npx2j~4cQ z0<@_-9ZNo;BY<0)+)!`{gB`02e;A%p>=L*^@KL&(m<};~`ttmaK^JOkn{hcSET*8d zf^{#B;p4XFgv=vHtkfI4}}kGwG^IZ>dX0a}!7k`cGNd zVJCtV)<)W5ks>o z0#B&^;fD{Mcnbs|TZBA1yG>W$=--_k{w$JHn*qA^QB;$%RK zkfoe&O#t^J@BQhFFW=>0kL;Q>!xLOv3Q?5^+Q7SPnhrBYC$Tns)6axJ;!hzS_|g5yENDs^R0Jyr z(o2Kx7hK8r%et2gJ$NA;e_pZOk%U_1{~#k4owv;+1>z)cwbc}T>`eIr{Cp(gKD~+S zX@I}&JzQUq{<~{n2FfUwWHGxm+wOoTSUR=gu8Q`@M)6M^qccy`h4WNK7mN;XXl(8p zWZE84#qcg&_=|FyQ1E3ouMit`Tyg5}Pt41zk1zT0CoTw?^cP#!d~7UCj@wvK-P&7V z(QrO#X{`6UfvUiFr(GV(Cd>mcL+9kNkMjV0LFi=PX?JH2`7AV;C&t&iRDVeFcyE1o z9UvN>6?E`hyOJ;;JF{>Nad1O9&9`w2T7K)oA8F2cYY*8Cz07tGM00hrv}VIt{w;>w zRu%lycKZ2u|9CcwH)7`0(I~sinlg{I#ar~ieBmLYLP~1;<#&ta*95qc3tw`y3}b`^ zYt7hE^={JsnfGgntxD}M*g5ik`(niM{Cb+D$l+d!BAV?H)OFy_9077Sb7 zi?6{e?!S)bE;fy5G!uYZyVrIKm9rL)VDe}`5(I}_WqC!_WfP2e?)wGvw$(R#YVOaZ zp{xXHK02X>_q+SZl~*ZPOLJ)vQBDR%V@x_@e}1(X(L4`Y*L#RL%z^UJ6{S;J6LDb- zHO_hY;Yp45)bU)4nvt~;nNlMAzdg?XJzGrs050uzv@^cS4oJUk@Rt0jnurmMdP_?W zWdMSK%qt@V%XKojy;OM~_eN8PJXs zbT>7;QTl4P*oe4zJrhk5;z8M;^q#P@`lEhn_ko>9Nq#$zsKr44C=2@H7zk$6>dk}K ze$y6-2b
m|d?E%(#XBGb!wll$kOW1%`>>8cvOGZV3Gu#Me`uLgHbwr6cAB~uFt zu}!e@jr9cX57iZ^;fEW!I+!~bLh;oj+Kg&TBv5iBV?EFx`&##$F^Lo`xYQf15v9hDse4^EJ^*v%pFe%Tbx-|I5;TdH&xmo zxAUmfXASw`yCZhY{;~+|Z|kE`|HCTa$0+4rl<=2GSJ1o)D4!j%Lxx2KBh&|vOqT&^ zDmjoL-h>r~54LNT86Jw#xir2t(*AznBiK{sif zZ?CG!^Q_b<^<6WmjeHXRcR7x?{cpmYW=5q>>jyJNwG3+PI{l9_08^dsRudWICvd*) zlo7WQiF>|!YruU|i=h4|5UwkcSv^T@KuGgysBo-dAu6oTzi(Xa zYGViCTc!awa5tciQ%fa8HLL4R2OU_R2^{30tZZX8Ey^6acl2vjpeq?x{ zz`WW}N5>^HRt{tfbDfV#HPnr=*3#tDF&Z?-!*HqjBmL$PNtecoYaH#!lkzs}4EZ3f zVJuc5wGV9pWSHMk7BJ9y+n{rREJ-qOV+SBgk^m;ciTOQbH_jgD<@I`19M+Upi6#3_ zKQnzAg}ejiI6d{nU!({oRg;k&dj*yHIyikx_gajOn4CPY=T8yua&AYF8e$F$gTAx> za`ty~lSvD_jYj`2hiN-HI2;~$o;ghyv$3@g-%}C1t>r_r%Mhw3~dDk_yB<) zts9-|lVxQTeYlq% zV?c>)B2W(t#uet_&MR2W2K7RHrnn}KU6W)H!o)}9GjWd?H z3*YO@$G#f1f6vj@w!-m~ZflDb1wrKGn8IL^0UH^E6Z+@4<9Q`Q0`jY9{U77yW*qSW z=s9sMj~AwB$((q0a3DLDYH_lOT6b+`W0Ja-si1aPw7gaJU5$Ezfr>0t1SSC;QdqnlmF=VE-Q;;(|&ELbY8Y~FH9D} z+QFe8Rz5P)5Z|?oiu?Mttht$14nF|^VLXtk+?cwoL#a|NiJXp$K#LZY=5G;U9DY&u z?Hkd*j&>BdP@e|+F&;$JJ11X2oBO%U6~#USve2`$($X?L2+}t2gtyG79UeZJtE#H% zthON#3?na&&wPR!x^!k0N~&4uZm(NfSb!3`C61__XA7#T$~@gG26}rheviu3+6?IG z>rV^~VXU42h9)M;%gRPSHHqDHc=@uTn$^ZdDm2$c1I?+>-`e_22sJMyadL|nb?a74 zyERWlW|^6u1Gl7zP_bZ=0d|1)O?`cQx#+4!(dCz^*f@Rr3puy1898B&ZV{V{-I)_< zGHmFW$`w}qZ!&CN&7XNs6I)f_b$itLEUc+gfi=PXG{n8lP&1){JbC(bQh2;M96H7~ z-aK6Q$I)cY@-xG7(9UP!OHHfwZDxM{_BlRmKPxpqJNTn#4dq%$@2I@r`}#j?69@Nb z!q-|G6?5xx_J)~Wb~a8XW@g!+dHS_~L#`++nK8^n+1lC`Mg|Qy?r#y{LNjZ)WoE2p zrV?lJ+_lojauLIMxxiTMp7#u1!R_2KgMaFY?)`iSgMQ&;e`mP2Lj$>R6XX-UoSHf` zt#UG9l(kozoCdzpa$K~xeqAZ%q>+~*1y zDURE%Gd4GC>yOe#omx9kE>i(<=s5Csff43TsYzNlb8|=pe_7d+P3Pm~S)w2psmSHYD$EuW=qd46 qD)s*>VQl~O_wP`U%^z^7K%(PpfzFD>^%8$JNHkQnRf-@lkpBYfkqMsw literal 0 HcmV?d00001 diff --git a/testdata/snapshots/darwin/color_palette-light.png b/testdata/snapshots/darwin/color_palette-light.png new file mode 100644 index 0000000000000000000000000000000000000000..65fe9adf757a41bf714937bb70ae403575735092 GIT binary patch literal 4264 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