package ui import ( "image/color" "opslag.de/schobers/geom" ) type overflow struct { Proxy Background color.Color 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 SetScrollbarColor(bar color.Color, hover color.Color) } func Overflow(content Control) ScrollControl { return OverflowBackground(content, nil) } func OverflowBackground(content Control, back color.Color) ScrollControl { var o = &overflow{Proxy: Proxy{Content: content}, Background: back} o.hor = BuildScrollbar(OrientationHorizontal, func(*Scrollbar) {}) o.ver = BuildScrollbar(OrientationVertical, func(*Scrollbar) {}) return o } 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()) } 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) 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) SetScrollbarColor(bar color.Color, hover color.Color) { o.hor.BarColor = bar o.hor.BarHoverColor = hover o.ver.BarColor = bar o.ver.BarHoverColor = hover }