Add Keys to allow default keybindings to be modified
parent
3feec6ebfb
commit
04a0149298
|
@ -1,5 +1,6 @@
|
|||
v1.4.6 (WIP)
|
||||
- Add Box.ShowFocus
|
||||
- Add Keys to allow default keybindings to be modified
|
||||
- Add List.GetOffset, List.SetOffset and List.SetSelectedTextAttributes
|
||||
- Add TextView.SetMaxLines
|
||||
- Add Vim-style keybindings to List
|
||||
|
|
|
@ -151,14 +151,13 @@ func (b *Button) Draw(screen tcell.Screen) {
|
|||
func (b *Button) InputHandler() 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.
|
||||
if matchesKeys(event, Keys.Select) {
|
||||
if b.selected != nil {
|
||||
b.selected()
|
||||
}
|
||||
case tcell.KeyBacktab, tcell.KeyTab, tcell.KeyEscape: // Leave. No action.
|
||||
} else if matchesKeys(event, Keys.Cancel) || matchesKeys(event, Keys.PreviousField) || matchesKeys(event, Keys.NextField) {
|
||||
if b.blur != nil {
|
||||
b.blur(key)
|
||||
b.blur(event.Key())
|
||||
}
|
||||
}
|
||||
})
|
||||
|
|
3
doc.go
3
doc.go
|
@ -144,7 +144,8 @@ feel of the primitives to your preferred style.
|
|||
Keyboard Shortcuts
|
||||
|
||||
Widgets use keyboard shortcuts (a.k.a. keybindings) such as arrow keys and
|
||||
H/J/k/L by default. You may override these shortcuts globally by setting a
|
||||
H/J/k/L by default. You may replace these defaults by modifying the keybindings
|
||||
listed in Keys. You may also override keyboard shortcuts globally by setting a
|
||||
handler with Application.SetInputCapture.
|
||||
|
||||
cbind is a library which simplifies adding support for custom keybindings to
|
||||
|
|
4
go.mod
4
go.mod
|
@ -7,6 +7,6 @@ require (
|
|||
github.com/lucasb-eyer/go-colorful v1.0.3
|
||||
github.com/mattn/go-runewidth v0.0.9
|
||||
github.com/rivo/uniseg v0.1.0
|
||||
golang.org/x/sys v0.0.0-20200509044756-6aff5f38e54f // indirect
|
||||
golang.org/x/text v0.3.2 // indirect
|
||||
gitlab.com/tslocum/cbind v0.1.1
|
||||
golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9 // indirect
|
||||
)
|
||||
|
|
8
go.sum
8
go.sum
|
@ -10,14 +10,18 @@ github.com/lucasb-eyer/go-colorful v1.0.3 h1:QIbQXiugsb+q10B+MI+7DI1oQLdmnep86tW
|
|||
github.com/lucasb-eyer/go-colorful v1.0.3/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
|
||||
github.com/mattn/go-runewidth v0.0.4 h1:2BvfKmzob6Bmd4YsL0zygOqfdFnK7GR4QL06Do4/p7Y=
|
||||
github.com/mattn/go-runewidth v0.0.4/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
|
||||
github.com/mattn/go-runewidth v0.0.8/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
|
||||
github.com/mattn/go-runewidth v0.0.9 h1:Lm995f3rfxdpd6TSmuVCHVb/QhupuXlYr8sCI/QdE+0=
|
||||
github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
|
||||
github.com/rivo/uniseg v0.1.0 h1:+2KBaVoUmb9XzDsrx/Ct0W/EYOSFf/nWTauy++DprtY=
|
||||
github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
||||
gitlab.com/tslocum/cbind v0.1.1 h1:JXXtxMWHgWLvoF+QkrvcNvOQ59juy7OE1RhT7hZfdt0=
|
||||
gitlab.com/tslocum/cbind v0.1.1/go.mod h1:rX7vkl0pUSg/yy427MmD1FZAf99S7WwpUlxF/qTpPqk=
|
||||
golang.org/x/sys v0.0.0-20190626150813-e07cf5db2756 h1:9nuHUbU8dRnRRfj9KjWUVrJeoexdbeMjttk6Oh1rD10=
|
||||
golang.org/x/sys v0.0.0-20190626150813-e07cf5db2756/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200509044756-6aff5f38e54f h1:mOhmO9WsBaJCNmaZHPtHs9wOcdqdKCjF6OPJlmDM3KI=
|
||||
golang.org/x/sys v0.0.0-20200509044756-6aff5f38e54f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9 h1:YTzHMGlqJu67/uEo1lBv0n3wBXhXNeUbB1XfN2vmTm0=
|
||||
golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs=
|
||||
|
|
|
@ -0,0 +1,41 @@
|
|||
package cview
|
||||
|
||||
// Key defines the keyboard shortcuts of an application.
|
||||
type Key struct {
|
||||
Cancel []string
|
||||
Select []string
|
||||
|
||||
FirstItem []string
|
||||
LastItem []string
|
||||
|
||||
PreviousItem []string
|
||||
NextItem []string
|
||||
|
||||
PreviousField []string
|
||||
NextField []string
|
||||
|
||||
PreviousPage []string
|
||||
NextPage []string
|
||||
|
||||
ShowContextMenu []string
|
||||
}
|
||||
|
||||
// Keys defines the keyboard shortcuts of an application.
|
||||
var Keys = Key{
|
||||
Cancel: []string{"Escape"},
|
||||
Select: []string{"Enter", "Ctrl+J"}, // Ctrl+J = keypad enter
|
||||
|
||||
FirstItem: []string{"Home", "g"},
|
||||
LastItem: []string{"End", "G"},
|
||||
|
||||
PreviousItem: []string{"Up", "k"},
|
||||
NextItem: []string{"Down", "j"},
|
||||
|
||||
PreviousField: []string{"Backtab"},
|
||||
NextField: []string{"Tab"},
|
||||
|
||||
PreviousPage: []string{"PageUp"},
|
||||
NextPage: []string{"PageDown"},
|
||||
|
||||
ShowContextMenu: []string{"Alt+Enter"},
|
||||
}
|
146
list.go
146
list.go
|
@ -850,7 +850,9 @@ func (l *List) InputHandler() func(event *tcell.EventKey, setFocus func(p Primit
|
|||
return l.WrapInputHandler(func(event *tcell.EventKey, setFocus func(p Primitive)) {
|
||||
l.Lock()
|
||||
|
||||
if event.Key() == tcell.KeyEscape {
|
||||
previousItem := l.currentItem
|
||||
|
||||
if matchesKeys(event, Keys.Cancel) {
|
||||
if l.ContextMenu.open {
|
||||
l.Unlock()
|
||||
|
||||
|
@ -865,49 +867,8 @@ func (l *List) InputHandler() func(event *tcell.EventKey, setFocus func(p Primit
|
|||
l.Unlock()
|
||||
}
|
||||
return
|
||||
} else if len(l.items) == 0 && (event.Key() != tcell.KeyEnter || event.Modifiers()&tcell.ModAlt == 0) {
|
||||
l.Unlock()
|
||||
return
|
||||
}
|
||||
|
||||
previousItem := l.currentItem
|
||||
|
||||
switch key := event.Key(); key {
|
||||
case tcell.KeyHome:
|
||||
l.transform(TransformFirstItem)
|
||||
case tcell.KeyEnd:
|
||||
l.transform(TransformLastItem)
|
||||
case tcell.KeyBacktab, tcell.KeyUp, tcell.KeyLeft:
|
||||
l.transform(TransformPreviousItem)
|
||||
case tcell.KeyTab, tcell.KeyDown, tcell.KeyRight:
|
||||
l.transform(TransformNextItem)
|
||||
case tcell.KeyPgUp:
|
||||
l.transform(TransformPreviousPage)
|
||||
case tcell.KeyPgDn:
|
||||
l.transform(TransformNextPage)
|
||||
case tcell.KeyEnter:
|
||||
if event.Modifiers()&tcell.ModAlt != 0 {
|
||||
// Do we show any shortcuts?
|
||||
var showShortcuts bool
|
||||
for _, item := range l.items {
|
||||
if item.Shortcut != 0 {
|
||||
showShortcuts = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
offsetX := 7
|
||||
if showShortcuts {
|
||||
offsetX += 4
|
||||
}
|
||||
offsetY := l.currentItem
|
||||
if l.showSecondaryText {
|
||||
offsetY *= 2
|
||||
}
|
||||
|
||||
x, y, _, _ := l.GetInnerRect()
|
||||
defer l.ContextMenu.show(l.currentItem, x+offsetX, y+offsetY, setFocus)
|
||||
} else if l.currentItem >= 0 && l.currentItem < len(l.items) {
|
||||
} else if matchesKeys(event, Keys.Select) {
|
||||
if l.currentItem >= 0 && l.currentItem < len(l.items) {
|
||||
item := l.items[l.currentItem]
|
||||
if item.Enabled {
|
||||
if item.Selected != nil {
|
||||
|
@ -922,43 +883,74 @@ func (l *List) InputHandler() func(event *tcell.EventKey, setFocus func(p Primit
|
|||
}
|
||||
}
|
||||
}
|
||||
case tcell.KeyRune:
|
||||
ch := event.Rune()
|
||||
if ch != ' ' {
|
||||
// It's not a space bar. Is it a shortcut?
|
||||
var found bool
|
||||
for index, item := range l.items {
|
||||
if item.Enabled && item.Shortcut == ch {
|
||||
// We have a shortcut.
|
||||
found = true
|
||||
l.currentItem = index
|
||||
break
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
switch ch {
|
||||
case 'g':
|
||||
l.transform(TransformFirstItem)
|
||||
case 'G':
|
||||
l.transform(TransformLastItem)
|
||||
case 'j':
|
||||
l.transform(TransformNextItem)
|
||||
case 'k':
|
||||
l.transform(TransformPreviousItem)
|
||||
}
|
||||
} else if matchesKeys(event, Keys.ShowContextMenu) {
|
||||
// Do we show any shortcuts?
|
||||
var showShortcuts bool
|
||||
for _, item := range l.items {
|
||||
if item.Shortcut != 0 {
|
||||
showShortcuts = true
|
||||
break
|
||||
}
|
||||
}
|
||||
item := l.items[l.currentItem]
|
||||
if item.Selected != nil {
|
||||
l.Unlock()
|
||||
item.Selected()
|
||||
l.Lock()
|
||||
|
||||
offsetX := 7
|
||||
if showShortcuts {
|
||||
offsetX += 4
|
||||
}
|
||||
if l.selected != nil {
|
||||
l.Unlock()
|
||||
l.selected(l.currentItem, item.MainText, item.SecondaryText, item.Shortcut)
|
||||
l.Lock()
|
||||
offsetY := l.currentItem
|
||||
if l.showSecondaryText {
|
||||
offsetY *= 2
|
||||
}
|
||||
|
||||
x, y, _, _ := l.GetInnerRect()
|
||||
defer l.ContextMenu.show(l.currentItem, x+offsetX, y+offsetY, setFocus)
|
||||
} else if len(l.items) == 0 {
|
||||
l.Unlock()
|
||||
return
|
||||
}
|
||||
|
||||
var matchesShortcut bool
|
||||
if event.Key() == tcell.KeyRune {
|
||||
ch := event.Rune()
|
||||
if ch != ' ' {
|
||||
// It's not a space bar. Is it a shortcut?
|
||||
for index, item := range l.items {
|
||||
if item.Enabled && item.Shortcut == ch {
|
||||
// We have a shortcut.
|
||||
matchesShortcut = true
|
||||
l.currentItem = index
|
||||
|
||||
item := l.items[l.currentItem]
|
||||
if item.Selected != nil {
|
||||
l.Unlock()
|
||||
item.Selected()
|
||||
l.Lock()
|
||||
}
|
||||
if l.selected != nil {
|
||||
l.Unlock()
|
||||
l.selected(l.currentItem, item.MainText, item.SecondaryText, item.Shortcut)
|
||||
l.Lock()
|
||||
}
|
||||
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if !matchesShortcut {
|
||||
if matchesKeys(event, Keys.FirstItem) {
|
||||
l.transform(TransformFirstItem)
|
||||
} else if matchesKeys(event, Keys.LastItem) {
|
||||
l.transform(TransformLastItem)
|
||||
} else if matchesKeys(event, Keys.PreviousItem) || matchesKeys(event, Keys.PreviousField) {
|
||||
l.transform(TransformPreviousItem)
|
||||
} else if matchesKeys(event, Keys.NextItem) || matchesKeys(event, Keys.NextField) {
|
||||
l.transform(TransformNextItem)
|
||||
} else if matchesKeys(event, Keys.PreviousPage) {
|
||||
l.transform(TransformPreviousPage)
|
||||
} else if matchesKeys(event, Keys.NextPage) {
|
||||
l.transform(TransformNextPage)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -31,7 +31,7 @@ type Theme struct {
|
|||
ScrollBarColor tcell.Color // Scroll bar color.
|
||||
}
|
||||
|
||||
// Styles defines the theme for applications. The default is for a black
|
||||
// Styles defines the appearance of an application. The default is for a black
|
||||
// background and some basic colors: black, white, yellow, green, cyan, and
|
||||
// blue.
|
||||
var Styles = Theme{
|
||||
|
|
40
treeview.go
40
treeview.go
|
@ -889,41 +889,25 @@ func (t *TreeView) InputHandler() func(event *tcell.EventKey, setFocus func(p Pr
|
|||
|
||||
// Because the tree is flattened into a list only at drawing time, we also
|
||||
// postpone the (selection) movement to drawing time.
|
||||
switch key := event.Key(); key {
|
||||
case tcell.KeyTab, tcell.KeyBacktab, tcell.KeyEscape:
|
||||
if matchesKeys(event, Keys.Cancel) || matchesKeys(event, Keys.PreviousField) || matchesKeys(event, Keys.NextField) {
|
||||
if t.done != nil {
|
||||
t.Unlock()
|
||||
t.done(key)
|
||||
t.done(event.Key())
|
||||
t.Lock()
|
||||
}
|
||||
case tcell.KeyDown, tcell.KeyRight:
|
||||
t.movement = treeDown
|
||||
case tcell.KeyUp, tcell.KeyLeft:
|
||||
t.movement = treeUp
|
||||
case tcell.KeyHome:
|
||||
} else if matchesKeys(event, Keys.FirstItem) {
|
||||
t.movement = treeHome
|
||||
case tcell.KeyEnd:
|
||||
} else if matchesKeys(event, Keys.LastItem) {
|
||||
t.movement = treeEnd
|
||||
case tcell.KeyPgDn, tcell.KeyCtrlF:
|
||||
t.movement = treePageDown
|
||||
case tcell.KeyPgUp, tcell.KeyCtrlB:
|
||||
} else if matchesKeys(event, Keys.PreviousItem) || matchesKeys(event, Keys.PreviousField) {
|
||||
t.movement = treeUp
|
||||
} else if matchesKeys(event, Keys.NextItem) || matchesKeys(event, Keys.NextField) {
|
||||
t.movement = treeDown
|
||||
} else if matchesKeys(event, Keys.PreviousPage) {
|
||||
t.movement = treePageUp
|
||||
case tcell.KeyRune:
|
||||
switch event.Rune() {
|
||||
case 'g':
|
||||
t.movement = treeHome
|
||||
case 'G':
|
||||
t.movement = treeEnd
|
||||
case 'j':
|
||||
t.movement = treeDown
|
||||
case 'k':
|
||||
t.movement = treeUp
|
||||
case ' ':
|
||||
t.Unlock()
|
||||
selectNode()
|
||||
t.Lock()
|
||||
}
|
||||
case tcell.KeyEnter:
|
||||
} else if matchesKeys(event, Keys.NextPage) {
|
||||
t.movement = treePageDown
|
||||
} else if matchesKeys(event, Keys.Select) || event.Rune() == ' ' { // TODO space is hardcoded
|
||||
t.Unlock()
|
||||
selectNode()
|
||||
t.Lock()
|
||||
|
|
17
util.go
17
util.go
|
@ -9,6 +9,7 @@ import (
|
|||
"github.com/gdamore/tcell"
|
||||
runewidth "github.com/mattn/go-runewidth"
|
||||
"github.com/rivo/uniseg"
|
||||
"gitlab.com/tslocum/cbind"
|
||||
)
|
||||
|
||||
// Text alignment within a box.
|
||||
|
@ -694,3 +695,19 @@ func RenderScrollBar(screen tcell.Screen, visibility ScrollBarVisibility, x int,
|
|||
}
|
||||
Print(screen, scrollBar, x, y, 1, AlignLeft, color)
|
||||
}
|
||||
|
||||
// matchesKeys returns whether the EventKey is present in the list of keybinds.
|
||||
func matchesKeys(event *tcell.EventKey, keybinds []string) bool {
|
||||
enc, err := cbind.Encode(event.Modifiers(), event.Key(), event.Rune())
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
for _, k := range keybinds {
|
||||
if k == enc {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue