From 817b2c66c4a5fd29475fa20b2ef8536a9c37a162 Mon Sep 17 00:00:00 2001 From: "Luke I. Wilson" Date: Sat, 20 Mar 2021 23:10:23 -0500 Subject: [PATCH] Complete revamp of shortcut system in Menu --- main.go | 34 ++++++++++++++++++--------------- ui/menu.go | 56 ++++++++++++++++++++++++++++-------------------------- 2 files changed, 48 insertions(+), 42 deletions(-) diff --git a/main.go b/main.go index 8364355..a7b8a50 100644 --- a/main.go +++ b/main.go @@ -87,10 +87,10 @@ func main() { 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 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) { for _, path := range filePaths { file, err := os.Open(path) @@ -124,7 +124,7 @@ func main() { }, ) changeFocus(dialog) - }}, &ui.ItemEntry{Name: "_Save", Shortcut: 's', Callback: func() { + }}, &ui.ItemEntry{Name: "_Save", Shortcut: "Ctrl+S", Callback: func() { if tabContainer.GetTabCount() > 0 { tab := tabContainer.GetTab(tabContainer.GetSelectedTabIdx()) te := tab.Child.(*ui.TextEdit) @@ -161,7 +161,7 @@ func main() { }, ) 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 { tabContainer.RemoveTab(tabContainer.GetSelectedTabIdx()) } else { // No tabs open; close the editor @@ -172,21 +172,25 @@ func main() { 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.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() { @@ -194,7 +198,7 @@ func main() { 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 { tab := tabContainer.GetTab(tabContainer.GetSelectedTabIdx()) te := tab.Child.(*ui.TextEdit) @@ -206,7 +210,7 @@ func main() { } changeFocus(tabContainer) } - }}, &ui.ItemEntry{Name: "_Copy", Shortcut: 'c', Callback: func() { + }}, &ui.ItemEntry{Name: "_Copy", Shortcut: "Ctrl+C", Callback: func() { if tabContainer.GetTabCount() > 0 { tab := tabContainer.GetTab(tabContainer.GetSelectedTabIdx()) te := tab.Child.(*ui.TextEdit) @@ -216,7 +220,7 @@ func main() { } changeFocus(tabContainer) } - }}, &ui.ItemEntry{Name: "_Paste", Shortcut: 'v', Callback: func() { + }}, &ui.ItemEntry{Name: "_Paste", Shortcut: "Ctrl+V", Callback: func() { if tabContainer.GetTabCount() > 0 { tab := tabContainer.GetTab(tabContainer.GetSelectedTabIdx()) te := tab.Child.(*ui.TextEdit) @@ -229,7 +233,7 @@ func main() { 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() { diff --git a/ui/menu.go b/ui/menu.go index 6e6fce7..3acd42d 100644 --- a/ui/menu.go +++ b/ui/menu.go @@ -3,7 +3,6 @@ package ui import ( "fmt" "strings" - "unicode" "github.com/gdamore/tcell/v2" 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. type Item interface { 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 @@ -24,14 +27,14 @@ func (i *ItemSeparator) GetName() string { return "" } -func (i *ItemSeparator) GetShortcut() rune { - return 0 +func (i *ItemSeparator) GetShortcut() string { + return "" } // ItemEntry is a listing in a Menu with a name and callback. type ItemEntry struct { Name string - Shortcut rune + Shortcut string Callback func() } @@ -40,7 +43,7 @@ func (i *ItemEntry) GetName() string { return i.Name } -func (i *ItemEntry) GetShortcut() rune { +func (i *ItemEntry) GetShortcut() string { return i.Shortcut } @@ -49,8 +52,8 @@ func (m *Menu) GetName() string { return m.Name } -func (m *Menu) GetShortcut() rune { - return 0 +func (m *Menu) GetShortcut() string { + return "" } // A MenuBar is a horizontal list of menus. @@ -204,16 +207,15 @@ func (b *MenuBar) HandleEvent(event tcell.Event) bool { switch ev := event.(type) { case *tcell.EventKey: // Shortcuts (Ctrl-s or Ctrl-A, for example) - if ev.Modifiers() & tcell.ModCtrl != 0 && strings.HasPrefix(tcell.KeyNames[ev.Key()], "Ctrl-") { - // 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] + if ev.Modifiers() != 0 { // If there is a modifier on the key... + // tcell names it "Ctrl+(Key)" so we want to remove the "Ctrl+" + // prefix, and use the remaining part of the string as the shortcut. - keyRune = unicode.ToLower(keyRune) // Make the rune lowercase. + keyName := ev.Name() // Find who the shortcut key belongs to for i := range b.menus { - handled := b.menus[i].handleShortcut(keyRune) + handled := b.menus[i].handleShortcut(keyName) if handled { return true } @@ -362,13 +364,13 @@ func (m *Menu) Draw(s tcell.Screen) { 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 - DrawStr(s, m.x+1+len, m.y+1+i, str, sty) + str := strings.Repeat(" ", m.width-2-nameLen) // Fill space after menu names to border + DrawStr(s, m.x+1+nameLen, m.y+1+i, str, sty) - if shortcut := item.GetShortcut(); shortcut != 0 { // If the item has a shortcut... - str := " Ctrl+" + string(shortcut) + " " + if shortcut := item.GetShortcut(); len(shortcut) > 0 { // If the item has a shortcut... + str := " " + shortcut + " " 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) { // TODO: no, pls don't do this 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 { nameLen := len(m.Items[i].GetName()) if nameLen > maxNameLen { maxNameLen = nameLen } - if s := m.Items[i].GetShortcut(); runewidth.RuneWidth(s) > widestRune { - widestRune = runewidth.RuneWidth(s) // For the sake of good unicode + if key := m.Items[i].GetShortcut(); runewidth.StringWidth(key) > widestShortcut { + widestShortcut = runewidth.StringWidth(key) // For the sake of good unicode } } shortcutsWidth := 0 - if widestRune > 0 { - shortcutsWidth = 2 + 5 + widestRune + 1 // " Ctrl+(rune) " (with one cell padding surrounding) + if widestShortcut > 0 { + shortcutsWidth = 1 + widestShortcut + 1 // " Ctrl+X " (with one cell padding surrounding) } 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 } -func (m *Menu) handleShortcut(r rune) bool { +func (m *Menu) handleShortcut(key string) 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 + return typ.handleShortcut(key) // Have the sub-menu handle the shortcut 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.ActivateItemUnderCursor() // Activate it return true