2019-12-30 23:12:17 +00:00
|
|
|
package cview
|
2017-12-16 21:48:26 +00:00
|
|
|
|
|
|
|
import (
|
2020-10-06 20:11:45 +00:00
|
|
|
"bytes"
|
2017-12-16 21:48:26 +00:00
|
|
|
"fmt"
|
2019-01-12 20:22:58 +00:00
|
|
|
"strings"
|
2020-03-25 14:32:57 +00:00
|
|
|
"sync"
|
2017-12-16 21:48:26 +00:00
|
|
|
|
2020-08-30 15:36:03 +00:00
|
|
|
"github.com/gdamore/tcell/v2"
|
2017-12-16 21:48:26 +00:00
|
|
|
)
|
|
|
|
|
2020-09-16 17:55:33 +00:00
|
|
|
// ListItem represents an item in a List.
|
|
|
|
type ListItem struct {
|
2020-10-16 04:28:54 +00:00
|
|
|
disabled bool // Whether or not the list item is selectable.
|
2020-10-07 01:19:40 +00:00
|
|
|
mainText []byte // The main text of the list item.
|
|
|
|
secondaryText []byte // A secondary text to be shown underneath the main text.
|
2020-09-16 18:03:14 +00:00
|
|
|
shortcut rune // The key to select the list item directly, 0 if there is no shortcut.
|
|
|
|
selected func() // The optional function which is called when the item is selected.
|
|
|
|
reference interface{} // An optional reference object.
|
2020-09-23 23:14:14 +00:00
|
|
|
|
|
|
|
sync.RWMutex
|
2020-09-16 17:55:33 +00:00
|
|
|
}
|
|
|
|
|
2020-09-23 23:14:14 +00:00
|
|
|
// NewListItem returns a new item for a list.
|
2020-09-16 17:55:33 +00:00
|
|
|
func NewListItem(mainText string) *ListItem {
|
|
|
|
return &ListItem{
|
2020-10-07 01:19:40 +00:00
|
|
|
mainText: []byte(mainText),
|
2020-09-16 17:55:33 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-10-07 01:19:40 +00:00
|
|
|
// SetMainBytes sets the main text of the list item.
|
2020-10-07 23:34:21 +00:00
|
|
|
func (l *ListItem) SetMainBytes(val []byte) {
|
2020-09-23 23:14:14 +00:00
|
|
|
l.Lock()
|
|
|
|
defer l.Unlock()
|
|
|
|
|
2020-09-16 17:55:33 +00:00
|
|
|
l.mainText = val
|
|
|
|
}
|
|
|
|
|
2020-10-07 01:19:40 +00:00
|
|
|
// SetMainText sets the main text of the list item.
|
2020-10-07 23:34:21 +00:00
|
|
|
func (l *ListItem) SetMainText(val string) {
|
|
|
|
l.SetMainBytes([]byte(val))
|
2020-10-07 01:19:40 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// GetMainBytes returns the item's main text.
|
|
|
|
func (l *ListItem) GetMainBytes() []byte {
|
2020-09-23 23:14:14 +00:00
|
|
|
l.RLock()
|
|
|
|
defer l.RUnlock()
|
|
|
|
|
2020-09-16 17:55:33 +00:00
|
|
|
return l.mainText
|
|
|
|
}
|
|
|
|
|
2020-10-07 01:19:40 +00:00
|
|
|
// GetMainText returns the item's main text.
|
|
|
|
func (l *ListItem) GetMainText() string {
|
|
|
|
return string(l.GetMainBytes())
|
|
|
|
}
|
|
|
|
|
|
|
|
// SetSecondaryBytes sets a secondary text to be shown underneath the main text.
|
2020-10-07 23:34:21 +00:00
|
|
|
func (l *ListItem) SetSecondaryBytes(val []byte) {
|
2020-09-23 23:14:14 +00:00
|
|
|
l.Lock()
|
|
|
|
defer l.Unlock()
|
|
|
|
|
2020-09-16 17:55:33 +00:00
|
|
|
l.secondaryText = val
|
|
|
|
}
|
|
|
|
|
2020-10-07 01:19:40 +00:00
|
|
|
// SetSecondaryText sets a secondary text to be shown underneath the main text.
|
2020-10-07 23:34:21 +00:00
|
|
|
func (l *ListItem) SetSecondaryText(val string) {
|
|
|
|
l.SetSecondaryBytes([]byte(val))
|
2020-10-07 01:19:40 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// GetSecondaryBytes returns the item's secondary text.
|
|
|
|
func (l *ListItem) GetSecondaryBytes() []byte {
|
2020-09-23 23:14:14 +00:00
|
|
|
l.RLock()
|
|
|
|
defer l.RUnlock()
|
|
|
|
|
2020-09-16 17:55:33 +00:00
|
|
|
return l.secondaryText
|
|
|
|
}
|
|
|
|
|
2020-10-07 01:19:40 +00:00
|
|
|
// GetSecondaryText returns the item's secondary text.
|
|
|
|
func (l *ListItem) GetSecondaryText() string {
|
|
|
|
return string(l.GetSecondaryBytes())
|
|
|
|
}
|
|
|
|
|
2020-09-16 17:55:33 +00:00
|
|
|
// SetShortcut sets the key to select the ListItem directly, 0 if there is no shortcut.
|
2020-10-07 23:34:21 +00:00
|
|
|
func (l *ListItem) SetShortcut(val rune) {
|
2020-09-23 23:14:14 +00:00
|
|
|
l.Lock()
|
|
|
|
defer l.Unlock()
|
|
|
|
|
2020-09-16 17:55:33 +00:00
|
|
|
l.shortcut = val
|
|
|
|
}
|
|
|
|
|
|
|
|
// GetShortcut returns the ListItem's shortcut.
|
|
|
|
func (l *ListItem) GetShortcut() rune {
|
2020-09-23 23:14:14 +00:00
|
|
|
l.RLock()
|
|
|
|
defer l.RUnlock()
|
|
|
|
|
2020-09-16 17:55:33 +00:00
|
|
|
return l.shortcut
|
|
|
|
}
|
|
|
|
|
|
|
|
// SetSelectedFunc sets a function which is called when the ListItem is selected.
|
2020-10-07 23:34:21 +00:00
|
|
|
func (l *ListItem) SetSelectedFunc(handler func()) {
|
2020-09-23 23:14:14 +00:00
|
|
|
l.Lock()
|
|
|
|
defer l.Unlock()
|
|
|
|
|
2020-09-16 17:55:33 +00:00
|
|
|
l.selected = handler
|
2017-12-16 21:48:26 +00:00
|
|
|
}
|
|
|
|
|
2020-09-16 18:03:14 +00:00
|
|
|
// SetReference allows you to store a reference of any type in the item
|
2020-10-07 23:34:21 +00:00
|
|
|
func (l *ListItem) SetReference(val interface{}) {
|
2020-09-23 23:14:14 +00:00
|
|
|
l.Lock()
|
|
|
|
defer l.Unlock()
|
|
|
|
|
2020-09-16 18:03:14 +00:00
|
|
|
l.reference = val
|
|
|
|
}
|
|
|
|
|
|
|
|
// GetReference returns the item's reference object.
|
|
|
|
func (l *ListItem) GetReference() interface{} {
|
2020-09-23 23:14:14 +00:00
|
|
|
l.RLock()
|
|
|
|
defer l.RUnlock()
|
|
|
|
|
2020-09-16 18:03:14 +00:00
|
|
|
return l.reference
|
|
|
|
}
|
|
|
|
|
2017-12-16 21:48:26 +00:00
|
|
|
// List displays rows of items, each of which can be selected.
|
|
|
|
type List struct {
|
|
|
|
*Box
|
2020-04-19 03:00:36 +00:00
|
|
|
*ContextMenu
|
2017-12-16 21:48:26 +00:00
|
|
|
|
|
|
|
// The items of the list.
|
2020-09-16 17:55:33 +00:00
|
|
|
items []*ListItem
|
2017-12-16 21:48:26 +00:00
|
|
|
|
|
|
|
// The index of the currently selected item.
|
|
|
|
currentItem int
|
|
|
|
|
|
|
|
// Whether or not to show the secondary item texts.
|
|
|
|
showSecondaryText bool
|
|
|
|
|
2017-12-18 19:04:52 +00:00
|
|
|
// The item main text color.
|
2017-12-16 21:48:26 +00:00
|
|
|
mainTextColor tcell.Color
|
|
|
|
|
|
|
|
// The item secondary text color.
|
|
|
|
secondaryTextColor tcell.Color
|
|
|
|
|
|
|
|
// The item shortcut text color.
|
|
|
|
shortcutColor tcell.Color
|
|
|
|
|
2017-12-18 19:04:52 +00:00
|
|
|
// The text color for selected items.
|
|
|
|
selectedTextColor tcell.Color
|
|
|
|
|
2020-04-28 21:59:40 +00:00
|
|
|
// The style attributes for selected items.
|
|
|
|
selectedTextAttributes tcell.AttrMask
|
|
|
|
|
2020-02-13 17:51:01 +00:00
|
|
|
// Visibility of the scroll bar.
|
|
|
|
scrollBarVisibility ScrollBarVisibility
|
2020-02-01 05:21:59 +00:00
|
|
|
|
|
|
|
// The scroll bar color.
|
|
|
|
scrollBarColor tcell.Color
|
|
|
|
|
2017-12-18 19:04:52 +00:00
|
|
|
// The background color for selected items.
|
|
|
|
selectedBackgroundColor tcell.Color
|
|
|
|
|
2018-11-26 10:00:48 +00:00
|
|
|
// If true, the selection is only shown when the list has focus.
|
|
|
|
selectedFocusOnly bool
|
|
|
|
|
2020-05-01 23:01:18 +00:00
|
|
|
// If true, the selection must remain visible when scrolling.
|
|
|
|
selectedAlwaysVisible bool
|
|
|
|
|
2020-05-20 22:36:19 +00:00
|
|
|
// If true, the selection must remain centered when scrolling.
|
|
|
|
selectedAlwaysCentered bool
|
|
|
|
|
2019-02-20 16:58:59 +00:00
|
|
|
// If true, the entire row is highlighted when selected.
|
|
|
|
highlightFullLine bool
|
2019-01-03 06:51:11 +00:00
|
|
|
|
2019-12-29 16:47:05 +00:00
|
|
|
// Whether or not navigating the list will wrap around.
|
|
|
|
wrapAround bool
|
|
|
|
|
2020-04-19 03:00:36 +00:00
|
|
|
// Whether or not hovering over an item will highlight it.
|
|
|
|
hover bool
|
|
|
|
|
2020-10-13 16:45:35 +00:00
|
|
|
// The number of list items and columns by which the list is scrolled
|
|
|
|
// down/to the right.
|
|
|
|
itemOffset, columnOffset int
|
2019-01-23 20:40:01 +00:00
|
|
|
|
2018-01-01 16:17:20 +00:00
|
|
|
// An optional function which is called when the user has navigated to a list
|
|
|
|
// item.
|
2020-09-16 17:55:33 +00:00
|
|
|
changed func(index int, item *ListItem)
|
2018-01-01 16:17:20 +00:00
|
|
|
|
2017-12-18 19:04:52 +00:00
|
|
|
// An optional function which is called when a list item was selected. This
|
|
|
|
// function will be called even if the list item defines its own callback.
|
2020-09-16 17:55:33 +00:00
|
|
|
selected func(index int, item *ListItem)
|
2018-01-01 16:17:20 +00:00
|
|
|
|
|
|
|
// An optional function which is called when the user presses the Escape key.
|
|
|
|
done func()
|
2020-03-25 14:32:57 +00:00
|
|
|
|
2020-05-01 23:01:18 +00:00
|
|
|
// The height of the list the last time it was drawn.
|
|
|
|
height int
|
|
|
|
|
2022-07-30 01:59:41 +00:00
|
|
|
// Prefix and suffix strings drawn for unselected elements.
|
|
|
|
unselectedPrefix, unselectedSuffix []byte
|
|
|
|
|
|
|
|
// Prefix and suffix strings drawn for selected elements.
|
|
|
|
selectedPrefix, selectedSuffix []byte
|
|
|
|
|
|
|
|
// Maximum prefix and suffix width.
|
|
|
|
prefixWidth, suffixWidth int
|
|
|
|
|
2020-03-25 14:32:57 +00:00
|
|
|
sync.RWMutex
|
2017-12-16 21:48:26 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// NewList returns a new form.
|
|
|
|
func NewList() *List {
|
2020-04-19 03:00:36 +00:00
|
|
|
l := &List{
|
2017-12-18 19:04:52 +00:00
|
|
|
Box: NewBox(),
|
|
|
|
showSecondaryText: true,
|
2020-02-13 17:51:01 +00:00
|
|
|
scrollBarVisibility: ScrollBarAuto,
|
2018-01-10 08:43:32 +00:00
|
|
|
mainTextColor: Styles.PrimaryTextColor,
|
|
|
|
secondaryTextColor: Styles.TertiaryTextColor,
|
|
|
|
shortcutColor: Styles.SecondaryTextColor,
|
|
|
|
selectedTextColor: Styles.PrimitiveBackgroundColor,
|
2020-02-01 05:21:59 +00:00
|
|
|
scrollBarColor: Styles.ScrollBarColor,
|
2018-01-10 08:43:32 +00:00
|
|
|
selectedBackgroundColor: Styles.PrimaryTextColor,
|
2017-12-16 21:48:26 +00:00
|
|
|
}
|
2020-04-19 03:00:36 +00:00
|
|
|
|
|
|
|
l.ContextMenu = NewContextMenu(l)
|
|
|
|
l.focus = l
|
|
|
|
|
|
|
|
return l
|
2017-12-16 21:48:26 +00:00
|
|
|
}
|
|
|
|
|
2019-01-12 20:22:58 +00:00
|
|
|
// SetCurrentItem sets the currently selected item by its index, starting at 0
|
|
|
|
// for the first item. If a negative index is provided, items are referred to
|
|
|
|
// from the back (-1 = last item, -2 = second-to-last item, and so on). Out of
|
|
|
|
// range indices are clamped to the beginning/end.
|
|
|
|
//
|
|
|
|
// Calling this function triggers a "changed" event if the selection changes.
|
2020-10-07 23:34:21 +00:00
|
|
|
func (l *List) SetCurrentItem(index int) {
|
2020-03-25 14:32:57 +00:00
|
|
|
l.Lock()
|
|
|
|
|
2019-01-12 20:22:58 +00:00
|
|
|
if index < 0 {
|
|
|
|
index = len(l.items) + index
|
|
|
|
}
|
|
|
|
if index >= len(l.items) {
|
|
|
|
index = len(l.items) - 1
|
|
|
|
}
|
|
|
|
if index < 0 {
|
|
|
|
index = 0
|
|
|
|
}
|
|
|
|
|
2020-05-08 22:48:52 +00:00
|
|
|
previousItem := l.currentItem
|
2020-04-29 21:46:22 +00:00
|
|
|
l.currentItem = index
|
|
|
|
|
2020-05-01 23:01:18 +00:00
|
|
|
l.updateOffset()
|
|
|
|
|
2020-10-09 23:51:45 +00:00
|
|
|
if index != previousItem && index < len(l.items) && l.changed != nil {
|
2019-07-11 10:37:27 +00:00
|
|
|
item := l.items[index]
|
2020-03-25 14:32:57 +00:00
|
|
|
l.Unlock()
|
2020-09-16 17:55:33 +00:00
|
|
|
l.changed(index, item)
|
2020-10-09 23:51:45 +00:00
|
|
|
} else {
|
|
|
|
l.Unlock()
|
2018-01-01 16:17:20 +00:00
|
|
|
}
|
2017-12-18 19:04:52 +00:00
|
|
|
}
|
|
|
|
|
2020-09-16 17:55:33 +00:00
|
|
|
// GetCurrentItem returns the currently selected list item,
|
|
|
|
// Returns nil if no item is selected.
|
|
|
|
func (l *List) GetCurrentItem() *ListItem {
|
2020-03-25 14:32:57 +00:00
|
|
|
l.RLock()
|
|
|
|
defer l.RUnlock()
|
|
|
|
|
2020-09-16 17:55:33 +00:00
|
|
|
if len(l.items) == 0 || l.currentItem >= len(l.items) {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
return l.items[l.currentItem]
|
|
|
|
}
|
|
|
|
|
|
|
|
// GetCurrentItemIndex returns the index of the currently selected list item,
|
|
|
|
// starting at 0 for the first item and its struct.
|
|
|
|
func (l *List) GetCurrentItemIndex() int {
|
|
|
|
l.RLock()
|
|
|
|
defer l.RUnlock()
|
2018-03-18 19:42:51 +00:00
|
|
|
return l.currentItem
|
|
|
|
}
|
|
|
|
|
2020-09-16 17:55:33 +00:00
|
|
|
// GetItems returns all list items.
|
|
|
|
func (l *List) GetItems() []*ListItem {
|
|
|
|
l.RLock()
|
|
|
|
defer l.RUnlock()
|
|
|
|
return l.items
|
|
|
|
}
|
|
|
|
|
2018-09-05 10:57:35 +00:00
|
|
|
// RemoveItem removes the item with the given index (starting at 0) from the
|
2019-01-12 20:22:58 +00:00
|
|
|
// list. If a negative index is provided, items are referred to from the back
|
|
|
|
// (-1 = last item, -2 = second-to-last item, and so on). Out of range indices
|
|
|
|
// are clamped to the beginning/end, i.e. unless the list is empty, an item is
|
|
|
|
// always removed.
|
|
|
|
//
|
|
|
|
// The currently selected item is shifted accordingly. If it is the one that is
|
|
|
|
// removed, a "changed" event is fired.
|
2020-10-07 23:34:21 +00:00
|
|
|
func (l *List) RemoveItem(index int) {
|
2020-03-25 14:32:57 +00:00
|
|
|
l.Lock()
|
|
|
|
|
2019-01-12 20:22:58 +00:00
|
|
|
if len(l.items) == 0 {
|
2020-03-25 14:32:57 +00:00
|
|
|
l.Unlock()
|
2020-10-07 23:34:21 +00:00
|
|
|
return
|
2018-09-05 10:57:35 +00:00
|
|
|
}
|
2019-01-12 20:22:58 +00:00
|
|
|
|
|
|
|
// Adjust index.
|
|
|
|
if index < 0 {
|
|
|
|
index = len(l.items) + index
|
|
|
|
}
|
|
|
|
if index >= len(l.items) {
|
|
|
|
index = len(l.items) - 1
|
|
|
|
}
|
|
|
|
if index < 0 {
|
|
|
|
index = 0
|
|
|
|
}
|
|
|
|
|
|
|
|
// Remove item.
|
2018-09-05 10:57:35 +00:00
|
|
|
l.items = append(l.items[:index], l.items[index+1:]...)
|
2019-01-12 20:22:58 +00:00
|
|
|
|
|
|
|
// If there is nothing left, we're done.
|
|
|
|
if len(l.items) == 0 {
|
2020-03-25 14:32:57 +00:00
|
|
|
l.Unlock()
|
2020-10-07 23:34:21 +00:00
|
|
|
return
|
2019-01-12 20:22:58 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Shift current item.
|
2020-05-08 22:48:52 +00:00
|
|
|
previousItem := l.currentItem
|
2020-04-19 03:00:36 +00:00
|
|
|
if l.currentItem >= index && l.currentItem > 0 {
|
2019-01-12 20:22:58 +00:00
|
|
|
l.currentItem--
|
|
|
|
}
|
|
|
|
|
|
|
|
// Fire "changed" event for removed items.
|
2020-10-09 23:51:45 +00:00
|
|
|
if previousItem == index && index < len(l.items) && l.changed != nil {
|
2019-01-12 20:22:58 +00:00
|
|
|
item := l.items[l.currentItem]
|
2020-03-25 14:32:57 +00:00
|
|
|
l.Unlock()
|
2020-09-16 17:55:33 +00:00
|
|
|
l.changed(l.currentItem, item)
|
2020-03-25 14:32:57 +00:00
|
|
|
} else {
|
|
|
|
l.Unlock()
|
2018-09-05 10:57:35 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-10-13 16:45:35 +00:00
|
|
|
// SetOffset sets the number of list items and columns by which the list is
|
|
|
|
// scrolled down/to the right.
|
|
|
|
func (l *List) SetOffset(items, columns int) {
|
2020-05-01 23:01:18 +00:00
|
|
|
l.Lock()
|
|
|
|
defer l.Unlock()
|
|
|
|
|
2020-10-13 16:45:35 +00:00
|
|
|
if items < 0 {
|
|
|
|
items = 0
|
|
|
|
}
|
|
|
|
if columns < 0 {
|
|
|
|
columns = 0
|
2020-06-02 22:51:03 +00:00
|
|
|
}
|
|
|
|
|
2020-10-13 16:45:35 +00:00
|
|
|
l.itemOffset, l.columnOffset = items, columns
|
2020-05-01 23:01:18 +00:00
|
|
|
}
|
|
|
|
|
2020-10-13 16:45:35 +00:00
|
|
|
// GetOffset returns the number of list items and columns by which the list is
|
|
|
|
// scrolled down/to the right.
|
|
|
|
func (l *List) GetOffset() (int, int) {
|
2020-05-01 23:01:18 +00:00
|
|
|
l.Lock()
|
|
|
|
defer l.Unlock()
|
|
|
|
|
2020-10-13 16:45:35 +00:00
|
|
|
return l.itemOffset, l.columnOffset
|
2020-05-01 23:01:18 +00:00
|
|
|
}
|
|
|
|
|
2017-12-18 19:04:52 +00:00
|
|
|
// SetMainTextColor sets the color of the items' main text.
|
2020-10-07 23:34:21 +00:00
|
|
|
func (l *List) SetMainTextColor(color tcell.Color) {
|
2020-03-25 14:32:57 +00:00
|
|
|
l.Lock()
|
|
|
|
defer l.Unlock()
|
|
|
|
|
2017-12-16 21:48:26 +00:00
|
|
|
l.mainTextColor = color
|
|
|
|
}
|
|
|
|
|
2017-12-18 19:04:52 +00:00
|
|
|
// SetSecondaryTextColor sets the color of the items' secondary text.
|
2020-10-07 23:34:21 +00:00
|
|
|
func (l *List) SetSecondaryTextColor(color tcell.Color) {
|
2020-03-25 14:32:57 +00:00
|
|
|
l.Lock()
|
|
|
|
defer l.Unlock()
|
|
|
|
|
2017-12-16 21:48:26 +00:00
|
|
|
l.secondaryTextColor = color
|
|
|
|
}
|
|
|
|
|
|
|
|
// SetShortcutColor sets the color of the items' shortcut.
|
2020-10-07 23:34:21 +00:00
|
|
|
func (l *List) SetShortcutColor(color tcell.Color) {
|
2020-03-25 14:32:57 +00:00
|
|
|
l.Lock()
|
|
|
|
defer l.Unlock()
|
|
|
|
|
2017-12-16 21:48:26 +00:00
|
|
|
l.shortcutColor = color
|
|
|
|
}
|
|
|
|
|
2017-12-18 19:04:52 +00:00
|
|
|
// SetSelectedTextColor sets the text color of selected items.
|
2020-10-07 23:34:21 +00:00
|
|
|
func (l *List) SetSelectedTextColor(color tcell.Color) {
|
2020-03-25 14:32:57 +00:00
|
|
|
l.Lock()
|
|
|
|
defer l.Unlock()
|
|
|
|
|
2017-12-18 19:04:52 +00:00
|
|
|
l.selectedTextColor = color
|
|
|
|
}
|
|
|
|
|
2020-04-28 21:59:40 +00:00
|
|
|
// SetSelectedTextAttributes sets the style attributes of selected items.
|
2020-10-07 23:34:21 +00:00
|
|
|
func (l *List) SetSelectedTextAttributes(attr tcell.AttrMask) {
|
2020-04-28 21:59:40 +00:00
|
|
|
l.Lock()
|
|
|
|
defer l.Unlock()
|
|
|
|
|
|
|
|
l.selectedTextAttributes = attr
|
|
|
|
}
|
|
|
|
|
2017-12-18 19:04:52 +00:00
|
|
|
// SetSelectedBackgroundColor sets the background color of selected items.
|
2020-10-07 23:34:21 +00:00
|
|
|
func (l *List) SetSelectedBackgroundColor(color tcell.Color) {
|
2020-03-25 14:32:57 +00:00
|
|
|
l.Lock()
|
|
|
|
defer l.Unlock()
|
|
|
|
|
2017-12-18 19:04:52 +00:00
|
|
|
l.selectedBackgroundColor = color
|
|
|
|
}
|
|
|
|
|
2018-11-26 10:00:48 +00:00
|
|
|
// SetSelectedFocusOnly sets a flag which determines when the currently selected
|
|
|
|
// list item is highlighted. If set to true, selected items are only highlighted
|
|
|
|
// when the list has focus. If set to false, they are always highlighted.
|
2020-10-07 23:34:21 +00:00
|
|
|
func (l *List) SetSelectedFocusOnly(focusOnly bool) {
|
2020-03-25 14:32:57 +00:00
|
|
|
l.Lock()
|
|
|
|
defer l.Unlock()
|
|
|
|
|
2018-11-26 10:00:48 +00:00
|
|
|
l.selectedFocusOnly = focusOnly
|
|
|
|
}
|
|
|
|
|
2020-05-01 23:01:18 +00:00
|
|
|
// SetSelectedAlwaysVisible sets a flag which determines whether the currently
|
|
|
|
// selected list item must remain visible when scrolling.
|
2020-10-07 23:34:21 +00:00
|
|
|
func (l *List) SetSelectedAlwaysVisible(alwaysVisible bool) {
|
2020-05-01 23:01:18 +00:00
|
|
|
l.Lock()
|
|
|
|
defer l.Unlock()
|
|
|
|
|
|
|
|
l.selectedAlwaysVisible = alwaysVisible
|
|
|
|
}
|
|
|
|
|
2020-05-20 22:36:19 +00:00
|
|
|
// SetSelectedAlwaysCentered sets a flag which determines whether the currently
|
|
|
|
// selected list item must remain centered when scrolling.
|
2020-10-07 23:34:21 +00:00
|
|
|
func (l *List) SetSelectedAlwaysCentered(alwaysCentered bool) {
|
2020-05-20 22:36:19 +00:00
|
|
|
l.Lock()
|
|
|
|
defer l.Unlock()
|
|
|
|
|
|
|
|
l.selectedAlwaysCentered = alwaysCentered
|
|
|
|
}
|
|
|
|
|
2019-02-20 16:58:59 +00:00
|
|
|
// SetHighlightFullLine sets a flag which determines whether the colored
|
|
|
|
// background of selected items spans the entire width of the view. If set to
|
|
|
|
// true, the highlight spans the entire view. If set to false, only the text of
|
|
|
|
// the selected item from beginning to end is highlighted.
|
2020-10-07 23:34:21 +00:00
|
|
|
func (l *List) SetHighlightFullLine(highlight bool) {
|
2020-03-25 14:32:57 +00:00
|
|
|
l.Lock()
|
|
|
|
defer l.Unlock()
|
|
|
|
|
2019-02-20 16:58:59 +00:00
|
|
|
l.highlightFullLine = highlight
|
2019-01-03 06:51:11 +00:00
|
|
|
}
|
|
|
|
|
2017-12-16 21:48:26 +00:00
|
|
|
// ShowSecondaryText determines whether or not to show secondary item texts.
|
2020-10-07 23:34:21 +00:00
|
|
|
func (l *List) ShowSecondaryText(show bool) {
|
2020-03-25 14:32:57 +00:00
|
|
|
l.Lock()
|
|
|
|
defer l.Unlock()
|
|
|
|
|
2017-12-16 21:48:26 +00:00
|
|
|
l.showSecondaryText = show
|
2020-10-07 23:34:21 +00:00
|
|
|
return
|
2017-12-16 21:48:26 +00:00
|
|
|
}
|
|
|
|
|
2020-02-13 17:51:01 +00:00
|
|
|
// SetScrollBarVisibility specifies the display of the scroll bar.
|
2020-10-07 23:34:21 +00:00
|
|
|
func (l *List) SetScrollBarVisibility(visibility ScrollBarVisibility) {
|
2020-03-25 14:32:57 +00:00
|
|
|
l.Lock()
|
|
|
|
defer l.Unlock()
|
|
|
|
|
2020-02-13 17:51:01 +00:00
|
|
|
l.scrollBarVisibility = visibility
|
2020-02-01 05:21:59 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// SetScrollBarColor sets the color of the scroll bar.
|
2020-10-07 23:34:21 +00:00
|
|
|
func (l *List) SetScrollBarColor(color tcell.Color) {
|
2020-03-25 14:32:57 +00:00
|
|
|
l.Lock()
|
|
|
|
defer l.Unlock()
|
|
|
|
|
2020-02-01 05:21:59 +00:00
|
|
|
l.scrollBarColor = color
|
|
|
|
}
|
|
|
|
|
2020-04-19 03:00:36 +00:00
|
|
|
// SetHover sets the flag that determines whether hovering over an item will
|
|
|
|
// highlight it (without triggering callbacks set with SetSelectedFunc).
|
2020-10-07 23:34:21 +00:00
|
|
|
func (l *List) SetHover(hover bool) {
|
2020-04-19 03:00:36 +00:00
|
|
|
l.Lock()
|
|
|
|
defer l.Unlock()
|
|
|
|
|
|
|
|
l.hover = hover
|
|
|
|
}
|
|
|
|
|
2019-12-29 16:47:05 +00:00
|
|
|
// 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
|
|
|
|
// false, the selection won't change when navigating downwards on the last item
|
|
|
|
// or navigating upwards on the first item.
|
2020-10-07 23:34:21 +00:00
|
|
|
func (l *List) SetWrapAround(wrapAround bool) {
|
2020-03-25 14:32:57 +00:00
|
|
|
l.Lock()
|
|
|
|
defer l.Unlock()
|
|
|
|
|
2019-12-29 16:47:05 +00:00
|
|
|
l.wrapAround = wrapAround
|
|
|
|
}
|
|
|
|
|
2018-01-01 16:17:20 +00:00
|
|
|
// SetChangedFunc sets the function which is called when the user navigates to
|
|
|
|
// a list item. The function receives the item's index in the list of items
|
2020-09-16 17:55:33 +00:00
|
|
|
// (starting with 0) and the list item.
|
2018-01-01 16:17:20 +00:00
|
|
|
//
|
|
|
|
// This function is also called when the first item is added or when
|
|
|
|
// SetCurrentItem() is called.
|
2020-10-07 23:34:21 +00:00
|
|
|
func (l *List) SetChangedFunc(handler func(index int, item *ListItem)) {
|
2020-03-25 14:32:57 +00:00
|
|
|
l.Lock()
|
|
|
|
defer l.Unlock()
|
|
|
|
|
2018-01-01 16:17:20 +00:00
|
|
|
l.changed = handler
|
|
|
|
}
|
|
|
|
|
2017-12-16 21:48:26 +00:00
|
|
|
// SetSelectedFunc sets the function which is called when the user selects a
|
2017-12-29 21:27:10 +00:00
|
|
|
// list item by pressing Enter on the current selection. The function receives
|
2020-09-16 17:55:33 +00:00
|
|
|
// the item's index in the list of items (starting with 0) and its struct.
|
2020-10-07 23:34:21 +00:00
|
|
|
func (l *List) SetSelectedFunc(handler func(int, *ListItem)) {
|
2020-03-25 14:32:57 +00:00
|
|
|
l.Lock()
|
|
|
|
defer l.Unlock()
|
|
|
|
|
2017-12-16 21:48:26 +00:00
|
|
|
l.selected = handler
|
|
|
|
}
|
|
|
|
|
2018-01-01 16:17:20 +00:00
|
|
|
// SetDoneFunc sets a function which is called when the user presses the Escape
|
|
|
|
// key.
|
2020-10-07 23:34:21 +00:00
|
|
|
func (l *List) SetDoneFunc(handler func()) {
|
2020-03-25 14:32:57 +00:00
|
|
|
l.Lock()
|
|
|
|
defer l.Unlock()
|
|
|
|
|
2018-01-01 16:17:20 +00:00
|
|
|
l.done = handler
|
|
|
|
}
|
|
|
|
|
2019-01-12 20:22:58 +00:00
|
|
|
// AddItem calls InsertItem() with an index of -1.
|
2020-10-07 23:34:21 +00:00
|
|
|
func (l *List) AddItem(item *ListItem) {
|
2020-09-16 17:55:33 +00:00
|
|
|
l.InsertItem(-1, item)
|
2019-01-12 20:22:58 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// InsertItem adds a new item to the list at the specified index. An index of 0
|
|
|
|
// will insert the item at the beginning, an index of 1 before the second item,
|
|
|
|
// and so on. An index of GetItemCount() or higher will insert the item at the
|
|
|
|
// end of the list. Negative indices are also allowed: An index of -1 will
|
|
|
|
// insert the item at the end of the list, an index of -2 before the last item,
|
|
|
|
// and so on. An index of -GetItemCount()-1 or lower will insert the item at the
|
|
|
|
// beginning.
|
|
|
|
//
|
|
|
|
// An item has a main text which will be highlighted when selected. It also has
|
|
|
|
// a secondary text which is shown underneath the main text (if it is set to
|
|
|
|
// visible) but which may remain empty.
|
2017-12-16 21:48:26 +00:00
|
|
|
//
|
|
|
|
// The shortcut is a key binding. If the specified rune is entered, the item
|
|
|
|
// is selected immediately. Set to 0 for no binding.
|
2017-12-18 19:04:52 +00:00
|
|
|
//
|
|
|
|
// The "selected" callback will be invoked when the user selects the item. You
|
2019-01-12 20:22:58 +00:00
|
|
|
// may provide nil if no such callback is needed or if all events are handled
|
2017-12-18 19:04:52 +00:00
|
|
|
// through the selected callback set with SetSelectedFunc().
|
2019-01-12 20:22:58 +00:00
|
|
|
//
|
|
|
|
// The currently selected item will shift its position accordingly. If the list
|
|
|
|
// was previously empty, a "changed" event is fired because the new item becomes
|
|
|
|
// selected.
|
2020-10-07 23:34:21 +00:00
|
|
|
func (l *List) InsertItem(index int, item *ListItem) {
|
2020-03-25 14:32:57 +00:00
|
|
|
l.Lock()
|
|
|
|
|
2019-01-12 20:22:58 +00:00
|
|
|
// Shift index to range.
|
|
|
|
if index < 0 {
|
|
|
|
index = len(l.items) + index + 1
|
|
|
|
}
|
|
|
|
if index < 0 {
|
|
|
|
index = 0
|
|
|
|
} else if index > len(l.items) {
|
|
|
|
index = len(l.items)
|
|
|
|
}
|
|
|
|
|
2019-01-23 20:40:01 +00:00
|
|
|
// Shift current item.
|
|
|
|
if l.currentItem < len(l.items) && l.currentItem >= index {
|
|
|
|
l.currentItem++
|
|
|
|
}
|
|
|
|
|
2019-01-12 20:22:58 +00:00
|
|
|
// Insert item (make space for the new item, then shift and insert).
|
|
|
|
l.items = append(l.items, nil)
|
|
|
|
if index < len(l.items)-1 { // -1 because l.items has already grown by one item.
|
|
|
|
copy(l.items[index+1:], l.items[index:])
|
|
|
|
}
|
|
|
|
l.items[index] = item
|
|
|
|
|
|
|
|
// Fire a "change" event for the first item in the list.
|
2018-01-01 16:17:20 +00:00
|
|
|
if len(l.items) == 1 && l.changed != nil {
|
|
|
|
item := l.items[0]
|
2020-03-25 14:32:57 +00:00
|
|
|
l.Unlock()
|
2020-09-16 17:55:33 +00:00
|
|
|
l.changed(0, item)
|
2020-03-25 14:32:57 +00:00
|
|
|
} else {
|
|
|
|
l.Unlock()
|
2018-01-01 16:17:20 +00:00
|
|
|
}
|
2017-12-16 21:48:26 +00:00
|
|
|
}
|
|
|
|
|
2020-09-16 17:55:33 +00:00
|
|
|
// GetItem returns the ListItem at the given index.
|
|
|
|
// Returns nil when index is out of bounds.
|
|
|
|
func (l *List) GetItem(index int) *ListItem {
|
|
|
|
if index > len(l.items)-1 {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
return l.items[index]
|
|
|
|
}
|
|
|
|
|
2018-04-19 19:17:13 +00:00
|
|
|
// GetItemCount returns the number of items in the list.
|
|
|
|
func (l *List) GetItemCount() int {
|
2020-03-25 14:32:57 +00:00
|
|
|
l.RLock()
|
|
|
|
defer l.RUnlock()
|
|
|
|
|
2018-04-19 19:17:13 +00:00
|
|
|
return len(l.items)
|
|
|
|
}
|
|
|
|
|
|
|
|
// GetItemText returns an item's texts (main and secondary). Panics if the index
|
|
|
|
// is out of range.
|
|
|
|
func (l *List) GetItemText(index int) (main, secondary string) {
|
2020-03-25 14:32:57 +00:00
|
|
|
l.RLock()
|
|
|
|
defer l.RUnlock()
|
2020-10-07 01:19:40 +00:00
|
|
|
return string(l.items[index].mainText), string(l.items[index].secondaryText)
|
2018-04-19 19:17:13 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// SetItemText sets an item's main and secondary text. Panics if the index is
|
|
|
|
// out of range.
|
2020-10-07 23:34:21 +00:00
|
|
|
func (l *List) SetItemText(index int, main, secondary string) {
|
2020-03-25 14:32:57 +00:00
|
|
|
l.Lock()
|
|
|
|
defer l.Unlock()
|
|
|
|
|
2018-04-19 19:17:13 +00:00
|
|
|
item := l.items[index]
|
2020-10-07 01:19:40 +00:00
|
|
|
item.mainText = []byte(main)
|
|
|
|
item.secondaryText = []byte(secondary)
|
2018-04-19 19:17:13 +00:00
|
|
|
}
|
|
|
|
|
2020-04-19 03:00:36 +00:00
|
|
|
// SetItemEnabled sets whether an item is selectable. Panics if the index is
|
|
|
|
// out of range.
|
2020-10-07 23:34:21 +00:00
|
|
|
func (l *List) SetItemEnabled(index int, enabled bool) {
|
2020-04-19 03:00:36 +00:00
|
|
|
l.Lock()
|
|
|
|
defer l.Unlock()
|
|
|
|
|
|
|
|
item := l.items[index]
|
2020-10-16 04:28:54 +00:00
|
|
|
item.disabled = !enabled
|
2020-04-19 03:00:36 +00:00
|
|
|
}
|
|
|
|
|
2022-07-30 01:59:41 +00:00
|
|
|
// SetIndicators is used to set prefix and suffix indicators for selected and unselected items.
|
|
|
|
func (l *List) SetIndicators(selectedPrefix, selectedSuffix, unselectedPrefix, unselectedSuffix string) {
|
|
|
|
l.Lock()
|
|
|
|
defer l.Unlock()
|
|
|
|
l.selectedPrefix = []byte(selectedPrefix)
|
|
|
|
l.selectedSuffix = []byte(selectedSuffix)
|
|
|
|
l.unselectedPrefix = []byte(unselectedPrefix)
|
|
|
|
l.unselectedSuffix = []byte(unselectedSuffix)
|
|
|
|
l.prefixWidth = len(selectedPrefix)
|
|
|
|
if len(unselectedPrefix) > l.prefixWidth {
|
|
|
|
l.prefixWidth = len(unselectedPrefix)
|
|
|
|
}
|
|
|
|
l.suffixWidth = len(selectedSuffix)
|
|
|
|
if len(unselectedSuffix) > l.suffixWidth {
|
|
|
|
l.suffixWidth = len(unselectedSuffix)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-01-12 20:22:58 +00:00
|
|
|
// 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
|
|
|
|
// returned in ascending order.
|
|
|
|
//
|
|
|
|
// If mustContainBoth is set to true, mainSearch must be contained in the main
|
|
|
|
// text AND secondarySearch must be contained in the secondary text. If it is
|
|
|
|
// false, only one of the two search strings must be contained.
|
|
|
|
//
|
|
|
|
// Set ignoreCase to true for case-insensitive search.
|
|
|
|
func (l *List) FindItems(mainSearch, secondarySearch string, mustContainBoth, ignoreCase bool) (indices []int) {
|
2020-03-25 14:32:57 +00:00
|
|
|
l.RLock()
|
|
|
|
defer l.RUnlock()
|
|
|
|
|
2019-01-12 20:22:58 +00:00
|
|
|
if mainSearch == "" && secondarySearch == "" {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
if ignoreCase {
|
|
|
|
mainSearch = strings.ToLower(mainSearch)
|
|
|
|
secondarySearch = strings.ToLower(secondarySearch)
|
|
|
|
}
|
|
|
|
|
2020-10-07 01:19:40 +00:00
|
|
|
mainSearchBytes := []byte(mainSearch)
|
|
|
|
secondarySearchBytes := []byte(secondarySearch)
|
|
|
|
|
2019-01-12 20:22:58 +00:00
|
|
|
for index, item := range l.items {
|
2020-09-16 17:55:33 +00:00
|
|
|
mainText := item.mainText
|
|
|
|
secondaryText := item.secondaryText
|
2019-01-12 20:22:58 +00:00
|
|
|
if ignoreCase {
|
2020-10-07 01:19:40 +00:00
|
|
|
mainText = bytes.ToLower(mainText)
|
|
|
|
secondaryText = bytes.ToLower(secondaryText)
|
2019-01-12 20:22:58 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// strings.Contains() always returns true for a "" search.
|
2020-10-07 01:19:40 +00:00
|
|
|
mainContained := bytes.Contains(mainText, mainSearchBytes)
|
|
|
|
secondaryContained := bytes.Contains(secondaryText, secondarySearchBytes)
|
2019-01-12 20:22:58 +00:00
|
|
|
if mustContainBoth && mainContained && secondaryContained ||
|
2020-10-07 01:19:40 +00:00
|
|
|
!mustContainBoth && (len(mainText) > 0 && mainContained || len(secondaryText) > 0 && secondaryContained) {
|
2019-01-12 20:22:58 +00:00
|
|
|
indices = append(indices, index)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2018-01-01 16:17:20 +00:00
|
|
|
// Clear removes all items from the list.
|
2020-10-07 23:34:21 +00:00
|
|
|
func (l *List) Clear() {
|
2020-03-25 14:32:57 +00:00
|
|
|
l.Lock()
|
|
|
|
defer l.Unlock()
|
|
|
|
|
2017-12-18 19:04:52 +00:00
|
|
|
l.items = nil
|
|
|
|
l.currentItem = 0
|
2020-10-13 16:45:35 +00:00
|
|
|
l.itemOffset = 0
|
|
|
|
l.columnOffset = 0
|
2017-12-18 19:04:52 +00:00
|
|
|
}
|
|
|
|
|
2020-04-19 03:00:36 +00:00
|
|
|
// 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()
|
|
|
|
}
|
2020-05-09 17:19:23 +00:00
|
|
|
|
|
|
|
l.RLock()
|
|
|
|
defer l.RUnlock()
|
2020-04-19 03:00:36 +00:00
|
|
|
return l.hasFocus
|
|
|
|
}
|
|
|
|
|
2020-04-21 22:16:03 +00:00
|
|
|
// Transform modifies the current selection.
|
|
|
|
func (l *List) Transform(tr Transformation) {
|
|
|
|
l.Lock()
|
2020-10-09 23:51:45 +00:00
|
|
|
|
|
|
|
previousItem := l.currentItem
|
2020-04-21 22:16:03 +00:00
|
|
|
|
|
|
|
l.transform(tr)
|
2020-10-09 23:51:45 +00:00
|
|
|
|
|
|
|
if l.currentItem != previousItem && l.currentItem < len(l.items) && l.changed != nil {
|
|
|
|
item := l.items[l.currentItem]
|
|
|
|
l.Unlock()
|
|
|
|
l.changed(l.currentItem, item)
|
|
|
|
} else {
|
|
|
|
l.Unlock()
|
|
|
|
}
|
2020-04-21 22:16:03 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func (l *List) transform(tr Transformation) {
|
|
|
|
var decreasing bool
|
|
|
|
|
2020-09-08 21:22:58 +00:00
|
|
|
pageItems := l.height
|
|
|
|
if l.showSecondaryText {
|
|
|
|
pageItems /= 2
|
|
|
|
}
|
|
|
|
if pageItems < 1 {
|
|
|
|
pageItems = 1
|
|
|
|
}
|
|
|
|
|
|