From 626453b2a6397afaed69cdb8b19ebc27aba84545 Mon Sep 17 00:00:00 2001 From: Oliver <480930+rivo@users.noreply.github.com> Date: Sun, 14 Jan 2018 21:29:34 +0100 Subject: [PATCH] All primitives now offer a way to intercept all key events sent to them. Also made the global key event intercept handler more general/consistent. Resolves #22 --- README.md | 3 ++ application.go | 78 ++++++++++---------------------------- box.go | 34 ++++++++++++++++- button.go | 4 +- checkbox.go | 4 +- demos/presentation/main.go | 13 ++++--- dropdown.go | 4 +- inputfield.go | 4 +- list.go | 4 +- primitive.go | 4 ++ table.go | 4 +- textview.go | 4 +- 12 files changed, 80 insertions(+), 80 deletions(-) diff --git a/README.md b/README.md index e11827c..a5edac0 100644 --- a/README.md +++ b/README.md @@ -62,6 +62,9 @@ Add your issue here on GitHub. Feel free to get in touch if you have any questio ## Releases +- v0.6 (2018-01-14) + - All primitives can now intercept all key events when they have focus. + - Key events can also be intercepted globally (changed to a more general, consistent handling) - v0.5 (2018-01-13) - `TextView` now has word wrapping and text alignment - v0.4 (2018-01-12) diff --git a/application.go b/application.go index 9f376a6..4c5a657 100644 --- a/application.go +++ b/application.go @@ -26,55 +26,28 @@ type Application struct { // Whether or not the application resizes the root primitive. rootAutoSize bool - // Key overrides. - keyOverrides map[tcell.Key]func(p Primitive) bool - - // Rune overrides. - runeOverrides map[rune]func(p Primitive) bool + // An optional capture function which receives a key event and returns the + // event to be forwarded to the default input handler (nil if nothing should + // be forwarded). + inputCapture func(event *tcell.EventKey) *tcell.EventKey } // NewApplication creates and returns a new application. func NewApplication() *Application { - return &Application{ - keyOverrides: make(map[tcell.Key]func(p Primitive) bool), - runeOverrides: make(map[rune]func(p Primitive) bool), - } + return &Application{} } -// SetKeyCapture installs a global capture function for the given key. It -// intercepts all events for the given key and routes them to the handler. -// The handler receives the Primitive to which the key is originally redirected, -// the one which has focus, or nil if it was not directed to a Primitive. The -// handler also returns whether or not the key event is then forwarded to that -// Primitive. Draw() is called implicitly if the event is not forwarded. +// SetInputCapture sets a function which captures all key events before they are +// forwarded to the key event handler of the primitive which currently has +// focus. This function can then choose to forward that key event (or a +// different one) by returning it or stop the key event processing by returning +// nil. // -// Special keys (e.g. Escape, Enter, or Ctrl-A) are defined by the "key" -// argument. The "ch" rune is ignored. Other keys (e.g. "a", "h", or "5") are -// specified by their rune, with key set to tcell.KeyRune. See also -// https://godoc.org/github.com/gdamore/tcell#EventKey for more information. -// -// To remove a handler again, provide a nil handler for the same key. -// -// The application itself will exit when Ctrl-C is pressed. You can intercept -// this with this function as well. -func (a *Application) SetKeyCapture(key tcell.Key, ch rune, handler func(p Primitive) bool) *Application { - if key == tcell.KeyRune { - if handler != nil { - a.runeOverrides[ch] = handler - } else { - if _, ok := a.runeOverrides[ch]; ok { - delete(a.runeOverrides, ch) - } - } - } else { - if handler != nil { - a.keyOverrides[key] = handler - } else { - if _, ok := a.keyOverrides[key]; ok { - delete(a.keyOverrides, key) - } - } - } +// Note that this also affects the default event handling of the application +// itself: Such a handler can intercept the Ctrl-C event which closes the +// applicaton. +func (a *Application) SetInputCapture(capture func(event *tcell.EventKey) *tcell.EventKey) *Application { + a.inputCapture = capture return a } @@ -131,23 +104,10 @@ func (a *Application) Run() error { a.RUnlock() // Intercept keys. - if event.Key() == tcell.KeyRune { - if handler, ok := a.runeOverrides[event.Rune()]; ok { - if !handler(p) { - a.Draw() - break - } - } - } else { - if handler, ok := a.keyOverrides[event.Key()]; ok { - pr := p - if event.Key() == tcell.KeyCtrlC { - pr = nil - } - if !handler(pr) { - a.Draw() - break - } + if a.inputCapture != nil { + event = a.inputCapture(event) + if event == nil { + break // Don't forward event. } } diff --git a/box.go b/box.go index e3718bb..fe8091f 100644 --- a/box.go +++ b/box.go @@ -9,6 +9,9 @@ import ( // border and a title. Most subclasses keep their content contained in the box // but don't necessarily have to. // +// Note that all classes which subclass from Box will also have access to its +// functions. +// // See https://github.com/rivo/tview/wiki/Box for an example. type Box struct { // The position of the rect. @@ -42,6 +45,11 @@ type Box struct { // Whether or not this box has focus. hasFocus bool + + // An optional capture function which receives a key event and returns the + // event to be forwarded to the primitive's default input handler (nil if + // nothing should be forwarded). + inputCapture func(event *tcell.EventKey) *tcell.EventKey } // NewBox returns a Box without a border. @@ -94,9 +102,33 @@ func (b *Box) SetRect(x, y, width, height int) { b.height = height } +// wrapInputHandler wraps an input handler (see InputHandler()) with the +// functionality to capture input (see SetInputCapture()) before passing it +// on to the provided (default) input handler. +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)) { + if b.inputCapture != nil { + event = b.inputCapture(event) + } + if event != nil && inputHandler != nil { + inputHandler(event, setFocus) + } + } +} + // InputHandler returns nil. func (b *Box) InputHandler() func(event *tcell.EventKey, setFocus func(p Primitive)) { - return nil + return b.wrapInputHandler(nil) +} + +// SetInputCapture sets a function which captures key events before they are +// forwarded to the primitive's default key event handler. This function can +// then choose to forward that key event (or a different one) to the default +// handler by returning it. If nil is returned, the default handler will not +// be called. +func (b *Box) SetInputCapture(capture func(event *tcell.EventKey) *tcell.EventKey) *Box { + b.inputCapture = capture + return b } // SetBackgroundColor sets the box's background color. diff --git a/button.go b/button.go index d83bad7..9b185c7 100644 --- a/button.go +++ b/button.go @@ -122,7 +122,7 @@ 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 func(event *tcell.EventKey, setFocus func(p Primitive)) { + return b.wrapInputHandler(func(event *tcell.EventKey, setFocus func(p Primitive)) { // Process key event. switch key := event.Key(); key { case tcell.KeyEnter: // Selected. @@ -134,5 +134,5 @@ func (b *Button) InputHandler() func(event *tcell.EventKey, setFocus func(p Prim b.blur(key) } } - } + }) } diff --git a/checkbox.go b/checkbox.go index aa37359..0feb6da 100644 --- a/checkbox.go +++ b/checkbox.go @@ -150,7 +150,7 @@ 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 func(event *tcell.EventKey, setFocus func(p Primitive)) { + return c.wrapInputHandler(func(event *tcell.EventKey, setFocus func(p Primitive)) { // Process key event. switch key := event.Key(); key { case tcell.KeyRune, tcell.KeyEnter: // Check. @@ -166,5 +166,5 @@ func (c *Checkbox) InputHandler() func(event *tcell.EventKey, setFocus func(p Pr c.done(key) } } - } + }) } diff --git a/demos/presentation/main.go b/demos/presentation/main.go index 60f2e68..8416aec 100644 --- a/demos/presentation/main.go +++ b/demos/presentation/main.go @@ -77,12 +77,13 @@ func main() { AddItem(info, 1, 1, false) // Shortcuts to navigate the slides. - app.SetKeyCapture(tcell.KeyCtrlN, 0, func(p tview.Primitive) bool { - nextSlide() - return false - }).SetKeyCapture(tcell.KeyCtrlP, 0, func(p tview.Primitive) bool { - previousSlide() - return false + app.SetInputCapture(func(event *tcell.EventKey) *tcell.EventKey { + if event.Key() == tcell.KeyCtrlN { + nextSlide() + } else if event.Key() == tcell.KeyCtrlP { + previousSlide() + } + return event }) // Start the application. diff --git a/dropdown.go b/dropdown.go index 9c360a6..c9e9cea 100644 --- a/dropdown.go +++ b/dropdown.go @@ -249,7 +249,7 @@ 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 func(event *tcell.EventKey, setFocus func(p Primitive)) { + return d.wrapInputHandler(func(event *tcell.EventKey, setFocus func(p Primitive)) { // Process key event. switch key := event.Key(); key { case tcell.KeyEnter, tcell.KeyRune, tcell.KeyDown: @@ -274,7 +274,7 @@ func (d *DropDown) InputHandler() func(event *tcell.EventKey, setFocus func(p Pr d.done(key) } } - } + }) } // Focus is called by the application when the primitive receives focus. diff --git a/inputfield.go b/inputfield.go index 4bfd2a7..717f62c 100644 --- a/inputfield.go +++ b/inputfield.go @@ -236,7 +236,7 @@ func (i *InputField) setCursor(screen tcell.Screen) { // InputHandler returns the handler for this primitive. func (i *InputField) InputHandler() func(event *tcell.EventKey, setFocus func(p Primitive)) { - return func(event *tcell.EventKey, setFocus func(p Primitive)) { + return i.wrapInputHandler(func(event *tcell.EventKey, setFocus func(p Primitive)) { // Trigger changed events. currentText := i.text defer func() { @@ -271,5 +271,5 @@ func (i *InputField) InputHandler() func(event *tcell.EventKey, setFocus func(p i.done(key) } } - } + }) } diff --git a/list.go b/list.go index 6f56977..07f816c 100644 --- a/list.go +++ b/list.go @@ -232,7 +232,7 @@ func (l *List) Draw(screen tcell.Screen) { // InputHandler returns the handler for this primitive. func (l *List) InputHandler() func(event *tcell.EventKey, setFocus func(p Primitive)) { - return func(event *tcell.EventKey, setFocus func(p Primitive)) { + return l.wrapInputHandler(func(event *tcell.EventKey, setFocus func(p Primitive)) { previousItem := l.currentItem switch key := event.Key(); key { @@ -296,5 +296,5 @@ func (l *List) InputHandler() func(event *tcell.EventKey, setFocus func(p Primit item := l.items[l.currentItem] l.changed(l.currentItem, item.MainText, item.SecondaryText, item.Shortcut) } - } + }) } diff --git a/primitive.go b/primitive.go index c0b0e1a..38f9e82 100644 --- a/primitive.go +++ b/primitive.go @@ -28,6 +28,10 @@ type Primitive interface { // // The Application's Draw() function will be called automatically after the // handler returns. + // + // 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)) // Focus is called by the application when the primitive receives focus. diff --git a/table.go b/table.go index 5de1c3a..0042040 100644 --- a/table.go +++ b/table.go @@ -717,7 +717,7 @@ ColumnLoop: // InputHandler returns the handler for this primitive. func (t *Table) InputHandler() func(event *tcell.EventKey, setFocus func(p Primitive)) { - return func(event *tcell.EventKey, setFocus func(p Primitive)) { + return t.wrapInputHandler(func(event *tcell.EventKey, setFocus func(p Primitive)) { key := event.Key() if (!t.rowsSelectable && !t.columnsSelectable && key == tcell.KeyEnter) || @@ -920,5 +920,5 @@ func (t *Table) InputHandler() func(event *tcell.EventKey, setFocus func(p Primi t.columnsSelectable && previouslySelectedColumn != t.selectedColumn) { t.selectionChanged(t.selectedRow, t.selectedColumn) } - } + }) } diff --git a/textview.go b/textview.go index 4ed7cf7..a2b771f 100644 --- a/textview.go +++ b/textview.go @@ -836,7 +836,7 @@ 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 func(event *tcell.EventKey, setFocus func(p Primitive)) { + return t.wrapInputHandler(func(event *tcell.EventKey, setFocus func(p Primitive)) { // Do we pass this event on? if t.capture != nil { if !t.capture(event) { @@ -899,5 +899,5 @@ func (t *TextView) InputHandler() func(event *tcell.EventKey, setFocus func(p Pr t.trackEnd = false t.lineOffset -= t.pageSize } - } + }) }