Complete revamp of shortcut system in Menu
This commit is contained in:
parent
3cf168355a
commit
817b2c66c4
34
main.go
34
main.go
@ -87,10 +87,10 @@ func main() {
|
|||||||
|
|
||||||
fileMenu := ui.NewMenu("_File", &theme)
|
fileMenu := ui.NewMenu("_File", &theme)
|
||||||
|
|
||||||
fileMenu.AddItems([]ui.Item{&ui.ItemEntry{Name: "_New File", Shortcut: 'n', Callback: func() {
|
fileMenu.AddItems([]ui.Item{&ui.ItemEntry{Name: "_New File", Shortcut: "Ctrl+N", Callback: func() {
|
||||||
textEdit := ui.NewTextEdit(&s, "", "", &theme) // No file path, no contents
|
textEdit := ui.NewTextEdit(&s, "", "", &theme) // No file path, no contents
|
||||||
tabContainer.AddTab("noname", textEdit)
|
tabContainer.AddTab("noname", textEdit)
|
||||||
}}, &ui.ItemEntry{Name: "_Open...", Shortcut: 'o', Callback: func() {
|
}}, &ui.ItemEntry{Name: "_Open...", Shortcut: "Ctrl+O", Callback: func() {
|
||||||
callback := func(filePaths []string) {
|
callback := func(filePaths []string) {
|
||||||
for _, path := range filePaths {
|
for _, path := range filePaths {
|
||||||
file, err := os.Open(path)
|
file, err := os.Open(path)
|
||||||
@ -124,7 +124,7 @@ func main() {
|
|||||||
},
|
},
|
||||||
)
|
)
|
||||||
changeFocus(dialog)
|
changeFocus(dialog)
|
||||||
}}, &ui.ItemEntry{Name: "_Save", Shortcut: 's', Callback: func() {
|
}}, &ui.ItemEntry{Name: "_Save", Shortcut: "Ctrl+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)
|
||||||
@ -161,7 +161,7 @@ func main() {
|
|||||||
},
|
},
|
||||||
)
|
)
|
||||||
changeFocus(dialog)
|
changeFocus(dialog)
|
||||||
}}, &ui.ItemSeparator{}, &ui.ItemEntry{Name: "_Close", Shortcut: 'q', Callback: func() {
|
}}, &ui.ItemSeparator{}, &ui.ItemEntry{Name: "_Close", Shortcut: "Ctrl+Q", Callback: func() {
|
||||||
if tabContainer.GetTabCount() > 0 {
|
if tabContainer.GetTabCount() > 0 {
|
||||||
tabContainer.RemoveTab(tabContainer.GetSelectedTabIdx())
|
tabContainer.RemoveTab(tabContainer.GetSelectedTabIdx())
|
||||||
} else { // No tabs open; close the editor
|
} else { // No tabs open; close the editor
|
||||||
@ -172,21 +172,25 @@ func main() {
|
|||||||
|
|
||||||
panelMenu := ui.NewMenu("_Panel", &theme)
|
panelMenu := ui.NewMenu("_Panel", &theme)
|
||||||
|
|
||||||
panelMenu.AddItems([]ui.Item{&ui.ItemEntry{Name: "_Up", Callback: func() {
|
panelMenu.AddItems([]ui.Item{&ui.ItemEntry{Name: "Focus Up", Shortcut: "Alt+Up", Callback: func() {
|
||||||
|
|
||||||
}}, &ui.ItemEntry{Name: "_Down", Callback: func() {
|
}}, &ui.ItemEntry{Name: "Focus Down", Shortcut: "Alt+Down", Callback: func() {
|
||||||
|
|
||||||
}}, &ui.ItemEntry{Name: "_Left", Callback: func() {
|
}}, &ui.ItemEntry{Name: "Focus Left", Shortcut: "Alt+Left", Callback: func() {
|
||||||
|
|
||||||
}}, &ui.ItemEntry{Name: "_Right", Callback: func() {
|
}}, &ui.ItemEntry{Name: "Focus Right", Shortcut: "Alt+Right", Callback: func() {
|
||||||
|
|
||||||
}}, &ui.ItemSeparator{}, &ui.ItemEntry{Name: "Split _Left", Callback: func() {
|
}}, &ui.ItemSeparator{}, &ui.ItemEntry{Name: "Split _Top", Callback: func() {
|
||||||
|
|
||||||
|
}}, &ui.ItemEntry{Name: "Split _Bottom", Callback: func() {
|
||||||
|
|
||||||
|
}}, &ui.ItemEntry{Name: "Split _Left", Callback: func() {
|
||||||
|
|
||||||
}}, &ui.ItemEntry{Name: "Split _Right", Callback: func() {
|
}}, &ui.ItemEntry{Name: "Split _Right", Callback: func() {
|
||||||
|
|
||||||
}}, &ui.ItemSeparator{}, &ui.ItemEntry{Name: "_Move", Shortcut: 'm', Callback: func() {
|
}}, &ui.ItemSeparator{}, &ui.ItemEntry{Name: "_Move", Shortcut: "Ctrl+M", Callback: func() {
|
||||||
|
|
||||||
}}, &ui.ItemEntry{Name: "_Resize", Shortcut: 'r', Callback: func() {
|
}}, &ui.ItemEntry{Name: "_Resize", Shortcut: "Ctrl+R", Callback: func() {
|
||||||
|
|
||||||
}}, &ui.ItemEntry{Name: "_Float", Callback: func() {
|
}}, &ui.ItemEntry{Name: "_Float", Callback: func() {
|
||||||
|
|
||||||
@ -194,7 +198,7 @@ func main() {
|
|||||||
|
|
||||||
editMenu := ui.NewMenu("_Edit", &theme)
|
editMenu := ui.NewMenu("_Edit", &theme)
|
||||||
|
|
||||||
editMenu.AddItems([]ui.Item{&ui.ItemEntry{Name: "_Cut", Shortcut: 'x', Callback: func() {
|
editMenu.AddItems([]ui.Item{&ui.ItemEntry{Name: "_Cut", Shortcut: "Ctrl+X", 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)
|
||||||
@ -206,7 +210,7 @@ func main() {
|
|||||||
}
|
}
|
||||||
changeFocus(tabContainer)
|
changeFocus(tabContainer)
|
||||||
}
|
}
|
||||||
}}, &ui.ItemEntry{Name: "_Copy", Shortcut: 'c', Callback: func() {
|
}}, &ui.ItemEntry{Name: "_Copy", Shortcut: "Ctrl+C", 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)
|
||||||
@ -216,7 +220,7 @@ func main() {
|
|||||||
}
|
}
|
||||||
changeFocus(tabContainer)
|
changeFocus(tabContainer)
|
||||||
}
|
}
|
||||||
}}, &ui.ItemEntry{Name: "_Paste", Shortcut: 'v', Callback: func() {
|
}}, &ui.ItemEntry{Name: "_Paste", Shortcut: "Ctrl+V", 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)
|
||||||
@ -229,7 +233,7 @@ func main() {
|
|||||||
|
|
||||||
changeFocus(tabContainer)
|
changeFocus(tabContainer)
|
||||||
}
|
}
|
||||||
}}, &ui.ItemSeparator{}, &ui.ItemEntry{Name: "Select _All", Shortcut: 'a', Callback: func() {
|
}}, &ui.ItemSeparator{}, &ui.ItemEntry{Name: "Select _All", Shortcut: "Ctrl+A", Callback: func() {
|
||||||
|
|
||||||
}}, &ui.ItemEntry{Name: "Select _Line", Callback: func() {
|
}}, &ui.ItemEntry{Name: "Select _Line", Callback: func() {
|
||||||
|
|
||||||
|
56
ui/menu.go
56
ui/menu.go
@ -3,7 +3,6 @@ package ui
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"strings"
|
"strings"
|
||||||
"unicode"
|
|
||||||
|
|
||||||
"github.com/gdamore/tcell/v2"
|
"github.com/gdamore/tcell/v2"
|
||||||
runewidth "github.com/mattn/go-runewidth"
|
runewidth "github.com/mattn/go-runewidth"
|
||||||
@ -12,7 +11,11 @@ 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() rune
|
// A Shortcut is a string of the modifiers+key name of the action that must be pressed
|
||||||
|
// to trigger the shortcut. For example: "Ctrl+Alt+X". The order of the modifiers is
|
||||||
|
// very important. Letters are case-sensitive. See the KeyEvent.Name() function of tcell
|
||||||
|
// for information. An empty string implies no shortcut.
|
||||||
|
GetShortcut() string
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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
|
||||||
@ -24,14 +27,14 @@ func (i *ItemSeparator) GetName() string {
|
|||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
func (i *ItemSeparator) GetShortcut() rune {
|
func (i *ItemSeparator) GetShortcut() string {
|
||||||
return 0
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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
|
||||||
Shortcut rune
|
Shortcut string
|
||||||
Callback func()
|
Callback func()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -40,7 +43,7 @@ func (i *ItemEntry) GetName() string {
|
|||||||
return i.Name
|
return i.Name
|
||||||
}
|
}
|
||||||
|
|
||||||
func (i *ItemEntry) GetShortcut() rune {
|
func (i *ItemEntry) GetShortcut() string {
|
||||||
return i.Shortcut
|
return i.Shortcut
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -49,8 +52,8 @@ func (m *Menu) GetName() string {
|
|||||||
return m.Name
|
return m.Name
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *Menu) GetShortcut() rune {
|
func (m *Menu) GetShortcut() string {
|
||||||
return 0
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
// A MenuBar is a horizontal list of menus.
|
// A MenuBar is a horizontal list of menus.
|
||||||
@ -204,16 +207,15 @@ 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-A, for example)
|
// Shortcuts (Ctrl-s or Ctrl-A, for example)
|
||||||
if ev.Modifiers() & tcell.ModCtrl != 0 && strings.HasPrefix(tcell.KeyNames[ev.Key()], "Ctrl-") {
|
if ev.Modifiers() != 0 { // If there is a modifier on the key...
|
||||||
// tcell calls Ctrl + rune keys "Ctrl-(RUNE)" so we want to remove the "Ctrl-"
|
// tcell names it "Ctrl+(Key)" so we want to remove the "Ctrl+"
|
||||||
// prefix, and turn the remaining part of the string into a rune.
|
// prefix, and use the remaining part of the string as the shortcut.
|
||||||
keyRune := []rune(tcell.KeyNames[ev.Key()][5:])[0]
|
|
||||||
|
|
||||||
keyRune = unicode.ToLower(keyRune) // Make the rune lowercase.
|
keyName := ev.Name()
|
||||||
|
|
||||||
// Find who the shortcut key belongs to
|
// Find who the shortcut key belongs to
|
||||||
for i := range b.menus {
|
for i := range b.menus {
|
||||||
handled := b.menus[i].handleShortcut(keyRune)
|
handled := b.menus[i].handleShortcut(keyName)
|
||||||
if handled {
|
if handled {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
@ -362,13 +364,13 @@ func (m *Menu) Draw(s tcell.Screen) {
|
|||||||
sty = defaultStyle
|
sty = defaultStyle
|
||||||
}
|
}
|
||||||
|
|
||||||
len := DrawQuickCharStr(s, m.x+1, m.y+1+i, item.GetName(), sty)
|
nameLen := DrawQuickCharStr(s, m.x+1, m.y+1+i, item.GetName(), sty)
|
||||||
|
|
||||||
str := strings.Repeat(" ", m.width-2-len) // Fill space after menu names to border
|
str := strings.Repeat(" ", m.width-2-nameLen) // Fill space after menu names to border
|
||||||
DrawStr(s, m.x+1+len, m.y+1+i, str, sty)
|
DrawStr(s, m.x+1+nameLen, m.y+1+i, str, sty)
|
||||||
|
|
||||||
if shortcut := item.GetShortcut(); shortcut != 0 { // If the item has a shortcut...
|
if shortcut := item.GetShortcut(); len(shortcut) > 0 { // If the item has a shortcut...
|
||||||
str := " Ctrl+" + string(shortcut) + " "
|
str := " " + shortcut + " "
|
||||||
DrawStr(s, m.x+m.width-1-runewidth.StringWidth(str), m.y+1+i, str, sty)
|
DrawStr(s, m.x+m.width-1-runewidth.StringWidth(str), m.y+1+i, str, sty)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -401,21 +403,21 @@ func (m *Menu) GetMinSize() (int, int) {
|
|||||||
func (m *Menu) GetSize() (int, int) {
|
func (m *Menu) GetSize() (int, int) {
|
||||||
// TODO: no, pls don't do this
|
// TODO: no, pls don't do this
|
||||||
maxNameLen := 0
|
maxNameLen := 0
|
||||||
var widestRune int = 0 // Will contribute to the width
|
var widestShortcut int = 0 // Will contribute to the width
|
||||||
for i := range m.Items {
|
for i := range m.Items {
|
||||||
nameLen := len(m.Items[i].GetName())
|
nameLen := len(m.Items[i].GetName())
|
||||||
if nameLen > maxNameLen {
|
if nameLen > maxNameLen {
|
||||||
maxNameLen = nameLen
|
maxNameLen = nameLen
|
||||||
}
|
}
|
||||||
|
|
||||||
if s := m.Items[i].GetShortcut(); runewidth.RuneWidth(s) > widestRune {
|
if key := m.Items[i].GetShortcut(); runewidth.StringWidth(key) > widestShortcut {
|
||||||
widestRune = runewidth.RuneWidth(s) // For the sake of good unicode
|
widestShortcut = runewidth.StringWidth(key) // For the sake of good unicode
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
shortcutsWidth := 0
|
shortcutsWidth := 0
|
||||||
if widestRune > 0 {
|
if widestShortcut > 0 {
|
||||||
shortcutsWidth = 2 + 5 + widestRune + 1 // " Ctrl+(rune) " (with one cell padding surrounding)
|
shortcutsWidth = 1 + widestShortcut + 1 // " Ctrl+X " (with one cell padding surrounding)
|
||||||
}
|
}
|
||||||
|
|
||||||
m.width = 1 + maxNameLen + shortcutsWidth + 1 // Add two for padding
|
m.width = 1 + maxNameLen + shortcutsWidth + 1 // Add two for padding
|
||||||
@ -428,15 +430,15 @@ 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 {
|
func (m *Menu) handleShortcut(key string) bool {
|
||||||
for i := range m.Items {
|
for i := range m.Items {
|
||||||
switch typ := m.Items[i].(type) {
|
switch typ := m.Items[i].(type) {
|
||||||
case *ItemSeparator:
|
case *ItemSeparator:
|
||||||
continue
|
continue
|
||||||
case *Menu:
|
case *Menu:
|
||||||
return typ.handleShortcut(r) // Have the sub-menu handle the shortcut
|
return typ.handleShortcut(key) // Have the sub-menu handle the shortcut
|
||||||
case *ItemEntry:
|
case *ItemEntry:
|
||||||
if typ.Shortcut == r { // If this item matches the shortcut we're finding...
|
if typ.Shortcut == key { // If this item matches the shortcut we're finding...
|
||||||
m.selected = i
|
m.selected = i
|
||||||
m.ActivateItemUnderCursor() // Activate it
|
m.ActivateItemUnderCursor() // Activate it
|
||||||
return true
|
return true
|
||||||
|
Loading…
x
Reference in New Issue
Block a user