diff --git a/application.go b/application.go index e5d97847..d770714f 100644 --- a/application.go +++ b/application.go @@ -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 diff --git a/box.go b/box.go index 23fd4161..fc5d96f4 100644 --- a/box.go +++ b/box.go @@ -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. diff --git a/button.go b/button.go index 4363cea1..316bd441 100644 --- a/button.go +++ b/button.go @@ -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 }) } diff --git a/checkbox.go b/checkbox.go index d7b3142d..4ad438a5 100644 --- a/checkbox.go +++ b/checkbox.go @@ -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 }) } diff --git a/demos/primitive/main.go b/demos/primitive/main.go index 398a45ee..a94a6696 100644 --- a/demos/primitive/main.go +++ b/demos/primitive/main.go @@ -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 }) } diff --git a/dropdown.go b/dropdown.go index 11400eda..0e59138c 100644 --- a/dropdown.go +++ b/dropdown.go @@ -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 }) } diff --git a/flex.go b/flex.go index e8713eb2..17b24d7e 100644 --- a/flex.go +++ b/flex.go @@ -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 }) } diff --git a/form.go b/form.go index 32846a8c..a7bb4b3a 100644 --- a/form.go +++ b/form.go @@ -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 }) } diff --git a/frame.go b/frame.go index fe2160e3..f53df5f3 100644 --- a/frame.go +++ b/frame.go @@ -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 }) } diff --git a/grid.go b/grid.go index c191bfef..7abab567 100644 --- a/grid.go +++ b/grid.go @@ -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 }) } diff --git a/inputfield.go b/inputfield.go index 27b74bce..faf31801 100644 --- a/inputfield.go +++ b/inputfield.go @@ -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 }) } diff --git a/list.go b/list.go index 22719260..71b558b4 100644 --- a/list.go +++ b/list.go @@ -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 }) } diff --git a/modal.go b/modal.go index d12eb91a..655d6b1a 100644 --- a/modal.go +++ b/modal.go @@ -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 }) } diff --git a/pages.go b/pages.go index 146cfa4e..c8e5cdde 100644 --- a/pages.go +++ b/pages.go @@ -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 }) } diff --git a/primitive.go b/primitive.go index 6badaeea..4ad867f0 100644 --- a/primitive.go +++ b/primitive.go @@ -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. diff --git a/table.go b/table.go index 92b94cce..fe248b66 100644 --- a/table.go +++ b/table.go @@ -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 }) } diff --git a/textarea.go b/textarea.go index e6452e3c..f97e6ea9 100644 --- a/textarea.go +++ b/textarea.go @@ -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 }) } diff --git a/textview.go b/textview.go index bd3ea0e2..b0ddc4b1 100644 --- a/textview.go +++ b/textview.go @@ -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 }) } diff --git a/treeview.go b/treeview.go index c473e0f1..127df372 100644 --- a/treeview.go +++ b/treeview.go @@ -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 }) }