Added shortcut keys possibility to Menus

This commit is contained in:
Luke I. Wilson 2021-03-20 16:22:06 -05:00
parent fefa34c2b6
commit ce66bc4bc6
3 changed files with 49 additions and 20 deletions

View File

@ -128,7 +128,7 @@ func main() {
}, },
) )
changeFocus(fileSelector) changeFocus(fileSelector)
}}, &ui.ItemEntry{Name: "_Save", Callback: func() { }}, &ui.ItemEntry{Name: "_Save", Shortcut: 's', Callback: func() {
if tabContainer.GetTabCount() > 0 { if tabContainer.GetTabCount() > 0 {
tab := tabContainer.GetTab(tabContainer.GetSelectedTabIdx()) tab := tabContainer.GetTab(tabContainer.GetSelectedTabIdx())
te := tab.Child.(*ui.TextEdit) te := tab.Child.(*ui.TextEdit)
@ -287,6 +287,13 @@ main_loop:
if ev.Key() == tcell.KeyCtrlQ { // TODO: replace with shortcut keys in menus if ev.Key() == tcell.KeyCtrlQ { // TODO: replace with shortcut keys in menus
break main_loop break main_loop
} }
if ev.Modifiers() & tcell.ModCtrl != 0 {
handled := bar.HandleEvent(ev)
if handled {
continue // Avoid passing the event to the focusedComponent
}
}
} }
focusedComponent.HandleEvent(ev) focusedComponent.HandleEvent(ev)

1
testfile.txt Executable file
View File

@ -0,0 +1 @@
wtf thisis a test

View File

@ -3,6 +3,7 @@ package ui
import ( import (
"fmt" "fmt"
"strings" "strings"
"unicode"
"github.com/gdamore/tcell/v2" "github.com/gdamore/tcell/v2"
) )
@ -10,11 +11,6 @@ import (
// Item is an interface implemented by ItemEntry and ItemMenu to be listed in Menus. // Item is an interface implemented by ItemEntry and ItemMenu to be listed in Menus.
type Item interface { type Item interface {
GetName() string GetName() string
// GetShortcut returns the shortcut character to activate the Item without opening
// the Menu. For example, if the shortcut were 'a', then the user would have to press
// `Ctrl + a`. If the shortcut were 'A', then the user would press `Ctrl + Shift + a`.
// The zero value means "no shortcut".
GetShortcut() rune
} }
// An ItemSeparator is like a blank Item that cannot actually be selected. It is useful // An ItemSeparator is like a blank Item that cannot actually be selected. It is useful
@ -26,15 +22,11 @@ func (i *ItemSeparator) GetName() string {
return "" return ""
} }
func (i *ItemSeparator) GetShortcut() rune {
return 0
}
// ItemEntry is a listing in a Menu with a name and callback. // ItemEntry is a listing in a Menu with a name and callback.
type ItemEntry struct { type ItemEntry struct {
Name string Name string
Callback func()
Shortcut rune Shortcut rune
Callback func()
} }
// GetName returns the name of the ItemEntry. // GetName returns the name of the ItemEntry.
@ -42,19 +34,11 @@ func (i *ItemEntry) GetName() string {
return i.Name return i.Name
} }
func (i *ItemEntry) GetShortcut() rune {
return i.Shortcut
}
// GetName returns the name of the Menu. // GetName returns the name of the Menu.
func (m *Menu) GetName() string { func (m *Menu) GetName() string {
return m.Name return m.Name
} }
func (m *Menu) GetShortcut() rune {
return 0 // Menus don't have shortcuts
}
// A MenuBar is a horizontal list of menus. // A MenuBar is a horizontal list of menus.
type MenuBar struct { type MenuBar struct {
ItemSelectedCallback func() ItemSelectedCallback func()
@ -211,6 +195,24 @@ func (b *MenuBar) SetSize(width, height int) {
func (b *MenuBar) HandleEvent(event tcell.Event) bool { func (b *MenuBar) HandleEvent(event tcell.Event) bool {
switch ev := event.(type) { switch ev := event.(type) {
case *tcell.EventKey: case *tcell.EventKey:
// Shortcuts (Ctrl + s or Ctrl + (Shift) A, for example)
if ev.Modifiers() & tcell.ModCtrl != 0 {
// tcell calls Ctrl + rune keys "Ctrl-(RUNE)" so we want to remove the "Ctrl-"
// prefix, and turn the remaining part of the string into a rune.
keyRune := []rune(tcell.KeyNames[ev.Key()][5:])[0]
keyRune = unicode.ToLower(keyRune) // Make the rune lowercase.
// Find who the shortcut key belongs to
for i := range b.menus {
handled := b.menus[i].handleShortcut(keyRune)
if handled {
return true
}
}
return false // The shortcut key was not handled by any menus
}
switch ev.Key() { switch ev.Key() {
case tcell.KeyEnter: case tcell.KeyEnter:
if !b.menusVisible { // If menus are not visible... if !b.menusVisible { // If menus are not visible...
@ -229,6 +231,7 @@ func (b *MenuBar) HandleEvent(event tcell.Event) bool {
b.CursorRight() b.CursorRight()
} }
// Quick char
case tcell.KeyRune: // Search for the matching quick char in menu names case tcell.KeyRune: // Search for the matching quick char in menu names
if !b.menusVisible { // If the selected Menu is not open/visible if !b.menusVisible { // If the selected Menu is not open/visible
for i, m := range b.menus { for i, m := range b.menus {
@ -241,7 +244,7 @@ func (b *MenuBar) HandleEvent(event tcell.Event) bool {
} }
} else { } else {
return b.menus[b.selected].HandleEvent(event) // Have menu handle quick char event return b.menus[b.selected].HandleEvent(event) // Have menu handle quick char event
} }
default: default:
if b.menusVisible { if b.menusVisible {
@ -397,6 +400,24 @@ func (m *Menu) SetSize(width, height int) {
// Cannot set the size of a Menu // Cannot set the size of a Menu
} }
func (m *Menu) handleShortcut(r rune) bool {
for i := range m.Items {
switch typ := m.Items[i].(type) {
case *ItemSeparator:
continue
case *Menu:
return typ.handleShortcut(r) // Have the sub-menu handle the shortcut
case *ItemEntry:
if typ.Shortcut == r { // If this item matches the shortcut we're finding...
m.selected = i
m.ActivateItemUnderCursor() // Activate it
return true
}
}
}
return false
}
// HandleEvent will handle events for a Menu and may propogate them // HandleEvent will handle events for a Menu and may propogate them
// to sub-menus. Returns true if the event was handled. // to sub-menus. Returns true if the event was handled.
func (m *Menu) HandleEvent(event tcell.Event) bool { func (m *Menu) HandleEvent(event tcell.Event) bool {