forked from tslocum/cview
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
327 lines
8.9 KiB
327 lines
8.9 KiB
package tview |
|
|
|
import ( |
|
"fmt" |
|
|
|
"github.com/gdamore/tcell" |
|
) |
|
|
|
// listItem represents one item in a List. |
|
type listItem struct { |
|
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. |
|
Selected func() // The optional function which is called when the item is selected. |
|
} |
|
|
|
// List displays rows of items, each of which can be selected. |
|
// |
|
// See https://github.com/rivo/tview/wiki/List for an example. |
|
type List struct { |
|
*Box |
|
|
|
// The items of the list. |
|
items []*listItem |
|
|
|
// The index of the currently selected item. |
|
currentItem int |
|
|
|
// Whether or not to show the secondary item texts. |
|
showSecondaryText bool |
|
|
|
// The item main text color. |
|
mainTextColor tcell.Color |
|
|
|
// The item secondary text color. |
|
secondaryTextColor tcell.Color |
|
|
|
// The item shortcut text color. |
|
shortcutColor tcell.Color |
|
|
|
// The text color for selected items. |
|
selectedTextColor tcell.Color |
|
|
|
// The background color for selected items. |
|
selectedBackgroundColor tcell.Color |
|
|
|
// An optional function which is called when the user has navigated to a list |
|
// item. |
|
changed func(index int, mainText, secondaryText string, shortcut rune) |
|
|
|
// 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. |
|
selected func(index int, mainText, secondaryText string, shortcut rune) |
|
|
|
// An optional function which is called when the user presses the Escape key. |
|
done func() |
|
} |
|
|
|
// NewList returns a new form. |
|
func NewList() *List { |
|
return &List{ |
|
Box: NewBox(), |
|
showSecondaryText: true, |
|
mainTextColor: Styles.PrimaryTextColor, |
|
secondaryTextColor: Styles.TertiaryTextColor, |
|
shortcutColor: Styles.SecondaryTextColor, |
|
selectedTextColor: Styles.PrimitiveBackgroundColor, |
|
selectedBackgroundColor: Styles.PrimaryTextColor, |
|
} |
|
} |
|
|
|
// SetCurrentItem sets the currently selected item by its index. This triggers |
|
// a "changed" event. |
|
func (l *List) SetCurrentItem(index int) *List { |
|
l.currentItem = index |
|
if l.currentItem < len(l.items) && l.changed != nil { |
|
item := l.items[l.currentItem] |
|
l.changed(l.currentItem, item.MainText, item.SecondaryText, item.Shortcut) |
|
} |
|
return l |
|
} |
|
|
|
// GetCurrentItem returns the index of the currently selected list item. |
|
func (l *List) GetCurrentItem() int { |
|
return l.currentItem |
|
} |
|
|
|
// SetMainTextColor sets the color of the items' main text. |
|
func (l *List) SetMainTextColor(color tcell.Color) *List { |
|
l.mainTextColor = color |
|
return l |
|
} |
|
|
|
// SetSecondaryTextColor sets the color of the items' secondary text. |
|
func (l *List) SetSecondaryTextColor(color tcell.Color) *List { |
|
l.secondaryTextColor = color |
|
return l |
|
} |
|
|
|
// SetShortcutColor sets the color of the items' shortcut. |
|
func (l *List) SetShortcutColor(color tcell.Color) *List { |
|
l.shortcutColor = color |
|
return l |
|
} |
|
|
|
// SetSelectedTextColor sets the text color of selected items. |
|
func (l *List) SetSelectedTextColor(color tcell.Color) *List { |
|
l.selectedTextColor = color |
|
return l |
|
} |
|
|
|
// SetSelectedBackgroundColor sets the background color of selected items. |
|
func (l *List) SetSelectedBackgroundColor(color tcell.Color) *List { |
|
l.selectedBackgroundColor = color |
|
return l |
|
} |
|
|
|
// ShowSecondaryText determines whether or not to show secondary item texts. |
|
func (l *List) ShowSecondaryText(show bool) *List { |
|
l.showSecondaryText = show |
|
return l |
|
} |
|
|
|
// 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 |
|
// (starting with 0), its main text, secondary text, and its shortcut rune. |
|
// |
|
// This function is also called when the first item is added or when |
|
// SetCurrentItem() is called. |
|
func (l *List) SetChangedFunc(handler func(int, string, string, rune)) *List { |
|
l.changed = handler |
|
return l |
|
} |
|
|
|
// SetSelectedFunc sets the function which is called when the user selects a |
|
// list item by pressing Enter on the current selection. The function receives |
|
// the item's index in the list of items (starting with 0), its main text, |
|
// secondary text, and its shortcut rune. |
|
func (l *List) SetSelectedFunc(handler func(int, string, string, rune)) *List { |
|
l.selected = handler |
|
return l |
|
} |
|
|
|
// SetDoneFunc sets a function which is called when the user presses the Escape |
|
// key. |
|
func (l *List) SetDoneFunc(handler func()) *List { |
|
l.done = handler |
|
return l |
|
} |
|
|
|
// AddItem adds a new item to the list. 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. |
|
// |
|
// The shortcut is a key binding. If the specified rune is entered, the item |
|
// is selected immediately. Set to 0 for no binding. |
|
// |
|
// The "selected" callback will be invoked when the user selects the item. You |
|
// may provide nil if no such item is needed or if all events are handled |
|
// through the selected callback set with SetSelectedFunc(). |
|
func (l *List) AddItem(mainText, secondaryText string, shortcut rune, selected func()) *List { |
|
l.items = append(l.items, &listItem{ |
|
MainText: mainText, |
|
SecondaryText: secondaryText, |
|
Shortcut: shortcut, |
|
Selected: selected, |
|
}) |
|
if len(l.items) == 1 && l.changed != nil { |
|
item := l.items[0] |
|
l.changed(0, item.MainText, item.SecondaryText, item.Shortcut) |
|
} |
|
return l |
|
} |
|
|
|
// Clear removes all items from the list. |
|
func (l *List) Clear() *List { |
|
l.items = nil |
|
l.currentItem = 0 |
|
return l |
|
} |
|
|
|
// Draw draws this primitive onto the screen. |
|
func (l *List) Draw(screen tcell.Screen) { |
|
l.Box.Draw(screen) |
|
|
|
// Determine the dimensions. |
|
x, y, width, height := l.GetInnerRect() |
|
bottomLimit := y + height |
|
|
|
// Do we show any shortcuts? |
|
var showShortcuts bool |
|
for _, item := range l.items { |
|
if item.Shortcut != 0 { |
|
showShortcuts = true |
|
x += 4 |
|
width -= 4 |
|
break |
|
} |
|
} |
|
|
|
// We want to keep the current selection in view. What is our offset? |
|
var offset int |
|
if l.showSecondaryText { |
|
if l.currentItem >= height/2 { |
|
offset = l.currentItem + 1 - (height / 2) |
|
} |
|
} else { |
|
if l.currentItem >= height { |
|
offset = l.currentItem + 1 - height |
|
} |
|
} |
|
|
|
// Draw the list items. |
|
for index, item := range l.items { |
|
if index < offset { |
|
continue |
|
} |
|
|
|
if y >= bottomLimit { |
|
break |
|
} |
|
|
|
// Shortcuts. |
|
if showShortcuts && item.Shortcut != 0 { |
|
Print(screen, fmt.Sprintf("(%s)", string(item.Shortcut)), x-5, y, 4, AlignRight, l.shortcutColor) |
|
} |
|
|
|
// Main text. |
|
Print(screen, item.MainText, x, y, width, AlignLeft, l.mainTextColor) |
|
|
|
// Background color of selected text. |
|
if index == l.currentItem { |
|
textWidth := StringWidth(item.MainText) |
|
for bx := 0; bx < textWidth && bx < width; bx++ { |
|
m, c, style, _ := screen.GetContent(x+bx, y) |
|
fg, _, _ := style.Decompose() |
|
if fg == l.mainTextColor { |
|
fg = l.selectedTextColor |
|
} |
|
style = style.Background(l.selectedBackgroundColor).Foreground(fg) |
|
screen.SetContent(x+bx, y, m, c, style) |
|
} |
|
} |
|
|
|
y++ |
|
|
|
if y >= bottomLimit { |
|
break |
|
} |
|
|
|
// Secondary text. |
|
if l.showSecondaryText { |
|
Print(screen, item.SecondaryText, x, y, width, AlignLeft, l.secondaryTextColor) |
|
y++ |
|
} |
|
} |
|
} |
|
|
|
// 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)) { |
|
previousItem := l.currentItem |
|
|
|
switch key := event.Key(); key { |
|
case tcell.KeyTab, tcell.KeyDown, tcell.KeyRight: |
|
l.currentItem++ |
|
case tcell.KeyBacktab, tcell.KeyUp, tcell.KeyLeft: |
|
l.currentItem-- |
|
case tcell.KeyHome: |
|
l.currentItem = 0 |
|
case tcell.KeyEnd: |
|
l.currentItem = len(l.items) - 1 |
|
case tcell.KeyPgDn: |
|
l.currentItem += 5 |
|
case tcell.KeyPgUp: |
|
l.currentItem -= 5 |
|
case tcell.KeyEnter: |
|
item := l.items[l.currentItem] |
|
if item.Selected != nil { |
|
item.Selected() |
|
} |
|
if l.selected != nil { |
|
l.selected(l.currentItem, item.MainText, item.SecondaryText, item.Shortcut) |
|
} |
|
case tcell.KeyEscape: |
|
if l.done != nil { |
|
l.done() |
|
} |
|
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 { |
|
// We have a shortcut. |
|
found = true |
|
l.currentItem = index |
|
break |
|
} |
|
} |
|
if !found { |
|
break |
|
} |
|
} |
|
item := l.items[l.currentItem] |
|
if item.Selected != nil { |
|
item.Selected() |
|
} |
|
if l.selected != nil { |
|
l.selected(l.currentItem, item.MainText, item.SecondaryText, item.Shortcut) |
|
} |
|
} |
|
|
|
if l.currentItem < 0 { |
|
l.currentItem = len(l.items) - 1 |
|
} else if l.currentItem >= len(l.items) { |
|
l.currentItem = 0 |
|
} |
|
|
|
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) |
|
} |
|
}) |
|
}
|
|
|