From 2c1b1a4bfee47c9308e2c093da705548e352488b Mon Sep 17 00:00:00 2001 From: Jakub Sobon Date: Sat, 28 Nov 2020 00:32:55 -0500 Subject: [PATCH] Allow widgets to request keyboard events exclusively. --- container/container.go | 13 ++++ container/container_test.go | 146 ++++++++++++++++++++++++++++++++++++ widgetapi/widgetapi.go | 7 ++ 3 files changed, 166 insertions(+) diff --git a/container/container.go b/container/container.go index ad26483..5a8542b 100644 --- a/container/container.go +++ b/container/container.go @@ -369,6 +369,9 @@ func (c *Container) keyEvTargets() []*keyEvTarget { var ( errStr string targets []*keyEvTarget + // If the currently focused widget set the ExclusiveKeyboardOnFocus + // option, this pointer is set to that widget. + exclusiveWidget widgetapi.Widget ) // All the targets that should receive this event. @@ -383,6 +386,10 @@ func (c *Container) keyEvTargets() []*keyEvTarget { Focused: focused, } wOpt := cur.opts.widget.Options() + if focused && wOpt.ExclusiveKeyboardOnFocus { + exclusiveWidget = cur.opts.widget + } + switch wOpt.WantKeyboard { case widgetapi.KeyScopeNone: // Widget doesn't want any keyboard events. @@ -398,6 +405,12 @@ func (c *Container) keyEvTargets() []*keyEvTarget { } return nil })) + + if exclusiveWidget != nil { + targets = []*keyEvTarget{ + newKeyEvTarget(exclusiveWidget, &widgetapi.EventMeta{Focused: true}), + } + } return targets } diff --git a/container/container_test.go b/container/container_test.go index b382a58..229badb 100644 --- a/container/container_test.go +++ b/container/container_test.go @@ -1219,6 +1219,152 @@ func TestKeyboard(t *testing.T) { return ft }, }, + { + desc: "keyboard event forwarded to exclusive widget only when focused", + termSize: image.Point{40, 20}, + container: func(ft *faketerm.Terminal) (*Container, error) { + return New( + ft, + SplitVertical( + Left( + PlaceWidget(fakewidget.New(widgetapi.Options{WantKeyboard: widgetapi.KeyScopeGlobal})), + ), + Right( + SplitHorizontal( + Top( + PlaceWidget(fakewidget.New(widgetapi.Options{WantKeyboard: widgetapi.KeyScopeFocused})), + ), + Bottom( + PlaceWidget(fakewidget.New( + widgetapi.Options{ + WantKeyboard: widgetapi.KeyScopeFocused, + ExclusiveKeyboardOnFocus: true, + }, + )), + ), + ), + ), + ), + ) + }, + events: []terminalapi.Event{ + // Move focus to the target container. + &terminalapi.Mouse{Position: image.Point{39, 19}, Button: mouse.ButtonLeft}, + &terminalapi.Mouse{Position: image.Point{39, 19}, Button: mouse.ButtonRelease}, + // Send the keyboard event. + &terminalapi.Keyboard{Key: keyboard.KeyEnter}, + }, + want: func(size image.Point) *faketerm.Terminal { + ft := faketerm.MustNew(size) + + // Widget that isn't focused, but registered for global + // keyboard events. + fakewidget.MustDraw( + ft, + testcanvas.MustNew(image.Rect(0, 0, 20, 20)), + &widgetapi.Meta{}, + widgetapi.Options{WantKeyboard: widgetapi.KeyScopeGlobal}, + ) + + // Widget that isn't focused and only wants focused events. + fakewidget.MustDraw( + ft, + testcanvas.MustNew(image.Rect(20, 0, 40, 10)), + &widgetapi.Meta{}, + widgetapi.Options{WantKeyboard: widgetapi.KeyScopeFocused}, + ) + + // The focused widget receives the key. + fakewidget.MustDraw( + ft, + testcanvas.MustNew(image.Rect(20, 10, 40, 20)), + &widgetapi.Meta{Focused: true}, + widgetapi.Options{WantKeyboard: widgetapi.KeyScopeFocused}, + &fakewidget.Event{ + Ev: &terminalapi.Keyboard{Key: keyboard.KeyEnter}, + Meta: &widgetapi.EventMeta{Focused: true}, + }, + ) + return ft + }, + }, + { + desc: "the ExclusiveKeyboardOnFocus option has no effect when widget not focused", + termSize: image.Point{40, 20}, + container: func(ft *faketerm.Terminal) (*Container, error) { + return New( + ft, + SplitVertical( + Left( + PlaceWidget(fakewidget.New(widgetapi.Options{WantKeyboard: widgetapi.KeyScopeGlobal})), + ), + Right( + SplitHorizontal( + Top( + PlaceWidget(fakewidget.New( + widgetapi.Options{ + WantKeyboard: widgetapi.KeyScopeFocused, + ExclusiveKeyboardOnFocus: true, + }, + )), + ), + Bottom( + PlaceWidget(fakewidget.New( + widgetapi.Options{ + WantKeyboard: widgetapi.KeyScopeFocused, + }, + )), + ), + ), + ), + ), + ) + }, + events: []terminalapi.Event{ + // Move focus to the target container. + &terminalapi.Mouse{Position: image.Point{39, 19}, Button: mouse.ButtonLeft}, + &terminalapi.Mouse{Position: image.Point{39, 19}, Button: mouse.ButtonRelease}, + // Send the keyboard event. + &terminalapi.Keyboard{Key: keyboard.KeyEnter}, + }, + want: func(size image.Point) *faketerm.Terminal { + ft := faketerm.MustNew(size) + + // Widget that isn't focused, but registered for global + // keyboard events. + fakewidget.MustDraw( + ft, + testcanvas.MustNew(image.Rect(0, 0, 20, 20)), + &widgetapi.Meta{}, + widgetapi.Options{WantKeyboard: widgetapi.KeyScopeGlobal}, + &fakewidget.Event{ + Ev: &terminalapi.Keyboard{Key: keyboard.KeyEnter}, + Meta: &widgetapi.EventMeta{Focused: false}, + }, + ) + + // Widget that isn't focused and only wants focused events. + fakewidget.MustDraw( + ft, + testcanvas.MustNew(image.Rect(20, 0, 40, 10)), + &widgetapi.Meta{}, + widgetapi.Options{WantKeyboard: widgetapi.KeyScopeFocused}, + ) + + // The focused widget receives the key. + fakewidget.MustDraw( + ft, + testcanvas.MustNew(image.Rect(20, 10, 40, 20)), + &widgetapi.Meta{Focused: true}, + widgetapi.Options{WantKeyboard: widgetapi.KeyScopeFocused}, + &fakewidget.Event{ + Ev: &terminalapi.Keyboard{Key: keyboard.KeyEnter}, + Meta: &widgetapi.EventMeta{Focused: true}, + }, + ) + return ft + }, + }, { desc: "event not forwarded if the widget didn't request it", termSize: image.Point{40, 20}, diff --git a/widgetapi/widgetapi.go b/widgetapi/widgetapi.go index 6f31c87..9ca2f70 100644 --- a/widgetapi/widgetapi.go +++ b/widgetapi/widgetapi.go @@ -130,6 +130,13 @@ type Options struct { // forwarded to the widget. WantKeyboard KeyScope + // ExclusiveKeyboardOnFocus allows a widget to request exclusive access to + // keyboard events when its container is focused. When set to true, no + // other widgets will receive any keyboard events that happen while the + // container of this widget is focused even if they registered for + // KeyScopeGlobal. + ExclusiveKeyboardOnFocus bool + // WantMouse allows a widget to request mouse events and specify their // desired scope. If set to MouseScopeNone, no mouse events are forwarded // to the widget.