Created unified spacing control.
Build control methods now accept nil as visitor method.
This commit is contained in:
parent
d14a9bd0e7
commit
3ea0d76efb
@ -10,9 +10,11 @@ type Button struct {
|
|||||||
Text string
|
Text string
|
||||||
}
|
}
|
||||||
|
|
||||||
func BuildButton(text string, factory func(b *Button)) *Button {
|
func BuildButton(text string, fn func(b *Button)) *Button {
|
||||||
var b = &Button{Text: text}
|
var b = &Button{Text: text}
|
||||||
factory(b)
|
if fn != nil {
|
||||||
|
fn(b)
|
||||||
|
}
|
||||||
return b
|
return b
|
||||||
}
|
}
|
||||||
|
|
||||||
|
43
ui/fixed.go
43
ui/fixed.go
@ -1,42 +1,23 @@
|
|||||||
package ui
|
package ui
|
||||||
|
|
||||||
import (
|
// FixedSize wraps the supplied control to fill exactly the space specified.
|
||||||
"opslag.de/schobers/geom"
|
func FixedSize(c Control, w, h float32) Control {
|
||||||
)
|
return BuildSpacing(c, func(s *Spacing) {
|
||||||
|
s.Width = Fixed(w)
|
||||||
var _ Control = &stretch{}
|
s.Height = Fixed(h)
|
||||||
|
})
|
||||||
type fixed struct {
|
|
||||||
Proxy
|
|
||||||
|
|
||||||
size geom.PointF32
|
|
||||||
}
|
|
||||||
|
|
||||||
func (f *fixed) DesiredSize(ctx Context) geom.PointF32 {
|
|
||||||
w, h := f.size.X, f.size.Y
|
|
||||||
if geom.IsNaN32(w) || geom.IsNaN32(h) {
|
|
||||||
child := f.Content.DesiredSize(ctx)
|
|
||||||
if geom.IsNaN32(w) {
|
|
||||||
w = child.X
|
|
||||||
}
|
|
||||||
if geom.IsNaN32(h) {
|
|
||||||
h = child.Y
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return geom.PtF32(w, h)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Fixed wraps the supplied control to fill exactly the space specified.
|
|
||||||
func Fixed(c Control, w, h float32) Control {
|
|
||||||
return &fixed{Proxy{Content: c}, geom.PtF32(w, h)}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// FixedHeight wraps the supplied control to fill exactly the height specified. Width is taken from wrapped control.
|
// FixedHeight wraps the supplied control to fill exactly the height specified. Width is taken from wrapped control.
|
||||||
func FixedHeight(c Control, h float32) Control {
|
func FixedHeight(c Control, h float32) Control {
|
||||||
return Fixed(c, geom.NaN32(), h)
|
return BuildSpacing(c, func(s *Spacing) {
|
||||||
|
s.Height = Fixed(h)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// FixedWidth wraps the supplied control to fill exactly the width specified. Height is taken from wrapped control.
|
// FixedWidth wraps the supplied control to fill exactly the width specified. Height is taken from wrapped control.
|
||||||
func FixedWidth(c Control, w float32) Control {
|
func FixedWidth(c Control, w float32) Control {
|
||||||
return Fixed(c, w, geom.NaN32())
|
return BuildSpacing(c, func(s *Spacing) {
|
||||||
|
s.Width = Fixed(w)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
@ -10,9 +10,11 @@ type Label struct {
|
|||||||
Text string
|
Text string
|
||||||
}
|
}
|
||||||
|
|
||||||
func BuildLabel(text string, factory func(*Label)) *Label {
|
func BuildLabel(text string, fn func(*Label)) *Label {
|
||||||
var l = &Label{Text: text}
|
var l = &Label{Text: text}
|
||||||
factory(l)
|
if fn != nil {
|
||||||
|
fn(l)
|
||||||
|
}
|
||||||
return l
|
return l
|
||||||
}
|
}
|
||||||
|
|
||||||
|
32
ui/length.go
Normal file
32
ui/length.go
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
package ui
|
||||||
|
|
||||||
|
import "opslag.de/schobers/geom"
|
||||||
|
|
||||||
|
type Length struct {
|
||||||
|
float32
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *Length) Value() float32 {
|
||||||
|
if l == nil {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
return l.float32
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *Length) Zero(value float32) *Length {
|
||||||
|
if l == nil {
|
||||||
|
return &Length{value}
|
||||||
|
}
|
||||||
|
return l
|
||||||
|
}
|
||||||
|
|
||||||
|
func Fixed(l float32) *Length { return &Length{l} }
|
||||||
|
|
||||||
|
func Infinite() *Length { return &Length{geom.NaN32()} }
|
||||||
|
|
||||||
|
type SideLengths struct {
|
||||||
|
Left *Length
|
||||||
|
Top *Length
|
||||||
|
Right *Length
|
||||||
|
Bottom *Length
|
||||||
|
}
|
@ -28,7 +28,9 @@ func BuildScrollbar(o Orientation, fn func(s *Scrollbar)) *Scrollbar {
|
|||||||
var offset = (s.Orientation.LengthParallel(move) - s.Orientation.LengthParallel(start)) / handleMaxOffset
|
var offset = (s.Orientation.LengthParallel(move) - s.Orientation.LengthParallel(start)) / handleMaxOffset
|
||||||
s.Offset = geom.Max32(0, geom.Min32(s.startDragOffset+offset*hidden, hidden))
|
s.Offset = geom.Max32(0, geom.Min32(s.startDragOffset+offset*hidden, hidden))
|
||||||
})
|
})
|
||||||
fn(s)
|
if fn != nil {
|
||||||
|
fn(s)
|
||||||
|
}
|
||||||
return s
|
return s
|
||||||
}
|
}
|
||||||
|
|
||||||
|
95
ui/spacing.go
Normal file
95
ui/spacing.go
Normal file
@ -0,0 +1,95 @@
|
|||||||
|
package ui
|
||||||
|
|
||||||
|
import (
|
||||||
|
"opslag.de/schobers/geom"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Spacing struct {
|
||||||
|
Proxy
|
||||||
|
|
||||||
|
Margin SideLengths
|
||||||
|
Width *Length
|
||||||
|
Height *Length
|
||||||
|
}
|
||||||
|
|
||||||
|
func BuildSpacing(content Control, fn func(*Spacing)) *Spacing {
|
||||||
|
s := &Spacing{}
|
||||||
|
s.Content = content
|
||||||
|
if fn != nil {
|
||||||
|
fn(s)
|
||||||
|
}
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
func sumLengths(lengths ...float32) (float32, int) {
|
||||||
|
var fixed float32
|
||||||
|
var infinite int
|
||||||
|
for _, l := range lengths {
|
||||||
|
if geom.IsNaN32(l) {
|
||||||
|
infinite++
|
||||||
|
} else {
|
||||||
|
fixed += l
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return fixed, infinite
|
||||||
|
}
|
||||||
|
|
||||||
|
func addMargin(bounds geom.PointF32, size geom.PointF32, margin SideLengths) geom.RectangleF32 {
|
||||||
|
var left = margin.Left.Value()
|
||||||
|
var top = margin.Top.Value()
|
||||||
|
var w, hor = sumLengths(left, size.X, margin.Right.Value())
|
||||||
|
var h, ver = sumLengths(top, size.Y, margin.Bottom.Value())
|
||||||
|
var rem = geom.PtF32(geom.Max32(0, bounds.X-w), geom.Max32(0, bounds.Y-h))
|
||||||
|
if geom.IsNaN32(left) {
|
||||||
|
left = rem.X / float32(hor)
|
||||||
|
} else if rem.X == 0 {
|
||||||
|
left = (left * bounds.X) / w
|
||||||
|
}
|
||||||
|
if geom.IsNaN32(size.X) {
|
||||||
|
size.X = rem.X / float32(hor)
|
||||||
|
} else if rem.X == 0 {
|
||||||
|
size.X = (size.X * bounds.X) / w
|
||||||
|
} else if hor == 0 { // nothing stretches
|
||||||
|
size.X += rem.X
|
||||||
|
}
|
||||||
|
if geom.IsNaN32(top) {
|
||||||
|
top = rem.Y / float32(ver)
|
||||||
|
} else if rem.Y == 0 {
|
||||||
|
top = (top * bounds.Y) / h
|
||||||
|
}
|
||||||
|
if geom.IsNaN32(size.Y) {
|
||||||
|
size.Y = rem.Y / float32(ver)
|
||||||
|
} else if rem.Y == 0 {
|
||||||
|
size.Y = (size.Y * bounds.Y) / h
|
||||||
|
} else if ver == 0 { // nothing stretches
|
||||||
|
size.Y += rem.Y
|
||||||
|
}
|
||||||
|
return geom.RectF32(left, top, left+size.X, top+size.Y)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Spacing) Arrange(ctx Context, bounds geom.RectangleF32, offset geom.PointF32) {
|
||||||
|
var size = s.Proxy.DesiredSize(ctx)
|
||||||
|
var w, h = s.Width.Zero(size.X), s.Height.Zero(size.Y)
|
||||||
|
var content = addMargin(geom.PtF32(bounds.Dx(), bounds.Dy()), geom.PtF32(w.Value(), h.Value()), s.Margin).Add(bounds.Min)
|
||||||
|
s.Proxy.Arrange(ctx, content, offset)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Spacing) Center() {
|
||||||
|
s.Margin.Left = Infinite()
|
||||||
|
s.Margin.Top = Infinite()
|
||||||
|
s.Margin.Right = Infinite()
|
||||||
|
s.Margin.Bottom = Infinite()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Spacing) DesiredSize(ctx Context) geom.PointF32 {
|
||||||
|
var size = s.Proxy.DesiredSize(ctx)
|
||||||
|
var w, h = s.Width.Zero(size.X), s.Height.Zero(size.Y)
|
||||||
|
return geom.PtF32(
|
||||||
|
s.Margin.Left.Value()+w.Value()+s.Margin.Right.Value(),
|
||||||
|
s.Margin.Top.Value()+h.Value()+s.Margin.Bottom.Value())
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Spacing) SetSize(w, h float32) {
|
||||||
|
s.Width = Fixed(w)
|
||||||
|
s.Height = Fixed(h)
|
||||||
|
}
|
62
ui/spacing_test.go
Normal file
62
ui/spacing_test.go
Normal file
@ -0,0 +1,62 @@
|
|||||||
|
package ui
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"opslag.de/schobers/geom"
|
||||||
|
)
|
||||||
|
|
||||||
|
var _ Control = &Mock{}
|
||||||
|
|
||||||
|
type Mock struct {
|
||||||
|
ControlBase
|
||||||
|
|
||||||
|
Size *geom.PointF32
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Mock) DesiredSize(ctx Context) geom.PointF32 {
|
||||||
|
if m.Size != nil {
|
||||||
|
return *m.Size
|
||||||
|
}
|
||||||
|
return m.ControlBase.DesiredSize(ctx)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNoStretchFillsAvailableSpace(t *testing.T) {
|
||||||
|
m := &Mock{}
|
||||||
|
s := BuildSpacing(m, func(s *Spacing) {
|
||||||
|
s.SetSize(0, 0)
|
||||||
|
})
|
||||||
|
s.Arrange(nil, geom.RectF32(31, 37, 73, 79), geom.ZeroPtF32)
|
||||||
|
assert.Equal(t, geom.RectF32(31, 37, 73, 79), m.bounds)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestStretch(t *testing.T) {
|
||||||
|
m := &Mock{}
|
||||||
|
s := BuildSpacing(m, func(s *Spacing) {
|
||||||
|
s.SetSize(geom.NaN32(), geom.NaN32())
|
||||||
|
})
|
||||||
|
s.Arrange(nil, geom.RectF32(31, 37, 73, 79), geom.ZeroPtF32)
|
||||||
|
assert.Equal(t, geom.RectF32(31, 37, 73, 79), m.bounds)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCenter(t *testing.T) {
|
||||||
|
m := &Mock{Size: &geom.PointF32{X: 2, Y: 3}}
|
||||||
|
s := BuildSpacing(m, func(s *Spacing) {
|
||||||
|
s.Center()
|
||||||
|
})
|
||||||
|
s.Arrange(nil, geom.RectF32(31, 37, 73, 79), geom.ZeroPtF32)
|
||||||
|
assert.Equal(t, geom.RectF32(51, 56.5, 53, 59.5), m.bounds)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFixedMargin(t *testing.T) {
|
||||||
|
m := &Mock{Size: &geom.PointF32{X: geom.NaN32(), Y: geom.NaN32()}}
|
||||||
|
s := BuildSpacing(m, func(s *Spacing) {
|
||||||
|
s.Margin.Left = Fixed(7)
|
||||||
|
s.Margin.Top = Fixed(2)
|
||||||
|
s.Margin.Right = Fixed(3)
|
||||||
|
s.Margin.Bottom = Fixed(5)
|
||||||
|
})
|
||||||
|
s.Arrange(nil, geom.RectF32(31, 37, 73, 79), geom.ZeroPtF32)
|
||||||
|
assert.Equal(t, geom.RectF32(38, 39, 70, 74), m.bounds)
|
||||||
|
}
|
@ -1,33 +1,14 @@
|
|||||||
package ui
|
package ui
|
||||||
|
|
||||||
import (
|
func newStretch(c Control, w, h bool) *Spacing {
|
||||||
"opslag.de/schobers/geom"
|
return BuildSpacing(c, func(s *Spacing) {
|
||||||
)
|
if w {
|
||||||
|
s.Width = Infinite()
|
||||||
var _ Control = &stretch{}
|
|
||||||
|
|
||||||
type stretch struct {
|
|
||||||
Proxy
|
|
||||||
|
|
||||||
w, h bool
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *stretch) DesiredSize(ctx Context) geom.PointF32 {
|
|
||||||
var size = geom.PtF32(geom.NaN32(), geom.NaN32())
|
|
||||||
if !s.w || !s.h {
|
|
||||||
child := s.Content.DesiredSize(ctx)
|
|
||||||
if !s.w {
|
|
||||||
size.X = child.X
|
|
||||||
}
|
}
|
||||||
if !s.h {
|
if h {
|
||||||
size.Y = child.Y
|
s.Height = Infinite()
|
||||||
}
|
}
|
||||||
}
|
})
|
||||||
return size
|
|
||||||
}
|
|
||||||
|
|
||||||
func newStretch(c Control, w, h bool) *stretch {
|
|
||||||
return &stretch{Proxy{Content: c}, w, h}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Stretch wraps the supplied control to stretch in both directions.
|
// Stretch wraps the supplied control to stretch in both directions.
|
||||||
|
Loading…
Reference in New Issue
Block a user