doodle/ui/frame.go
2018-07-31 17:18:13 -07:00

326 lines
6.4 KiB
Go

package ui
import (
"fmt"
"time"
"git.kirsle.net/apps/doodle/render"
)
// Frame is a widget that contains other widgets.
type Frame struct {
BaseWidget
packs map[Anchor][]packedWidget
widgets []Widget
}
// NewFrame creates a new Frame.
func NewFrame() *Frame {
return &Frame{
packs: map[Anchor][]packedWidget{},
widgets: []Widget{},
}
}
func (w *Frame) String() string {
return fmt.Sprintf("Frame<%d widgets>",
len(w.widgets),
)
}
// Compute the size of the Frame.
func (w *Frame) Compute(e render.Engine) {
var (
frameSize = w.Size()
maxWidth int32
maxHeight int32
visited = []packedWidget{}
)
// Initialize the dimensions?
if w.FixedSize() {
maxWidth = frameSize.W
maxHeight = frameSize.H
}
for anchor := AnchorMin; anchor <= AnchorMax; anchor++ {
if _, ok := w.packs[anchor]; !ok {
continue
}
var (
x int32
y int32
yDirection int32 = 1
xDirection int32 = 1
)
if anchor.IsSouth() {
y = frameSize.H
yDirection = -1 - w.BoxThickness(1)
} else if anchor == E {
x = frameSize.W
xDirection = -1
}
for _, packedWidget := range w.packs[anchor] {
child := packedWidget.widget
pack := packedWidget.pack
child.Compute(e)
var (
// point = child.Point()
size = child.Size()
yStep = y * yDirection
xStep = x * xDirection
)
if !w.FixedSize() {
log.Warn("not fixed")
if xStep+size.W+(pack.PadX*2) > maxWidth {
var old = maxWidth
maxWidth = xStep + size.W + (pack.PadX * 2)
log.Error("%s %s Upgrading maxWidth %d -> %d (size %s) xstep %d",
w,
child,
old,
maxWidth,
size,
xStep,
)
time.Sleep(5)
}
if yStep+size.H+(pack.PadY*2) > maxHeight {
maxHeight = yStep + size.H + (pack.PadY * 2)
}
}
if anchor.IsSouth() {
y -= size.H + (pack.PadY * 2)
}
if anchor.IsEast() {
x -= size.W + (pack.PadX * 2)
}
child.MoveTo(render.Point{
X: x + pack.PadX,
Y: y + pack.PadY,
})
if anchor.IsNorth() {
y += size.H + (pack.PadY * 2)
}
if anchor == W {
x += size.W + (pack.PadX * 2)
}
visited = append(visited, packedWidget)
}
}
// Rescan all the widgets in this anchor to re-center them
// in their space.
for _, pw := range visited {
var (
child = pw.widget
pack = pw.pack
point = child.Point()
size = child.Size()
resized bool
moved bool
)
if pack.Anchor.IsNorth() || pack.Anchor.IsSouth() {
if pack.FillX && size.W < maxWidth {
size.W = maxWidth - w.BoxThickness(2)
resized = true
}
if size.W < maxWidth {
if pack.Anchor.IsCenter() {
point.X = (maxWidth / 2) - (size.W / 2)
} else if pack.Anchor.IsWest() {
point.X = pack.PadX
} else if pack.Anchor.IsEast() {
point.X = maxWidth - size.W - pack.PadX
}
moved = true
}
} else if pack.Anchor.IsWest() || pack.Anchor.IsEast() {
if pack.FillY && size.H < maxHeight {
size.H = maxHeight - w.BoxThickness(2)
resized = true
}
if size.H < maxHeight {
if pack.Anchor.IsMiddle() {
point.Y = (maxHeight / 2) - (size.H / 2)
} else if pack.Anchor.IsNorth() {
point.Y = pack.PadY
} else if pack.Anchor.IsSouth() {
point.Y = maxHeight - size.H - pack.PadY
}
moved = true
}
} else {
log.Error("unsupported pack.Anchor")
}
if resized {
child.Resize(size)
}
if moved {
child.MoveTo(point)
}
}
if !w.FixedSize() {
w.Resize(render.Rect{
W: maxWidth + w.BoxThickness(2),
H: maxHeight + w.BoxThickness(2),
})
}
}
// Present the Frame.
func (w *Frame) Present(e render.Engine) {
var (
P = w.Point()
S = w.Size()
)
// Draw the widget's border and everything.
w.DrawBox(e)
// Draw the background color.
e.DrawBox(w.Background(), render.Rect{
X: P.X + w.BoxThickness(1),
Y: P.Y + w.BoxThickness(1),
W: S.W - w.BoxThickness(2),
H: S.H - w.BoxThickness(2),
})
// Draw the widgets.
for _, child := range w.widgets {
p := child.Point()
child.MoveTo(render.NewPoint(
P.X+p.X+w.BoxThickness(1),
P.Y+p.Y+w.BoxThickness(1),
))
child.Present(e)
}
}
// Pack provides configuration fields for Frame.Pack().
type Pack struct {
// Side of the parent to anchor the position to, like N, SE, W. Default
// is Center.
Anchor Anchor
// If the widget is smaller than its allocated space, grow the widget
// to fill its space in the Frame.
Fill bool
FillX bool
FillY bool
Padding int32 // Equal padding on X and Y.
PadX int32
PadY int32
// Expand bool // Widget should grow to fill any remaining space.
}
// Anchor is a cardinal direction.
type Anchor uint8
// Anchor values.
const (
Center Anchor = iota
N
NE
E
SE
S
SW
W
NW
)
// Range of Anchor values.
const (
AnchorMin = Center
AnchorMax = NW
)
// IsNorth returns if the anchor is N, NE or NW.
func (a Anchor) IsNorth() bool {
return a == N || a == NE || a == NW
}
// IsSouth returns if the anchor is S, SE or SW.
func (a Anchor) IsSouth() bool {
return a == S || a == SE || a == SW
}
// IsEast returns if the anchor is E, NE or SE.
func (a Anchor) IsEast() bool {
return a == E || a == NE || a == SE
}
// IsWest returns if the anchor is W, NW or SW.
func (a Anchor) IsWest() bool {
return a == W || a == NW || a == SW
}
// IsCenter returns if the anchor is Center, N or S, to determine
// whether to align text as centered for North/South anchors.
func (a Anchor) IsCenter() bool {
return a == Center || a == N || a == S
}
// IsMiddle returns if the anchor is Center, E or W, to determine
// whether to align text as middled for East/West anchors.
func (a Anchor) IsMiddle() bool {
return a == Center || a == W || a == E
}
// Pack a widget along a side of the frame.
func (w *Frame) Pack(child Widget, config ...Pack) {
var C Pack
if len(config) > 0 {
C = config[0]
}
// Initialize the pack list for this anchor?
if _, ok := w.packs[C.Anchor]; !ok {
w.packs[C.Anchor] = []packedWidget{}
}
// Padding: if the user only provided Padding add it to both
// the X and Y value. If the user additionally provided the X
// and Y value, it will add to the base padding as you'd expect.
C.PadX += C.Padding
C.PadY += C.Padding
w.packs[C.Anchor] = append(w.packs[C.Anchor], packedWidget{
widget: child,
pack: C,
})
w.widgets = append(w.widgets, child)
}
type packLayout struct {
widgets []packedWidget
}
type packedWidget struct {
widget Widget
pack Pack
fill uint8
}
// packedWidget.fill values
const (
fillNone uint8 = iota
fillX
fillY
fillBoth
)