mirror of
https://github.com/rivo/tview.git
synced 2024-11-15 06:12:46 +00:00
Add ContextMenu
Initially supported by List
This commit is contained in:
parent
8e06c826b3
commit
c111597668
@ -13,7 +13,7 @@ Among these components are:
|
||||
- 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__
|
||||
- An __application__ wrapper
|
||||
|
12
box.go
12
box.go
@ -52,6 +52,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
|
||||
@ -342,7 +346,13 @@ func (b *Box) Draw(screen tcell.Screen) {
|
||||
if b.border && b.width >= 2 && b.height >= 2 {
|
||||
border := background.Foreground(b.borderColor) | tcell.Style(b.borderAttributes)
|
||||
var vertical, horizontal, topLeft, topRight, bottomLeft, bottomRight rune
|
||||
if b.focus.HasFocus() {
|
||||
var hasFocus bool
|
||||
if b.focus == b {
|
||||
hasFocus = b.hasFocus
|
||||
} else {
|
||||
hasFocus = b.focus.HasFocus()
|
||||
}
|
||||
if hasFocus && b.showFocus {
|
||||
horizontal = Borders.HorizontalFocus
|
||||
vertical = Borders.VerticalFocus
|
||||
topLeft = Borders.TopLeftFocus
|
||||
|
171
contextmenu.go
Normal file
171
contextmenu.go
Normal file
@ -0,0 +1,171 @@
|
||||
package tview
|
||||
|
||||
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 {
|
||||
return &ContextMenu{
|
||||
parent: parent,
|
||||
}
|
||||
}
|
||||
|
||||
func (c *ContextMenu) initializeList() {
|
||||
if c.list != nil {
|
||||
return
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
// ContextMenuList returns the underlying List of the context menu.
|
||||
func (c *ContextMenu) ContextMenuList() *List {
|
||||
c.l.Lock()
|
||||
defer c.l.Unlock()
|
||||
|
||||
c.initializeList()
|
||||
|
||||
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()
|
||||
|
||||
c.initializeList()
|
||||
|
||||
c.list.AddItem(text, "", shortcut, c.wrap(selected))
|
||||
if text == "" && shortcut == 0 {
|
||||
index := len(c.list.items) - 1
|
||||
c.list.items[index].Enabled = false
|
||||
}
|
||||
|
||||
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()
|
||||
|
||||
c.initializeList()
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
// HideContextMenu hides the context menu.
|
||||
func (c *ContextMenu) HideContextMenu(setFocus func(Primitive)) {
|
||||
c.l.Lock()
|
||||
defer c.l.Unlock()
|
||||
|
||||
c.hide(setFocus)
|
||||
}
|
||||
|
||||
// ContextMenuVisible returns whether or not the context menu is visible.
|
||||
func (c *ContextMenu) ContextMenuVisible() bool {
|
||||
c.l.Lock()
|
||||
defer c.l.Unlock()
|
||||
|
||||
return c.open
|
||||
}
|
||||
|
||||
func (c *ContextMenu) show(item int, x int, y int, setFocus func(Primitive)) {
|
||||
c.initializeList()
|
||||
|
||||
if len(c.list.items) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
c.open = true
|
||||
c.item = item
|
||||
c.x, c.y = x, y
|
||||
|
||||
for i, item := range c.list.items {
|
||||
if item.Enabled {
|
||||
c.list.currentItem = i
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
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.initializeList()
|
||||
|
||||
c.open = false
|
||||
|
||||
if c.list.HasFocus() {
|
||||
setFocus(c.parent)
|
||||
}
|
||||
}
|
@ -7,7 +7,11 @@ import (
|
||||
|
||||
func main() {
|
||||
app := tview.NewApplication()
|
||||
list := tview.NewList().
|
||||
list := tview.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).
|
||||
@ -15,6 +19,39 @@ func main() {
|
||||
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)
|
||||
}
|
||||
|
@ -4,11 +4,49 @@ import "github.com/rivo/tview"
|
||||
|
||||
// Introduction returns a tview.List with the highlights of the tview package.
|
||||
func Introduction(nextSlide func()) (title string, content tview.Primitive) {
|
||||
list := tview.NewList().
|
||||
list := tview.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("Extensive documentation", "Everything is documented, examples in GitHub wiki, demo code for each widget", '5', nextSlide)
|
||||
return "Introduction", Center(80, 10, list)
|
||||
AddItem("Supports context menus", "Right click on one of these items or press Alt+Enter", '5', nextSlide).
|
||||
AddItem("Extensive documentation", "Everything is documented, examples in GitHub wiki, demo code 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)
|
||||
}
|
||||
|
241
list.go
241
list.go
@ -9,6 +9,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.
|
||||
@ -20,6 +21,7 @@ type listItem struct {
|
||||
// See https://github.com/rivo/tview/wiki/List for an example.
|
||||
type List struct {
|
||||
*Box
|
||||
*ContextMenu
|
||||
|
||||
// The items of the list.
|
||||
items []*listItem
|
||||
@ -54,6 +56,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
|
||||
|
||||
@ -71,7 +76,7 @@ type List struct {
|
||||
|
||||
// NewList returns a new form.
|
||||
func NewList() *List {
|
||||
return &List{
|
||||
l := &List{
|
||||
Box: NewBox(),
|
||||
showSecondaryText: true,
|
||||
wrapAround: true,
|
||||
@ -81,6 +86,11 @@ func NewList() *List {
|
||||
selectedTextColor: Styles.PrimitiveBackgroundColor,
|
||||
selectedBackgroundColor: Styles.PrimaryTextColor,
|
||||
}
|
||||
|
||||
l.ContextMenu = NewContextMenu(l)
|
||||
l.focus = l
|
||||
|
||||
return l
|
||||
}
|
||||
|
||||
// SetCurrentItem sets the currently selected item by its index, starting at 0
|
||||
@ -150,7 +160,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--
|
||||
}
|
||||
|
||||
@ -216,6 +226,13 @@ func (l *List) ShowSecondaryText(show bool) *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.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
|
||||
@ -283,6 +300,7 @@ func (l *List) AddItem(mainText, secondaryText string, shortcut rune, selected f
|
||||
// selected.
|
||||
func (l *List) InsertItem(index int, mainText, secondaryText string, shortcut rune, selected func()) *List {
|
||||
item := &listItem{
|
||||
Enabled: true,
|
||||
MainText: mainText,
|
||||
SecondaryText: secondaryText,
|
||||
Shortcut: shortcut,
|
||||
@ -340,6 +358,14 @@ 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 {
|
||||
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
|
||||
@ -387,9 +413,26 @@ 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 {
|
||||
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()
|
||||
|
||||
// Determine the dimensions.
|
||||
x, y, width, height := l.GetInnerRect()
|
||||
@ -433,6 +476,26 @@ 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)
|
||||
|
||||
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)
|
||||
|
||||
y++
|
||||
continue
|
||||
}
|
||||
|
||||
// Shortcuts.
|
||||
if showShortcuts && item.Shortcut != 0 {
|
||||
Print(screen, fmt.Sprintf("(%s)", string(item.Shortcut)), x-5, y, 4, AlignRight, l.shortcutColor)
|
||||
@ -473,17 +536,71 @@ func (l *List) Draw(screen tcell.Screen) {
|
||||
y++
|
||||
}
|
||||
}
|
||||
|
||||
// Draw context menu.
|
||||
if hasFocus && l.ContextMenu.open {
|
||||
ctx := l.ContextMenu.list
|
||||
|
||||
x, y, width, height = l.GetInnerRect()
|
||||
|
||||
// What's the longest option text?
|
||||
maxWidth := 0
|
||||
for _, option := range ctx.items {
|
||||
strWidth := TaggedStringWidth(option.MainText)
|
||||
if option.Shortcut != 0 {
|
||||
strWidth += 4
|
||||
}
|
||||
if strWidth > maxWidth {
|
||||
maxWidth = strWidth
|
||||
}
|
||||
}
|
||||
|
||||
lheight := len(ctx.items)
|
||||
lwidth := maxWidth
|
||||
|
||||
// Add space for borders
|
||||
lwidth += 2
|
||||
lheight += 2
|
||||
|
||||
lwidth += ctx.paddingLeft + ctx.paddingRight
|
||||
lheight += ctx.paddingTop + ctx.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
|
||||
}
|
||||
|
||||
ctx.SetRect(cx, cy, lwidth, lheight)
|
||||
ctx.Draw(screen)
|
||||
}
|
||||
}
|
||||
|
||||
// InputHandler returns the handler for this primitive.
|
||||
func (l *List) InputHandler() func(event *tcell.EventKey, setFocus func(p Primitive)) {
|
||||
return l.WrapInputHandler(func(event *tcell.EventKey, setFocus func(p Primitive)) {
|
||||
if event.Key() == tcell.KeyEscape {
|
||||
if l.ContextMenu.open {
|
||||
l.ContextMenu.hide(setFocus)
|
||||
return
|
||||
}
|
||||
|
||||
if l.done != nil {
|
||||
l.done()
|
||||
}
|
||||
return
|
||||
} else if len(l.items) == 0 {
|
||||
} else if len(l.items) == 0 && (event.Key() != tcell.KeyEnter || event.Modifiers()&tcell.ModAlt == 0) {
|
||||
return
|
||||
}
|
||||
|
||||
@ -505,8 +622,30 @@ func (l *List) InputHandler() func(event *tcell.EventKey, setFocus func(p Primit
|
||||
_, _, _, height := l.GetInnerRect()
|
||||
l.currentItem -= height
|
||||
case tcell.KeyEnter:
|
||||
if l.currentItem >= 0 && l.currentItem < len(l.items) {
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
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 {
|
||||
item.Selected()
|
||||
}
|
||||
@ -514,13 +653,14 @@ func (l *List) InputHandler() func(event *tcell.EventKey, setFocus func(p Primit
|
||||
l.selected(l.currentItem, item.MainText, item.SecondaryText, item.Shortcut)
|
||||
}
|
||||
}
|
||||
}
|
||||
case tcell.KeyRune:
|
||||
ch := event.Rune()
|
||||
if ch != ' ' {
|
||||
// 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
|
||||
@ -540,6 +680,8 @@ func (l *List) InputHandler() func(event *tcell.EventKey, setFocus func(p Primit
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
@ -554,6 +696,18 @@ func (l *List) InputHandler() func(event *tcell.EventKey, setFocus func(p Primit
|
||||
}
|
||||
}
|
||||
|
||||
item := l.items[l.currentItem]
|
||||
if item.Enabled {
|
||||
break
|
||||
}
|
||||
|
||||
if decreasing {
|
||||
l.currentItem--
|
||||
} else {
|
||||
l.currentItem++
|
||||
}
|
||||
}
|
||||
|
||||
if l.currentItem != previousItem && l.currentItem < len(l.items) && l.changed != nil {
|
||||
item := l.items[l.currentItem]
|
||||
l.changed(l.currentItem, item.MainText, item.SecondaryText, item.Shortcut)
|
||||
@ -561,11 +715,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
|
||||
}
|
||||
|
||||
@ -584,6 +758,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.ContextMenuVisible() && l.ContextMenuList().InRect(event.Position()) {
|
||||
defer l.ContextMenuList().MouseHandler()(action, event, setFocus)
|
||||
consumed = true
|
||||
return
|
||||
}
|
||||
|
||||
if !l.InRect(event.Position()) {
|
||||
return false, nil
|
||||
}
|
||||
@ -591,10 +772,17 @@ func (l *List) MouseHandler() func(action MouseAction, event *tcell.EventMouse,
|
||||
// Process mouse event.
|
||||
switch action {
|
||||
case MouseLeftClick:
|
||||
if l.ContextMenuVisible() {
|
||||
defer l.ContextMenu.hide(setFocus)
|
||||
consumed = true
|
||||
return
|
||||
}
|
||||
|
||||
setFocus(l)
|
||||
index := l.indexAtPoint(event.Position())
|
||||
if index != -1 {
|
||||
item := l.items[index]
|
||||
if item.Enabled {
|
||||
if item.Selected != nil {
|
||||
item.Selected()
|
||||
}
|
||||
@ -606,7 +794,48 @@ func (l *List) MouseHandler() func(action MouseAction, event *tcell.EventMouse,
|
||||
}
|
||||
l.currentItem = index
|
||||
}
|
||||
}
|
||||
consumed = true
|
||||
case MouseMiddleClick:
|
||||
if l.ContextMenuVisible() {
|
||||
defer l.ContextMenu.hide(setFocus)
|
||||
consumed = true
|
||||
return
|
||||
}
|
||||
case MouseRightDown:
|
||||
if len(l.ContextMenuList().items) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
x, y := event.Position()
|
||||
|
||||
index := l.indexAtPoint(event.Position())
|
||||
if index != -1 {
|
||||
item := l.items[index]
|
||||
if item.Enabled {
|
||||
l.currentItem = index
|
||||
if index != l.currentItem && l.changed != nil {
|
||||
l.changed(index, item.MainText, item.SecondaryText, item.Shortcut)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
defer l.ContextMenu.show(l.currentItem, x, y, setFocus)
|
||||
l.ContextMenu.drag = true
|
||||
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--
|
||||
|
34
styles.go
34
styles.go
@ -4,32 +4,50 @@ import "github.com/gdamore/tcell"
|
||||
|
||||
// Theme defines the colors used when primitives are initialized.
|
||||
type Theme struct {
|
||||
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.
|
||||
// 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.
|
||||
|
||||
// Context menu
|
||||
ContextMenuPaddingTop int // Top padding.
|
||||
ContextMenuPaddingBottom int // Bottom padding.
|
||||
ContextMenuPaddingLeft int // Left padding.
|
||||
ContextMenuPaddingRight int // Right padding.
|
||||
}
|
||||
|
||||
// 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{
|
||||
PrimitiveBackgroundColor: tcell.ColorBlack,
|
||||
ContrastBackgroundColor: tcell.ColorBlue,
|
||||
MoreContrastBackgroundColor: tcell.ColorGreen,
|
||||
BorderColor: tcell.ColorWhite,
|
||||
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,
|
||||
|
||||
ContextMenuPaddingTop: 0,
|
||||
ContextMenuPaddingBottom: 0,
|
||||
ContextMenuPaddingLeft: 1,
|
||||
ContextMenuPaddingRight: 1,
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user