Add ItemSelectedCallback to MenuBar & make Menus field private

This commit is contained in:
Luke I. Wilson 2021-03-20 14:53:41 -05:00
parent bc08505fa8
commit fefa34c2b6
2 changed files with 52 additions and 30 deletions

10
main.go
View File

@ -79,10 +79,16 @@ func main() {
var fileSelector *ui.FileSelectorDialog // if nil, we don't draw it var fileSelector *ui.FileSelectorDialog // if nil, we don't draw it
bar := ui.NewMenuBar(&theme)
barFocused := false barFocused := false
bar := ui.NewMenuBar(&theme)
bar.ItemSelectedCallback = func() {
// When something is selected in the MenuBar,
// we change focus back to the tab container.
changeFocus(tabContainer)
barFocused = false
}
fileMenu := ui.NewMenu("_File", &theme) fileMenu := ui.NewMenu("_File", &theme)
fileMenu.AddItems([]ui.Item{&ui.ItemEntry{Name: "_New File", Callback: func() { fileMenu.AddItems([]ui.Item{&ui.ItemEntry{Name: "_New File", Callback: func() {

View File

@ -9,57 +9,69 @@ 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 {
isItem()
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
// for separating items in a Menu. // for separating items in a Menu.
type ItemSeparator struct{} type ItemSeparator struct{}
func (i *ItemSeparator) isItem() {}
// GetName returns an empty string. // GetName returns an empty string.
func (i *ItemSeparator) GetName() string { 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() Callback func()
Shortcut rune
} }
func (i *ItemEntry) isItem() {}
// GetName returns the name of the ItemEntry. // GetName returns the name of the ItemEntry.
func (i *ItemEntry) GetName() string { func (i *ItemEntry) GetName() string {
return i.Name return i.Name
} }
func (m *Menu) isItem() {} 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 {
Menus []*Menu ItemSelectedCallback func()
menus []*Menu
x, y int x, y int
width, height int width, height int
focused bool focused bool
selected int // Index of selection in MenuBar selected int // Index of selection in MenuBar
menusVisible bool // Whether to draw the selected menu menusVisible bool // Whether to draw the selected menu
Theme *Theme Theme *Theme
} }
func NewMenuBar(theme *Theme) *MenuBar { func NewMenuBar(theme *Theme) *MenuBar {
return &MenuBar{ return &MenuBar{
Menus: make([]*Menu, 0, 6), menus: make([]*Menu, 0, 6),
Theme: theme, Theme: theme,
} }
} }
@ -68,50 +80,54 @@ func (b *MenuBar) AddMenu(menu *Menu) {
menu.itemSelectedCallback = func() { menu.itemSelectedCallback = func() {
b.menusVisible = false b.menusVisible = false
menu.SetFocused(false) menu.SetFocused(false)
if b.ItemSelectedCallback != nil {
b.ItemSelectedCallback()
}
} }
b.Menus = append(b.Menus, menu) b.menus = append(b.menus, menu)
} }
// GetMenuXPos returns the X position of the name of Menu at `idx` visually. // GetMenuXPos returns the X position of the name of Menu at `idx` visually.
func (b *MenuBar) GetMenuXPos(idx int) int { func (b *MenuBar) GetMenuXPos(idx int) int {
x := 1 x := 1
for i := 0; i < idx; i++ { for i := 0; i < idx; i++ {
x += len(b.Menus[i].Name) + 2 // two for padding x += len(b.menus[i].Name) + 2 // two for padding
} }
return x return x
} }
func (b *MenuBar) ActivateMenuUnderCursor() { func (b *MenuBar) ActivateMenuUnderCursor() {
b.menusVisible = true // Show menus b.menusVisible = true // Show menus
menu := &b.Menus[b.selected] menu := &b.menus[b.selected]
(*menu).SetPos(b.GetMenuXPos(b.selected), b.y+1) (*menu).SetPos(b.GetMenuXPos(b.selected), b.y+1)
(*menu).SetFocused(true) (*menu).SetFocused(true)
} }
func (b *MenuBar) CursorLeft() { func (b *MenuBar) CursorLeft() {
if b.menusVisible { if b.menusVisible {
b.Menus[b.selected].SetFocused(false) // Unfocus current menu b.menus[b.selected].SetFocused(false) // Unfocus current menu
} }
if b.selected <= 0 { if b.selected <= 0 {
b.selected = len(b.Menus) - 1 // Wrap to end b.selected = len(b.menus) - 1 // Wrap to end
} else { } else {
b.selected-- b.selected--
} }
if b.menusVisible { if b.menusVisible {
// Update position of new menu after changing menu selection // Update position of new menu after changing menu selection
b.Menus[b.selected].SetPos(b.GetMenuXPos(b.selected), b.y+1) b.menus[b.selected].SetPos(b.GetMenuXPos(b.selected), b.y+1)
b.Menus[b.selected].SetFocused(true) // Focus new menu b.menus[b.selected].SetFocused(true) // Focus new menu
} }
} }
func (b *MenuBar) CursorRight() { func (b *MenuBar) CursorRight() {
if b.menusVisible { if b.menusVisible {
b.Menus[b.selected].SetFocused(false) b.menus[b.selected].SetFocused(false)
} }
if b.selected >= len(b.Menus)-1 { if b.selected >= len(b.menus)-1 {
b.selected = 0 // Wrap to beginning b.selected = 0 // Wrap to beginning
} else { } else {
b.selected++ b.selected++
@ -119,8 +135,8 @@ func (b *MenuBar) CursorRight() {
if b.menusVisible { if b.menusVisible {
// Update position of new menu after changing menu selection // Update position of new menu after changing menu selection
b.Menus[b.selected].SetPos(b.GetMenuXPos(b.selected), b.y+1) b.menus[b.selected].SetPos(b.GetMenuXPos(b.selected), b.y+1)
b.Menus[b.selected].SetFocused(true) // Focus new menu b.menus[b.selected].SetFocused(true) // Focus new menu
} }
} }
@ -131,7 +147,7 @@ func (b *MenuBar) Draw(s tcell.Screen) {
// Draw menus based on whether b.focused and which is selected // Draw menus based on whether b.focused and which is selected
DrawRect(s, b.x, b.y, b.width, 1, ' ', normalStyle) DrawRect(s, b.x, b.y, b.width, 1, ' ', normalStyle)
col := b.x + 1 col := b.x + 1
for i, item := range b.Menus { for i, item := range b.menus {
str := fmt.Sprintf(" %s ", item.Name) // Surround the name in spaces str := fmt.Sprintf(" %s ", item.Name) // Surround the name in spaces
sty := normalStyle sty := normalStyle
@ -145,7 +161,7 @@ func (b *MenuBar) Draw(s tcell.Screen) {
} }
if b.menusVisible { if b.menusVisible {
menu := b.Menus[b.selected] menu := b.menus[b.selected]
menu.Draw(s) // Draw menu when it is expanded / visible menu.Draw(s) // Draw menu when it is expanded / visible
} }
} }
@ -153,7 +169,7 @@ func (b *MenuBar) Draw(s tcell.Screen) {
// SetFocused highlights the MenuBar and focuses any sub-menus. // SetFocused highlights the MenuBar and focuses any sub-menus.
func (b *MenuBar) SetFocused(v bool) { func (b *MenuBar) SetFocused(v bool) {
b.focused = v b.focused = v
b.Menus[b.selected].SetFocused(v) b.menus[b.selected].SetFocused(v)
if !v { if !v {
b.selected = 0 // Reset cursor position every time component is unfocused b.selected = 0 // Reset cursor position every time component is unfocused
if b.menusVisible { if b.menusVisible {
@ -200,7 +216,7 @@ func (b *MenuBar) HandleEvent(event tcell.Event) bool {
if !b.menusVisible { // If menus are not visible... if !b.menusVisible { // If menus are not visible...
b.ActivateMenuUnderCursor() b.ActivateMenuUnderCursor()
} else { // The selected Menu is visible, send the event to it } else { // The selected Menu is visible, send the event to it
return b.Menus[b.selected].HandleEvent(event) return b.menus[b.selected].HandleEvent(event)
} }
case tcell.KeyLeft: case tcell.KeyLeft:
b.CursorLeft() b.CursorLeft()
@ -208,14 +224,14 @@ func (b *MenuBar) HandleEvent(event tcell.Event) bool {
b.CursorRight() b.CursorRight()
case tcell.KeyTab: case tcell.KeyTab:
if b.menusVisible { if b.menusVisible {
return b.Menus[b.selected].HandleEvent(event) return b.menus[b.selected].HandleEvent(event)
} else { } else {
b.CursorRight() b.CursorRight()
} }
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 {
found, r := QuickCharInString(m.Name) found, r := QuickCharInString(m.Name)
if found && r == ev.Rune() { if found && r == ev.Rune() {
b.selected = i // Select menu at i b.selected = i // Select menu at i
@ -224,12 +240,12 @@ 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 {
return b.Menus[b.selected].HandleEvent(event) return b.menus[b.selected].HandleEvent(event)
} else { } else {
return false // Nobody to propogate our event to return false // Nobody to propogate our event to
} }