From 3a0d044a4dece30061f52b37a5815b37f7623357 Mon Sep 17 00:00:00 2001 From: Jakub Sobon Date: Tue, 24 Nov 2020 23:02:21 -0500 Subject: [PATCH] Allow button to specify multiple trigger keys. --- widgets/button/button.go | 4 +- widgets/button/button_test.go | 125 ++++++++++++++++++++++++++++++++++ widgets/button/options.go | 52 ++++++++++---- 3 files changed, 165 insertions(+), 16 deletions(-) diff --git a/widgets/button/button.go b/widgets/button/button.go index 2e15fe1..c0cc91c 100644 --- a/widgets/button/button.go +++ b/widgets/button/button.go @@ -159,7 +159,7 @@ func (b *Button) keyActivated(k *terminalapi.Keyboard) bool { b.mu.Lock() defer b.mu.Unlock() - if k.Key == b.opts.key { + if b.opts.keys[k.Key] { b.state = button.Down now := time.Now().UTC() b.keyTriggerTime = &now @@ -220,7 +220,7 @@ func (b *Button) Options() widgetapi.Options { return widgetapi.Options{ MinimumSize: image.Point{width, height}, MaximumSize: image.Point{width, height}, - WantKeyboard: widgetapi.KeyScopeGlobal, + WantKeyboard: b.opts.keyScope, WantMouse: widgetapi.MouseScopeGlobal, } } diff --git a/widgets/button/button_test.go b/widgets/button/button_test.go index 20721b7..0fd829d 100644 --- a/widgets/button/button_test.go +++ b/widgets/button/button_test.go @@ -243,6 +243,105 @@ func TestButton(t *testing.T) { count: 1, }, }, + { + desc: "draws button in down state due to a keyboard event when multiple keys are specified", + callback: &callbackTracker{}, + text: "hello", + opts: []Option{ + Keys(keyboard.KeyEnter, keyboard.KeyTab), + }, + canvas: image.Rect(0, 0, 8, 4), + events: []terminalapi.Event{ + &terminalapi.Keyboard{Key: keyboard.KeyTab}, + }, + want: func(size image.Point) *faketerm.Terminal { + ft := faketerm.MustNew(size) + cvs := testcanvas.MustNew(ft.Area()) + + // Button. + testcanvas.MustSetAreaCells(cvs, image.Rect(1, 1, 8, 4), 'x', cell.BgColor(cell.ColorNumber(117))) + + // Text. + testdraw.MustText(cvs, "hello", image.Point{2, 2}, + draw.TextCellOpts( + cell.FgColor(cell.ColorBlack), + cell.BgColor(cell.ColorNumber(117))), + ) + + testcanvas.MustApply(cvs, ft) + return ft + }, + wantCallback: &callbackTracker{ + called: true, + count: 1, + }, + }, + { + desc: "draws button in down state due to a keyboard event when single global key is specified", + callback: &callbackTracker{}, + text: "hello", + opts: []Option{ + GlobalKey(keyboard.KeyTab), + }, + canvas: image.Rect(0, 0, 8, 4), + events: []terminalapi.Event{ + &terminalapi.Keyboard{Key: keyboard.KeyTab}, + }, + want: func(size image.Point) *faketerm.Terminal { + ft := faketerm.MustNew(size) + cvs := testcanvas.MustNew(ft.Area()) + + // Button. + testcanvas.MustSetAreaCells(cvs, image.Rect(1, 1, 8, 4), 'x', cell.BgColor(cell.ColorNumber(117))) + + // Text. + testdraw.MustText(cvs, "hello", image.Point{2, 2}, + draw.TextCellOpts( + cell.FgColor(cell.ColorBlack), + cell.BgColor(cell.ColorNumber(117))), + ) + + testcanvas.MustApply(cvs, ft) + return ft + }, + wantCallback: &callbackTracker{ + called: true, + count: 1, + }, + }, + { + desc: "draws button in down state due to a keyboard event when multiple global keys are specified", + callback: &callbackTracker{}, + text: "hello", + opts: []Option{ + GlobalKeys(keyboard.KeyEnter, keyboard.KeyTab), + }, + canvas: image.Rect(0, 0, 8, 4), + events: []terminalapi.Event{ + &terminalapi.Keyboard{Key: keyboard.KeyTab}, + }, + want: func(size image.Point) *faketerm.Terminal { + ft := faketerm.MustNew(size) + cvs := testcanvas.MustNew(ft.Area()) + + // Button. + testcanvas.MustSetAreaCells(cvs, image.Rect(1, 1, 8, 4), 'x', cell.BgColor(cell.ColorNumber(117))) + + // Text. + testdraw.MustText(cvs, "hello", image.Point{2, 2}, + draw.TextCellOpts( + cell.FgColor(cell.ColorBlack), + cell.BgColor(cell.ColorNumber(117))), + ) + + testcanvas.MustApply(cvs, ft) + return ft + }, + wantCallback: &callbackTracker{ + called: true, + count: 1, + }, + }, { desc: "keyboard event ignored when no key specified", callback: &callbackTracker{}, @@ -814,6 +913,19 @@ func TestOptions(t *testing.T) { WantMouse: widgetapi.MouseScopeGlobal, }, }, + { + desc: "registers for focused keyboard events when multiple keys are specified", + text: "hello", + opts: []Option{ + Keys(keyboard.KeyEnter, keyboard.KeyTab), + }, + want: widgetapi.Options{ + MinimumSize: image.Point{8, 4}, + MaximumSize: image.Point{8, 4}, + WantKeyboard: widgetapi.KeyScopeFocused, + WantMouse: widgetapi.MouseScopeGlobal, + }, + }, { desc: "registers for global keyboard events", text: "hello", @@ -827,6 +939,19 @@ func TestOptions(t *testing.T) { WantMouse: widgetapi.MouseScopeGlobal, }, }, + { + desc: "registers for global keyboard events when multiple keys are specified", + text: "hello", + opts: []Option{ + GlobalKeys(keyboard.KeyEnter, keyboard.KeyTab), + }, + want: widgetapi.Options{ + MinimumSize: image.Point{8, 4}, + MaximumSize: image.Point{8, 4}, + WantKeyboard: widgetapi.KeyScopeGlobal, + WantMouse: widgetapi.MouseScopeGlobal, + }, + }, } for _, tc := range tests { diff --git a/widgets/button/options.go b/widgets/button/options.go index cc13429..1b29b50 100644 --- a/widgets/button/options.go +++ b/widgets/button/options.go @@ -47,7 +47,8 @@ type options struct { shadowColor cell.Color height int width int - keyScopes []*keyScope + keys map[keyboard.Key]bool + keyScope widgetapi.KeyScope keyUpDelay time.Duration } @@ -80,6 +81,7 @@ func newOptions(text string) *options { height: DefaultHeight, width: widthFor(text), keyUpDelay: DefaultKeyUpDelay, + keys: map[keyboard.Key]bool{}, } } @@ -134,31 +136,53 @@ func WidthFor(text string) Option { }) } -// Key configures the keyboard keys that press the button. -// Can be specified multiple times to provide multiple keys. +// Key configures the keyboard key that presses the button. // The widget responds to this key only if its container is focused. // When not provided, the widget ignores all keyboard events. +// +// Clears all keys set previously. +// Mutually exclusive with GlobalKey() and GlobalKeys(). func Key(k keyboard.Key) Option { return option(func(opts *options) { - ks := &keyScope{ - key: k, - scope: widgetapi.KeyScopeFocused, - } - opts.keyScopes = append(opts.keyScopes, ks) + opts.keys = map[keyboard.Key]bool{} + opts.keys[k] = true + opts.keyScope = widgetapi.KeyScopeFocused }) } -// GlobalKey is like Key, but makes the widget respond to the keys even if its +// GlobalKey is like Key, but makes the widget respond to the key even if its // container isn't focused. -// Can be specified multiple times to provide multiple keys. // When not provided, the widget ignores all keyboard events. +// +// Clears all keys set previously. +// Mutually exclusive with Key() and Keys(). func GlobalKey(k keyboard.Key) Option { return option(func(opts *options) { - ks := &keyScope{ - key: k, - scope: widgetapi.KeyScopeGlobal, + opts.keys = map[keyboard.Key]bool{} + opts.keys[k] = true + opts.keyScope = widgetapi.KeyScopeGlobal + }) +} + +// Keys is like Key, but allows to configure multiple keys. +func Keys(keys ...keyboard.Key) Option { + return option(func(opts *options) { + opts.keys = map[keyboard.Key]bool{} + for _, k := range keys { + opts.keys[k] = true } - opts.keyScopes = append(opts.keyScopes, ks) + opts.keyScope = widgetapi.KeyScopeFocused + }) +} + +// GlobalKeys is like GlobalKey, but allows to configure multiple keys. +func GlobalKeys(keys ...keyboard.Key) Option { + return option(func(opts *options) { + opts.keys = map[keyboard.Key]bool{} + for _, k := range keys { + opts.keys[k] = true + } + opts.keyScope = widgetapi.KeyScopeGlobal }) }