Add ContextMenu

This commit is contained in:
Trevor Slocum 2020-04-18 20:00:36 -07:00
parent ae4c7b398c
commit 57ef7437e3
10 changed files with 573 additions and 75 deletions

View file

@ -1,5 +1,6 @@
v1.4.5 (WIP)
- Add multithreading support
- Add ContextMenu (initially supported by List)
- Merge upstream mouse support
v1.4.4 (2020-02-24)

View file

@ -22,7 +22,7 @@ Available widgets:
- Navigable multi-color __text views__
- Sophisticated navigable __table views__
- Flexible __tree views__
- Selectable __lists__
- Selectable __lists__ with __context menus__
- __Grid__, __Flexbox__ and __page layouts__
- Modal __message windows__
- Horizontal and vertical __progress bars__

6
box.go
View file

@ -50,6 +50,9 @@ type Box struct {
// Whether or not this box has focus.
hasFocus bool
// Whether or not this box shows its focus.
showFocus bool
// An optional capture function which receives a key event and returns the
// event to be forwarded to the primitive's default input handler (nil if
// nothing should be forwarded).
@ -76,6 +79,7 @@ func NewBox() *Box {
borderColor: Styles.BorderColor,
titleColor: Styles.TitleColor,
titleAlign: AlignCenter,
showFocus: true,
}
b.focus = b
return b
@ -389,7 +393,7 @@ func (b *Box) Draw(screen tcell.Screen) {
} else {
hasFocus = b.focus.HasFocus()
}
if hasFocus {
if hasFocus && b.showFocus {
horizontal = Borders.HorizontalFocus
vertical = Borders.VerticalFocus
topLeft = Borders.TopLeftFocus

151
contextmenu.go Normal file
View file

@ -0,0 +1,151 @@
package cview
import "sync"
// ContextMenu is a menu that appears upon user interaction, such as right
// clicking or pressing Alt+Enter.
type ContextMenu struct {
parent Primitive
item int
open bool
drag bool
list *List
x, y int
selected func(int, string, rune)
l sync.Mutex
}
// NewContextMenu returns a new context menu.
func NewContextMenu(parent Primitive) *ContextMenu {
c := &ContextMenu{parent: parent}
return c
}
// ContextMenuList returns the underlying List of the context menu.
func (c *ContextMenu) ContextMenuList() *List {
c.l.Lock()
defer c.l.Unlock()
return c.list
}
// AddContextItem adds an item to the context menu. Adding an item with no text
// or shortcut will add a divider.
func (c *ContextMenu) AddContextItem(text string, shortcut rune, selected func(index int)) *ContextMenu {
c.l.Lock()
defer c.l.Unlock()
if c.list == nil {
c.list = NewList().
ShowSecondaryText(false).
SetHover(true).
SetWrapAround(true)
c.list.
SetBorder(true).
SetBorderPadding(
Styles.ContextMenuPaddingTop,
Styles.ContextMenuPaddingBottom,
Styles.ContextMenuPaddingLeft,
Styles.ContextMenuPaddingRight)
c.list.showFocus = false
}
c.list.AddItem(text, "", shortcut, c.wrap(selected))
if text == "" && shortcut == 0 {
c.list.Lock()
index := len(c.list.items) - 1
c.list.items[index].Enabled = false
c.list.Unlock()
}
return c
}
func (c *ContextMenu) wrap(f func(index int)) func() {
return func() {
f(c.item)
}
}
// ClearContextMenu removes all items from the context menu.
func (c *ContextMenu) ClearContextMenu() *ContextMenu {
c.l.Lock()
defer c.l.Unlock()
if c.list != nil {
c.list.Clear()
}
return c
}
// SetContextSelectedFunc sets the function which is called when the user
// selects a context menu item. The function receives the item's index in the
// menu (starting with 0), its text and its shortcut rune. SetSelectedFunc must
// be called before the context menu is shown.
func (c *ContextMenu) SetContextSelectedFunc(handler func(index int, text string, shortcut rune)) *ContextMenu {
c.l.Lock()
defer c.l.Unlock()
c.selected = handler
return c
}
// ShowContextMenu shows the context menu.
func (c *ContextMenu) ShowContextMenu(item int, x int, y int, setFocus func(Primitive)) {
c.l.Lock()
defer c.l.Unlock()
c.show(item, x, y, setFocus)
}
func (c *ContextMenu) show(item int, x int, y int, setFocus func(Primitive)) {
if c.list == nil || len(c.list.items) == 0 {
return
}
c.open = true
c.item = item
c.x, c.y = x, y
c.list.Lock()
for i, item := range c.list.items {
if item.Enabled {
c.list.currentItem = i
break
}
}
c.list.Unlock()
c.list.SetSelectedFunc(func(index int, mainText, secondaryText string, shortcut rune) {
c.l.Lock()
// A context item was selected. Close the menu.
c.hide(setFocus)
if c.selected != nil {
c.l.Unlock()
c.selected(index, mainText, shortcut)
} else {
c.l.Unlock()
}
}).SetDoneFunc(func() {
c.hide(setFocus)
})
setFocus(c.list)
}
func (c *ContextMenu) hide(setFocus func(Primitive)) {
c.open = false
if c.list == nil {
return
}
if c.list.HasFocus() {
setFocus(c.parent)
}
}

View file

@ -7,14 +7,51 @@ import (
func main() {
app := cview.NewApplication()
list := cview.NewList().
AddItem("List item 1", "Some explanatory text", 'a', nil).
AddItem("List item 2", "Some explanatory text", 'b', nil).
AddItem("List item 3", "Some explanatory text", 'c', nil).
AddItem("List item 4", "Some explanatory text", 'd', nil).
AddItem("Quit", "Press to exit", 'q', func() {
app.Stop()
})
list := cview.NewList()
reset := func() {
list.
Clear().
AddItem("List item 1", "Some explanatory text", 'a', nil).
AddItem("List item 2", "Some explanatory text", 'b', nil).
AddItem("List item 3", "Some explanatory text", 'c', nil).
AddItem("List item 4", "Some explanatory text", 'd', nil).
AddItem("Quit", "Press to exit", 'q', func() {
app.Stop()
})
list.ContextMenuList().SetItemEnabled(3, false)
}
list.AddContextItem("Delete item", 'i', func(index int) {
list.RemoveItem(index)
if list.GetItemCount() == 0 {
list.ContextMenuList().SetItemEnabled(0, false)
list.ContextMenuList().SetItemEnabled(1, false)
}
list.ContextMenuList().SetItemEnabled(3, true)
})
list.AddContextItem("Delete all", 'a', func(index int) {
list.Clear()
list.ContextMenuList().SetItemEnabled(0, false)
list.ContextMenuList().SetItemEnabled(1, false)
list.ContextMenuList().SetItemEnabled(3, true)
})
list.AddContextItem("", 0, nil)
list.AddContextItem("Reset", 'r', func(index int) {
reset()
list.ContextMenuList().SetItemEnabled(0, true)
list.ContextMenuList().SetItemEnabled(1, true)
list.ContextMenuList().SetItemEnabled(3, false)
})
reset()
if err := app.SetRoot(list, true).EnableMouse(true).Run(); err != nil {
panic(err)
}

View file

@ -4,11 +4,49 @@ import "gitlab.com/tslocum/cview"
// Introduction returns a cview.List with the highlights of the cview package.
func Introduction(nextSlide func()) (title string, content cview.Primitive) {
list := cview.NewList().
AddItem("A Go package for terminal based UIs", "with a special focus on rich interactive widgets", '1', nextSlide).
AddItem("Based on github.com/gdamore/tcell", "Like termbox but better (see tcell docs)", '2', nextSlide).
AddItem("Designed to be simple", `"Hello world" is 5 lines of code`, '3', nextSlide).
AddItem("Good for data entry", `For charts, use "termui" - for low-level views, use "gocui" - ...`, '4', nextSlide).
AddItem("Extensive documentation", "Demo code is available for each widget", '5', nextSlide)
return "Introduction", Center(80, 10, list)
list := cview.NewList()
reset := func() {
list.
Clear().
AddItem("A Go package for terminal based UIs", "with a special focus on rich interactive widgets", '1', nextSlide).
AddItem("Based on github.com/gdamore/tcell", "Like termbox but better (see tcell docs)", '2', nextSlide).
AddItem("Designed to be simple", `"Hello world" is 5 lines of code`, '3', nextSlide).
AddItem("Good for data entry", `For charts, use "termui" - for low-level views, use "gocui" - ...`, '4', nextSlide).
AddItem("Supports context menus", "Right click on one of these items or press Alt+Enter", '5', nextSlide).
AddItem("Extensive documentation", "Demo code is available for each widget", '6', nextSlide)
list.ContextMenuList().SetItemEnabled(3, false)
}
list.AddContextItem("Delete item", 'i', func(index int) {
list.RemoveItem(index)
if list.GetItemCount() == 0 {
list.ContextMenuList().SetItemEnabled(0, false)
list.ContextMenuList().SetItemEnabled(1, false)
}
list.ContextMenuList().SetItemEnabled(3, true)
})
list.AddContextItem("Delete all", 'a', func(index int) {
list.Clear()
list.ContextMenuList().SetItemEnabled(0, false)
list.ContextMenuList().SetItemEnabled(1, false)
list.ContextMenuList().SetItemEnabled(3, true)
})
list.AddContextItem("", 0, nil)
list.AddContextItem("Reset", 'r', func(index int) {
reset()
list.ContextMenuList().SetItemEnabled(0, true)
list.ContextMenuList().SetItemEnabled(1, true)
list.ContextMenuList().SetItemEnabled(3, false)
})
reset()
return "Introduction", Center(80, 12, list)
}

2
go.mod
View file

@ -7,6 +7,6 @@ require (
github.com/lucasb-eyer/go-colorful v1.0.3
github.com/mattn/go-runewidth v0.0.9
github.com/rivo/uniseg v0.1.0
golang.org/x/sys v0.0.0-20200327173247-9dae0f8f5775 // indirect
golang.org/x/sys v0.0.0-20200413165638-669c56c373c4 // indirect
golang.org/x/text v0.3.2 // indirect
)

4
go.sum
View file

@ -16,8 +16,8 @@ github.com/rivo/uniseg v0.1.0 h1:+2KBaVoUmb9XzDsrx/Ct0W/EYOSFf/nWTauy++DprtY=
github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
golang.org/x/sys v0.0.0-20190626150813-e07cf5db2756 h1:9nuHUbU8dRnRRfj9KjWUVrJeoexdbeMjttk6Oh1rD10=
golang.org/x/sys v0.0.0-20190626150813-e07cf5db2756/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200327173247-9dae0f8f5775 h1:TC0v2RSO1u2kn1ZugjrFXkRZAEaqMN/RW+OTZkBzmLE=
golang.org/x/sys v0.0.0-20200327173247-9dae0f8f5775/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200413165638-669c56c373c4 h1:opSr2sbRXk5X5/givKrrKj9HXxFpW2sdCiP8MJSKLQY=
golang.org/x/sys v0.0.0-20200413165638-669c56c373c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs=

320
list.go
View file

@ -10,6 +10,7 @@ import (
// listItem represents one item in a List.
type listItem struct {
Enabled bool // Whether or not the list item is selectable.
MainText string // The main text of the list item.
SecondaryText string // A secondary text to be shown underneath the main text.
Shortcut rune // The key to select the list item directly, 0 if there is no shortcut.
@ -21,6 +22,7 @@ type listItem struct {
// See https://gitlab.com/tslocum/cview/wiki/List for an example.
type List struct {
*Box
*ContextMenu
// The items of the list.
items []*listItem
@ -61,6 +63,9 @@ type List struct {
// Whether or not navigating the list will wrap around.
wrapAround bool
// Whether or not hovering over an item will highlight it.
hover bool
// The number of list items skipped at the top before the first item is drawn.
offset int
@ -80,7 +85,7 @@ type List struct {
// NewList returns a new form.
func NewList() *List {
return &List{
l := &List{
Box: NewBox(),
showSecondaryText: true,
scrollBarVisibility: ScrollBarAuto,
@ -91,6 +96,11 @@ func NewList() *List {
scrollBarColor: Styles.ScrollBarColor,
selectedBackgroundColor: Styles.PrimaryTextColor,
}
l.ContextMenu = NewContextMenu(l)
l.focus = l
return l
}
// SetCurrentItem sets the currently selected item by its index, starting at 0
@ -172,7 +182,7 @@ func (l *List) RemoveItem(index int) *List {
// Shift current item.
previousCurrentItem := l.currentItem
if l.currentItem >= index {
if l.currentItem >= index && l.currentItem > 0 {
l.currentItem--
}
@ -283,6 +293,16 @@ func (l *List) SetScrollBarColor(color tcell.Color) *List {
return l
}
// SetHover sets the flag that determines whether hovering over an item will
// highlight it (without triggering callbacks set with SetSelectedFunc).
func (l *List) SetHover(hover bool) *List {
l.Lock()
defer l.Unlock()
l.hover = hover
return l
}
// SetWrapAround sets the flag that determines whether navigating the list will
// wrap around. That is, navigating downwards on the last item will move the
// selection to the first item (similarly in the other direction). If set to
@ -364,6 +384,7 @@ func (l *List) InsertItem(index int, mainText, secondaryText string, shortcut ru
l.Lock()
item := &listItem{
Enabled: true,
MainText: mainText,
SecondaryText: secondaryText,
Shortcut: shortcut,
@ -433,6 +454,17 @@ func (l *List) SetItemText(index int, main, secondary string) *List {
return l
}
// SetItemEnabled sets whether an item is selectable. Panics if the index is
// out of range.
func (l *List) SetItemEnabled(index int, enabled bool) *List {
l.Lock()
defer l.Unlock()
item := l.items[index]
item.Enabled = enabled
return l
}
// FindItems searches the main and secondary texts for the given strings and
// returns a list of item indices in which those strings are found. One of the
// two search strings may be empty, it will then be ignored. Indices are always
@ -486,9 +518,29 @@ func (l *List) Clear() *List {
return l
}
// Focus is called by the application when the primitive receives focus.
func (l *List) Focus(delegate func(p Primitive)) {
l.Box.Focus(delegate)
if l.ContextMenu.open {
delegate(l.ContextMenu.list)
}
}
// HasFocus returns whether or not this primitive has focus.
func (l *List) HasFocus() bool {
l.RLock()
defer l.RUnlock()
if l.ContextMenu.open {
return l.ContextMenu.list.HasFocus()
}
return l.hasFocus
}
// Draw draws this primitive onto the screen.
func (l *List) Draw(screen tcell.Screen) {
l.Box.Draw(screen)
hasFocus := l.GetFocusable().HasFocus()
l.Lock()
defer l.Unlock()
@ -499,7 +551,7 @@ func (l *List) Draw(screen tcell.Screen) {
screenWidth, _ := screen.Size()
scrollBarHeight := height
scrollBarX := x + (width - 1)
scrollBarX := x + (width - 1) + l.paddingLeft + l.paddingRight
if scrollBarX > screenWidth-1 {
scrollBarX = screenWidth - 1
}
@ -543,6 +595,28 @@ func (l *List) Draw(screen tcell.Screen) {
break
}
if item.MainText == "" && item.SecondaryText == "" && item.Shortcut == 0 { // Divider
Print(screen, string(tcell.RuneLTee), (x-5)-l.paddingLeft, y, 1, AlignLeft, l.mainTextColor)
Print(screen, strings.Repeat(string(tcell.RuneHLine), width+4+l.paddingLeft+l.paddingRight), (x-4)-l.paddingLeft, y, width+4+l.paddingLeft+l.paddingRight, AlignLeft, l.mainTextColor)
Print(screen, string(tcell.RuneRTee), (x-5)+width+5+l.paddingRight, y, 1, AlignLeft, l.mainTextColor)
RenderScrollBar(screen, l.scrollBarVisibility, scrollBarX, y, scrollBarHeight, len(l.items), l.currentItem, index-l.offset, l.hasFocus, l.scrollBarColor)
y++
continue
} else if !item.Enabled { // Disabled item
// Shortcuts.
if showShortcuts && item.Shortcut != 0 {
Print(screen, fmt.Sprintf("(%s)", string(item.Shortcut)), x-5, y, 4, AlignRight, tcell.ColorDarkSlateGray)
}
// Main text.
Print(screen, item.MainText, x, y, width, AlignLeft, tcell.ColorGray)
RenderScrollBar(screen, l.scrollBarVisibility, scrollBarX, y, scrollBarHeight, len(l.items), l.currentItem, index-l.offset, l.hasFocus, l.scrollBarColor)
y++
continue
}
// Shortcuts.
if showShortcuts && item.Shortcut != 0 {
Print(screen, fmt.Sprintf("(%s)", string(item.Shortcut)), x-5, y, 4, AlignRight, l.shortcutColor)
@ -595,6 +669,59 @@ func (l *List) Draw(screen tcell.Screen) {
y++
}
// Draw context menu.
if hasFocus && l.ContextMenu.open {
list := l.ContextMenu.list
x, y, width, height = l.GetInnerRect()
// What's the longest option text?
maxWidth := 0
for _, option := range list.items {
strWidth := TaggedStringWidth(option.MainText)
if option.Shortcut != 0 {
strWidth += 4
}
if strWidth > maxWidth {
maxWidth = strWidth
}
}
lheight := len(list.items)
lwidth := maxWidth
// Add space for borders
lwidth += 2
lheight += 2
lwidth += l.list.paddingLeft + l.list.paddingRight
lheight += l.list.paddingTop + l.list.paddingBottom
cx, cy := l.ContextMenu.x, l.ContextMenu.y
if cx < 0 || cy < 0 {
cx = x + (width / 2)
cy = y + (height / 2)
}
_, sheight := screen.Size()
if cy+lheight >= sheight && cy-2 > lheight-cy {
cy = y - lheight
if cy < 0 {
cy = 0
}
}
if cy+lheight >= sheight {
lheight = sheight - cy
}
if list.scrollBarVisibility == ScrollBarAlways || (list.scrollBarVisibility == ScrollBarAuto && len(list.items) > lheight) {
lwidth++ // Add space for scroll bar
}
list.SetRect(cx, cy, lwidth, lheight)
list.Draw(screen)
}
}
// InputHandler returns the handler for this primitive.
@ -603,12 +730,19 @@ func (l *List) InputHandler() func(event *tcell.EventKey, setFocus func(p Primit
l.Lock()
if event.Key() == tcell.KeyEscape {
if l.done != nil {
l.done()
if l.ContextMenu.open {
l.ContextMenu.hide(setFocus)
return
}
if l.done != nil {
l.Unlock()
l.done()
} else {
l.Unlock()
}
l.Unlock()
return
} else if len(l.items) == 0 {
} else if len(l.items) == 0 && (event.Key() != tcell.KeyEnter || event.Modifiers()&tcell.ModAlt == 0) {
l.Unlock()
return
}
@ -629,17 +763,40 @@ func (l *List) InputHandler() func(event *tcell.EventKey, setFocus func(p Primit
case tcell.KeyPgUp:
l.currentItem -= 5
case tcell.KeyEnter:
if l.currentItem >= 0 && l.currentItem < len(l.items) {
item := l.items[l.currentItem]
if item.Selected != nil {
l.Unlock()
item.Selected()
l.Lock()
if event.Modifiers()&tcell.ModAlt != 0 {
// Do we show any shortcuts?
var showShortcuts bool
for _, item := range l.items {
if item.Shortcut != 0 {
showShortcuts = true
break
}
}
if l.selected != nil {
l.Unlock()
l.selected(l.currentItem, item.MainText, item.SecondaryText, item.Shortcut)
l.Lock()
offsetX := 7
if showShortcuts {
offsetX += 4
}
offsetY := l.currentItem
if l.showSecondaryText {
offsetY *= 2
}
x, y, _, _ := l.GetInnerRect()
l.ContextMenu.show(l.currentItem, x+offsetX, y+offsetY, setFocus)
} else if l.currentItem >= 0 && l.currentItem < len(l.items) {
item := l.items[l.currentItem]
if item.Enabled {
if item.Selected != nil {
l.Unlock()
item.Selected()
l.Lock()
}
if l.selected != nil {
l.Unlock()
l.selected(l.currentItem, item.MainText, item.SecondaryText, item.Shortcut)
l.Lock()
}
}
}
case tcell.KeyRune:
@ -648,7 +805,7 @@ func (l *List) InputHandler() func(event *tcell.EventKey, setFocus func(p Primit
// It's not a space bar. Is it a shortcut?
var found bool
for index, item := range l.items {
if item.Shortcut == ch {
if item.Enabled && item.Shortcut == ch {
// We have a shortcut.
found = true
l.currentItem = index
@ -672,17 +829,31 @@ func (l *List) InputHandler() func(event *tcell.EventKey, setFocus func(p Primit
}
}
if l.currentItem < 0 {
if l.wrapAround {
l.currentItem = len(l.items) - 1
} else {
l.currentItem = 0
decreasing := l.currentItem < previousItem
for i := 0; i < len(l.items); i++ {
if l.currentItem < 0 {
if l.wrapAround {
l.currentItem = len(l.items) - 1
} else {
l.currentItem = 0
}
} else if l.currentItem >= len(l.items) {
if l.wrapAround {
l.currentItem = 0
} else {
l.currentItem = len(l.items) - 1
}
}
} else if l.currentItem >= len(l.items) {
if l.wrapAround {
l.currentItem = 0
item := l.items[l.currentItem]
if item.Enabled {
break
}
if decreasing {
l.currentItem--
} else {
l.currentItem = len(l.items) - 1
l.currentItem++
}
}
@ -696,11 +867,31 @@ func (l *List) InputHandler() func(event *tcell.EventKey, setFocus func(p Primit
})
}
// indexAtY returns the index of the list item found at the given Y position
// or a negative value if there is no such list item.
func (l *List) indexAtY(y int) int {
_, rectY, _, height := l.GetInnerRect()
if y < rectY || y >= rectY+height {
return -1
}
index := y - rectY
if l.showSecondaryText {
index /= 2
}
index += l.offset
if index >= len(l.items) {
return -1
}
return index
}
// indexAtPoint returns the index of the list item found at the given position
// or a negative value if there is no such list item.
func (l *List) indexAtPoint(x, y int) int {
rectX, rectY, width, height := l.GetInnerRect()
if rectX < 0 || rectX >= rectX+width || y < rectY || y >= rectY+height {
if x < rectX || x >= rectX+width || y < rectY || y >= rectY+height {
return -1
}
@ -719,6 +910,13 @@ func (l *List) indexAtPoint(x, y int) int {
// MouseHandler returns the mouse handler for this primitive.
func (l *List) MouseHandler() func(action MouseAction, event *tcell.EventMouse, setFocus func(p Primitive)) (consumed bool, capture Primitive) {
return l.WrapMouseHandler(func(action MouseAction, event *tcell.EventMouse, setFocus func(p Primitive)) (consumed bool, capture Primitive) {
// Pass events to context menu.
if l.ContextMenu.open && l.ContextMenu.list != nil && l.ContextMenu.list.InRect(event.Position()) {
l.ContextMenu.list.MouseHandler()(action, event, setFocus)
consumed = true
return
}
if !l.InRect(event.Position()) {
return false, nil
}
@ -726,22 +924,70 @@ func (l *List) MouseHandler() func(action MouseAction, event *tcell.EventMouse,
// Process mouse event.
switch action {
case MouseLeftClick:
if l.ContextMenu.open {
l.ContextMenu.hide(setFocus)
consumed = true
return
}
setFocus(l)
index := l.indexAtPoint(event.Position())
if index != -1 {
item := l.items[index]
if item.Selected != nil {
item.Selected()
if item.Enabled {
if item.Selected != nil {
item.Selected()
}
if l.selected != nil {
l.selected(index, item.MainText, item.SecondaryText, item.Shortcut)
}
if index != l.currentItem && l.changed != nil {
l.changed(index, item.MainText, item.SecondaryText, item.Shortcut)
}
l.currentItem = index
}
if l.selected != nil {
l.selected(index, item.MainText, item.SecondaryText, item.Shortcut)
}
if index != l.currentItem && l.changed != nil {
l.changed(index, item.MainText, item.SecondaryText, item.Shortcut)
}
l.currentItem = index
}
consumed = true
case MouseMiddleClick:
if l.ContextMenu.open {
l.ContextMenu.hide(setFocus)
consumed = true
return
}
case MouseRightDown:
x, y := event.Position()
index := l.indexAtPoint(event.Position())
if index != -1 {
item := l.items[index]
if item.Enabled {
if index != l.currentItem && l.changed != nil {
l.changed(index, item.MainText, item.SecondaryText, item.Shortcut)
}
l.currentItem = index
}
}
if l.ContextMenu.list != nil && len(l.ContextMenu.list.items) > 0 {
l.ContextMenu.show(l.currentItem, x, y, setFocus)
l.ContextMenu.drag = true
} else {
defer l.MouseHandler()(MouseLeftClick, event, setFocus)
}
consumed = true
case MouseMove:
if l.hover {
_, y := event.Position()
index := l.indexAtY(y)
if index >= 0 {
item := l.items[index]
if item.Enabled {
l.currentItem = index
}
}
consumed = true
}
case MouseScrollUp:
if l.offset > 0 {
l.offset--

View file

@ -4,34 +4,55 @@ import "github.com/gdamore/tcell"
// Theme defines the colors used when primitives are initialized.
type Theme struct {
// Title, border and other lines
TitleColor tcell.Color // Box titles.
BorderColor tcell.Color // Box borders.
GraphicsColor tcell.Color // Graphics.
// Text
PrimaryTextColor tcell.Color // Primary text.
SecondaryTextColor tcell.Color // Secondary text (e.g. labels).
TertiaryTextColor tcell.Color // Tertiary text (e.g. subtitles, notes).
InverseTextColor tcell.Color // Text on primary-colored backgrounds.
ContrastSecondaryTextColor tcell.Color // Secondary text on ContrastBackgroundColor-colored backgrounds.
// Background
PrimitiveBackgroundColor tcell.Color // Main background color for primitives.
ContrastBackgroundColor tcell.Color // Background color for contrasting elements.
MoreContrastBackgroundColor tcell.Color // Background color for even more contrasting elements.
BorderColor tcell.Color // Box borders.
TitleColor tcell.Color // Box titles.
GraphicsColor tcell.Color // Graphics.
PrimaryTextColor tcell.Color // Primary text.
SecondaryTextColor tcell.Color // Secondary text (e.g. labels).
TertiaryTextColor tcell.Color // Tertiary text (e.g. subtitles, notes).
InverseTextColor tcell.Color // Text on primary-colored backgrounds.
ContrastSecondaryTextColor tcell.Color // Secondary text on ContrastBackgroundColor-colored backgrounds.
ScrollBarColor tcell.Color // Scroll bar.
// Context menu
ContextMenuPaddingTop int // Top padding.
ContextMenuPaddingBottom int // Bottom padding.
ContextMenuPaddingLeft int // Left padding.
ContextMenuPaddingRight int // Right padding.
// Scroll bar
ScrollBarColor tcell.Color // Scroll bar color.
}
// Styles defines the theme for applications. The default is for a black
// background and some basic colors: black, white, yellow, green, cyan, and
// blue.
var Styles = Theme{
TitleColor: tcell.ColorWhite,
BorderColor: tcell.ColorWhite,
GraphicsColor: tcell.ColorWhite,
PrimaryTextColor: tcell.ColorWhite,
SecondaryTextColor: tcell.ColorYellow,
TertiaryTextColor: tcell.ColorGreen,
InverseTextColor: tcell.ColorBlue,
ContrastSecondaryTextColor: tcell.ColorDarkCyan,
PrimitiveBackgroundColor: tcell.ColorBlack,
ContrastBackgroundColor: tcell.ColorBlue,
MoreContrastBackgroundColor: tcell.ColorGreen,
BorderColor: tcell.ColorWhite,
TitleColor: tcell.ColorWhite,
GraphicsColor: tcell.ColorWhite,
PrimaryTextColor: tcell.ColorWhite,
SecondaryTextColor: tcell.ColorYellow,
TertiaryTextColor: tcell.ColorGreen,
InverseTextColor: tcell.ColorBlue,
ContrastSecondaryTextColor: tcell.ColorDarkCyan,
ScrollBarColor: tcell.ColorWhite,
ContextMenuPaddingTop: 0,
ContextMenuPaddingBottom: 0,
ContextMenuPaddingLeft: 1,
ContextMenuPaddingRight: 1,
ScrollBarColor: tcell.ColorWhite,
}