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

This commit is contained in:
Oliver 2018-01-14 21:29:34 +01:00
parent f61c66bb82
commit 626453b2a6
12 changed files with 80 additions and 80 deletions

View File

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

View File

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

34
box.go
View File

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

View File

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

View File

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

View File

@ -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 {
app.SetInputCapture(func(event *tcell.EventKey) *tcell.EventKey {
if event.Key() == tcell.KeyCtrlN {
nextSlide()
return false
}).SetKeyCapture(tcell.KeyCtrlP, 0, func(p tview.Primitive) bool {
} else if event.Key() == tcell.KeyCtrlP {
previousSlide()
return false
}
return event
})
// Start the application.

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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