From 02800a16fecdeebc9bdc4a844cd994bf3ca352cb 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:10:22 -0400 Subject: [PATCH] WIP essentials 3 --- bulk_action_bar.go | 53 ++++++++ bulk_action_bar_test.go | 75 +++++++++++ hold_to_confirm.go | 113 ++++++++++++++++ hold_to_confirm_test.go | 124 ++++++++++++++++++ messages_essentials.go | 7 + .../snapshots/darwin/bulk_action_bar-dark.png | Bin 0 -> 7721 bytes .../darwin/bulk_action_bar-light.png | Bin 0 -> 9109 bytes .../snapshots/darwin/hold_to_confirm-dark.png | Bin 0 -> 3631 bytes .../darwin/hold_to_confirm-light.png | Bin 0 -> 3533 bytes 9 files changed, 372 insertions(+) create mode 100644 bulk_action_bar.go create mode 100644 bulk_action_bar_test.go create mode 100644 hold_to_confirm.go create mode 100644 hold_to_confirm_test.go create mode 100644 testdata/snapshots/darwin/bulk_action_bar-dark.png create mode 100644 testdata/snapshots/darwin/bulk_action_bar-light.png create mode 100644 testdata/snapshots/darwin/hold_to_confirm-dark.png create mode 100644 testdata/snapshots/darwin/hold_to_confirm-light.png diff --git a/bulk_action_bar.go b/bulk_action_bar.go new file mode 100644 index 0000000..ad4e0d7 --- /dev/null +++ b/bulk_action_bar.go @@ -0,0 +1,53 @@ +package mujica + +import ( + "fmt" + "log/slog" + + "github.com/egoist/mygo/ui" + + "mujicaui/icons" + "mujicaui/theme" +) + +// BulkActionBarOptions configure a BulkActionBar. +type BulkActionBarOptions struct { + Disabled bool +} + +// BulkActionBar shows while selected > 0 with the count, actions and a clear button; Changed reports a clear. +func BulkActionBar(c *ui.Context, selected int, opts BulkActionBarOptions, actions func()) ChoiceResult { + if selected < 0 { + panic(fmt.Sprintf("mujica: BulkActionBar selected %d is negative", selected)) + } + if selected == 0 { + return ChoiceResult{} + } + k := Tokens(c) + u := Density(c).Unit() + count := fmt.Sprintf(msg(c, "bulk_action_bar.selected"), selected) + bar := ui.Row(c).AlignItems(ui.Center).Gap(u*3).Padding(u*2, u*2, u*2, u*4).MinHeight(Density(c).ControlHeight()+4*u). + Role(ui.RoleToolbar).Label(count).Disabled(opts.Disabled) + overlayStyle(c, bar, overlayEnter(c, bar)) + said := ui.Local(bar, "bulk_action_bar.said", func() int { return 0 }) + if *said != selected { + *said = selected + c.Announce(count) + } + var clear bool + bar.Children(func() { + ui.Text(c, count).Bold().TextColor(k.Text).SingleLine() + ui.Box(c).Size(theme.BorderWidth, u*5).Background(k.Border).Role(ui.RoleNone) + ui.Row(c).Gap(u * 2).AlignItems(ui.Center).Grow(1).Children(func() { + if actions != nil { + actions() + } + }) + b := Button(c, msg(c, "bulk_action_bar.clear"), ButtonOptions{Variant: Ghost, Icon: icons.Must("x")}) + clear = b.Clicked() || bar.Shortcut(0, ui.KeyEscape) && !bar.IsDisabled() + }) + if clear { + slog.Debug("mujica: bulk_action_bar clear", "selected", selected) + } + return ChoiceResult{bar, clear} +} diff --git a/bulk_action_bar_test.go b/bulk_action_bar_test.go new file mode 100644 index 0000000..b1c14c2 --- /dev/null +++ b/bulk_action_bar_test.go @@ -0,0 +1,75 @@ +package mujica + +import ( + "slices" + "testing" + + "github.com/egoist/mygo/ui" +) + +func TestBulkActionBarCountActionsClear(t *testing.T) { + selected, deletes := 0, 0 + dis := false + tt := ui.NewTester(func(c *ui.Context) { + Use(c, Settings{}) + if Button(c, "Select", ButtonOptions{}).Clicked() { + selected += 3 + } + r := BulkActionBar(c, selected, BulkActionBarOptions{Disabled: dis}, func() { + if Button(c, "Delete", ButtonOptions{Variant: Danger}).Clicked() { + deletes++ + } + }) + if r.Changed() { + selected = 0 + } + }, 600, 300) + if tt.HasText("selected") { + t.Fatal("bar shown with nothing selected") + } + _ = tt.Click("Select") + if !tt.HasText("3 selected") || !slices.Contains(tt.Announcements(), "3 selected") { + t.Fatalf("texts %q", tt.Texts()) + } + _ = tt.Click("Delete") + if deletes != 1 { + t.Fatal("action slot did not run") + } + dis = true + tt.Frame() + _ = tt.Click("Clear selection") + if selected != 3 { + t.Fatal("disabled bar cleared") + } + dis = false + tt.Frame() + _ = tt.Click("Delete") // focus inside the bar + tt.Key(0, ui.KeyEscape) + if selected != 0 || tt.HasText("selected") { + t.Fatalf("Escape left %d selected", selected) + } + _ = tt.Click("Select") + if err := tt.Click("Clear selection"); err != nil || selected != 0 { + t.Fatalf("clear button: %v %d", err, selected) + } +} + +func TestBulkActionBarRejectsNegative(t *testing.T) { + defer func() { + if recover() == nil { + t.Fatal("no panic") + } + }() + ui.NewTester(func(c *ui.Context) { Use(c, Settings{}); BulkActionBar(c, -1, BulkActionBarOptions{}, nil) }, 200, 100) +} + +func TestBulkActionBarSnapshot(t *testing.T) { + snapshot(t, "bulk_action_bar", 600, 90, func(c *ui.Context) { + ui.Column(c).Padding(16).Children(func() { + BulkActionBar(c, 12, BulkActionBarOptions{}, func() { + Button(c, "Archive", ButtonOptions{Variant: Secondary}) + Button(c, "Delete", ButtonOptions{Variant: Danger}) + }) + }) + }) +} diff --git a/hold_to_confirm.go b/hold_to_confirm.go new file mode 100644 index 0000000..76f1440 --- /dev/null +++ b/hold_to_confirm.go @@ -0,0 +1,113 @@ +package mujica + +import ( + "fmt" + "log/slog" + "time" + + "github.com/egoist/mygo/ui" + + "mujicaui/internal" + "mujicaui/theme" +) + +// HoldToConfirmOptions configure a HoldToConfirm. +type HoldToConfirmOptions struct { + // Duration is how long to hold; zero is 1.5 s. + Duration time.Duration + Disabled bool +} + +// holdToConfirmState tracks one hold: by key or pointer, since start. +type holdToConfirmState struct { + key, done bool + start time.Time +} + +// step advances the hold to now; it reports completion once per hold. +func (s *holdToConfirmState) step(held bool, now time.Time, d time.Duration) (progress float64, confirmed bool) { + switch { + case !held: + s.start, s.done = time.Time{}, false + return 0, false + case s.done: + return 1, false + case s.start.IsZero(): + s.start = now + } + progress = min(1, float64(now.Sub(s.start))/float64(d)) + if progress >= 1 { + s.done = true + return 1, true + } + return progress, false +} + +// HoldToConfirm runs a dangerous action only after the pointer or Space is held for Duration. +func HoldToConfirm(c *ui.Context, label string, opts HoldToConfirmOptions) ChoiceResult { + if label == "" { + panic("mujica: HoldToConfirm needs a label") + } + if opts.Duration < 0 { + panic("mujica: negative HoldToConfirm Duration") + } + d := opts.Duration + if d == 0 { + d = 1500 * time.Millisecond + } + k := Tokens(c) + den := Density(c) + b := ui.ButtonBase(c).MinHeight(den.ControlHeight()).MinWidth(theme.MinHitSize).Padding(0, den.Unit()*4). + Radius(theme.ControlRadius).Disabled(opts.Disabled).Description(msg(c, "hold_to_confirm.hint")).Clip() + internal.Ring(b, k.Focus, theme.ControlRadius) + st := ui.Local(b, "hold_to_confirm", func() *holdToConfirmState { return &holdToConfirmState{} }) + s := *st + // Space holds; Enter and Space never click, so no single key press confirms. + b.HandleInput(func(ev ui.InputEvent) bool { + if ev.Key != ui.KeySpace && ev.Key != ui.KeyEnter || b.IsDisabled() { + return false + } + if ev.Key == ui.KeySpace && (ev.Kind == ui.InputKeyDown || ev.Kind == ui.InputKeyUp) { + s.key = ev.Kind == ui.InputKeyDown + } + return true + }) + _ = b.Clicked() // a click alone does nothing; it keeps the base pressable + disabled := b.IsDisabled() + held := !disabled && (s.key || b.Pressed()) + if !b.Focused() { + s.key = false + held = !disabled && b.Pressed() + } + progress, confirmed := s.step(held, c.Now(), d) + still := c.Preferences().ReduceMotion + if held && !confirmed && progress < 1 { + if still { + c.After(100 * time.Millisecond) + } else { + c.AnimationFrame() + } + } + bg, fg, _ := buttonColors(k, Danger, b.Hovered() && !disabled, false) + b.Background(bg).TextColor(fg) + text := label + if held && still && progress < 1 { + left := d - time.Duration(progress*float64(d)) + text = fmt.Sprintf(msg(c, "hold_to_confirm.left"), left.Seconds()) + } + b.Draw(func(p *ui.Painter, r ui.Rect) { + if progress > 0 && !still { + p.Fill(ui.Rect{X: r.X, Y: r.Y, W: r.W * float32(progress), H: r.H}, k.OnDanger.Alpha(0.28), 0) + } + }) + // Room for the countdown, so the width holds. + w1, _ := c.MeasureText(0, ui.Span{Text: label}) + w2, _ := c.MeasureText(0, ui.Span{Text: fmt.Sprintf(msg(c, "hold_to_confirm.left"), d.Seconds())}) + b.MinWidth(max(w1, w2) + den.Unit()*8) + b.Children(func() { ui.Text(c, text).SingleLine() }) + if confirmed { + c.Announce(msg(c, "hold_to_confirm.done")) + slog.Debug("mujica: hold_to_confirm confirmed", "label", label) + } + return ChoiceResult{b, confirmed} +} diff --git a/hold_to_confirm_test.go b/hold_to_confirm_test.go new file mode 100644 index 0000000..062449e --- /dev/null +++ b/hold_to_confirm_test.go @@ -0,0 +1,124 @@ +package mujica + +import ( + "strings" + "testing" + "time" + + "github.com/egoist/mygo/ui" +) + +func TestHoldToConfirmStep(t *testing.T) { + var s holdToConfirmState + t0 := time.Unix(0, 0) + d := time.Second + if p, ok := s.step(true, t0, d); p != 0 || ok { + t.Fatalf("start: %v %v", p, ok) + } + if p, ok := s.step(true, t0.Add(d/2), d); p != 0.5 || ok { + t.Fatalf("half: %v %v", p, ok) + } + if _, ok := s.step(true, t0.Add(d), d); !ok { + t.Fatal("full hold did not confirm") + } + if _, ok := s.step(true, t0.Add(2*d), d); ok { + t.Fatal("one hold confirmed twice") + } + s.step(false, t0.Add(2*d), d) + if p, _ := s.step(true, t0.Add(3*d), d); p != 0 { + t.Fatalf("release did not restart: %v", p) + } +} + +func holdTester(dis *bool, n *int) *ui.Tester { + return ui.NewTester(func(c *ui.Context) { + Use(c, Settings{}) + ui.Row(c).Padding(20).Children(func() { + if HoldToConfirm(c, "Delete repo", HoldToConfirmOptions{Duration: 60 * time.Millisecond, Disabled: *dis}).Changed() { + *n++ + } + }) + }, 400, 200) +} + +func TestHoldToConfirmPointerHold(t *testing.T) { + dis, n := false, 0 + tt := holdTester(&dis, &n) + r, _ := tt.Find("Delete repo") + cx, cy := r.X+r.W/2, r.Y+r.H/2 + _ = tt.Click("Delete repo") + tt.Key(0, ui.KeyEnter) + tt.Key(0, ui.KeySpace) + if n != 0 { + t.Fatalf("a click or key tap confirmed: %d", n) + } + tt.Press(cx, cy) + time.Sleep(20 * time.Millisecond) + tt.Release(cx, cy) + time.Sleep(60 * time.Millisecond) + tt.Frame() + if n != 0 { + t.Fatal("an early release confirmed") + } + tt.Press(cx, cy) + time.Sleep(80 * time.Millisecond) + tt.Frame() + tt.Frame() + tt.Release(cx, cy) + if n != 1 { + t.Fatalf("full hold confirmed %d times", n) + } + dis = true + tt.Frame() + tt.Press(cx, cy) + time.Sleep(80 * time.Millisecond) + tt.Frame() + tt.Release(cx, cy) + if n != 1 { + t.Fatal("disabled hold confirmed") + } +} + +func TestHoldToConfirmSpaceHold(t *testing.T) { + n := 0 + tt := ui.NewTester(func(c *ui.Context) { + Use(c, Settings{}) + // A nanosecond hold completes in the frames between Space down and up. + if HoldToConfirm(c, "Drop table", HoldToConfirmOptions{Duration: time.Nanosecond}).Changed() { + n++ + } + }, 300, 100) + tt.Key(0, ui.KeyTab) + tt.Key(0, ui.KeyEnter) + if n != 0 { + t.Fatal("Enter confirmed") + } + tt.Key(0, ui.KeySpace) + if n != 1 { + t.Fatalf("Space hold confirmed %d times", n) + } +} + +func TestHoldToConfirmReducedMotionCountdown(t *testing.T) { + dis, n := false, 0 + tt := holdTester(&dis, &n) + tt.SetPreferences(ui.Preferences{ReduceMotion: true, TextScale: 1}) + r, _ := tt.Find("Delete repo") + tt.Press(r.X+r.W/2, r.Y+r.H/2) + found := false + for _, s := range tt.Texts() { + found = found || strings.HasPrefix(s, "Keep holding… 0.") + } + if !found { + t.Fatalf("no countdown text: %q", tt.Texts()) + } +} + +func TestHoldToConfirmSnapshot(t *testing.T) { + snapshot(t, "hold_to_confirm", 400, 80, func(c *ui.Context) { + ui.Row(c).Padding(16).Gap(12).Children(func() { + HoldToConfirm(c, "Delete project", HoldToConfirmOptions{}) + HoldToConfirm(c, "Wipe", HoldToConfirmOptions{Disabled: true}) + }) + }) +} diff --git a/messages_essentials.go b/messages_essentials.go index ca8dd7e..3943040 100644 --- a/messages_essentials.go +++ b/messages_essentials.go @@ -7,6 +7,13 @@ func init() { "split_button.more": {"More options", "更多选项"}, + "hold_to_confirm.hint": {"Press and hold, or hold Space, to confirm", "按住鼠标或空格键以确认"}, + "hold_to_confirm.left": {"Keep holding… %.1f s", "继续按住… %.1f 秒"}, + "hold_to_confirm.done": {"Confirmed", "已确认"}, + + "bulk_action_bar.selected": {"%d selected", "已选 %d 项"}, + "bulk_action_bar.clear": {"Clear selection", "清除选择"}, + "qr_code.label": {"QR code", "二维码"}, "qr_code.too_long": {"Text too long for a QR code", "文字过长,无法生成二维码"}, }) diff --git a/testdata/snapshots/darwin/bulk_action_bar-dark.png b/testdata/snapshots/darwin/bulk_action_bar-dark.png new file mode 100644 index 0000000000000000000000000000000000000000..adb314654ee04086ef4803f6492d4b12f9b3a29c GIT binary patch literal 7721 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 RA+itzXsYU|ltOGH{tN9F!mI!Q literal 0 HcmV?d00001 diff --git a/testdata/snapshots/darwin/hold_to_confirm-light.png b/testdata/snapshots/darwin/hold_to_confirm-light.png new file mode 100644 index 0000000000000000000000000000000000000000..30625a2e5d59ec5dd708b777479be4d521b9a798 GIT binary patch literal 3533 zcmd^CX*3iL_nt(JrAS0}8v8O?L;A^37;7U8*;QjpvNRaP*oW+d$yhRkv5$Ri7-Pv& zB3ZH}%V-di{q_I$|MY%(&->;5@|@@1bM86!Jm=nXpBrmxtb2)xhY0`xT+-LmfdK&L zlFqm(knRi{Aw=w1K22Xo>!JVK^_&1-4stuCtl?WRqVF-Q7|2k_aYP6R6rtbiH4En( zp%kE7Sc5mGhqSeVhpD@Ac1eX#B<-#U zPT_R^!OjG%#rqeFKN%df6cy*_Xn}rgJmA?|tA+8mou<#mUC%3%WT8ml6@KhR(`kK? zg9=lC8got}3_vuj*Q%1$QSZ6o9`SUFFzo?I^`AUH+khToS|R!hQn zmvX|D?Ymsk=#=gRzM;&i^}0*%ZRM@*qYJE|{g5yKL{H$|D8AUb%>BIQeMlKRmoJDH|51yEPI|eF4i1=$_0R$WOKu+8Mtt7(7L_7%o*!?h&ArDxZf`9d zkrw+xd!w&>woWkAk1>pVa7>s5KPbCBEsZWlVNEq*W0?f}?_}ssHTdcj>AAVw6f;6o7*3K)OrY!zyrEPSD_}JiEEC#Hi%&#G1 zfss%VM|~khFWgUjoISXU!ay}ophct8wSR(Pat2L)K_^4XhxP%>9{aJlsr-kSRjQ^& z+eXp{B^ynVE~{C18-BC<)@?RO>h}Lh3kXJ(BM8bfaAI;6f_+`?T(&=j34b=oV92>-)<_Dv^^a(inbyQ+gnz>an`0i z^|Ae6S4kdszpbiYQfbhSJI&L1cP8|s(tHZ|xRUCb3tF!c?GZKNbH4fd{p{|$CHPp( zY3r+X1CwNLtHFBP8tz{aPr!wm!|T38(iGd7A8~b(8R1`|F62r-e*c+YNaXHM9#X0%cWji>qlMa~P>^PcS^%53fNO zafJCFE&vwqG*O7@bj&1F0vXP^=j-I*L(ULnVt_>9ItTq@ZuaR^!G7}%N zu}eLlfi8vzEag7`yVM1)T^_8m{CfQR16Q!CxCgChoD>ju-m}PQu;{Qp=&hP!rQ`59 z;hH-f2cARQhP73F^7D1eGe+eM+gulC1hVWJlvnKDrk}W8qqromj(Qe8ok!8HDPPd(TILqHeju|HeYdi`IYKGvTIHnI1P5_IYtx;xmSKUpqDq(^wP)1h!zZMtY(e6b2j5>h(&d zYL83{pl~Hw3_q3zk$WH&&^;;{o2;8A!iXUO>FjusS$4d)l0xx<$y$062KSV;lDQKY zW(>Kn0rRt;wea~BlhNoa3XGH}btWD5j$kz_`^i|1g$2twV7{qPOau68gs%NGl%fxr zu-bSS&s?LbCB(8(XP~N2+78abZO}e$c$KDH{c(X`e6Ek|(>llH?qUPk&mrOARjHFm zg2+`~aG^ZkRWXo2^)Gv{$c{Um2H^wEQNM0SwgY7~5v2*EAsaKF`Xp`f1#SmBE?(_Q z5r6#)pw*4iv`bYD@!dzvs`B!5Ri&;eX^DA*&}joxcTT@gF+L*w<$DWK%Sa!Q3YQ$U zFqi%wD>3J*X!JAU#jS|AWCd57A-%&svmE^|wr^x8CSu=%-hEIy^4Mo|Ojj4TPfsyD zicS=#{eEcnxtrH)qjh(swC<1juG5__RmBqSwDS$+fi1tO&)BV9t#1za6+JT))7hg6l2ZZKgvWJ{rt7=BK6-CziSjuSD zcBt+q^z|1u_U}%Sv3LARLiSDwP5WlwQOAERAEj?qTlomMoO}hxEO*%q;BJQ_n$BvS z6!Q%7Lb3A@;kQCTe|lf05C68M_zxNAq}-rHE|Uzx10s$BP#Br0p73L0C>~ zpjQbipJFO5S{2fi1=bKZi7-abp}VLE)gYZ`9^teUc6{i8J8+4{U4b_1UVX`fw~ z1*0$8Z)7;l5AA~yI|Y;;xd8O?{&>po@XFKc!av+xO25OB0x3VRcEJ}y{anK${pSnQ zZHjll@$+{Ot}tD=k_fN#SIFWDA;)tyT!D9J{$O>1MQ{G{D<4C@;rCLkPxaeL0;$U~ zXu}VeO7#4y1{^YL%U?yaHy8)%8QKLqs*2^W=3!UoeFgCx7O(i@K3t{-?X*U}EVLe6 z7re!1)l{^;LFK&A_SJ2JJWshPS-v)rBMSO*Jf*o!tmpjax2999XN!*HQcw&tST2M! zYM%a6m!`j~>}ltL=SFPlkwLk?p(ak!IreZ*ZS9RRWn0$ z^caG*4BX?>I!jwwxG#P?mzf{CaO`$*tF^eCRr5v9Ha`)KN(da36E|;fIt>(wm5{F=!T_zilRuFb2bY8>#*$E4uEooMS(4a5`Ocxr=dq zrp^7*Q*MIu7Qh1BdFDCD+06Fm_z#_GGwClm1jwq$Gd5?FE?P%_=C=yz5pIiSuj9uaBat`X9^e8r-qg+Sz6{v6)(q7!g2Pd0rc+~ K>r_A-(EkE*WQ0lp literal 0 HcmV?d00001