forked from tslocum/cview
Allow scrolling List horizontally
This commit is contained in:
parent
a3a0d67f65
commit
2877b77c18
|
@ -6,6 +6,7 @@ v1.5.1 (WIP)
|
|||
- Fix List.Transform not calling handler set via SetChangedFunc
|
||||
- Fix WordWrap bounds out of range
|
||||
- Allow modification of scroll bar render text
|
||||
- Allow scrolling List horizontally
|
||||
- Optimize TextView (writing is 90% faster, drawing is 50% faster)
|
||||
- Remove return values from methods which return their primitive (breaks chaining)
|
||||
- Remove Application.ForceDraw (Application.Draw may be called anywhere)
|
||||
|
|
2
go.mod
2
go.mod
|
@ -8,5 +8,5 @@ require (
|
|||
github.com/mattn/go-runewidth v0.0.9
|
||||
github.com/rivo/uniseg v0.1.0
|
||||
gitlab.com/tslocum/cbind v0.1.2
|
||||
golang.org/x/sys v0.0.0-20201009025420-dfb3f7c4e634 // indirect
|
||||
golang.org/x/sys v0.0.0-20201013132646-2da7054afaeb // indirect
|
||||
)
|
||||
|
|
4
go.sum
4
go.sum
|
@ -15,8 +15,8 @@ gitlab.com/tslocum/cbind v0.1.2/go.mod h1:HfB7qAhHSZbn1rFK8M9SvSN5NG6ScAg/3h3iE6
|
|||
golang.org/x/sys v0.0.0-20190626150813-e07cf5db2756/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200826173525-f9321e4c35a6 h1:DvY3Zkh7KabQE/kfzMvYvKirSiguP9Q/veMtkYyf0o8=
|
||||
golang.org/x/sys v0.0.0-20200826173525-f9321e4c35a6/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201009025420-dfb3f7c4e634 h1:bNEHhJCnrwMKNMmOx3yAynp5vs5/gRy+XWFtZFu7NBM=
|
||||
golang.org/x/sys v0.0.0-20201009025420-dfb3f7c4e634/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201013132646-2da7054afaeb h1:HS9IzC4UFbpMBLQUDSQcU+ViVT1vdFCQVjdPVpTlZrs=
|
||||
golang.org/x/sys v0.0.0-20201013132646-2da7054afaeb/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.3 h1:cokOdA+Jmi5PJGXLlLllQSgYigAEfHXJAERHVMaCc2k=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
|
|
154
list.go
154
list.go
|
@ -177,8 +177,9 @@ type List struct {
|
|||
// 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
|
||||
// The number of list items and columns by which the list is scrolled
|
||||
// down/to the right.
|
||||
itemOffset, columnOffset int
|
||||
|
||||
// An optional function which is called when the user has navigated to a list
|
||||
// item.
|
||||
|
@ -329,26 +330,29 @@ func (l *List) RemoveItem(index int) {
|
|||
}
|
||||
}
|
||||
|
||||
// SetOffset sets the number of list items skipped at the top before the first
|
||||
// item is drawn.
|
||||
func (l *List) SetOffset(offset int) {
|
||||
// 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) {
|
||||
l.Lock()
|
||||
defer l.Unlock()
|
||||
|
||||
if offset < 0 {
|
||||
offset = 0
|
||||
if items < 0 {
|
||||
items = 0
|
||||
}
|
||||
if columns < 0 {
|
||||
columns = 0
|
||||
}
|
||||
|
||||
l.offset = offset
|
||||
l.itemOffset, l.columnOffset = items, columns
|
||||
}
|
||||
|
||||
// GetOffset returns the number of list items skipped at the top before the
|
||||
// first item is drawn.
|
||||
func (l *List) GetOffset() int {
|
||||
// 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) {
|
||||
l.Lock()
|
||||
defer l.Unlock()
|
||||
|
||||
return l.offset
|
||||
return l.itemOffset, l.columnOffset
|
||||
}
|
||||
|
||||
// SetMainTextColor sets the color of the items' main text.
|
||||
|
@ -679,7 +683,8 @@ func (l *List) Clear() {
|
|||
|
||||
l.items = nil
|
||||
l.currentItem = 0
|
||||
l.offset = 0
|
||||
l.itemOffset = 0
|
||||
l.columnOffset = 0
|
||||
}
|
||||
|
||||
// Focus is called by the application when the primitive receives focus.
|
||||
|
@ -732,7 +737,7 @@ func (l *List) transform(tr Transformation) {
|
|||
switch tr {
|
||||
case TransformFirstItem:
|
||||
l.currentItem = 0
|
||||
l.offset = 0
|
||||
l.itemOffset = 0
|
||||
decreasing = true
|
||||
case TransformLastItem:
|
||||
l.currentItem = len(l.items) - 1
|
||||
|
@ -746,7 +751,7 @@ func (l *List) transform(tr Transformation) {
|
|||
decreasing = true
|
||||
case TransformNextPage:
|
||||
l.currentItem += pageItems
|
||||
l.offset += pageItems
|
||||
l.itemOffset += pageItems
|
||||
}
|
||||
|
||||
for i := 0; i < len(l.items); i++ {
|
||||
|
@ -755,12 +760,12 @@ func (l *List) transform(tr Transformation) {
|
|||
l.currentItem = len(l.items) - 1
|
||||
} else {
|
||||
l.currentItem = 0
|
||||
l.offset = 0
|
||||
l.itemOffset = 0
|
||||
}
|
||||
} else if l.currentItem >= len(l.items) {
|
||||
if l.wrapAround {
|
||||
l.currentItem = 0
|
||||
l.offset = 0
|
||||
l.itemOffset = 0
|
||||
} else {
|
||||
l.currentItem = len(l.items) - 1
|
||||
}
|
||||
|
@ -789,30 +794,62 @@ func (l *List) updateOffset() {
|
|||
h /= 2
|
||||
}
|
||||
|
||||
if l.currentItem < l.offset {
|
||||
l.offset = l.currentItem
|
||||
if l.currentItem < l.itemOffset {
|
||||
l.itemOffset = l.currentItem
|
||||
} else if l.showSecondaryText {
|
||||
if 2*(l.currentItem-l.offset) >= h-1 {
|
||||
l.offset = (2*l.currentItem + 3 - h) / 2
|
||||
if 2*(l.currentItem-l.itemOffset) >= h-1 {
|
||||
l.itemOffset = (2*l.currentItem + 3 - h) / 2
|
||||
}
|
||||
} else {
|
||||
if l.currentItem-l.offset >= h {
|
||||
l.offset = l.currentItem + 1 - h
|
||||
if l.currentItem-l.itemOffset >= h {
|
||||
l.itemOffset = l.currentItem + 1 - h
|
||||
}
|
||||
}
|
||||
|
||||
if l.showSecondaryText {
|
||||
if l.offset > len(l.items)-(l.height/2) {
|
||||
l.offset = len(l.items) - l.height/2
|
||||
if l.itemOffset > len(l.items)-(l.height/2) {
|
||||
l.itemOffset = len(l.items) - l.height/2
|
||||
}
|
||||
} else {
|
||||
if l.offset > len(l.items)-l.height {
|
||||
l.offset = len(l.items) - l.height
|
||||
if l.itemOffset > len(l.items)-l.height {
|
||||
l.itemOffset = len(l.items) - l.height
|
||||
}
|
||||
}
|
||||
|
||||
if l.offset < 0 {
|
||||
l.offset = 0
|
||||
if l.itemOffset < 0 {
|
||||
l.itemOffset = 0
|
||||
}
|
||||
|
||||
// Maximum width of item text
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
// Additional width for scroll bar
|
||||
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 < 0 {
|
||||
l.columnOffset = 0
|
||||
} else if l.columnOffset > (maxWidth-l.innerWidth)+addWidth {
|
||||
l.columnOffset = (maxWidth - l.innerWidth) + addWidth
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -858,11 +895,11 @@ func (l *List) Draw(screen tcell.Screen) {
|
|||
l.updateOffset()
|
||||
}
|
||||
|
||||
scrollBarCursor := int(float64(len(l.items)) * (float64(l.offset) / float64(len(l.items)-height)))
|
||||
scrollBarCursor := int(float64(len(l.items)) * (float64(l.itemOffset) / float64(len(l.items)-height)))
|
||||
|
||||
// Draw the list items.
|
||||
for index, item := range l.items {
|
||||
if index < l.offset {
|
||||
if index < l.itemOffset {
|
||||
continue
|
||||
}
|
||||
|
||||
|
@ -870,12 +907,25 @@ func (l *List) Draw(screen tcell.Screen) {
|
|||
break
|
||||
}
|
||||
|
||||
if len(item.mainText) == 0 && len(item.secondaryText) == 0 && item.shortcut == 0 { // Divider
|
||||
Print(screen, []byte(string(tcell.RuneLTee)), (x-5)-l.paddingLeft, y, 1, AlignLeft, l.mainTextColor)
|
||||
Print(screen, bytes.Repeat([]byte(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, []byte(string(tcell.RuneRTee)), (x-5)+width+5+l.paddingRight, y, 1, AlignLeft, l.mainTextColor)
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
RenderScrollBar(screen, l.scrollBarVisibility, scrollBarX, y, scrollBarHeight, len(l.items), scrollBarCursor, index-l.offset, l.hasFocus, l.scrollBarColor)
|
||||
if len(item.mainText) == 0 && len(item.secondaryText) == 0 && item.shortcut == 0 { // Divider
|
||||
Print(screen, bytes.Repeat([]byte(string(tcell.RuneHLine)), width+l.paddingLeft+l.paddingRight), x-l.paddingLeft, y, width+l.paddingLeft+l.paddingRight, AlignLeft, l.mainTextColor)
|
||||
|
||||
RenderScrollBar(screen, l.scrollBarVisibility, scrollBarX, y, scrollBarHeight, len(l.items), scrollBarCursor, index-l.itemOffset, l.hasFocus, l.scrollBarColor)
|
||||
y++
|
||||
continue
|
||||
} else if !item.enabled { // Disabled item
|
||||
|
@ -885,9 +935,9 @@ func (l *List) Draw(screen tcell.Screen) {
|
|||
}
|
||||
|
||||
// Main text.
|
||||
Print(screen, item.mainText, x, y, width, AlignLeft, tcell.ColorGray.TrueColor())
|
||||
Print(screen, mainText, x, y, width, AlignLeft, tcell.ColorGray.TrueColor())
|
||||
|
||||
RenderScrollBar(screen, l.scrollBarVisibility, scrollBarX, y, scrollBarHeight, len(l.items), scrollBarCursor, index-l.offset, l.hasFocus, l.scrollBarColor)
|
||||
RenderScrollBar(screen, l.scrollBarVisibility, scrollBarX, y, scrollBarHeight, len(l.items), scrollBarCursor, index-l.itemOffset, l.hasFocus, l.scrollBarColor)
|
||||
y++
|
||||
continue
|
||||
}
|
||||
|
@ -898,13 +948,13 @@ func (l *List) Draw(screen tcell.Screen) {
|
|||
}
|
||||
|
||||
// Main text.
|
||||
Print(screen, item.mainText, x, y, width, AlignLeft, l.mainTextColor)
|
||||
Print(screen, mainText, x, y, width, AlignLeft, l.mainTextColor)
|
||||
|
||||
// Background color of selected text.
|
||||
if index == l.currentItem && (!l.selectedFocusOnly || hasFocus) {
|
||||
textWidth := width
|
||||
if !l.highlightFullLine {
|
||||
if w := TaggedTextWidth(item.mainText); w < textWidth {
|
||||
if w := TaggedTextWidth(mainText); w < textWidth {
|
||||
textWidth = w
|
||||
}
|
||||
}
|
||||
|
@ -920,7 +970,7 @@ func (l *List) Draw(screen tcell.Screen) {
|
|||
}
|
||||
}
|
||||
|
||||
RenderScrollBar(screen, l.scrollBarVisibility, scrollBarX, y, scrollBarHeight, len(l.items), scrollBarCursor, index-l.offset, l.hasFocus, l.scrollBarColor)
|
||||
RenderScrollBar(screen, l.scrollBarVisibility, scrollBarX, y, scrollBarHeight, len(l.items), scrollBarCursor, index-l.itemOffset, l.hasFocus, l.scrollBarColor)
|
||||
|
||||
y++
|
||||
|
||||
|
@ -930,9 +980,9 @@ func (l *List) Draw(screen tcell.Screen) {
|
|||
|
||||
// Secondary text.
|
||||
if l.showSecondaryText {
|
||||
Print(screen, item.secondaryText, x, y, width, AlignLeft, l.secondaryTextColor)
|
||||
Print(screen, secondaryText, x, y, width, AlignLeft, l.secondaryTextColor)
|
||||
|
||||
RenderScrollBar(screen, l.scrollBarVisibility, scrollBarX, y, scrollBarHeight, len(l.items), scrollBarCursor, index-l.offset, l.hasFocus, l.scrollBarColor)
|
||||
RenderScrollBar(screen, l.scrollBarVisibility, scrollBarX, y, scrollBarHeight, len(l.items), scrollBarCursor, index-l.itemOffset, l.hasFocus, l.scrollBarColor)
|
||||
|
||||
y++
|
||||
}
|
||||
|
@ -1093,6 +1143,12 @@ func (l *List) InputHandler() func(event *tcell.EventKey, setFocus func(p Primit
|
|||
l.transform(TransformPreviousItem)
|
||||
} else if HitShortcut(event, Keys.MoveDown, Keys.MoveDown2, Keys.MoveNextField) {
|
||||
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) {
|
||||
|
@ -1121,7 +1177,7 @@ func (l *List) indexAtY(y int) int {
|
|||
if l.showSecondaryText {
|
||||
index /= 2
|
||||
}
|
||||
index += l.offset
|
||||
index += l.itemOffset
|
||||
|
||||
if index >= len(l.items) {
|
||||
return -1
|
||||
|
@ -1141,7 +1197,7 @@ func (l *List) indexAtPoint(x, y int) int {
|
|||
if l.showSecondaryText {
|
||||
index /= 2
|
||||
}
|
||||
index += l.offset
|
||||
index += l.itemOffset
|
||||
|
||||
if index >= len(l.items) {
|
||||
return -1
|
||||
|
@ -1249,17 +1305,17 @@ func (l *List) MouseHandler() func(action MouseAction, event *tcell.EventMouse,
|
|||
consumed = true
|
||||
}
|
||||
case MouseScrollUp:
|
||||
if l.offset > 0 {
|
||||
l.offset--
|
||||
if l.itemOffset > 0 {
|
||||
l.itemOffset--
|
||||
}
|
||||
consumed = true
|
||||
case MouseScrollDown:
|
||||
lines := len(l.items) - l.offset
|
||||
lines := len(l.items) - l.itemOffset
|
||||
if l.showSecondaryText {
|
||||
lines *= 2
|
||||
}
|
||||
if _, _, _, height := l.GetInnerRect(); lines > height {
|
||||
l.offset++
|
||||
l.itemOffset++
|
||||
}
|
||||
consumed = true
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue