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 365a74a2d9
6 changed files with 145 additions and 6 deletions

View File

@ -17,6 +17,9 @@ func BuildContainerBase(controls ...Control) ContainerBase {
func (c *ContainerBase) AddChild(child ...Control) { func (c *ContainerBase) AddChild(child ...Control) {
c.Children = append(c.Children, child...) 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) { 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) 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 { func (c *ContainerBase) DesiredSize(ctx Context, size geom.PointF32) geom.PointF32 {
var max geom.PointF32 var max geom.PointF32
for _, child := range c.Children { for _, child := range c.Children {

View File

@ -11,16 +11,39 @@ type Control interface {
Render(Context) Render(Context)
Bounds() geom.RectangleF32 Bounds() geom.RectangleF32
BoundsUnclipped(Context, ControlPath) geom.RectangleF32
Disable() Disable()
Enable() Enable()
IsDisabled() bool IsDisabled() bool
IsInBounds(p geom.PointF32) bool IsInBounds(p geom.PointF32) bool
IsOver() bool IsOver() bool
Offset() geom.PointF32 Offset() geom.PointF32
ScrollIntoView(Context, ControlPath)
Self() Control
SetSelf(Control)
Parent() 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 { type RootControl interface {
Control Control

View File

@ -90,6 +90,7 @@ var _ Control = &ControlBase{}
type ControlBase struct { type ControlBase struct {
bounds geom.RectangleF32 bounds geom.RectangleF32
offset geom.PointF32 offset geom.PointF32
self Control
parent Control parent Control
drag Dragable drag Dragable
over bool 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) 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) ControlClicked() ControlClickedEventHandler { return &c.clicked }
func (c *ControlBase) DesiredSize(Context, geom.PointF32) geom.PointF32 { return geom.ZeroPtF32 } 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) 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) } 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) 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 { func (o *overflow) DesiredSize(Context, geom.PointF32) geom.PointF32 {
return geom.PtF32(geom.NaN32(), geom.NaN32()) 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.BarColor = bar
o.ver.BarHoverColor = hover 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() 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) Disable() { p.Content.Disable() }
func (p *Proxy) Enable() { p.Content.Enable() } func (p *Proxy) Enable() { p.Content.Enable() }
@ -56,3 +60,13 @@ func (p *Proxy) Shown() {
overlay.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 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) bounds = p.Orientation.FlipRect(bounds)
var length float32 var length float32
var stretch int var stretch int
var desired = make([]geom.PointF32, len(p.Children)) var desired = make([]geom.PointF32, len(p.Children))
for i, child := range 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) { if geom.IsNaN32(size.Y) {
stretch++ stretch++
} else { } else {
@ -33,7 +33,8 @@ func (p *StackPanel) Arrange(ctx Context, bounds geom.RectangleF32, offset geom.
} }
var remainder = bounds.Dy() - length var remainder = bounds.Dy() - length
var childOffset float32 var childOffset float32
for i, size := range desired { childBounds := make([]geom.RectangleF32, 0, len(p.Children))
for _, size := range desired {
var height = size.Y var height = size.Y
if geom.IsNaN32(size.Y) { if geom.IsNaN32(size.Y) {
if remainder < 0 { if remainder < 0 {
@ -42,15 +43,46 @@ func (p *StackPanel) Arrange(ctx Context, bounds geom.RectangleF32, offset geom.
height = remainder / float32(stretch) height = remainder / float32(stretch)
} }
} }
minY := geom.Min32(bounds.Max.Y, bounds.Min.Y+childOffset) minY := bounds.Min.Y + childOffset
maxY := geom.Min32(bounds.Max.Y, bounds.Min.Y+childOffset+height) 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) 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 childOffset += height
} }
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, p.Orientation.FlipRect(childBounds[i]), offset, p)
}
p.ControlBase.Arrange(ctx, p.Orientation.FlipRect(bounds), offset, parent) p.ControlBase.Arrange(ctx, p.Orientation.FlipRect(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 { func (p *StackPanel) DesiredSize(ctx Context, size geom.PointF32) geom.PointF32 {
var length float32 var length float32
var width float32 var width float32
@ -87,3 +119,10 @@ func (p *StackPanel) Render(ctx Context) {
child.Render(ctx) child.Render(ctx)
} }
} }
func (p *StackPanel) ScrollIntoView(ctx Context, path ControlPath) {
if p.parent == nil {
return
}
p.parent.ScrollIntoView(ctx, path.Prepend(p))
}