package cview
import (
"bytes"
"fmt"
"strings"
"sync"
"github.com/gdamore/tcell/v2"
)
type ListItem struct {
disabled bool
mainText []byte
secondaryText []byte
shortcut rune
selected func ()
reference interface {}
sync .RWMutex
}
func NewListItem (mainText string ) *ListItem {
return &ListItem {
mainText : []byte (mainText ),
}
}
func (l *ListItem ) SetMainBytes (val []byte ) {
l .Lock ()
defer l .Unlock ()
l .mainText = val
}
func (l *ListItem ) SetMainText (val string ) {
l .SetMainBytes ([]byte (val ))
}
func (l *ListItem ) GetMainBytes () []byte {
l .RLock ()
defer l .RUnlock ()
return l .mainText
}
func (l *ListItem ) GetMainText () string {
return string (l .GetMainBytes ())
}
func (l *ListItem ) SetSecondaryBytes (val []byte ) {
l .Lock ()
defer l .Unlock ()
l .secondaryText = val
}
func (l *ListItem ) SetSecondaryText (val string ) {
l .SetSecondaryBytes ([]byte (val ))
}
func (l *ListItem ) GetSecondaryBytes () []byte {
l .RLock ()
defer l .RUnlock ()
return l .secondaryText
}
func (l *ListItem ) GetSecondaryText () string {
return string (l .GetSecondaryBytes ())
}
func (l *ListItem ) SetShortcut (val rune ) {
l .Lock ()
defer l .Unlock ()
l .shortcut = val
}
func (l *ListItem ) GetShortcut () rune {
l .RLock ()
defer l .RUnlock ()
return l .shortcut
}
func (l *ListItem ) SetSelectedFunc (handler func ()) {
l .Lock ()
defer l .Unlock ()
l .selected = handler
}
func (l *ListItem ) SetReference (val interface {}) {
l .Lock ()
defer l .Unlock ()
l .reference = val
}
func (l *ListItem ) GetReference () interface {} {
l .RLock ()
defer l .RUnlock ()
return l .reference
}
type List struct {
*Box
*ContextMenu
items []*ListItem
currentItem int
showSecondaryText bool
mainTextColor tcell .Color
secondaryTextColor tcell .Color
shortcutColor tcell .Color
selectedTextColor tcell .Color
selectedTextAttributes tcell .AttrMask
scrollBarVisibility ScrollBarVisibility
scrollBarColor tcell .Color
selectedBackgroundColor tcell .Color
selectedFocusOnly bool
selectedAlwaysVisible bool
selectedAlwaysCentered bool
highlightFullLine bool
wrapAround bool
hover bool
itemOffset, columnOffset int
changed func (index int , item *ListItem )
selected func (index int , item *ListItem )
done func ()
height int
unselectedPrefix, unselectedSuffix []byte
selectedPrefix, selectedSuffix []byte
prefixWidth, suffixWidth int
sync .RWMutex
}
func NewList () *List {
l := &List {
Box : NewBox (),
showSecondaryText : true ,
scrollBarVisibility : ScrollBarAuto ,
mainTextColor : Styles .PrimaryTextColor ,
secondaryTextColor : Styles .TertiaryTextColor ,
shortcutColor : Styles .SecondaryTextColor ,
selectedTextColor : Styles .PrimitiveBackgroundColor ,
scrollBarColor : Styles .ScrollBarColor ,
selectedBackgroundColor : Styles .PrimaryTextColor ,
}
l .ContextMenu = NewContextMenu (l )
l .focus = l
return l
}
func (l *List ) SetCurrentItem (index int ) {
l .Lock ()
if index < 0 {
index = len (l .items ) + index
}
if index >= len (l .items ) {
index = len (l .items ) - 1
}
if index < 0 {
index = 0
}
previousItem := l .currentItem
l .currentItem = index
l .updateOffset ()
if index != previousItem && index < len (l .items ) && l .changed != nil {
item := l .items [index ]
l .Unlock ()
l .changed (index , item )
} else {
l .Unlock ()
}
}
func (l *List ) GetCurrentItem () *ListItem {
l .RLock ()
defer l .RUnlock ()
if len (l .items ) == 0 || l .currentItem >= len (l .items ) {
return nil
}
return l .items [l .currentItem ]
}
func (l *List ) GetCurrentItemIndex () int {
l .RLock ()
defer l .RUnlock ()
return l .currentItem
}
func (l *List ) GetItems () []*ListItem {
l .RLock ()
defer l .RUnlock ()
return l .items
}
func (l *List ) RemoveItem (index int ) {
l .Lock ()
if len (l .items ) == 0 {
l .Unlock ()
return
}
if index < 0 {
index = len (l .items ) + index
}
if index >= len (l .items ) {
index = len (l .items ) - 1
}
if index < 0 {
index = 0
}
l .items = append (l .items [:index ], l .items [index +1 :]...)
if len (l .items ) == 0 {
l .Unlock ()
return
}
previousItem := l .currentItem
if l .currentItem >= index && l .currentItem > 0 {
l .currentItem --
}
if previousItem == index && index < len (l .items ) && l .changed != nil {
item := l .items [l .currentItem ]
l .Unlock ()
l .changed (l .currentItem , item )
} else {
l .Unlock ()
}
}
func (l *List ) SetOffset (items , columns int ) {
l .Lock ()
defer l .Unlock ()
if items < 0 {
items = 0
}
if columns < 0 {
columns = 0
}
l .itemOffset , l .columnOffset = items , columns
}
func (l *List ) GetOffset () (int , int ) {
l .Lock ()
defer l .Unlock ()
return l .itemOffset , l .columnOffset
}
func (l *List ) SetMainTextColor (color tcell .Color ) {
l .Lock ()
defer l .Unlock ()
l .mainTextColor = color
}
func (l *List ) SetSecondaryTextColor (color tcell .Color ) {
l .Lock ()
defer l .Unlock ()
l .secondaryTextColor = color
}
func (l *List ) SetShortcutColor (color tcell .Color ) {
l .Lock ()
defer l .Unlock ()
l .shortcutColor = color
}
func (l *List ) SetSelectedTextColor (color tcell .Color ) {
l .Lock ()
defer l .Unlock ()
l .selectedTextColor = color
}
func (l *List ) SetSelectedTextAttributes (attr tcell .AttrMask ) {
l .Lock ()
defer l .Unlock ()
l .selectedTextAttributes = attr
}
func (l *List ) SetSelectedBackgroundColor (color tcell .Color ) {
l .Lock ()
defer l .Unlock ()
l .selectedBackgroundColor = color
}
func (l *List ) SetSelectedFocusOnly (focusOnly bool ) {
l .Lock ()
defer l .Unlock ()
l .selectedFocusOnly = focusOnly
}
func (l *List ) SetSelectedAlwaysVisible (alwaysVisible bool ) {
l .Lock ()
defer l .Unlock ()
l .selectedAlwaysVisible = alwaysVisible
}
func (l *List ) SetSelectedAlwaysCentered (alwaysCentered bool ) {
l .Lock ()
defer l .Unlock ()
l .selectedAlwaysCentered = alwaysCentered
}
func (l *List ) SetHighlightFullLine (highlight bool ) {
l .Lock ()
defer l .Unlock ()
l .highlightFullLine = highlight
}
func (l *List ) ShowSecondaryText (show bool ) {
l .Lock ()
defer l .Unlock ()
l .showSecondaryText = show
return
}
func (l *List ) SetScrollBarVisibility (visibility ScrollBarVisibility ) {
l .Lock ()
defer l .Unlock ()
l .scrollBarVisibility = visibility
}
func (l *List ) SetScrollBarColor (color tcell .Color ) {
l .Lock ()
defer l .Unlock ()
l .scrollBarColor = color
}
func (l *List ) SetHover (hover bool ) {
l .Lock ()
defer l .Unlock ()
l .hover = hover
}
func (l *List ) SetWrapAround (wrapAround bool ) {
l .Lock ()
defer l .Unlock ()
l .wrapAround = wrapAround
}
func (l *List ) SetChangedFunc (handler func (index int , item *ListItem )) {
l .Lock ()
defer l .Unlock ()
l .changed = handler
}
func (l *List ) SetSelectedFunc (handler func (int , *ListItem )) {
l .Lock ()
defer l .Unlock ()
l .selected = handler
}
func (l *List ) SetDoneFunc (handler func ()) {
l .Lock ()
defer l .Unlock ()
l .done = handler
}
func (l *List ) AddItem (item *ListItem ) {
l .InsertItem (-1 , item )
}
func (l *List ) InsertItem (index int , item *ListItem ) {
l .Lock ()
if index < 0 {
index = len (l .items ) + index + 1
}
if index < 0 {
index = 0
} else if index > len (l .items ) {
index = len (l .items )
}
if l .currentItem < len (l .items ) && l .currentItem >= index {
l .currentItem ++
}
l .items = append (l .items , nil )
if index < len (l .items )-1 {
copy (l .items [index +1 :], l .items [index :])
}
l .items [index ] = item
if len (l .items ) == 1 && l .changed != nil {
item := l .items [0 ]
l .Unlock ()
l .changed (0 , item )
} else {
l .Unlock ()
}
}
func (l *List ) GetItem (index int ) *ListItem {
if index > len (l .items )-1 {
return nil
}
return l .items [index ]
}
func (l *List ) GetItemCount () int {
l .RLock ()
defer l .RUnlock ()
return len (l .items )
}
func (l *List ) GetItemText (index int ) (main , secondary string ) {
l .RLock ()
defer l .RUnlock ()
return string (l .items [index ].mainText ), string (l .items [index ].secondaryText )
}
func (l *List ) SetItemText (index int , main , secondary string ) {
l .Lock ()
defer l .Unlock ()
item := l .items [index ]
item .mainText = []byte (main )
item .secondaryText = []byte (secondary )
}
func (l *List ) SetItemEnabled (index int , enabled bool ) {
l .Lock ()
defer l .Unlock ()
item := l .items [index ]
item .disabled = !enabled
}
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 )
}
}
func (l *List ) FindItems (mainSearch , secondarySearch string , mustContainBoth , ignoreCase bool ) (indices []int ) {
l .RLock ()
defer l .RUnlock ()
if mainSearch == "" && secondarySearch == "" {
return
}
if ignoreCase {
mainSearch = strings .ToLower (mainSearch )
secondarySearch = strings .ToLower (secondarySearch )
}
mainSearchBytes := []byte (mainSearch )
secondarySearchBytes := []byte (secondarySearch )
for index , item := range l .items {
mainText := item .mainText
secondaryText := item .secondaryText
if ignoreCase {
mainText = bytes .ToLower (mainText )
secondaryText = bytes .ToLower (secondaryText )
}
mainContained := bytes .Contains (mainText , mainSearchBytes )
secondaryContained := bytes .Contains (secondaryText , secondarySearchBytes )
if mustContainBoth && mainContained && secondaryContained ||
!mustContainBoth && (len (mainText ) > 0 && mainContained || len (secondaryText ) > 0 && secondaryContained ) {
indices = append (indices , index )
}
}
return
}
func (l *List ) Clear () {
l .Lock ()
defer l .Unlock ()
l .items = nil
l .currentItem = 0
l .itemOffset = 0
l .columnOffset = 0
}
func (l *List ) Focus (delegate func (p Primitive )) {
l .Box .Focus (delegate )
if l .ContextMenu .open {
delegate (l .ContextMenu .list )
}
}
func (l *List ) HasFocus () bool {
if l .ContextMenu .open {
return l .ContextMenu .list .HasFocus ()
}
l .RLock ()
defer l .RUnlock ()
return l .hasFocus
}
func (l *List ) Transform (tr Transformation ) {
l .Lock ()
previousItem := l .currentItem
l .transform (tr )
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 ()
}
}
func (l *List ) transform (tr Transformation ) {
var decreasing bool
pageItems := l .height
if l .showSecondaryText {
pageItems /= 2
}
if pageItems < 1 {
pageItems = 1
}
switch tr {
case TransformFirstItem :
l .currentItem = 0
l .itemOffset = 0
decreasing = true
case TransformLastItem :
l .currentItem = len (l .items ) - 1
case TransformPreviousItem :
l .currentItem --
decreasing = true
case TransformNextItem :
l .currentItem ++
case TransformPreviousPage :
l .currentItem -= pageItems
decreasing = true
case TransformNextPage :
l .currentItem += pageItems
l .itemOffset += pageItems
}
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
l .itemOffset = 0
}
} else if l .currentItem >= len (l .items ) {
if l .wrapAround {
l .currentItem = 0
l .itemOffset = 0
} else {
l .currentItem = len (l .items ) - 1
}
}
item := l .items [l .currentItem ]
item .RLock ()
if !item .disabled && (item .shortcut > 0 || len (item .mainText ) > 0 || len (item .secondaryText ) > 0 ) {
item .RUnlock ()
break
}
if decreasing {
l .currentItem --
} else {
l .currentItem ++
}
item .RUnlock ()
}
l .updateOffset ()
}
func (l *List ) updateOffset () {
_, _, _, l .height = l .GetInnerRect ()
h := l .height
if l .selectedAlwaysCentered {
h /= 2
}
if l .currentItem < l .itemOffset {
l .itemOffset = l .currentItem
} else if l .showSecondaryText {
if 2 *(l .currentItem -l .itemOffset ) >= h -1 {
l .itemOffset = (2 *l .currentItem + 3 - h ) / 2
}
} else {
if l .currentItem -l .itemOffset >= h {
l .itemOffset = l .currentItem + 1 - h
}
}
if l .showSecondaryText {
if l .itemOffset > len (l .items )-(l .height /2 ) {
l .itemOffset = len (l .items ) - l .height /2
}
} else {
if l .itemOffset > len (l .items )-l .height {
l .itemOffset = len (l .items ) - l .height
}
}
if l .itemOffset < 0 {
l .itemOffset = 0
}
maxWidth := 0
for _ , option := range l .items {
strWidth := TaggedTextWidth (option .mainText )
secondaryWidth := TaggedTextWidth (option .secondaryText )
if secondaryWidth > strWidth {
strWidth = secondaryWidth
}
if option .shortcut != 0 {
strWidth += 4
}
if strWidth > maxWidth {
maxWidth = strWidth
}
}
addWidth := 0
if l .scrollBarVisibility == ScrollBarAlways ||
(l .scrollBarVisibility == ScrollBarAuto &&
((!l .showSecondaryText && len (l .items ) > l .innerHeight ) ||
(l .showSecondaryText && len (l .items ) > l .innerHeight /2 ))) {
addWidth = 1
}
if l .columnOffset > (maxWidth -l .innerWidth )+addWidth {
l .columnOffset = (maxWidth - l .innerWidth ) + addWidth
}
if l .columnOffset < 0 {
l .columnOffset = 0
}
}
func (l *List ) Draw (screen tcell .Screen ) {
if !l .GetVisible () {
return
}
l .Box .Draw (screen )
hasFocus := l .GetFocusable ().HasFocus ()
l .Lock ()
defer l .Unlock ()
x , y , width , height := l .GetInnerRect ()
leftEdge := x
fullWidth := width + l .paddingLeft + l .paddingRight + l .prefixWidth + l .suffixWidth
bottomLimit := y + height
l .height = height
screenWidth , _ := screen .Size ()
scrollBarHeight := height
scrollBarX := x + (width - 1 ) + l .paddingLeft + l .paddingRight
if scrollBarX > screenWidth -1 {
scrollBarX = screenWidth - 1
}
if l .showSecondaryText {
scrollBarHeight /= 2
}
var showShortcuts bool
for _ , item := range l .items {
if item .shortcut != 0 {
showShortcuts = true
x += 4
width -= 4
break
}
}
if l .selectedAlwaysVisible || l .selectedAlwaysCentered {
l .updateOffset ()
}
scrollBarCursor := int (float64 (len (l .items )) * (float64 (l .itemOffset ) / float64 (len (l .items )-height )))
for index , item := range l .items {
if index < l .itemOffset {
continue
}
if y >= bottomLimit {
break
}
mainText := item .mainText
secondaryText := item .secondaryText
if l .columnOffset > 0 {
if l .columnOffset < len (mainText ) {
mainText = mainText [l .columnOffset :]
} else {
mainText = nil
}
if l .columnOffset < len (secondaryText ) {
secondaryText = secondaryText [l .columnOffset :]
} else {
secondaryText = nil
}
}
if len (item .mainText ) == 0 && len (item .secondaryText ) == 0 && item .shortcut == 0 {
Print (screen , []byte (string (tcell .RuneLTee )), leftEdge -2 , y , 1 , AlignLeft , l .mainTextColor )
Print (screen , bytes .Repeat ([]byte (string (tcell .RuneHLine )), fullWidth ), leftEdge -1 , y , fullWidth , AlignLeft , l .mainTextColor )
Print (screen , []byte (string (tcell .RuneRTee )), leftEdge +fullWidth -1 , y , 1 , AlignLeft , l .mainTextColor )
RenderScrollBar (screen , l .scrollBarVisibility , scrollBarX , y , scrollBarHeight , len (l .items ), scrollBarCursor , index -l .itemOffset , l .hasFocus , l .scrollBarColor )
y ++
continue
}
if index == l .currentItem {
if len (l .selectedPrefix ) > 0 {
mainText = append (l .selectedPrefix , mainText ...)
}
if len (l .selectedSuffix ) > 0 {
mainText = append (mainText , l .selectedSuffix ...)
}
} else {
if len (l .unselectedPrefix ) > 0 {
mainText = append (l .unselectedPrefix , mainText ...)
}
if len (l .unselectedSuffix ) > 0 {
mainText = append (mainText , l .unselectedSuffix ...)
}
}
if item .disabled {
if showShortcuts && item .shortcut != 0 {
Print (screen , []byte (fmt .Sprintf ("(%c)" , item .shortcut )), x -5 , y , 4 , AlignRight , tcell .ColorDarkSlateGray .TrueColor ())
}
Print (screen , mainText , x , y , width , AlignLeft , tcell .ColorGray .TrueColor ())
RenderScrollBar (screen , l .scrollBarVisibility , scrollBarX , y , scrollBarHeight , len (l .items ), scrollBarCursor , index -l .itemOffset , l .hasFocus , l .scrollBarColor )
y ++
continue
}
if showShortcuts && item .shortcut != 0 {
Print (screen , []byte (fmt .Sprintf ("(%c)" , item .shortcut )), x -5 , y , 4 , AlignRight , l .shortcutColor )
}
Print (screen , mainText , x , y , width , AlignLeft , l .mainTextColor )
if index == l .currentItem && (!l .selectedFocusOnly || hasFocus ) {
textWidth := width
if !l .highlightFullLine {
if w := TaggedTextWidth (mainText ); w < textWidth {
textWidth = w
}
}
for bx := 0 ; bx < textWidth ; bx ++ {
m , c , style , _ := screen .GetContent (x +bx , y )
fg , _ , _ := style .Decompose ()
if fg == l .mainTextColor {
fg = l .selectedTextColor
}
style = SetAttributes (style .Background (l .selectedBackgroundColor ).Foreground (fg ), l .selectedTextAttributes )
screen .SetContent (x +bx , y , m , c , style )
}
}
RenderScrollBar (screen , l .scrollBarVisibility , scrollBarX , y , scrollBarHeight , len (l .items ), scrollBarCursor , index -l .itemOffset , l .hasFocus , l .scrollBarColor )
y ++
if y >= bottomLimit {
break
}
if l .showSecondaryText {
Print (screen , secondaryText , x , y , width , AlignLeft , l .secondaryTextColor )
RenderScrollBar (screen , l .scrollBarVisibility , scrollBarX , y , scrollBarHeight , len (l .items ), scrollBarCursor , index -l .itemOffset , l .hasFocus , l .scrollBarColor )
y ++
}
}
for y < bottomLimit {
RenderScrollBar (screen , l .scrollBarVisibility , scrollBarX , y , scrollBarHeight , len (l .items ), scrollBarCursor , bottomLimit -y , l .hasFocus , l .scrollBarColor )
y ++
}
if hasFocus && l .ContextMenu .open {
ctx := l .ContextMenuList ()
x , y , width , height = l .GetInnerRect ()
maxWidth := 0
for _ , option := range ctx .items {
strWidth := TaggedTextWidth (option .mainText )
if option .shortcut != 0 {
strWidth += 4
}
if strWidth > maxWidth {
maxWidth = strWidth
}
}
lheight := len (ctx .items )
lwidth := maxWidth
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 {
offsetX := 7
if showShortcuts {
offsetX += 4
}
offsetY := l .currentItem
if l .showSecondaryText {
offsetY *= 2
}
x , y , _ , _ := l .GetInnerRect ()
cx , cy = x +offsetX , y +offsetY
}
_ , sheight := screen .Size ()
if cy +lheight >= sheight && cy -2 > lheight -cy {
for i := (cy + lheight ) - sheight ; i > 0 ; i -- {
cy --
if cy +lheight < sheight {
break
}
}
if cy < 0 {
cy = 0
}
}
if cy +lheight >= sheight {
lheight = sheight - cy
}
if ctx .scrollBarVisibility == ScrollBarAlways || (ctx .scrollBarVisibility == ScrollBarAuto && len (ctx .items ) > lheight ) {
lwidth ++
}
ctx .SetRect (cx , cy , lwidth , lheight )
ctx .Draw (screen )
}
}
func (l *List ) InputHandler () func (event *tcell .EventKey , setFocus func (p Primitive )) {
return l .WrapInputHandler (func (event *tcell .EventKey , setFocus func (p Primitive )) {
l .Lock ()
if HitShortcut (event , Keys .Cancel ) {
if l .ContextMenu .open {
l .Unlock ()
l .ContextMenu .hide (setFocus )
return
}
if l .done != nil {
l .Unlock ()
l .done ()
} else {
l .Unlock ()
}
return
} else if HitShortcut (event , Keys .Select , Keys .Select2 ) {
if l .currentItem >= 0 && l .currentItem < len (l .items ) {
item := l .items [l .currentItem ]
if !item .disabled {
if item .selected != nil {
l .Unlock ()
item .selected ()
l .Lock ()
}
if l .selected != nil {
l .Unlock ()
l .selected (l .currentItem , item )
l .Lock ()
}
}
}
} else if HitShortcut (event , Keys .ShowContextMenu ) {
defer l .ContextMenu .show (l .currentItem , -1 , -1 , setFocus )
} else if len (l .items ) == 0 {
l .Unlock ()
return
}
if event .Key () == tcell .KeyRune {
ch := event .Rune ()
if ch != ' ' {
for index , item := range l .items {
if !item .disabled && item .shortcut == ch {
l .currentItem = index
item := l .items [l .currentItem ]
if item .selected != nil {
l .Unlock ()
item .selected ()
l .Lock ()
}
if l .selected != nil {
l .Unlock ()
l .selected (l .currentItem , item )
l .Lock ()
}
l .Unlock ()
return
}
}
}
}
previousItem := l .currentItem
if HitShortcut (event , Keys .MoveFirst , Keys .MoveFirst2 ) {
l .transform (TransformFirstItem )
} else if HitShortcut (event , Keys .MoveLast , Keys .MoveLast2 ) {
l .transform (TransformLastItem )
} else if HitShortcut (event , Keys .MoveUp , Keys .MoveUp2 ) {
l .transform (TransformPreviousItem )
} else if HitShortcut (event , Keys .MoveDown , Keys .MoveDown2 ) {
l .transform (TransformNextItem )
} else if HitShortcut (event , Keys .MoveLeft , Keys .MoveLeft2 ) {
l .columnOffset --
l .updateOffset ()
} else if HitShortcut (event , Keys .MoveRight , Keys .MoveRight2 ) {
l .columnOffset ++
l .updateOffset ()
} else if HitShortcut (event , Keys .MovePreviousPage ) {
l .transform (TransformPreviousPage )
} else if HitShortcut (event , Keys .MoveNextPage ) {
l .transform (TransformNextPage )
}
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 ()
}
})
}
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 .itemOffset
if index >= len (l .items ) {
return -1
}
return index
}
func (l *List ) indexAtPoint (x , y int ) int {
rectX , rectY , width , height := l .GetInnerRect ()
if x < rectX || x >= rectX +width || y < rectY || y >= rectY +height {
return -1
}
index := y - rectY
if l .showSecondaryText {
index /= 2
}
index += l .itemOffset
if index >= len (l .items ) {
return -1
}
return index
}
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 ) {
x , y := event .Position ()
if !l .InRect (x , y ) {
return false , nil
}
l .Lock ()
if l .ContextMenuVisible () && l .ContextMenuList ().InRect (event .Position ()) {
defer l .ContextMenuList ().MouseHandler ()(action , event , setFocus )
consumed = true
l .Unlock ()
return
}
if !l .InRect (event .Position ()) {
l .Unlock ()
return false , nil
}
switch action {
case MouseLeftClick :
if l .ContextMenuVisible () {
defer l .ContextMenu .hide (setFocus )
consumed = true
l .Unlock ()
return
}
l .Unlock ()
setFocus (l )
l .Lock ()
index := l .indexAtPoint (event .Position ())
if index != -1 {
item := l .items [index ]
if !item .disabled {
l .currentItem = index
if item .selected != nil {
l .Unlock ()
item .selected ()
l .Lock ()
}
if l .selected != nil {
l .Unlock ()
l .selected (index , item )
l .Lock ()
}
if index != l .currentItem && l .changed != nil {
l .Unlock ()
l .changed (index , item )
l .Lock ()
}
}
}
consumed = true
case MouseMiddleClick :
if l .ContextMenu .open {
defer l .ContextMenu .hide (setFocus )
consumed = true
l .Unlock ()
return
}
case MouseRightDown :
if len (l .ContextMenuList ().items ) == 0 {
l .Unlock ()
return
}
x , y := event .Position ()
index := l .indexAtPoint (event .Position ())
if index != -1 {
item := l .items [index ]
if !item .disabled {
l .currentItem = index
if index != l .currentItem && l .changed != nil {
l .Unlock ()
l .changed (index , item )
l .Lock ()
}
}
}
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 .disabled {
l .currentItem = index
}
}
consumed = true
}
case MouseScrollUp :
if l .itemOffset > 0 {
l .itemOffset --
}
consumed = true
case MouseScrollDown :
lines := len (l .items ) - l .itemOffset
if l .showSecondaryText {
lines *= 2
}
if _ , _ , _ , height := l .GetInnerRect (); lines > height {
l .itemOffset ++
}
consumed = true
}
l .Unlock ()
return
})
}
The pages are generated with Golds v0.8.2 . (GOOS=linux GOARCH=amd64)
Golds is a Go 101 project developed by Tapir Liu .
PR and bug reports are welcome and can be submitted to the issue list .
Please follow @zigo_101 (reachable from the left QR code) to get the latest news of Golds .