gui/views/view_scroll.go
2023-01-22 12:40:56 +01:00

192 lines
6.0 KiB
Go

package views
import (
"image"
"math"
"time"
"git.milar.in/milarin/gmath"
"git.milar.in/milarin/gui"
"github.com/hajimehoshi/ebiten/v2"
)
// ScrollView is a gui.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 {
gui.WrapperTmpl
buf *gui.Image
viewportWidth, viewportHeight int
viewWidth, viewHeight int
VerticalScrollOffset float64
HorizontalScrollOffset float64
ScrollStep int
targetOffset gui.Point
ScrollSpeed int
lastDraw time.Time
verticalScrollBar *ScrollbarView
horizontalScrollBar *ScrollbarView
}
var _ gui.Wrapper = &ScrollView{}
func NewScrollView(view gui.View) *ScrollView {
v := new(ScrollView)
v.SetView(view)
v.ScrollStep = 100
v.ScrollSpeed = 1000
v.lastDraw = time.Now()
v.verticalScrollBar = NewScrollbarView(gui.Vertical)
v.horizontalScrollBar = NewScrollbarView(gui.Horizontal)
return v
}
func (v *ScrollView) update(deltaTime float64) {
deltaSpeed := deltaTime * float64(v.ScrollSpeed)
if math.Abs(v.VerticalScrollOffset-float64(v.targetOffset.Y)) >= deltaSpeed {
vDir := iff(v.VerticalScrollOffset-float64(v.targetOffset.Y) < 0, 1.0, -1.0)
v.VerticalScrollOffset += deltaSpeed * vDir
} else {
v.VerticalScrollOffset = float64(v.targetOffset.Y)
}
if math.Abs(v.HorizontalScrollOffset-float64(v.targetOffset.X)) >= deltaSpeed {
hDir := iff(v.HorizontalScrollOffset-float64(v.targetOffset.X) < 0, 1.0, -1.0)
v.HorizontalScrollOffset += deltaSpeed * hDir
} else {
v.HorizontalScrollOffset = float64(v.targetOffset.X)
}
}
func (v *ScrollView) Layout(ctx gui.AppContext) (prefWidth, prefHeight int) {
return -1, -1
}
func (v *ScrollView) Draw(img *gui.Image, ctx gui.AppContext) {
now := time.Now()
deltaTime := now.Sub(v.lastDraw).Seconds()
v.lastDraw = now
v.viewWidth = img.Bounds().Dx()
v.viewHeight = img.Bounds().Dy()
v.update(deltaTime)
v.ViewTmpl.Draw(img, ctx)
w, h := v.View().Layout(ctx)
w = iff(w >= 0, w, img.Bounds().Dx()-v.verticalScrollBar.Thickness)
h = iff(h >= 0, h, img.Bounds().Dy()-v.horizontalScrollBar.Thickness)
if v.buf == nil || v.buf.Bounds().Dx() != w || v.buf.Bounds().Dy() != h {
v.buf = gui.NewImage(w, h)
}
v.limit()
v.View().Draw(v.buf, ctx)
scrollH, scrollV := v.determineViewportSize(img)
copyBufferWidth, copyBufferHeight := 0, 0
if scrollH {
copyBufferWidth = v.viewportWidth
} else {
copyBufferWidth = v.buf.Bounds().Dx()
}
if scrollV {
copyBufferHeight = v.viewportHeight
} else {
copyBufferHeight = v.buf.Bounds().Dy()
}
// copy buffer
min := image.Pt(int(v.HorizontalScrollOffset), int(v.VerticalScrollOffset))
size := image.Pt(copyBufferWidth, copyBufferHeight)
max := min.Add(size)
rect := image.Rectangle{min, max}
op := new(ebiten.DrawImageOptions)
//op.GeoM.Translate(-float64(min.X), -float64(min.Y))
img.DrawImage(v.buf.SubImage(rect).(*gui.Image), op) //-min.X, -min.Y)
if scrollV {
v.verticalScrollBar.ViewSize = v.buf.Bounds().Dy()
v.verticalScrollBar.ViewportSize = size.Y
v.verticalScrollBar.ViewportPos = int(v.VerticalScrollOffset)
v.verticalScrollBar.Draw(img.SubImage(image.Rect(img.Bounds().Max.X-v.verticalScrollBar.Thickness, 0, img.Bounds().Max.X, img.Bounds().Max.Y)).(*gui.Image), ctx)
}
if scrollH {
v.horizontalScrollBar.ViewSize = v.buf.Bounds().Dx()
v.horizontalScrollBar.ViewportSize = size.X
v.horizontalScrollBar.ViewportPos = int(v.HorizontalScrollOffset)
v.horizontalScrollBar.Draw(img.SubImage(image.Rect(0, img.Bounds().Max.Y-v.horizontalScrollBar.Thickness, img.Bounds().Max.X, img.Bounds().Max.Y)).(*gui.Image), ctx)
}
}
func (v *ScrollView) limit() {
if v.buf != nil {
v.targetOffset.Y = gmath.Clamp(v.targetOffset.Y, 0, gmath.Max(v.buf.Bounds().Dy()-v.viewportHeight, 0))
v.targetOffset.X = gmath.Clamp(v.targetOffset.X, 0, gmath.Max(v.buf.Bounds().Dx()-v.viewportWidth, 0))
v.VerticalScrollOffset = gmath.Clamp(v.VerticalScrollOffset, 0, float64(gmath.Max(v.buf.Bounds().Dy()-v.viewportHeight, 0)))
v.HorizontalScrollOffset = gmath.Clamp(v.HorizontalScrollOffset, 0, float64(gmath.Max(v.buf.Bounds().Dx()-v.viewportWidth, 0)))
}
}
func (v *ScrollView) Scroll(horizontalOffset, verticalOffset int) {
v.targetOffset.Y = v.targetOffset.Y + verticalOffset
v.targetOffset.X = v.targetOffset.X + horizontalOffset
}
func (v *ScrollView) determineViewportSize(img *gui.Image) (scrollbarH, scrollbarV bool) {
v.viewportWidth, v.viewportHeight = img.Bounds().Dx()-v.verticalScrollBar.Thickness, img.Bounds().Dy()-v.horizontalScrollBar.Thickness
scrollbarV = v.buf.Bounds().Dy() > v.viewportHeight
scrollbarH = v.buf.Bounds().Dx() > v.viewportWidth
if scrollbarV && !scrollbarH {
v.viewportHeight += v.horizontalScrollBar.Thickness
scrollbarV = v.buf.Bounds().Dy() > v.viewportHeight
}
if !scrollbarV && scrollbarH {
v.viewportWidth += v.verticalScrollBar.Thickness
scrollbarH = v.buf.Bounds().Dx() > v.viewportWidth
}
return
}
func (v *ScrollView) OnMouseClick(event gui.MouseEvent) (consumed bool) {
return v.View().OnMouseClick(event.AddPos(gui.P(int(v.HorizontalScrollOffset), int(v.VerticalScrollOffset))))
}
func (v *ScrollView) OnMouseMove(event gui.MouseEvent) (consumed bool) {
if event.Position.In(gui.D(v.viewportWidth, 0, v.viewWidth-v.viewportWidth, v.viewHeight)) {
return v.verticalScrollBar.OnMouseMove(event.SubtractPos(gui.P(v.viewportWidth, 0)))
}
if event.Position.In(gui.D(0, v.viewportHeight, v.viewWidth, v.viewHeight-v.viewportHeight)) {
return v.horizontalScrollBar.OnMouseMove(event.SubtractPos(gui.P(0, v.viewportHeight)))
}
return v.View().OnMouseMove(event.AddPos(gui.P(int(v.HorizontalScrollOffset), int(v.VerticalScrollOffset))))
}
func (v *ScrollView) OnMouseScroll(event gui.MouseEvent) (consumed bool) {
if v.View().OnMouseScroll(event.AddPos(gui.P(int(v.HorizontalScrollOffset), int(v.VerticalScrollOffset)))) {
return true
}
if event.Wheel.X != 0 || event.Wheel.Y != 0 {
v.Scroll(-event.Wheel.X*v.ScrollStep, -event.Wheel.Y*v.ScrollStep)
return true
}
return false
}