Added support for scrolling a control into view (Overflow, only vertically currently).

This commit is contained in:
Sander Schobers 2021-08-19 12:27:34 +02:00
parent a6415a1d60
commit eb46741165
6 changed files with 146 additions and 7 deletions

View File

@ -17,6 +17,9 @@ func BuildContainerBase(controls ...Control) ContainerBase {
func (c *ContainerBase) AddChild(child ...Control) {
c.Children = append(c.Children, child...)
for _, child := range child {
child.SetSelf(child)
}
}
func (c *ContainerBase) Arrange(ctx Context, bounds geom.RectangleF32, offset geom.PointF32, parent Control) {
@ -26,6 +29,19 @@ func (c *ContainerBase) Arrange(ctx Context, bounds geom.RectangleF32, offset ge
c.ControlBase.Arrange(ctx, bounds, offset, parent)
}
func (c *ContainerBase) BoundsUnclipped(ctx Context, path ControlPath) geom.RectangleF32 {
if len(path) == 0 {
return c.bounds
}
next := path[0]
for _, child := range c.Children {
if child == next {
return child.BoundsUnclipped(ctx, path[1:])
}
}
panic("child not found in path")
}
func (c *ContainerBase) DesiredSize(ctx Context, size geom.PointF32) geom.PointF32 {
var max geom.PointF32
for _, child := range c.Children {

View File

@ -11,16 +11,39 @@ type Control interface {
Render(Context)
Bounds() geom.RectangleF32
BoundsUnclipped(Context, ControlPath) geom.RectangleF32
Disable()
Enable()
IsDisabled() bool
IsInBounds(p geom.PointF32) bool
IsOver() bool
Offset() geom.PointF32
ScrollIntoView(Context, ControlPath)
Self() Control
SetSelf(Control)
Parent() Control
}
type ControlPath []Control
func (p ControlPath) BoundsUnclipped(ctx Context, c Control) geom.RectangleF32 {
if len(p) == 0 {
return c.Bounds()
}
// switch next := p[0].(type) {
// case *ContainerBase:
// return next.BoundsUnclipped(ctx, p[1:])
// case *StackPanel:
// return next.BoundsUnclipped(ctx, p[1:])
// }
return p[0].BoundsUnclipped(ctx, p[1:])
}
func (p ControlPath) Prepend(control Control) ControlPath {
return append(ControlPath{control}, p...)
}
type RootControl interface {
Control

View File

@ -90,6 +90,7 @@ var _ Control = &ControlBase{}
type ControlBase struct {
bounds geom.RectangleF32
offset geom.PointF32
self Control
parent Control
drag Dragable
over bool
@ -118,6 +119,10 @@ func (c *ControlBase) Arrange(ctx Context, bounds geom.RectangleF32, offset geom
func (c *ControlBase) Bounds() geom.RectangleF32 { return c.bounds }
func (c *ControlBase) BoundsUnclipped(ctx Context, path ControlPath) geom.RectangleF32 {
return path.BoundsUnclipped(ctx, c)
}
func (c *ControlBase) ControlClicked() ControlClickedEventHandler { return &c.clicked }
func (c *ControlBase) DesiredSize(Context, geom.PointF32) geom.PointF32 { return geom.ZeroPtF32 }
@ -282,4 +287,20 @@ func (c *ControlBase) TextColor(ctx Context) color.Color {
return c.FontColor(ctx, ctx.Style().Palette.Text)
}
func (c *ControlBase) ScrollIntoView(ctx Context, path ControlPath) {
if c.parent == nil {
return
}
c.parent.ScrollIntoView(ctx, path.Prepend(c))
}
func (c *ControlBase) Self() Control {
if c.self == nil {
return c
}
return c.self
}
func (c *ControlBase) SetSelf(self Control) { c.self = self }
func (c *ControlBase) ToControlPosition(p geom.PointF32) geom.PointF32 { return p.Sub(c.offset) }

View File

@ -114,6 +114,10 @@ func (o *overflow) Arrange(ctx Context, bounds geom.RectangleF32, offset geom.Po
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())
}
@ -193,3 +197,25 @@ func (o *overflow) SetScrollbarColor(bar color.Color, hover color.Color) {
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
}
}

View File

@ -29,6 +29,10 @@ func (p *Proxy) Bounds() geom.RectangleF32 {
return p.Content.Bounds()
}
func (p *Proxy) BoundsUnclipped(ctx Context, path ControlPath) geom.RectangleF32 {
return path.BoundsUnclipped(ctx, p)
}
func (p *Proxy) Disable() { p.Content.Disable() }
func (p *Proxy) Enable() { p.Content.Enable() }
@ -56,3 +60,13 @@ func (p *Proxy) Shown() {
overlay.Shown()
}
}
func (p *Proxy) ScrollIntoView(ctx Context, path ControlPath) {
p.Content.Parent().ScrollIntoView(ctx, path.Prepend(p))
}
func (p *Proxy) Self() Control {
return p.Content.Self()
}
func (p *Proxy) SetSelf(self Control) { p.Content.SetSelf(self) }

View File

@ -17,13 +17,13 @@ func BuildStackPanel(o Orientation, fn func(*StackPanel)) *StackPanel {
return p
}
func (p *StackPanel) Arrange(ctx Context, bounds geom.RectangleF32, offset geom.PointF32, parent Control) {
func (p *StackPanel) preArrange(ctx Context, bounds geom.RectangleF32, clip bool) []geom.RectangleF32 {
bounds = p.Orientation.FlipRect(bounds)
var length float32
var stretch int
var desired = make([]geom.PointF32, len(p.Children))
for i, child := range p.Children {
var size = p.Orientation.FlipPt(child.DesiredSize(ctx, bounds.Size()))
var size = p.Orientation.FlipPt(child.DesiredSize(ctx, p.Orientation.FlipPt(bounds.Size())))
if geom.IsNaN32(size.Y) {
stretch++
} else {
@ -33,7 +33,8 @@ func (p *StackPanel) Arrange(ctx Context, bounds geom.RectangleF32, offset geom.
}
var remainder = bounds.Dy() - length
var childOffset float32
for i, size := range desired {
childBounds := make([]geom.RectangleF32, 0, len(p.Children))
for _, size := range desired {
var height = size.Y
if geom.IsNaN32(size.Y) {
if remainder < 0 {
@ -42,13 +43,44 @@ func (p *StackPanel) Arrange(ctx Context, bounds geom.RectangleF32, offset geom.
height = remainder / float32(stretch)
}
}
minY := geom.Min32(bounds.Max.Y, bounds.Min.Y+childOffset)
maxY := geom.Min32(bounds.Max.Y, bounds.Min.Y+childOffset+height)
minY := bounds.Min.Y + childOffset
maxY := minY + height
if clip {
if minY > bounds.Max.Y {
minY = bounds.Max.Y
}
if maxY > bounds.Max.Y {
maxY = bounds.Max.Y
}
}
var child = geom.RectF32(bounds.Min.X, minY, bounds.Max.X, maxY)
p.Children[i].Arrange(ctx, p.Orientation.FlipRect(child), offset, p)
childBounds = append(childBounds, p.Orientation.FlipRect(child))
childOffset += height
}
p.ControlBase.Arrange(ctx, p.Orientation.FlipRect(bounds), offset, parent)
return childBounds
}
func (p *StackPanel) Arrange(ctx Context, bounds geom.RectangleF32, offset geom.PointF32, parent Control) {
childBounds := p.preArrange(ctx, bounds, true)
for i := range p.Children {
p.Children[i].Arrange(ctx, childBounds[i], offset, p)
}
p.ControlBase.Arrange(ctx, bounds, offset, parent)
}
func (p *StackPanel) BoundsUnclipped(ctx Context, path ControlPath) geom.RectangleF32 {
if len(path) == 0 {
return p.bounds
}
next := path[0]
childBounds := p.preArrange(ctx, p.bounds, false)
for i, child := range p.Children {
if child != next && next.Self() != child.Self() {
continue
}
return childBounds[i]
}
panic("child not found in path")
}
func (p *StackPanel) DesiredSize(ctx Context, size geom.PointF32) geom.PointF32 {
@ -87,3 +119,10 @@ func (p *StackPanel) Render(ctx Context) {
child.Render(ctx)
}
}
func (p *StackPanel) ScrollIntoView(ctx Context, path ControlPath) {
if p.parent == nil {
return
}
p.parent.ScrollIntoView(ctx, path.Prepend(p))
}