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 } }