222 lines
5.4 KiB
Go
222 lines
5.4 KiB
Go
package ui
|
|
|
|
import (
|
|
"image/color"
|
|
|
|
"opslag.de/schobers/geom"
|
|
)
|
|
|
|
type overflow struct {
|
|
Proxy
|
|
|
|
Background color.Color
|
|
|
|
ClipHorizontal bool
|
|
ClipVertical bool
|
|
|
|
barWidth float32
|
|
desired geom.PointF32
|
|
bounds geom.RectangleF32
|
|
offset geom.PointF32
|
|
parent Control
|
|
content Buffer
|
|
proxied Control
|
|
|
|
hor *Scrollbar
|
|
ver *Scrollbar
|
|
}
|
|
|
|
type ScrollControl interface {
|
|
Control
|
|
|
|
SetBackgroundColor(color.Color)
|
|
SetClipHorizontal(bool)
|
|
SetClipVertical(bool)
|
|
SetScrollbarColor(bar color.Color, hover color.Color)
|
|
}
|
|
|
|
func BuildOverflow(content Control, build func(c ScrollControl)) ScrollControl {
|
|
o := &overflow{Proxy: Proxy{Content: content}}
|
|
o.hor = BuildScrollbar(OrientationHorizontal, func(*Scrollbar) {})
|
|
o.ver = BuildScrollbar(OrientationVertical, func(*Scrollbar) {})
|
|
if build != nil {
|
|
build(o)
|
|
}
|
|
return o
|
|
}
|
|
|
|
func Overflow(content Control) ScrollControl {
|
|
return OverflowBackground(content, nil)
|
|
}
|
|
|
|
func OverflowBackground(content Control, back color.Color) ScrollControl {
|
|
return BuildOverflow(content, func(c ScrollControl) {
|
|
c.SetBackgroundColor(back)
|
|
})
|
|
}
|
|
|
|
func (o *overflow) shouldScroll(bounds geom.RectangleF32) (hor bool, ver bool) {
|
|
var scroll = func(need, actual float32) bool {
|
|
return !geom.IsNaN32(need) && need > actual
|
|
}
|
|
var size = o.desired
|
|
hor = scroll(size.X, bounds.Dx())
|
|
ver = scroll(size.Y, bounds.Dy())
|
|
if ver && !hor {
|
|
hor = scroll(size.X+o.barWidth, bounds.Dx())
|
|
}
|
|
if hor && !ver {
|
|
ver = scroll(size.Y+o.barWidth, bounds.Dy())
|
|
}
|
|
hor = hor && !o.ClipHorizontal
|
|
ver = ver && !o.ClipVertical
|
|
return
|
|
}
|
|
|
|
func (o *overflow) doOnVisibleBars(fn func(bar *Scrollbar)) {
|
|
hor, ver := o.shouldScroll(o.bounds)
|
|
if hor {
|
|
fn(o.hor)
|
|
}
|
|
if ver {
|
|
fn(o.ver)
|
|
}
|
|
}
|
|
|
|
func (o *overflow) Arrange(ctx Context, bounds geom.RectangleF32, offset geom.PointF32, parent Control) {
|
|
o.barWidth = ctx.Style().Dimensions.ScrollbarWidth
|
|
o.desired = o.Content.DesiredSize(ctx, bounds.Size())
|
|
o.bounds = bounds
|
|
o.offset = offset
|
|
o.parent = parent
|
|
|
|
var hor, ver = o.shouldScroll(bounds)
|
|
var contentX, contentY float32 = 0, 0
|
|
var contentW, contentH = bounds.Dx(), bounds.Dy()
|
|
if hor {
|
|
contentX -= o.hor.ContentOffset
|
|
contentH = geom.Max32(0, contentH-o.barWidth)
|
|
}
|
|
if ver {
|
|
contentY -= o.ver.ContentOffset
|
|
contentW = geom.Max32(0, contentW-o.barWidth)
|
|
}
|
|
o.Content.Arrange(ctx, geom.RectF32(contentX, contentY, contentW, contentH), offset.Add(bounds.Min), o)
|
|
if hor {
|
|
o.hor.ContentLength = o.desired.X
|
|
o.hor.Arrange(ctx, geom.RectF32(bounds.Min.X, bounds.Min.Y+contentH, bounds.Min.X+contentW, bounds.Max.Y), offset, o)
|
|
}
|
|
if ver {
|
|
o.ver.ContentLength = o.desired.Y
|
|
o.ver.Arrange(ctx, geom.RectF32(bounds.Min.X+contentW, bounds.Min.Y, bounds.Max.X, bounds.Min.Y+contentH), offset, o)
|
|
}
|
|
}
|
|
|
|
func (o *overflow) Bounds() geom.RectangleF32 { return o.bounds }
|
|
|
|
func (o *overflow) BoundsUnclipped(ctx Context, path ControlPath) geom.RectangleF32 {
|
|
return path.BoundsUnclipped(ctx, o)
|
|
}
|
|
|
|
func (o *overflow) DesiredSize(Context, geom.PointF32) geom.PointF32 {
|
|
return geom.PtF32(geom.NaN32(), geom.NaN32())
|
|
}
|
|
|
|
func (o *overflow) Handle(ctx Context, e Event) bool {
|
|
if o.Content != o.proxied {
|
|
o.hor.ContentOffset = 0
|
|
o.ver.ContentOffset = 0
|
|
o.proxied = o.Content
|
|
}
|
|
hor, ver := o.shouldScroll(o.bounds)
|
|
if hor {
|
|
o.hor.Handle(ctx, e)
|
|
}
|
|
if ver {
|
|
o.ver.Handle(ctx, e)
|
|
}
|
|
switch e := e.(type) {
|
|
case *MouseMoveEvent:
|
|
if ver {
|
|
var contentO = o.Content.Offset()
|
|
var content = o.Content.Bounds()
|
|
content.Min = contentO
|
|
content.Max = content.Max.Add(contentO)
|
|
if e.MouseWheel != 0 && e.Pos().In(content) {
|
|
o.ver.ContentOffset = geom.Max32(0, geom.Min32(o.ver.ContentLength-content.Dy(), o.ver.ContentOffset-36*e.MouseWheel))
|
|
return true
|
|
}
|
|
}
|
|
}
|
|
return o.Content.Handle(ctx, e)
|
|
}
|
|
|
|
func (o *overflow) IsInBounds(p geom.PointF32) bool { return p.Sub(o.offset).In(o.bounds) }
|
|
|
|
func (o *overflow) Offset() geom.PointF32 { return o.offset }
|
|
|
|
func (o *overflow) Parent() Control { return o.parent }
|
|
|
|
func (o *overflow) Render(ctx Context) {
|
|
var renderer = ctx.Renderer()
|
|
|
|
if o.Background != nil {
|
|
renderer.FillRectangle(o.bounds, o.Background)
|
|
}
|
|
|
|
var content = o.Content.Bounds()
|
|
content.Min = geom.ZeroPtF32
|
|
err := o.content.Update(ctx, content.Size())
|
|
if err != nil && err != ErrNewBufferSize {
|
|
panic(err)
|
|
}
|
|
o.content.Render(ctx, o.bounds.Min, func(Context, geom.PointF32) {
|
|
renderer.Clear(color.Transparent)
|
|
o.Content.Render(ctx)
|
|
})
|
|
|
|
o.doOnVisibleBars(func(bar *Scrollbar) {
|
|
bar.Render(ctx)
|
|
})
|
|
}
|
|
func (o *overflow) SetBackgroundColor(c color.Color) {
|
|
o.Background = c
|
|
}
|
|
|
|
func (o *overflow) SetClipHorizontal(clip bool) {
|
|
o.ClipHorizontal = clip
|
|
}
|
|
|
|
func (o *overflow) SetClipVertical(clip bool) {
|
|
o.ClipVertical = clip
|
|
}
|
|
|
|
func (o *overflow) SetScrollbarColor(bar color.Color, hover color.Color) {
|
|
o.hor.BarColor = bar
|
|
o.hor.BarHoverColor = hover
|
|
o.ver.BarColor = bar
|
|
o.ver.BarHoverColor = hover
|
|
}
|
|
|
|
func (o *overflow) ScrollIntoView(ctx Context, path ControlPath) {
|
|
view := path.BoundsUnclipped(ctx, o)
|
|
size := geom.PtF32(o.hor.Bounds().Dx(), o.ver.Bounds().Dy())
|
|
view.Min.Y += o.ver.ContentOffset
|
|
view.Max.Y += o.ver.ContentOffset
|
|
if view.Max.Y > o.ver.ContentLength {
|
|
view.Max.Y = o.ver.ContentLength
|
|
if view.Min.Y > view.Max.Y {
|
|
view.Min.Y = view.Max.Y
|
|
}
|
|
}
|
|
if view.Min.Y < 0 {
|
|
view.Min.Y = 0
|
|
}
|
|
if view.Max.Y > o.ver.ContentOffset+size.Y {
|
|
o.ver.ContentOffset = view.Max.Y - size.Y
|
|
}
|
|
if view.Min.Y < o.ver.ContentOffset {
|
|
o.ver.ContentOffset = view.Min.Y
|
|
}
|
|
}
|