Menu & MenuBar experience improvements
This commit is contained in:
parent
1d77aae277
commit
83567c1341
23
main.go
23
main.go
@ -70,8 +70,9 @@ func main() {
|
||||
|
||||
barFocused := false
|
||||
|
||||
// TODO: load menus in another function
|
||||
bar.AddMenu(ui.NewMenu("File", &theme, []ui.Item{&ui.ItemEntry{Name: "New File", Callback: func() {
|
||||
fileMenu := ui.NewMenu("File", &theme)
|
||||
|
||||
fileMenu.AddItems([]ui.Item{&ui.ItemEntry{Name: "New File", Callback: func() {
|
||||
textEdit := ui.NewTextEdit(&s, "", "", &theme) // No file path, no contents
|
||||
tabContainer.AddTab("noname", textEdit)
|
||||
}}, &ui.ItemEntry{Name: "Open...", Callback: func() {
|
||||
@ -144,9 +145,11 @@ func main() {
|
||||
}}, &ui.ItemSeparator{}, &ui.ItemEntry{Name: "Exit", Callback: func() {
|
||||
s.Fini()
|
||||
os.Exit(0)
|
||||
}}}))
|
||||
}}})
|
||||
|
||||
bar.AddMenu(ui.NewMenu("Edit", &theme, []ui.Item{&ui.ItemEntry{Name: "Cut", Callback: func() {
|
||||
editMenu := ui.NewMenu("Edit", &theme)
|
||||
|
||||
editMenu.AddItems([]ui.Item{&ui.ItemEntry{Name: "Cut", Callback: func() {
|
||||
if tabContainer.GetTabCount() > 0 {
|
||||
tab := tabContainer.GetTab(tabContainer.GetSelectedTabIdx())
|
||||
te := tab.Child.(*ui.TextEdit)
|
||||
@ -177,11 +180,17 @@ func main() {
|
||||
}
|
||||
te.Insert(contents)
|
||||
}
|
||||
}}}))
|
||||
}}})
|
||||
|
||||
bar.AddMenu(ui.NewMenu("Search", &theme, []ui.Item{&ui.ItemEntry{Name: "New", Callback: func() {
|
||||
searchMenu := ui.NewMenu("Search", &theme)
|
||||
|
||||
searchMenu.AddItems([]ui.Item{&ui.ItemEntry{Name: "New", Callback: func() {
|
||||
s.Beep()
|
||||
}}}))
|
||||
}}})
|
||||
|
||||
bar.AddMenu(fileMenu)
|
||||
bar.AddMenu(editMenu)
|
||||
bar.AddMenu(searchMenu)
|
||||
|
||||
changeFocus(tabContainer) // TabContainer is focused by default
|
||||
|
||||
|
122
ui/menu.go
122
ui/menu.go
@ -52,6 +52,7 @@ type MenuBar struct {
|
||||
width, height int
|
||||
focused bool
|
||||
selected int // Index of selection in MenuBar
|
||||
menusVisible bool // Whether to draw the selected menu
|
||||
|
||||
Theme *Theme
|
||||
}
|
||||
@ -65,7 +66,8 @@ func NewMenuBar(theme *Theme) *MenuBar {
|
||||
|
||||
func (b *MenuBar) AddMenu(menu *Menu) {
|
||||
menu.itemSelectedCallback = func() {
|
||||
// TODO: figure out what im doing here
|
||||
b.menusVisible = false
|
||||
menu.SetFocused(false)
|
||||
}
|
||||
b.Menus = append(b.Menus, menu)
|
||||
}
|
||||
@ -84,7 +86,7 @@ func (b *MenuBar) Draw(s tcell.Screen) {
|
||||
normalStyle := b.Theme.GetOrDefault("MenuBar")
|
||||
|
||||
// Draw menus based on whether b.focused and which is selected
|
||||
DrawRect(s, b.x, b.y, 200, 1, ' ', normalStyle) // TODO: calculate actual width
|
||||
DrawRect(s, b.x, b.y, b.width, 1, ' ', normalStyle)
|
||||
col := b.x + 1
|
||||
for i, item := range b.Menus {
|
||||
str := fmt.Sprintf(" %s ", item.Name) // Surround the name in spaces
|
||||
@ -98,7 +100,7 @@ func (b *MenuBar) Draw(s tcell.Screen) {
|
||||
col += len(str)
|
||||
}
|
||||
|
||||
if b.Menus[b.selected].Visible {
|
||||
if b.menusVisible {
|
||||
menu := b.Menus[b.selected]
|
||||
menu.Draw(s) // Draw menu when it is expanded / visible
|
||||
}
|
||||
@ -107,8 +109,12 @@ func (b *MenuBar) Draw(s tcell.Screen) {
|
||||
// SetFocused highlights the MenuBar and focuses any sub-menus.
|
||||
func (b *MenuBar) SetFocused(v bool) {
|
||||
b.focused = v
|
||||
b.Menus[b.selected].SetFocused(v)
|
||||
if !v {
|
||||
b.Menus[b.selected].SetFocused(false)
|
||||
b.selected = 0 // Reset cursor position every time component is unfocused
|
||||
if b.menusVisible {
|
||||
b.menusVisible = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -145,11 +151,19 @@ func (b *MenuBar) SetSize(width, height int) {
|
||||
func (b *MenuBar) HandleEvent(event tcell.Event) bool {
|
||||
switch ev := event.(type) {
|
||||
case *tcell.EventKey:
|
||||
if ev.Key() == tcell.KeyEnter && !b.Menus[b.selected].Visible {
|
||||
switch ev.Key() {
|
||||
case tcell.KeyEnter:
|
||||
if !b.menusVisible { // If menus are not visible...
|
||||
b.menusVisible = true
|
||||
|
||||
menu := &b.Menus[b.selected]
|
||||
(*menu).SetPos(b.GetMenuXPos(b.selected), b.y+1)
|
||||
(*menu).SetFocused(true) // Makes .Visible true for the Menu
|
||||
} else if ev.Key() == tcell.KeyLeft {
|
||||
} else { // The selected Menu is visible, send the event to it
|
||||
return b.Menus[b.selected].HandleEvent(event)
|
||||
}
|
||||
case tcell.KeyLeft:
|
||||
b.Menus[b.selected].SetFocused(false) // Unfocus current menu
|
||||
if b.selected <= 0 {
|
||||
b.selected = len(b.Menus) - 1 // Wrap to end
|
||||
} else {
|
||||
@ -157,7 +171,9 @@ func (b *MenuBar) HandleEvent(event tcell.Event) bool {
|
||||
}
|
||||
// Update position of new menu after changing menu selection
|
||||
b.Menus[b.selected].SetPos(b.GetMenuXPos(b.selected), b.y+1)
|
||||
} else if ev.Key() == tcell.KeyRight {
|
||||
b.Menus[b.selected].SetFocused(true) // Focus new menu
|
||||
case tcell.KeyRight:
|
||||
b.Menus[b.selected].SetFocused(false)
|
||||
if b.selected >= len(b.Menus)-1 {
|
||||
b.selected = 0 // Wrap to beginning
|
||||
} else {
|
||||
@ -165,8 +181,26 @@ func (b *MenuBar) HandleEvent(event tcell.Event) bool {
|
||||
}
|
||||
// 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].SetFocused(true) // Focus new menu
|
||||
|
||||
case tcell.KeyRune: // Search for the matching quick char in menu names
|
||||
if !b.menusVisible { // If the selected Menu is not open/visible
|
||||
for _, m := range b.Menus {
|
||||
found, r := QuickCharInString(m.Name)
|
||||
if found && r == ev.Rune() {
|
||||
b.menusVisible = true
|
||||
|
||||
menu := &b.Menus[b.selected]
|
||||
(*menu).SetPos(b.GetMenuXPos(b.selected), b.y+1)
|
||||
(*menu).SetFocused(true)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if b.Menus[b.selected].Visible {
|
||||
return b.Menus[b.selected].HandleEvent(event) // Have menu handle quick char event
|
||||
}
|
||||
|
||||
default:
|
||||
if b.menusVisible {
|
||||
return b.Menus[b.selected].HandleEvent(event)
|
||||
} else {
|
||||
return false // Nobody to propogate our event to
|
||||
@ -181,7 +215,6 @@ func (b *MenuBar) HandleEvent(event tcell.Event) bool {
|
||||
type Menu struct {
|
||||
Name string
|
||||
Items []Item
|
||||
Visible bool // True when focused
|
||||
|
||||
x, y int
|
||||
width, height int // Size may not be settable
|
||||
@ -192,18 +225,40 @@ type Menu struct {
|
||||
}
|
||||
|
||||
// New creates a new Menu. `items` can be `nil`.
|
||||
func NewMenu(name string, theme *Theme, items []Item) *Menu {
|
||||
if items == nil {
|
||||
items = make([]Item, 0, 6)
|
||||
}
|
||||
|
||||
func NewMenu(name string, theme *Theme) *Menu {
|
||||
return &Menu{
|
||||
Name: name,
|
||||
Items: items,
|
||||
Items: make([]Item, 0, 6),
|
||||
Theme: theme,
|
||||
}
|
||||
}
|
||||
|
||||
func (m *Menu) AddItem(item Item) {
|
||||
switch typ := item.(type) {
|
||||
case *Menu:
|
||||
typ.itemSelectedCallback = func() {
|
||||
m.itemSelectedCallback()
|
||||
}
|
||||
}
|
||||
m.Items = append(m.Items, item)
|
||||
}
|
||||
|
||||
func (m *Menu) AddItems(items []Item) {
|
||||
for _, item := range items {
|
||||
m.AddItem(item)
|
||||
}
|
||||
}
|
||||
|
||||
func (m *Menu) ActivateItemUnderCursor() {
|
||||
switch item := m.Items[m.selected].(type) {
|
||||
case *ItemEntry:
|
||||
item.Callback()
|
||||
m.itemSelectedCallback()
|
||||
case *Menu:
|
||||
// TODO: implement sub-menus ...
|
||||
}
|
||||
}
|
||||
|
||||
func (m *Menu) CursorUp() {
|
||||
if m.selected <= 0 {
|
||||
m.selected = len(m.Items) - 1 // Wrap to end
|
||||
@ -260,7 +315,10 @@ func (m *Menu) Draw(s tcell.Screen) {
|
||||
|
||||
// SetFocused does not do anything for a Menu.
|
||||
func (m *Menu) SetFocused(v bool) {
|
||||
m.Visible = v
|
||||
// TODO: when adding sub-menus, set all focus to v
|
||||
if !v {
|
||||
m.selected = 0
|
||||
}
|
||||
}
|
||||
|
||||
// GetPos returns the position of the Menu.
|
||||
@ -299,22 +357,28 @@ func (m *Menu) HandleEvent(event tcell.Event) bool {
|
||||
// TODO: simplify this function
|
||||
switch ev := event.(type) {
|
||||
case *tcell.EventKey:
|
||||
if ev.Key() == tcell.KeyEnter {
|
||||
m.SetFocused(false) // Hides the menu
|
||||
switch item := m.Items[m.selected].(type) {
|
||||
case *ItemEntry:
|
||||
item.Callback()
|
||||
case *Menu:
|
||||
// TODO: implement sub-menus ...
|
||||
}
|
||||
return true
|
||||
} else if ev.Key() == tcell.KeyUp {
|
||||
switch ev.Key() {
|
||||
case tcell.KeyEnter:
|
||||
m.ActivateItemUnderCursor()
|
||||
case tcell.KeyUp:
|
||||
m.CursorUp()
|
||||
return true
|
||||
} else if ev.Key() == tcell.KeyDown {
|
||||
case tcell.KeyDown:
|
||||
m.CursorDown()
|
||||
return true
|
||||
|
||||
case tcell.KeyRune:
|
||||
// TODO: support quick chars for sub-menus
|
||||
for i, item := range m.Items {
|
||||
found, r := QuickCharInString(item.GetName())
|
||||
if found && r == ev.Rune() {
|
||||
m.selected = i
|
||||
m.ActivateItemUnderCursor()
|
||||
}
|
||||
}
|
||||
|
||||
default:
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
20
ui/util.go
20
ui/util.go
@ -1,5 +1,25 @@
|
||||
package ui
|
||||
|
||||
import "unicode"
|
||||
|
||||
// QuickCharInString is used for finding the "quick char" in a string. A quick char
|
||||
// suffixes a '_' (underscore). So basically, this function returns any rune after
|
||||
// an underscore. The rune is always made lowercase. The bool returned is whether
|
||||
// the rune was found.
|
||||
func QuickCharInString(s string) (bool, rune) {
|
||||
runes := []rune(s)
|
||||
for i, r := range runes {
|
||||
if r == '_' {
|
||||
if i+1 < len(runes) {
|
||||
return true, unicode.ToLower(runes[i+1])
|
||||
} else {
|
||||
return false, ' '
|
||||
}
|
||||
}
|
||||
}
|
||||
return false, ' '
|
||||
}
|
||||
|
||||
// Max returns the larger integer.
|
||||
func Max(a, b int) int {
|
||||
if a > b {
|
||||
|
Loading…
x
Reference in New Issue
Block a user