2
0
mirror of https://github.com/rivo/tview.git synced 2024-11-15 06:12:46 +00:00

Add scroll bar to List, DropDown, Table and TreeView

This commit is contained in:
Trevor Slocum 2020-01-31 21:21:59 -08:00
parent 3a5c6317e4
commit 4c0a19f6a1
7 changed files with 192 additions and 9 deletions

View File

@ -374,7 +374,6 @@ func (d *DropDown) Draw(screen tcell.Screen) {
// We prefer to drop down but if there is no space, maybe drop up? // We prefer to drop down but if there is no space, maybe drop up?
lx := x lx := x
ly := y + 1 ly := y + 1
lwidth := maxWidth
lheight := len(d.options) lheight := len(d.options)
_, sheight := screen.Size() _, sheight := screen.Size()
if ly+lheight >= sheight && ly-2 > lheight-ly { if ly+lheight >= sheight && ly-2 > lheight-ly {
@ -386,6 +385,10 @@ func (d *DropDown) Draw(screen tcell.Screen) {
if ly+lheight >= sheight { if ly+lheight >= sheight {
lheight = sheight - ly lheight = sheight - ly
} }
lwidth := maxWidth
if d.list.scrollBarVisibility == ScrollBarAlways || (d.list.scrollBarVisibility == ScrollBarAuto && len(d.options) > lheight) {
lwidth++ // Add space for scroll bar
}
d.list.SetRect(lx, ly, lwidth, lheight) d.list.SetRect(lx, ly, lwidth, lheight)
d.list.Draw(screen) d.list.Draw(screen)
} }

View File

@ -422,6 +422,9 @@ func (i *InputField) Draw(screen tcell.Screen) {
if ly+lheight >= sheight { if ly+lheight >= sheight {
lheight = sheight - ly lheight = sheight - ly
} }
if i.autocompleteList.scrollBarVisibility == ScrollBarAlways || (i.autocompleteList.scrollBarVisibility == ScrollBarAuto && i.autocompleteList.GetItemCount() > lheight) {
lwidth++ // Add space for scroll bar
}
i.autocompleteList.SetRect(lx, ly, lwidth, lheight) i.autocompleteList.SetRect(lx, ly, lwidth, lheight)
i.autocompleteList.Draw(screen) i.autocompleteList.Draw(screen)
} }

41
list.go
View File

@ -42,6 +42,12 @@ type List struct {
// The text color for selected items. // The text color for selected items.
selectedTextColor tcell.Color selectedTextColor tcell.Color
// Visibility of the scroll bar.
scrollBarVisibility ScrollBarVisibility
// The scroll bar color.
scrollBarColor tcell.Color
// The background color for selected items. // The background color for selected items.
selectedBackgroundColor tcell.Color selectedBackgroundColor tcell.Color
@ -75,10 +81,12 @@ func NewList() *List {
Box: NewBox(), Box: NewBox(),
showSecondaryText: true, showSecondaryText: true,
wrapAround: true, wrapAround: true,
scrollBarVisibility: ScrollBarAuto,
mainTextColor: Styles.PrimaryTextColor, mainTextColor: Styles.PrimaryTextColor,
secondaryTextColor: Styles.TertiaryTextColor, secondaryTextColor: Styles.TertiaryTextColor,
shortcutColor: Styles.SecondaryTextColor, shortcutColor: Styles.SecondaryTextColor,
selectedTextColor: Styles.PrimitiveBackgroundColor, selectedTextColor: Styles.PrimitiveBackgroundColor,
scrollBarColor: Styles.ScrollBarColor,
selectedBackgroundColor: Styles.PrimaryTextColor, selectedBackgroundColor: Styles.PrimaryTextColor,
} }
} }
@ -216,6 +224,18 @@ func (l *List) ShowSecondaryText(show bool) *List {
return l return l
} }
// SetScrollBarVisibility specifies the display of the scroll bar.
func (l *List) SetScrollBarVisibility(visibility ScrollBarVisibility) *List {
l.scrollBarVisibility = visibility
return l
}
// SetScrollBarColor sets the color of the scroll bar.
func (l *List) SetScrollBarColor(color tcell.Color) *List {
l.scrollBarColor = color
return l
}
// SetWrapAround sets the flag that determines whether navigating the list will // SetWrapAround sets the flag that determines whether navigating the list will
// wrap around. That is, navigating downwards on the last item will move the // 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 // selection to the first item (similarly in the other direction). If set to
@ -395,6 +415,18 @@ func (l *List) Draw(screen tcell.Screen) {
x, y, width, height := l.GetInnerRect() x, y, width, height := l.GetInnerRect()
bottomLimit := y + height bottomLimit := y + height
screenWidth, _ := screen.Size()
scrollBarHeight := height
scrollBarX := x + (width - 1)
if scrollBarX > screenWidth-1 {
scrollBarX = screenWidth - 1
}
// Halve scroll bar height when drawing two lines per list item.
if l.showSecondaryText {
scrollBarHeight /= 2
}
// Do we show any shortcuts? // Do we show any shortcuts?
var showShortcuts bool var showShortcuts bool
for _, item := range l.items { for _, item := range l.items {
@ -457,6 +489,10 @@ func (l *List) Draw(screen tcell.Screen) {
} }
} }
if l.scrollBarVisibility == ScrollBarAlways || (l.scrollBarVisibility == ScrollBarAuto && len(l.items) > scrollBarHeight) {
RenderScrollBar(screen, scrollBarX, y, scrollBarHeight, len(l.items), l.currentItem, index-l.offset, l.hasFocus, l.scrollBarColor)
}
y++ y++
if y >= bottomLimit { if y >= bottomLimit {
@ -466,6 +502,11 @@ func (l *List) Draw(screen tcell.Screen) {
// Secondary text. // Secondary text.
if l.showSecondaryText { if l.showSecondaryText {
Print(screen, item.SecondaryText, x, y, width, AlignLeft, l.secondaryTextColor) Print(screen, item.SecondaryText, x, y, width, AlignLeft, l.secondaryTextColor)
if l.scrollBarVisibility == ScrollBarAlways || (l.scrollBarVisibility == ScrollBarAuto && len(l.items) > scrollBarHeight) {
RenderScrollBar(screen, scrollBarX, y, scrollBarHeight, len(l.items), l.currentItem, index-l.offset, l.hasFocus, l.scrollBarColor)
}
y++ y++
} }
} }

View File

@ -15,6 +15,7 @@ type Theme struct {
TertiaryTextColor tcell.Color // Tertiary text (e.g. subtitles, notes). TertiaryTextColor tcell.Color // Tertiary text (e.g. subtitles, notes).
InverseTextColor tcell.Color // Text on primary-colored backgrounds. InverseTextColor tcell.Color // Text on primary-colored backgrounds.
ContrastSecondaryTextColor tcell.Color // Secondary text on ContrastBackgroundColor-colored backgrounds. ContrastSecondaryTextColor tcell.Color // Secondary text on ContrastBackgroundColor-colored backgrounds.
ScrollBarColor tcell.Color // Scroll bar.
} }
// Styles defines the theme for applications. The default is for a black // Styles defines the theme for applications. The default is for a black
@ -32,4 +33,5 @@ var Styles = Theme{
TertiaryTextColor: tcell.ColorGreen, TertiaryTextColor: tcell.ColorGreen,
InverseTextColor: tcell.ColorBlue, InverseTextColor: tcell.ColorBlue,
ContrastSecondaryTextColor: tcell.ColorDarkCyan, ContrastSecondaryTextColor: tcell.ColorDarkCyan,
ScrollBarColor: tcell.ColorWhite,
} }

View File

@ -251,6 +251,12 @@ type Table struct {
// The number of visible rows the last time the table was drawn. // The number of visible rows the last time the table was drawn.
visibleRows int visibleRows int
// Visibility of the scroll bar.
scrollBarVisibility ScrollBarVisibility
// The scroll bar color.
scrollBarColor tcell.Color
// The style of the selected rows. If this value is 0, selected rows are // The style of the selected rows. If this value is 0, selected rows are
// simply inverted. // simply inverted.
selectedStyle tcell.Style selectedStyle tcell.Style
@ -273,10 +279,12 @@ type Table struct {
// NewTable returns a new table. // NewTable returns a new table.
func NewTable() *Table { func NewTable() *Table {
return &Table{ return &Table{
Box: NewBox(), Box: NewBox(),
bordersColor: Styles.GraphicsColor, scrollBarVisibility: ScrollBarAuto,
separator: ' ', scrollBarColor: Styles.ScrollBarColor,
lastColumn: -1, bordersColor: Styles.GraphicsColor,
separator: ' ',
lastColumn: -1,
} }
} }
@ -300,6 +308,18 @@ func (t *Table) SetBordersColor(color tcell.Color) *Table {
return t return t
} }
// SetScrollBarVisibility specifies the display of the scroll bar.
func (t *Table) SetScrollBarVisibility(visibility ScrollBarVisibility) *Table {
t.scrollBarVisibility = visibility
return t
}
// SetScrollBarColor sets the color of the scroll bar.
func (t *Table) SetScrollBarColor(color tcell.Color) *Table {
t.scrollBarColor = color
return t
}
// SetSelectedStyle sets a specific style for selected cells. If no such style // SetSelectedStyle sets a specific style for selected cells. If no such style
// is set, per default, selected cells are inverted (i.e. their foreground and // is set, per default, selected cells are inverted (i.e. their foreground and
// background colors are swapped). // background colors are swapped).
@ -570,6 +590,11 @@ func (t *Table) Draw(screen tcell.Screen) {
t.visibleRows = height t.visibleRows = height
} }
showVerticalScrollBar := t.scrollBarVisibility == ScrollBarAlways || (t.scrollBarVisibility == ScrollBarAuto && len(t.cells) > t.visibleRows-t.fixedRows)
if showVerticalScrollBar {
width-- // Subtract space for scroll bar.
}
// Return the cell at the specified position (nil if it doesn't exist). // Return the cell at the specified position (nil if it doesn't exist).
getCell := func(row, column int) *TableCell { getCell := func(row, column int) *TableCell {
if row < 0 || column < 0 || row >= len(t.cells) || column >= len(t.cells[row]) { if row < 0 || column < 0 || row >= len(t.cells) || column >= len(t.cells[row]) {
@ -766,6 +791,7 @@ ColumnLoop:
toDistribute -= expWidth toDistribute -= expWidth
expansionTotal -= expansion expansionTotal -= expansion
} }
tableWidth = width - toDistribute
} }
// Helper function which draws border runes. // Helper function which draws border runes.
@ -861,6 +887,38 @@ ColumnLoop:
} }
} }
if showVerticalScrollBar {
// Calculate scroll bar position and dimensions.
rows := len(t.cells)
scrollBarItems := rows - t.fixedRows
scrollBarHeight := t.visibleRows - t.fixedRows
scrollBarX := x + width
scrollBarY := y + t.fixedRows
if scrollBarX > x+tableWidth {
scrollBarX = x + tableWidth
}
padTotalOffset := 1
if t.borders {
padTotalOffset = 2
scrollBarItems *= 2
scrollBarHeight = (scrollBarHeight * 2) - 1
scrollBarY += t.fixedRows + 1
}
// Draw scroll bar.
cursor := int(float64(scrollBarItems) * (float64(t.rowOffset) / float64(((rows-t.fixedRows)-t.visibleRows)+padTotalOffset)))
for printed := 0; printed < scrollBarHeight; printed++ {
RenderScrollBar(screen, scrollBarX, scrollBarY+printed, scrollBarHeight, scrollBarItems, cursor, printed, t.hasFocus, t.scrollBarColor)
}
}
// TODO Draw horizontal scroll bar
// Helper function which colors the background of a box. // Helper function which colors the background of a box.
// backgroundColor == tcell.ColorDefault => Don't color the background. // backgroundColor == tcell.ColorDefault => Don't color the background.
// textColor == tcell.ColorDefault => Don't change the text color. // textColor == tcell.ColorDefault => Don't change the text color.

View File

@ -270,6 +270,12 @@ type TreeView struct {
// The color of the lines. // The color of the lines.
graphicsColor tcell.Color graphicsColor tcell.Color
// Visibility of the scroll bar.
scrollBarVisibility ScrollBarVisibility
// The scroll bar color.
scrollBarColor tcell.Color
// An optional function which is called when the user has navigated to a new // An optional function which is called when the user has navigated to a new
// tree node. // tree node.
changed func(node *TreeNode) changed func(node *TreeNode)
@ -288,9 +294,11 @@ type TreeView struct {
// NewTreeView returns a new tree view. // NewTreeView returns a new tree view.
func NewTreeView() *TreeView { func NewTreeView() *TreeView {
return &TreeView{ return &TreeView{
Box: NewBox(), Box: NewBox(),
graphics: true, scrollBarVisibility: ScrollBarAuto,
graphicsColor: Styles.GraphicsColor, graphics: true,
graphicsColor: Styles.GraphicsColor,
scrollBarColor: Styles.ScrollBarColor,
} }
} }
@ -365,6 +373,18 @@ func (t *TreeView) SetGraphicsColor(color tcell.Color) *TreeView {
return t return t
} }
// SetScrollBarVisibility specifies the display of the scroll bar.
func (t *TreeView) SetScrollBarVisibility(visibility ScrollBarVisibility) *TreeView {
t.scrollBarVisibility = visibility
return t
}
// SetScrollBarColor sets the color of the scroll bar.
func (t *TreeView) SetScrollBarColor(color tcell.Color) *TreeView {
t.scrollBarColor = color
return t
}
// SetChangedFunc sets the function which is called when the user navigates to // SetChangedFunc sets the function which is called when the user navigates to
// a new tree node. // a new tree node.
func (t *TreeView) SetChangedFunc(handler func(node *TreeNode)) *TreeView { func (t *TreeView) SetChangedFunc(handler func(node *TreeNode)) *TreeView {
@ -601,12 +621,16 @@ func (t *TreeView) Draw(screen tcell.Screen) {
t.offsetY = 0 t.offsetY = 0
} }
// Calculate scroll bar position.
rows := len(t.nodes)
cursor := int(float64(rows) * (float64(t.offsetY) / float64(rows-height)))
// Draw the tree. // Draw the tree.
posY := y posY := y
lineStyle := tcell.StyleDefault.Background(t.backgroundColor).Foreground(t.graphicsColor) lineStyle := tcell.StyleDefault.Background(t.backgroundColor).Foreground(t.graphicsColor)
for index, node := range t.nodes { for index, node := range t.nodes {
// Skip invisible parts. // Skip invisible parts.
if posY >= y+height+1 { if posY >= y+height {
break break
} }
if index < t.offsetY { if index < t.offsetY {
@ -668,6 +692,11 @@ func (t *TreeView) Draw(screen tcell.Screen) {
} }
} }
// Draw scroll bar.
if t.scrollBarVisibility == ScrollBarAlways || (t.scrollBarVisibility == ScrollBarAuto && rows > height) {
RenderScrollBar(screen, x+(width-1), posY, height, rows, cursor, posY-y, t.hasFocus, tcell.ColorWhite)
}
// Advance. // Advance.
posY++ posY++
} }

47
util.go
View File

@ -628,3 +628,50 @@ func iterateStringReverse(text string, callback func(main rune, comb []rune, tex
return false return false
} }
// ScrollBarVisibility specifies the display of a scroll bar.
type ScrollBarVisibility int
const (
// ScrollBarNever never shows a scroll bar.
ScrollBarNever ScrollBarVisibility = iota
// ScrollBarAuto shows a scroll bar when there are items offscreen.
ScrollBarAuto
// ScrollBarAlways always shows a scroll bar.
ScrollBarAlways
)
// RenderScrollBar renders a scroll bar character at the specified position.
func RenderScrollBar(screen tcell.Screen, x int, y int, height int, items int, cursor int, printed int, focused bool, color tcell.Color) {
// Do not render a scroll bar when all items are visible.
if items <= height {
return
}
// Handle negative cursor.
if cursor < 0 {
cursor = 0
}
// Calculate handle position.
handlePosition := int(float64(height-1) * (float64(cursor) / float64(items-1)))
// Print character.
var scrollBar string
if printed == handlePosition {
if focused {
scrollBar = "[::r] [-:-:-]"
} else {
scrollBar = "▓"
}
} else {
if focused {
scrollBar = "▒"
} else {
scrollBar = "░"
}
}
Print(screen, scrollBar, x, y, 1, AlignLeft, color)
}