Allow widgets to request keyboard events exclusively.

This commit is contained in:
Jakub Sobon 2020-11-28 00:32:55 -05:00
parent 5c2597d4db
commit 2c1b1a4bfe
No known key found for this signature in database
GPG Key ID: F2451A77FB05D3B7
3 changed files with 166 additions and 0 deletions

View File

@ -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
}

View File

@ -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},

View File

@ -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.