add support for installing capture functions after general key processing, allowing for opt-in inside out even propagation

pull/968/head
Johan Kjölhede 1 month ago
parent b0d41c484b
commit 5ec9818914

@ -401,7 +401,7 @@ EventLoop:
// Pass other key events to the root primitive.
if root != nil && root.HasFocus() {
if handler := root.InputHandler(); handler != nil {
handler(event, func(p Primitive) {
_ = handler(event, func(p Primitive) {
a.SetFocus(p)
})
draw = true

@ -59,6 +59,14 @@ type Box struct {
// nothing should be forwarded).
inputCapture func(event *tcell.EventKey) *tcell.EventKey
// An optional capture function which receives a key event and returns a bool
// which indicates if the event should be potentially further processed by
// parents' own inputCaptureAfter function. This can be used to construct
// an inside-out event handling chain.
// It has a default value of capturing all events, to preserve semantics
// of tview (originally, tview never passed events back to parents).
inputCaptureAfter func(event *tcell.EventKey) *tcell.EventKey
// An optional function which is called before the box is drawn.
draw func(screen tcell.Screen, x, y, width, height int) (int, int, int, int)
@ -71,13 +79,14 @@ type Box struct {
// NewBox returns a Box without a border.
func NewBox() *Box {
b := &Box{
width: 15,
height: 10,
innerX: -1, // Mark as uninitialized.
backgroundColor: Styles.PrimitiveBackgroundColor,
borderStyle: tcell.StyleDefault.Foreground(Styles.BorderColor).Background(Styles.PrimitiveBackgroundColor),
titleColor: Styles.TitleColor,
titleAlign: AlignCenter,
width: 15,
height: 10,
innerX: -1, // Mark as uninitialized.
backgroundColor: Styles.PrimitiveBackgroundColor,
borderStyle: tcell.StyleDefault.Foreground(Styles.BorderColor).Background(Styles.PrimitiveBackgroundColor),
titleColor: Styles.TitleColor,
titleAlign: AlignCenter,
inputCaptureAfter: func(event *tcell.EventKey) *tcell.EventKey { return nil },
}
return b
}
@ -158,19 +167,29 @@ func (b *Box) GetDrawFunc() func(screen tcell.Screen, x, y, width, height int) (
// on to the provided (default) input handler.
//
// This is only meant to be used by subclassing primitives.
func (b *Box) WrapInputHandler(inputHandler func(*tcell.EventKey, func(p Primitive))) func(*tcell.EventKey, func(p Primitive)) {
return func(event *tcell.EventKey, setFocus func(p Primitive)) {
func (b *Box) WrapInputHandler(inputHandler func(*tcell.EventKey, func(p Primitive)) (consumed bool)) func(*tcell.EventKey, func(p Primitive)) (consumed bool) {
return func(event *tcell.EventKey, setFocus func(p Primitive)) (consumed bool) {
if b.inputCapture != nil {
event = b.inputCapture(event)
if event == nil {
consumed = true
}
}
if event != nil && inputHandler != nil {
inputHandler(event, setFocus)
if event != nil && !consumed && inputHandler != nil {
consumed = inputHandler(event, setFocus)
}
if event != nil && !consumed && b.inputCaptureAfter != nil {
event = b.inputCaptureAfter(event)
if event == nil {
consumed = true
}
}
return
}
}
// InputHandler returns nil. Box has no default input handling.
func (b *Box) InputHandler() func(event *tcell.EventKey, setFocus func(p Primitive)) {
func (b *Box) InputHandler() func(event *tcell.EventKey, setFocus func(p Primitive)) (consumed bool) {
return b.WrapInputHandler(nil)
}
@ -206,12 +225,39 @@ func (b *Box) SetInputCapture(capture func(event *tcell.EventKey) *tcell.EventKe
return b
}
// SetInputCaptureAfter installs a function which captures key events after they are
// forwarded to the primitive's default key event handler. This function can
// then choose to consume the event and prevent any further processing by
// returning nil. If a non-nil event is returned, it will be passed back to
// parent, and potentially further up the tree.
//
// Providing a nil handler will remove a previously existing handler. NOTE:
// the default value of this function is a function which captures all
// events, so setting it to nil, will instead configure to capture none.
//
// This function can also be used on container primitives (like Flex, Grid, or
// Form) as keyboard events will likewise just be given back to the parent,
// and processed if it has its own InputCaptureAfter.
//
// Pasted key events are not forwarded to the input capture function if pasting
// is enabled (see [Application.EnablePaste]).
func (b *Box) SetInputCaptureAfter(capture func(event *tcell.EventKey) *tcell.EventKey) *Box {
b.inputCaptureAfter = capture
return b
}
// GetInputCapture returns the function installed with SetInputCapture() or nil
// if no such function has been installed.
func (b *Box) GetInputCapture() func(event *tcell.EventKey) *tcell.EventKey {
return b.inputCapture
}
// GetInputCaptureAfter returns the function installed with SetInputCaptureAfter() or nil
// if no such function has been installed.
func (b *Box) GetInputCaptureAfter() func(event *tcell.EventKey) *tcell.EventKey {
return b.inputCaptureAfter
}
// WrapMouseHandler wraps a mouse event handler (see [Box.MouseHandler]) with the
// functionality to capture mouse events (see [Box.SetMouseCapture]) before passing
// them on to the provided (default) event handler.

@ -160,8 +160,8 @@ func (b *Button) Draw(screen tcell.Screen) {
}
// InputHandler returns the handler for this primitive.
func (b *Button) InputHandler() func(event *tcell.EventKey, setFocus func(p Primitive)) {
return b.WrapInputHandler(func(event *tcell.EventKey, setFocus func(p Primitive)) {
func (b *Button) InputHandler() func(event *tcell.EventKey, setFocus func(p Primitive)) (consumed bool) {
return b.WrapInputHandler(func(event *tcell.EventKey, setFocus func(p Primitive)) (consumed bool) {
if b.disabled {
return
}
@ -177,6 +177,8 @@ func (b *Button) InputHandler() func(event *tcell.EventKey, setFocus func(p Prim
b.exit(key)
}
}
return
})
}

@ -279,8 +279,8 @@ func (c *Checkbox) Draw(screen tcell.Screen) {
}
// InputHandler returns the handler for this primitive.
func (c *Checkbox) InputHandler() func(event *tcell.EventKey, setFocus func(p Primitive)) {
return c.WrapInputHandler(func(event *tcell.EventKey, setFocus func(p Primitive)) {
func (c *Checkbox) InputHandler() func(event *tcell.EventKey, setFocus func(p Primitive)) (consumed bool) {
return c.WrapInputHandler(func(event *tcell.EventKey, setFocus func(p Primitive)) (consumed bool) {
if c.disabled {
return
}
@ -303,6 +303,8 @@ func (c *Checkbox) InputHandler() func(event *tcell.EventKey, setFocus func(p Pr
c.finished(key)
}
}
return
})
}

@ -42,8 +42,8 @@ func (r *RadioButtons) Draw(screen tcell.Screen) {
}
// InputHandler returns the handler for this primitive.
func (r *RadioButtons) InputHandler() func(event *tcell.EventKey, setFocus func(p tview.Primitive)) {
return r.WrapInputHandler(func(event *tcell.EventKey, setFocus func(p tview.Primitive)) {
func (r *RadioButtons) InputHandler() func(event *tcell.EventKey, setFocus func(p tview.Primitive)) (consumed bool) {
return r.WrapInputHandler(func(event *tcell.EventKey, setFocus func(p tview.Primitive)) (consumed bool) {
switch event.Key() {
case tcell.KeyUp:
r.currentOption--
@ -56,6 +56,8 @@ func (r *RadioButtons) InputHandler() func(event *tcell.EventKey, setFocus func(
r.currentOption = len(r.options) - 1
}
}
return
})
}

@ -448,8 +448,8 @@ func (d *DropDown) Draw(screen tcell.Screen) {
}
// InputHandler returns the handler for this primitive.
func (d *DropDown) InputHandler() func(event *tcell.EventKey, setFocus func(p Primitive)) {
return d.WrapInputHandler(func(event *tcell.EventKey, setFocus func(p Primitive)) {
func (d *DropDown) InputHandler() func(event *tcell.EventKey, setFocus func(p Primitive)) (consumed bool) {
return d.WrapInputHandler(func(event *tcell.EventKey, setFocus func(p Primitive)) (consumed bool) {
if d.disabled {
return
}
@ -482,6 +482,8 @@ func (d *DropDown) InputHandler() func(event *tcell.EventKey, setFocus func(p Pr
d.finished(key)
}
}
return
})
}

@ -247,16 +247,17 @@ func (f *Flex) MouseHandler() func(action MouseAction, event *tcell.EventMouse,
}
// InputHandler returns the handler for this primitive.
func (f *Flex) InputHandler() func(event *tcell.EventKey, setFocus func(p Primitive)) {
return f.WrapInputHandler(func(event *tcell.EventKey, setFocus func(p Primitive)) {
func (f *Flex) InputHandler() func(event *tcell.EventKey, setFocus func(p Primitive)) (consumed bool) {
return f.WrapInputHandler(func(event *tcell.EventKey, setFocus func(p Primitive)) (consumed bool) {
for _, item := range f.items {
if item.Item != nil && item.Item.HasFocus() {
if handler := item.Item.InputHandler(); handler != nil {
handler(event, setFocus)
consumed = handler(event, setFocus)
return
}
}
}
return
})
}

@ -846,12 +846,12 @@ func (f *Form) MouseHandler() func(action MouseAction, event *tcell.EventMouse,
}
// InputHandler returns the handler for this primitive.
func (f *Form) InputHandler() func(event *tcell.EventKey, setFocus func(p Primitive)) {
return f.WrapInputHandler(func(event *tcell.EventKey, setFocus func(p Primitive)) {
func (f *Form) InputHandler() func(event *tcell.EventKey, setFocus func(p Primitive)) (consumed bool) {
return f.WrapInputHandler(func(event *tcell.EventKey, setFocus func(p Primitive)) (consumed bool) {
for _, item := range f.items {
if item != nil && item.HasFocus() {
if handler := item.InputHandler(); handler != nil {
handler(event, setFocus)
consumed = handler(event, setFocus)
return
}
}
@ -860,11 +860,13 @@ func (f *Form) InputHandler() func(event *tcell.EventKey, setFocus func(p Primit
for _, button := range f.buttons {
if button.HasFocus() {
if handler := button.InputHandler(); handler != nil {
handler(event, setFocus)
consumed = handler(event, setFocus)
return
}
}
}
return
})
}

@ -209,15 +209,16 @@ func (f *Frame) MouseHandler() func(action MouseAction, event *tcell.EventMouse,
}
// InputHandler returns the handler for this primitive.
func (f *Frame) InputHandler() func(event *tcell.EventKey, setFocus func(p Primitive)) {
return f.WrapInputHandler(func(event *tcell.EventKey, setFocus func(p Primitive)) {
func (f *Frame) InputHandler() func(event *tcell.EventKey, setFocus func(p Primitive)) (consumed bool) {
return f.WrapInputHandler(func(event *tcell.EventKey, setFocus func(p Primitive)) (consumed bool) {
if f.primitive == nil {
return
}
if handler := f.primitive.InputHandler(); handler != nil {
handler(event, setFocus)
consumed = handler(event, setFocus)
return
}
return
})
}

@ -653,14 +653,14 @@ func (g *Grid) MouseHandler() func(action MouseAction, event *tcell.EventMouse,
}
// InputHandler returns the handler for this primitive.
func (g *Grid) InputHandler() func(event *tcell.EventKey, setFocus func(p Primitive)) {
return g.WrapInputHandler(func(event *tcell.EventKey, setFocus func(p Primitive)) {
func (g *Grid) InputHandler() func(event *tcell.EventKey, setFocus func(p Primitive)) (consumed bool) {
return g.WrapInputHandler(func(event *tcell.EventKey, setFocus func(p Primitive)) (consumed bool) {
if !g.hasFocus {
// Pass event on to child primitive.
for _, item := range g.items {
if item != nil && item.Item.HasFocus() {
if handler := item.Item.InputHandler(); handler != nil {
handler(event, setFocus)
consumed = handler(event, setFocus)
return
}
}
@ -698,6 +698,8 @@ func (g *Grid) InputHandler() func(event *tcell.EventKey, setFocus func(p Primit
case tcell.KeyRight:
g.columnOffset++
}
return
})
}

@ -508,8 +508,8 @@ func (i *InputField) Draw(screen tcell.Screen) {
}
// InputHandler returns the handler for this primitive.
func (i *InputField) InputHandler() func(event *tcell.EventKey, setFocus func(p Primitive)) {
return i.WrapInputHandler(func(event *tcell.EventKey, setFocus func(p Primitive)) {
func (i *InputField) InputHandler() func(event *tcell.EventKey, setFocus func(p Primitive)) (consumed bool) {
return i.WrapInputHandler(func(event *tcell.EventKey, setFocus func(p Primitive)) (consumed bool) {
if i.textArea.GetDisabled() {
return
}
@ -610,8 +610,10 @@ func (i *InputField) InputHandler() func(event *tcell.EventKey, setFocus func(p
fallthrough
default:
// Forward other key events to the text area.
i.textArea.InputHandler()(event, setFocus)
consumed = i.textArea.InputHandler()(event, setFocus)
}
return
})
}

@ -568,8 +568,8 @@ func (l *List) adjustOffset() {
}
// InputHandler returns the handler for this primitive.
func (l *List) InputHandler() func(event *tcell.EventKey, setFocus func(p Primitive)) {
return l.WrapInputHandler(func(event *tcell.EventKey, setFocus func(p Primitive)) {
func (l *List) InputHandler() func(event *tcell.EventKey, setFocus func(p Primitive)) (consumed bool) {
return l.WrapInputHandler(func(event *tcell.EventKey, setFocus func(p Primitive)) (consumed bool) {
if event.Key() == tcell.KeyEscape {
if l.done != nil {
l.done()
@ -663,6 +663,8 @@ func (l *List) InputHandler() func(event *tcell.EventKey, setFocus func(p Primit
}
l.adjustOffset()
}
return
})
}

@ -202,13 +202,14 @@ func (m *Modal) MouseHandler() func(action MouseAction, event *tcell.EventMouse,
}
// InputHandler returns the handler for this primitive.
func (m *Modal) InputHandler() func(event *tcell.EventKey, setFocus func(p Primitive)) {
return m.WrapInputHandler(func(event *tcell.EventKey, setFocus func(p Primitive)) {
func (m *Modal) InputHandler() func(event *tcell.EventKey, setFocus func(p Primitive)) (consumed bool) {
return m.WrapInputHandler(func(event *tcell.EventKey, setFocus func(p Primitive)) (consumed bool) {
if m.frame.HasFocus() {
if handler := m.frame.InputHandler(); handler != nil {
handler(event, setFocus)
consumed = handler(event, setFocus)
return
}
}
return
})
}

@ -303,16 +303,17 @@ func (p *Pages) MouseHandler() func(action MouseAction, event *tcell.EventMouse,
}
// InputHandler returns the handler for this primitive.
func (p *Pages) InputHandler() func(event *tcell.EventKey, setFocus func(p Primitive)) {
return p.WrapInputHandler(func(event *tcell.EventKey, setFocus func(p Primitive)) {
func (p *Pages) InputHandler() func(event *tcell.EventKey, setFocus func(p Primitive)) (consumed bool) {
return p.WrapInputHandler(func(event *tcell.EventKey, setFocus func(p Primitive)) (consumed bool) {
for _, page := range p.pages {
if page.Item.HasFocus() {
if handler := page.Item.InputHandler(); handler != nil {
handler(event, setFocus)
consumed = handler(event, setFocus)
return
}
}
}
return
})
}

@ -32,7 +32,7 @@ type Primitive interface {
// The Box class provides functionality to intercept keyboard input. If you
// subclass from Box, it is recommended that you wrap your handler using
// Box.WrapInputHandler() so you inherit that functionality.
InputHandler() func(event *tcell.EventKey, setFocus func(p Primitive))
InputHandler() func(event *tcell.EventKey, setFocus func(p Primitive)) (consumed bool)
// Focus is called by the application when the primitive receives focus.
// Implementers may call delegate() to pass the focus on to another primitive.

@ -1366,8 +1366,8 @@ func (t *Table) Draw(screen tcell.Screen) {
}
// InputHandler returns the handler for this primitive.
func (t *Table) InputHandler() func(event *tcell.EventKey, setFocus func(p Primitive)) {
return t.WrapInputHandler(func(event *tcell.EventKey, setFocus func(p Primitive)) {
func (t *Table) InputHandler() func(event *tcell.EventKey, setFocus func(p Primitive)) (consumed bool) {
return t.WrapInputHandler(func(event *tcell.EventKey, setFocus func(p Primitive)) (consumed bool) {
key := event.Key()
if (!t.rowsSelectable && !t.columnsSelectable && key == tcell.KeyEnter) ||
@ -1682,6 +1682,8 @@ func (t *Table) InputHandler() func(event *tcell.EventKey, setFocus func(p Primi
t.columnsSelectable && previouslySelectedColumn != t.selectedColumn) {
t.selectionChanged(t.selectedRow, t.selectedColumn)
}
return
})
}

@ -1945,8 +1945,8 @@ func (t *TextArea) getSelectedText() string {
}
// InputHandler returns the handler for this primitive.
func (t *TextArea) InputHandler() func(event *tcell.EventKey, setFocus func(p Primitive)) {
return t.WrapInputHandler(func(event *tcell.EventKey, setFocus func(p Primitive)) {
func (t *TextArea) InputHandler() func(event *tcell.EventKey, setFocus func(p Primitive)) (consumed bool) {
return t.WrapInputHandler(func(event *tcell.EventKey, setFocus func(p Primitive)) (consumed bool) {
if t.disabled {
return
}
@ -2332,6 +2332,8 @@ func (t *TextArea) InputHandler() func(event *tcell.EventKey, setFocus func(p Pr
defer t.changed()
}
}
return
})
}

@ -1300,8 +1300,8 @@ func (t *TextView) Draw(screen tcell.Screen) {
}
// InputHandler returns the handler for this primitive.
func (t *TextView) InputHandler() func(event *tcell.EventKey, setFocus func(p Primitive)) {
return t.WrapInputHandler(func(event *tcell.EventKey, setFocus func(p Primitive)) {
func (t *TextView) InputHandler() func(event *tcell.EventKey, setFocus func(p Primitive)) (consumed bool) {
return t.WrapInputHandler(func(event *tcell.EventKey, setFocus func(p Primitive)) (consumed bool) {
key := event.Key()
if key == tcell.KeyEscape || key == tcell.KeyEnter || key == tcell.KeyTab || key == tcell.KeyBacktab {
@ -1360,6 +1360,8 @@ func (t *TextView) InputHandler() func(event *tcell.EventKey, setFocus func(p Pr
t.trackEnd = false
t.lineOffset -= t.pageSize
}
return
})
}

@ -760,8 +760,8 @@ func (t *TreeView) Draw(screen tcell.Screen) {
}
// InputHandler returns the handler for this primitive.
func (t *TreeView) InputHandler() func(event *tcell.EventKey, setFocus func(p Primitive)) {
return t.WrapInputHandler(func(event *tcell.EventKey, setFocus func(p Primitive)) {
func (t *TreeView) InputHandler() func(event *tcell.EventKey, setFocus func(p Primitive)) (consumed bool) {
return t.WrapInputHandler(func(event *tcell.EventKey, setFocus func(p Primitive)) (consumed bool) {
selectNode := func() {
node := t.currentNode
if node != nil {
@ -823,6 +823,8 @@ func (t *TreeView) InputHandler() func(event *tcell.EventKey, setFocus func(p Pr
}
t.process(true)
return
})
}

Loading…
Cancel
Save