144 lines
3.7 KiB
Go
144 lines
3.7 KiB
Go
package views
|
|
|
|
import (
|
|
"math"
|
|
|
|
"git.milar.in/milarin/buf2d"
|
|
"git.milar.in/milarin/gmath"
|
|
"git.milar.in/milarin/tui"
|
|
"github.com/gdamore/tcell"
|
|
)
|
|
|
|
// ScrollView is a tui.View which can hold an arbitrary large view.
|
|
// If the sub view does not fit into its bounds, scroll bars will be shown
|
|
type ScrollView struct {
|
|
tui.WrapperTmpl
|
|
buf *tui.ViewBuffer
|
|
|
|
width, height int
|
|
|
|
verticalScrollOffset int
|
|
horizontalScrollOffset int
|
|
}
|
|
|
|
var _ tui.Wrapper = &ScrollView{}
|
|
|
|
func NewScrollView(view tui.View) *ScrollView {
|
|
v := new(ScrollView)
|
|
v.SetView(view)
|
|
return v
|
|
}
|
|
|
|
func (v *ScrollView) Draw(buf *tui.ViewBuffer) {
|
|
v.ViewTmpl.Draw(buf)
|
|
|
|
w, h := v.View().Layout()
|
|
|
|
if v.buf == nil || v.buf.Width() != w || v.buf.Height() != h {
|
|
v.buf = buf2d.NewBuffer(w, h, tui.DefaultRune)
|
|
}
|
|
|
|
v.Scroll(0, 0) // limit scroll offset boundaries
|
|
v.View().Draw(v.buf)
|
|
|
|
scrollH, scrollV := v.determineViewportSize(buf)
|
|
copyBufferWidth, copyBufferHeight := 0, 0
|
|
|
|
if scrollH {
|
|
copyBufferWidth = v.width
|
|
} else {
|
|
copyBufferWidth = v.buf.Width()
|
|
}
|
|
|
|
if scrollV {
|
|
copyBufferHeight = v.height
|
|
} else {
|
|
copyBufferHeight = v.buf.Height()
|
|
}
|
|
|
|
// copy buffer
|
|
for x := 0; x < copyBufferWidth; x++ {
|
|
for y := 0; y < copyBufferHeight; y++ {
|
|
buf.Set(x, y, v.buf.Get(v.horizontalScrollOffset+x, v.verticalScrollOffset+y))
|
|
}
|
|
}
|
|
|
|
scrollVHeight := int(float64(buf.Height()) / float64(v.buf.Height()) * float64(v.height))
|
|
scrollVStart := int(math.Ceil(float64(v.verticalScrollOffset) / float64(v.buf.Height()) * float64(v.height)))
|
|
|
|
scrollHStart := int(float64(v.horizontalScrollOffset) / float64(v.buf.Width()) * float64(v.width))
|
|
scrollHWidth := int(math.Ceil(float64(buf.Width()) / float64(v.buf.Width()) * float64(v.width)))
|
|
|
|
// guarantee minimum scroll bar thumb size of 1 TODO inaccurate for small scales
|
|
if scrollVHeight <= 0 {
|
|
scrollVHeight = 1
|
|
if scrollVStart >= v.height {
|
|
scrollVStart = v.height - 1
|
|
}
|
|
}
|
|
if scrollHWidth <= 0 {
|
|
scrollHWidth = 1
|
|
if scrollHStart >= v.width {
|
|
scrollHStart = v.width - 1
|
|
}
|
|
}
|
|
|
|
// vertical scrollbar
|
|
if scrollV {
|
|
for y := 0; y < v.height; y++ {
|
|
var style tcell.Style
|
|
if y >= scrollVStart && y < scrollVStart+scrollVHeight {
|
|
style = tui.StyleDefault.Background(tcell.ColorWhite)
|
|
} else {
|
|
style = tui.StyleDefault.Background(tcell.ColorDarkSlateGray)
|
|
}
|
|
buf.Set(v.width, y, tui.Rune{Rn: ' ', Style: style})
|
|
}
|
|
}
|
|
|
|
// horizontal scrollbar
|
|
if scrollH {
|
|
for x := 0; x < v.width; x++ {
|
|
var style tcell.Style
|
|
if x >= scrollHStart && x < scrollHStart+scrollHWidth {
|
|
style = tui.StyleDefault.Background(tcell.ColorWhite)
|
|
} else {
|
|
style = tui.StyleDefault.Background(tcell.ColorDarkSlateGray)
|
|
}
|
|
buf.Set(x, v.height, tui.Rune{Rn: ' ', Style: style})
|
|
}
|
|
}
|
|
}
|
|
|
|
func (v *ScrollView) Layout() (prefWidth, prefHeight int) {
|
|
return -1, -1
|
|
}
|
|
|
|
func (v *ScrollView) Scroll(verticalOffset, horizontalOffset int) {
|
|
if v.buf != nil {
|
|
v.verticalScrollOffset = gmath.Clamp(v.verticalScrollOffset+verticalOffset, 0, gmath.Max(v.buf.Height()-v.height, 0))
|
|
v.horizontalScrollOffset = gmath.Clamp(v.horizontalScrollOffset+horizontalOffset, 0, gmath.Max(v.buf.Width()-v.width, 0))
|
|
} else {
|
|
v.verticalScrollOffset = v.verticalScrollOffset + verticalOffset
|
|
v.horizontalScrollOffset = v.horizontalScrollOffset + horizontalOffset
|
|
}
|
|
}
|
|
|
|
func (v *ScrollView) determineViewportSize(buf *tui.ViewBuffer) (scrollbarH, scrollbarV bool) {
|
|
v.width, v.height = buf.Width()-1, buf.Height()-1
|
|
scrollbarV = v.buf.Height() > v.height
|
|
scrollbarH = v.buf.Width() > v.width
|
|
|
|
if scrollbarV && !scrollbarH {
|
|
v.height++
|
|
scrollbarV = v.buf.Height() > v.height
|
|
}
|
|
|
|
if !scrollbarV && scrollbarH {
|
|
v.width++
|
|
scrollbarH = v.buf.Width() > v.width
|
|
}
|
|
|
|
return
|
|
}
|