Add CheckButton and CheckBox with Bound Booleans
CheckButton is a generic component based on Button that additionally takes a *bool variable to manage. When the CheckButton is clicked or unclicked, it will toggle the bool var and its border style will "stick" in or out depending on the state. Checkbox is a Frame widget that wraps a CheckButton and another child widget, such as a Label. Interacting with the child widget will forward all of its mouse events to the CheckButton, so that the Label could be clicked instead of just the box itself.
This commit is contained in:
parent
cbef5a46cb
commit
316456ef03
|
@ -144,6 +144,20 @@ func (s *GUITestScene) Setup(d *Doodle) error {
|
||||||
Anchor: ui.NW,
|
Anchor: ui.NW,
|
||||||
Padding: 2,
|
Padding: 2,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
cb := ui.NewCheckbox("Overlay",
|
||||||
|
&DebugOverlay,
|
||||||
|
ui.NewLabel(render.Text{
|
||||||
|
Text: "Toggle Debug Overlay",
|
||||||
|
Size: 14,
|
||||||
|
Color: render.Black,
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
frame.Pack(cb, ui.Pack{
|
||||||
|
Anchor: ui.NW,
|
||||||
|
Padding: 4,
|
||||||
|
})
|
||||||
|
cb.Supervise(s.Supervisor)
|
||||||
frame.Pack(ui.NewLabel(render.Text{
|
frame.Pack(ui.NewLabel(render.Text{
|
||||||
Text: "Like Tk!",
|
Text: "Like Tk!",
|
||||||
Size: 16,
|
Size: 16,
|
||||||
|
|
73
ui/check_button.go
Normal file
73
ui/check_button.go
Normal file
|
@ -0,0 +1,73 @@
|
||||||
|
package ui
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"git.kirsle.net/apps/doodle/render"
|
||||||
|
"git.kirsle.net/apps/doodle/ui/theme"
|
||||||
|
)
|
||||||
|
|
||||||
|
// CheckButton is a button that is bound to a boolean variable and stays clicked
|
||||||
|
// once pressed, until clicked again to release.
|
||||||
|
type CheckButton struct {
|
||||||
|
Button
|
||||||
|
BoolVar *bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewCheckButton creates a new CheckButton.
|
||||||
|
func NewCheckButton(name string, boolVar *bool, child Widget) *CheckButton {
|
||||||
|
w := &CheckButton{
|
||||||
|
BoolVar: boolVar,
|
||||||
|
}
|
||||||
|
w.Button.child = child
|
||||||
|
w.IDFunc(func() string {
|
||||||
|
return fmt.Sprintf("CheckButton<%s %+v>", name, w.BoolVar)
|
||||||
|
})
|
||||||
|
|
||||||
|
var borderStyle BorderStyle = BorderRaised
|
||||||
|
if w.BoolVar != nil {
|
||||||
|
if *w.BoolVar == true {
|
||||||
|
borderStyle = BorderSunken
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
w.Configure(Config{
|
||||||
|
Padding: 4,
|
||||||
|
BorderSize: 2,
|
||||||
|
BorderStyle: borderStyle,
|
||||||
|
OutlineSize: 1,
|
||||||
|
OutlineColor: theme.ButtonOutlineColor,
|
||||||
|
Background: theme.ButtonBackgroundColor,
|
||||||
|
})
|
||||||
|
|
||||||
|
w.Handle("MouseOver", func(p render.Point) {
|
||||||
|
w.hovering = true
|
||||||
|
w.SetBackground(theme.ButtonHoverColor)
|
||||||
|
})
|
||||||
|
w.Handle("MouseOut", func(p render.Point) {
|
||||||
|
w.hovering = false
|
||||||
|
w.SetBackground(theme.ButtonBackgroundColor)
|
||||||
|
})
|
||||||
|
|
||||||
|
w.Handle("MouseDown", func(p render.Point) {
|
||||||
|
w.clicked = true
|
||||||
|
w.SetBorderStyle(BorderSunken)
|
||||||
|
})
|
||||||
|
w.Handle("MouseUp", func(p render.Point) {
|
||||||
|
w.clicked = false
|
||||||
|
})
|
||||||
|
|
||||||
|
w.Handle("MouseDown", func(p render.Point) {
|
||||||
|
if w.BoolVar != nil {
|
||||||
|
if *w.BoolVar {
|
||||||
|
*w.BoolVar = false
|
||||||
|
w.SetBorderStyle(BorderRaised)
|
||||||
|
} else {
|
||||||
|
*w.BoolVar = true
|
||||||
|
w.SetBorderStyle(BorderSunken)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
return w
|
||||||
|
}
|
46
ui/checkbox.go
Normal file
46
ui/checkbox.go
Normal file
|
@ -0,0 +1,46 @@
|
||||||
|
package ui
|
||||||
|
|
||||||
|
import "git.kirsle.net/apps/doodle/render"
|
||||||
|
|
||||||
|
// Checkbox combines a CheckButton with a widget like a Label.
|
||||||
|
type Checkbox struct {
|
||||||
|
Frame
|
||||||
|
button *CheckButton
|
||||||
|
child Widget
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewCheckbox creates a new Checkbox.
|
||||||
|
func NewCheckbox(name string, boolVar *bool, child Widget) *Checkbox {
|
||||||
|
// Our custom checkbutton widget.
|
||||||
|
mark := NewFrame(name + "_mark")
|
||||||
|
|
||||||
|
w := &Checkbox{
|
||||||
|
button: NewCheckButton(name+"_button", boolVar, mark),
|
||||||
|
child: child,
|
||||||
|
}
|
||||||
|
w.Frame.Setup()
|
||||||
|
|
||||||
|
// Forward clicks on the child widget to the CheckButton.
|
||||||
|
for _, e := range []string{"MouseOver", "MouseOut", "MouseUp", "MouseDown"} {
|
||||||
|
func(e string) {
|
||||||
|
w.child.Handle(e, func(p render.Point) {
|
||||||
|
w.button.Event(e, p)
|
||||||
|
})
|
||||||
|
}(e)
|
||||||
|
}
|
||||||
|
|
||||||
|
w.Pack(w.button, Pack{
|
||||||
|
Anchor: W,
|
||||||
|
})
|
||||||
|
w.Pack(w.child, Pack{
|
||||||
|
Anchor: W,
|
||||||
|
})
|
||||||
|
|
||||||
|
return w
|
||||||
|
}
|
||||||
|
|
||||||
|
// Supervise the checkbutton inside the widget.
|
||||||
|
func (w *Checkbox) Supervise(s *Supervisor) {
|
||||||
|
s.Add(w.button)
|
||||||
|
s.Add(w.child)
|
||||||
|
}
|
296
ui/frame.go
296
ui/frame.go
|
@ -30,172 +30,19 @@ func NewFrame(name string) *Frame {
|
||||||
return w
|
return w
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Setup ensures all the Frame's data is initialized and not null.
|
||||||
|
func (w *Frame) Setup() {
|
||||||
|
if w.packs == nil {
|
||||||
|
w.packs = map[Anchor][]packedWidget{}
|
||||||
|
}
|
||||||
|
if w.widgets == nil {
|
||||||
|
w.widgets = []Widget{}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Compute the size of the Frame.
|
// Compute the size of the Frame.
|
||||||
func (w *Frame) Compute(e render.Engine) {
|
func (w *Frame) Compute(e render.Engine) {
|
||||||
var (
|
w.computePacked(e)
|
||||||
frameSize = w.Size()
|
|
||||||
|
|
||||||
// maxWidth and maxHeight are always the computed minimum dimensions
|
|
||||||
// that the Frame must be to contain all of its children. If the Frame
|
|
||||||
// was configured with an explicit Size, the Frame will be that Size,
|
|
||||||
// but we still calculate how much space the widgets _actually_ take
|
|
||||||
// so we can expand them to fill remaining space in fixed size widgets.
|
|
||||||
maxWidth int32
|
|
||||||
maxHeight int32
|
|
||||||
visited = []packedWidget{}
|
|
||||||
expanded = []packedWidget{}
|
|
||||||
)
|
|
||||||
|
|
||||||
// Iterate through all anchored directions and compute how much space to
|
|
||||||
// reserve to contain all of their widgets.
|
|
||||||
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(2) // parent + child BoxThickness(1) = 2
|
|
||||||
} else if anchor == E {
|
|
||||||
x = frameSize.W
|
|
||||||
xDirection = -1 - w.BoxThickness(2)
|
|
||||||
}
|
|
||||||
|
|
||||||
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 xStep+size.W+(pack.PadX*2) > maxWidth {
|
|
||||||
maxWidth = xStep + size.W + (pack.PadX * 2)
|
|
||||||
}
|
|
||||||
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)
|
|
||||||
if pack.Expand {
|
|
||||||
expanded = append(expanded, packedWidget)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// If we have extra space in the Frame and any expanding widgets, let the
|
|
||||||
// expanding widgets grow and share the remaining space.
|
|
||||||
computedSize := render.NewRect(maxWidth, maxHeight)
|
|
||||||
if len(expanded) > 0 && !frameSize.IsZero() && frameSize.Bigger(computedSize) {
|
|
||||||
// Divy up the size available.
|
|
||||||
growBy := render.Rect{
|
|
||||||
W: ((frameSize.W - computedSize.W) / int32(len(expanded))) - w.BoxThickness(2),
|
|
||||||
H: ((frameSize.H - computedSize.H) / int32(len(expanded))) - w.BoxThickness(2),
|
|
||||||
}
|
|
||||||
for _, pw := range expanded {
|
|
||||||
pw.widget.ResizeBy(growBy)
|
|
||||||
pw.widget.Compute(e)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// If we're not using a fixed Frame size, use the dynamically computed one.
|
|
||||||
if !w.FixedSize() {
|
|
||||||
frameSize = render.NewRect(maxWidth, maxHeight)
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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()
|
|
||||||
resize = size
|
|
||||||
resized bool
|
|
||||||
moved bool
|
|
||||||
)
|
|
||||||
|
|
||||||
if pack.Anchor.IsNorth() || pack.Anchor.IsSouth() {
|
|
||||||
if pack.FillX && resize.W < frameSize.W {
|
|
||||||
resize.W = frameSize.W - w.BoxThickness(2)
|
|
||||||
resized = true
|
|
||||||
}
|
|
||||||
if resize.W < frameSize.W-w.BoxThickness(4) {
|
|
||||||
if pack.Anchor.IsCenter() {
|
|
||||||
point.X = (frameSize.W / 2) - (resize.W / 2)
|
|
||||||
} else if pack.Anchor.IsWest() {
|
|
||||||
point.X = pack.PadX
|
|
||||||
} else if pack.Anchor.IsEast() {
|
|
||||||
point.X = frameSize.W - resize.W - pack.PadX
|
|
||||||
}
|
|
||||||
|
|
||||||
moved = true
|
|
||||||
}
|
|
||||||
} else if pack.Anchor.IsWest() || pack.Anchor.IsEast() {
|
|
||||||
if pack.FillY && resize.H < frameSize.H {
|
|
||||||
resize.H = frameSize.H - w.BoxThickness(2) // BoxThickness(2) for parent + child
|
|
||||||
// point.Y -= (w.BoxThickness(4) + child.BoxThickness(2))
|
|
||||||
moved = true
|
|
||||||
resized = true
|
|
||||||
}
|
|
||||||
|
|
||||||
// Vertically align the widgets.
|
|
||||||
if resize.H < frameSize.H {
|
|
||||||
if pack.Anchor.IsMiddle() {
|
|
||||||
point.Y = (frameSize.H / 2) - (resize.H / 2)
|
|
||||||
} else if pack.Anchor.IsNorth() {
|
|
||||||
point.Y = pack.PadY - w.BoxThickness(4)
|
|
||||||
} else if pack.Anchor.IsSouth() {
|
|
||||||
point.Y = frameSize.H - resize.H - pack.PadY
|
|
||||||
}
|
|
||||||
moved = true
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
log.Error("unsupported pack.Anchor")
|
|
||||||
}
|
|
||||||
|
|
||||||
if resized && size != resize {
|
|
||||||
child.Resize(resize)
|
|
||||||
child.Compute(e)
|
|
||||||
}
|
|
||||||
if moved {
|
|
||||||
child.MoveTo(point)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if !w.FixedSize() {
|
|
||||||
w.Resize(frameSize)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Present the Frame.
|
// Present the Frame.
|
||||||
|
@ -226,124 +73,3 @@ func (w *Frame) Present(e render.Engine) {
|
||||||
child.Present(e)
|
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 its allocated space to better fill the parent.
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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
|
|
||||||
|
|
||||||
// Fill: true implies both directions.
|
|
||||||
if C.Fill {
|
|
||||||
C.FillX = true
|
|
||||||
C.FillY = true
|
|
||||||
}
|
|
||||||
|
|
||||||
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
|
|
||||||
)
|
|
||||||
|
|
292
ui/frame_pack.go
Normal file
292
ui/frame_pack.go
Normal file
|
@ -0,0 +1,292 @@
|
||||||
|
package ui
|
||||||
|
|
||||||
|
import "git.kirsle.net/apps/doodle/render"
|
||||||
|
|
||||||
|
// computePacked processes all the Pack layout widgets in the Frame.
|
||||||
|
func (w *Frame) computePacked(e render.Engine) {
|
||||||
|
var (
|
||||||
|
frameSize = w.Size()
|
||||||
|
|
||||||
|
// maxWidth and maxHeight are always the computed minimum dimensions
|
||||||
|
// that the Frame must be to contain all of its children. If the Frame
|
||||||
|
// was configured with an explicit Size, the Frame will be that Size,
|
||||||
|
// but we still calculate how much space the widgets _actually_ take
|
||||||
|
// so we can expand them to fill remaining space in fixed size widgets.
|
||||||
|
maxWidth int32
|
||||||
|
maxHeight int32
|
||||||
|
visited = []packedWidget{}
|
||||||
|
expanded = []packedWidget{}
|
||||||
|
)
|
||||||
|
|
||||||
|
// Iterate through all anchored directions and compute how much space to
|
||||||
|
// reserve to contain all of their widgets.
|
||||||
|
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(2) // parent + child BoxThickness(1) = 2
|
||||||
|
} else if anchor == E {
|
||||||
|
x = frameSize.W
|
||||||
|
xDirection = -1 - w.BoxThickness(2)
|
||||||
|
}
|
||||||
|
|
||||||
|
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 xStep+size.W+(pack.PadX*2) > maxWidth {
|
||||||
|
maxWidth = xStep + size.W + (pack.PadX * 2)
|
||||||
|
}
|
||||||
|
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)
|
||||||
|
if pack.Expand {
|
||||||
|
expanded = append(expanded, packedWidget)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we have extra space in the Frame and any expanding widgets, let the
|
||||||
|
// expanding widgets grow and share the remaining space.
|
||||||
|
computedSize := render.NewRect(maxWidth, maxHeight)
|
||||||
|
if len(expanded) > 0 && !frameSize.IsZero() && frameSize.Bigger(computedSize) {
|
||||||
|
// Divy up the size available.
|
||||||
|
growBy := render.Rect{
|
||||||
|
W: ((frameSize.W - computedSize.W) / int32(len(expanded))) - w.BoxThickness(2),
|
||||||
|
H: ((frameSize.H - computedSize.H) / int32(len(expanded))) - w.BoxThickness(2),
|
||||||
|
}
|
||||||
|
for _, pw := range expanded {
|
||||||
|
pw.widget.ResizeBy(growBy)
|
||||||
|
pw.widget.Compute(e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we're not using a fixed Frame size, use the dynamically computed one.
|
||||||
|
if !w.FixedSize() {
|
||||||
|
frameSize = render.NewRect(maxWidth, maxHeight)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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()
|
||||||
|
resize = size
|
||||||
|
resized bool
|
||||||
|
moved bool
|
||||||
|
)
|
||||||
|
|
||||||
|
if pack.Anchor.IsNorth() || pack.Anchor.IsSouth() {
|
||||||
|
if pack.FillX && resize.W < frameSize.W {
|
||||||
|
resize.W = frameSize.W - w.BoxThickness(2)
|
||||||
|
resized = true
|
||||||
|
}
|
||||||
|
if resize.W < frameSize.W-w.BoxThickness(4) {
|
||||||
|
if pack.Anchor.IsCenter() {
|
||||||
|
point.X = (frameSize.W / 2) - (resize.W / 2)
|
||||||
|
} else if pack.Anchor.IsWest() {
|
||||||
|
point.X = pack.PadX
|
||||||
|
} else if pack.Anchor.IsEast() {
|
||||||
|
point.X = frameSize.W - resize.W - pack.PadX
|
||||||
|
}
|
||||||
|
|
||||||
|
moved = true
|
||||||
|
}
|
||||||
|
} else if pack.Anchor.IsWest() || pack.Anchor.IsEast() {
|
||||||
|
if pack.FillY && resize.H < frameSize.H {
|
||||||
|
resize.H = frameSize.H - w.BoxThickness(2) // BoxThickness(2) for parent + child
|
||||||
|
// point.Y -= (w.BoxThickness(4) + child.BoxThickness(2))
|
||||||
|
moved = true
|
||||||
|
resized = true
|
||||||
|
}
|
||||||
|
|
||||||
|
// Vertically align the widgets.
|
||||||
|
if resize.H < frameSize.H {
|
||||||
|
if pack.Anchor.IsMiddle() {
|
||||||
|
point.Y = (frameSize.H / 2) - (resize.H / 2)
|
||||||
|
} else if pack.Anchor.IsNorth() {
|
||||||
|
point.Y = pack.PadY - w.BoxThickness(4)
|
||||||
|
} else if pack.Anchor.IsSouth() {
|
||||||
|
point.Y = frameSize.H - resize.H - pack.PadY
|
||||||
|
}
|
||||||
|
moved = true
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
log.Error("unsupported pack.Anchor")
|
||||||
|
}
|
||||||
|
|
||||||
|
if resized && size != resize {
|
||||||
|
child.Resize(resize)
|
||||||
|
child.Compute(e)
|
||||||
|
}
|
||||||
|
if moved {
|
||||||
|
child.MoveTo(point)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !w.FixedSize() {
|
||||||
|
w.Resize(frameSize)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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 its allocated space to better fill the parent.
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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
|
||||||
|
|
||||||
|
// Fill: true implies both directions.
|
||||||
|
if C.Fill {
|
||||||
|
C.FillX = true
|
||||||
|
C.FillY = true
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
)
|
Loading…
Reference in New Issue
Block a user