func (sl *ScrollableList) PageUp() {
	sl.Offset = max(0, sl.Offset-sl.InnerHeight())
	if sl.Cursor >= sl.Offset+sl.InnerHeight() {
		sl.Cursor = sl.Offset + sl.InnerHeight() - 1
	}
	sl.render()
}
Move the window up one frame; this will move the cursor as well.
package jiraui
import (
	ui "gopkg.in/gizak/termui.v2"
)
type ScrollableList struct {
	ui.Block
	Items []string
	Offset int
	ItemFgColor ui.Attribute
	ItemBgColor ui.Attribute
	CursorFgColor ui.Attribute
	CursorBgColor ui.Attribute
	Cursor int
}
func NewScrollableList() *ScrollableList {
	l := &ScrollableList{Block: *ui.NewBlock()}
	l.CursorBgColor = ui.ColorBlue
	l.CursorFgColor = ui.ColorWhite
	return l
}
func (sl *ScrollableList) Add(s string) {
	sl.Items = append(sl.Items, s)
	sl.render()
}
func (sl *ScrollableList) render() {
	ui.Render(sl)
}
func (sl *ScrollableList) colorsForItem(i int) (fg, bg ui.Attribute) {
	if i == sl.Cursor {
		return sl.CursorFgColor, sl.CursorBgColor
	}
	return sl.ItemFgColor, sl.ItemBgColor
}
func min(a, b int) int {
	if a < b {
		return a
	}
	return b
}
func max(a, b int) int {
	if a > b {
		return a
	}
	return b
}
func (sl *ScrollableList) Buffer() ui.Buffer {
	buf := sl.Block.Buffer()
	start := min(sl.Offset, len(sl.Items))
	end := min(sl.Offset+sl.InnerHeight(), len(sl.Items))
	for i, item := range sl.Items[start:end] {
		fg, bg := sl.colorsForItem(start + i)
		if item == "" {
			item = " "
		}
		cells := ui.DefaultTxBuilder.Build(item, fg, bg)
		cells = ui.DTrimTxCls(cells, sl.InnerWidth())
		offsetX := 0
		for _, cell := range cells {
			width := cell.Width()
			buf.Set(
				sl.InnerBounds().Min.X+offsetX,
				sl.InnerBounds().Min.Y+i,
				cell,
			)
			offsetX += width
		}
	}
	return buf
}
func (sl *ScrollableList) ScrollUp() {
	if sl.Offset > 0 {
		sl.Offset -= 1
		if sl.Cursor >= sl.Offset+sl.InnerHeight() {
			sl.Cursor = sl.Offset + sl.InnerHeight() - 1
		}
		sl.render()
	}
}
func (sl *ScrollableList) ScrollDown() {
	if sl.Offset < len(sl.Items) {
		sl.Offset += 1
		if sl.Offset > sl.Cursor {
			sl.Cursor = sl.Offset
		}
		sl.render()
	}
}
func (sl *ScrollableList) MoveUp(n int) {
	if sl.Cursor >= n {
		cur := sl.Items[sl.Cursor]
		up := sl.Items[sl.Cursor-n]
		sl.Items[sl.Cursor] = up
		sl.Items[sl.Cursor-n] = cur
	}
	sl.CursorUpLines(n)
}
func (sl *ScrollableList) MoveDown(n int) {
	if sl.Cursor < len(sl.Items)-n {
		cur := sl.Items[sl.Cursor]
		down := sl.Items[sl.Cursor+n]
		sl.Items[sl.Cursor] = down
		sl.Items[sl.Cursor+n] = cur
	}
	sl.CursorDownLines(n)
}
func (sl *ScrollableList) CursorDown() {
	sl.CursorDownLines(1)
}
func (sl *ScrollableList) CursorDownLines(n int) {
	sl.SilentCursorDownLines(n)
	sl.render()
}
func (sl *ScrollableList) SilentCursorDownLines(n int) {
	if sl.Cursor < len(sl.Items)-n {
		sl.Cursor += n
	} else {
		sl.Cursor = len(sl.Items) - 1
	}
	if sl.Cursor > sl.Offset+sl.InnerHeight()-n {
		sl.Offset += n
	}
}
func (sl *ScrollableList) CursorUp() {
	sl.CursorUpLines(1)
}
func (sl *ScrollableList) CursorUpLines(n int) {
	sl.SilentCursorUpLines(n)
	sl.render()
}
func (sl *ScrollableList) SilentCursorUpLines(n int) {
	if sl.Cursor > n {
		sl.Cursor -= n
	} else {
		sl.Cursor = 0
	}
	if sl.Cursor < sl.Offset {
		sl.Offset = sl.Cursor
	}
}
func (sl *ScrollableList) SetCursorLine(n int) {
	if n > len(sl.Items) || n < 0 {
		return
	}
	if !(n >= sl.Offset && n < min(sl.Offset+sl.InnerHeight(), len(sl.Items))) {
		if n < sl.Cursor {
			if sl.Offset > n {
				sl.Offset = n
			}
		} else {
			if sl.Offset < n {
				sl.Offset = n
			}
		}
	}
	sl.Cursor = n
	sl.render()
}
func (sl *ScrollableList) PageDown() {
	if sl.Offset < len(sl.Items)-sl.InnerHeight() {
		sl.Offset += sl.InnerHeight()
		if sl.Offset > sl.Cursor {
			sl.Cursor = sl.Offset
		}
		sl.render()
	}
}