191 lines
5.9 KiB
Go
191 lines
5.9 KiB
Go
|
package views
|
||
|
|
||
|
import (
|
||
|
"image"
|
||
|
"math"
|
||
|
"time"
|
||
|
|
||
|
"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 = limit(v.targetOffset.Y, 0, max(v.buf.Bounds().Dy()-v.viewportHeight, 0))
|
||
|
v.targetOffset.X = limit(v.targetOffset.X, 0, max(v.buf.Bounds().Dx()-v.viewportWidth, 0))
|
||
|
v.VerticalScrollOffset = limit(v.VerticalScrollOffset, 0, float64(max(v.buf.Bounds().Dy()-v.viewportHeight, 0)))
|
||
|
v.HorizontalScrollOffset = limit(v.HorizontalScrollOffset, 0, float64(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
|
||
|
}
|