UI: Finish Frame Packing

* Frame.Pack() now supports Fill and Expand and works like Tk.
* The GUITest Scene now draws a large window with two fixed side panels,
  an expanding body panel, and a fixed footer with buttons. The panels
  are filled with other buttons and widgets showing off the Frame
  packing.
This commit is contained in:
Noah 2018-08-01 18:52:52 -07:00
parent 2e36d9ca85
commit cbef5a46cb
9 changed files with 307 additions and 124 deletions

View File

@ -37,6 +37,12 @@ func (c Command) Run(d *Doodle) error {
return c.Quit() return c.Quit()
case "help": case "help":
return c.Help(d) return c.Help(d)
case "reload":
d.Goto(d.Scene)
return nil
case "guitest":
d.Goto(&GUITestScene{})
return nil
case "eval": case "eval":
case "$": case "$":
out, err := d.shell.js.Run(c.ArgsLiteral) out, err := d.shell.js.Run(c.ArgsLiteral)

View File

@ -1,6 +1,8 @@
package doodle package doodle
import ( import (
"fmt"
"git.kirsle.net/apps/doodle/events" "git.kirsle.net/apps/doodle/events"
"git.kirsle.net/apps/doodle/render" "git.kirsle.net/apps/doodle/render"
"git.kirsle.net/apps/doodle/ui" "git.kirsle.net/apps/doodle/ui"
@ -9,8 +11,10 @@ import (
// GUITestScene implements the main menu of Doodle. // GUITestScene implements the main menu of Doodle.
type GUITestScene struct { type GUITestScene struct {
Supervisor *ui.Supervisor Supervisor *ui.Supervisor
frame *ui.Frame
window *ui.Frame // Private widgets.
Frame *ui.Frame
Window *ui.Frame
} }
// Name of the scene. // Name of the scene.
@ -22,64 +26,151 @@ func (s *GUITestScene) Name() string {
func (s *GUITestScene) Setup(d *Doodle) error { func (s *GUITestScene) Setup(d *Doodle) error {
s.Supervisor = ui.NewSupervisor() s.Supervisor = ui.NewSupervisor()
window := ui.NewFrame() window := ui.NewFrame("window")
s.window = window s.Window = window
window.Configure(ui.Config{ window.Configure(ui.Config{
Width: 400, Width: 750,
Height: 400, Height: 450,
Background: render.Grey, Background: render.Grey,
BorderStyle: ui.BorderRaised, BorderStyle: ui.BorderRaised,
BorderSize: 2, BorderSize: 2,
}) })
// Title Bar
titleBar := ui.NewLabel(render.Text{ titleBar := ui.NewLabel(render.Text{
Text: "Alert", Text: "Widget Toolkit",
Size: 12, Size: 12,
Color: render.White, Color: render.White,
Stroke: render.Black, Stroke: render.DarkBlue,
}) })
titleBar.Configure(ui.Config{ titleBar.Configure(ui.Config{
Background: render.Blue, Background: render.Blue,
OutlineSize: 1,
OutlineColor: render.Black,
}) })
window.Pack(titleBar, ui.Pack{ window.Pack(titleBar, ui.Pack{
Anchor: ui.N, Anchor: ui.N,
FillX: true, Fill: true,
}) })
msgFrame := ui.NewFrame() // Window Body
msgFrame.Configure(ui.Config{ body := ui.NewFrame("Window Body")
Background: render.Grey, body.Configure(ui.Config{
BorderStyle: ui.BorderRaised, Background: render.Yellow,
BorderSize: 1,
}) })
window.Pack(msgFrame, ui.Pack{ window.Pack(body, ui.Pack{
Anchor: ui.N,
Expand: true,
})
// Left Frame
leftFrame := ui.NewFrame("Left Frame")
leftFrame.Configure(ui.Config{
Background: render.Grey,
BorderStyle: ui.BorderSolid,
BorderSize: 4,
Width: 100,
})
body.Pack(leftFrame, ui.Pack{
Anchor: ui.W,
FillY: true,
})
// Some left frame buttons.
for _, label := range []string{"New", "Edit", "Play", "Help"} {
btn := ui.NewButton("dummy "+label, ui.NewLabel(render.Text{
Text: label,
Size: 12,
Color: render.Black,
}))
s.Supervisor.Add(btn)
leftFrame.Pack(btn, ui.Pack{
Anchor: ui.N, Anchor: ui.N,
Fill: true, Fill: true,
Padding: 4, PadY: 2,
})
}
// Main Frame
frame := ui.NewFrame("Main Frame")
frame.Configure(ui.Config{
Background: render.White,
BorderSize: 0,
})
body.Pack(frame, ui.Pack{
Anchor: ui.W,
Expand: true,
}) })
btnFrame := ui.NewFrame() // Right Frame
btnFrame.Configure(ui.Config{ rightFrame := ui.NewFrame("Right Frame")
Background: render.DarkRed, rightFrame.Configure(ui.Config{
Background: render.SkyBlue,
BorderStyle: ui.BorderSunken,
BorderSize: 2,
Width: 80,
}) })
window.Pack(btnFrame, ui.Pack{ body.Pack(rightFrame, ui.Pack{
Anchor: ui.W,
Fill: true,
})
// A grid of buttons.
for row := 0; row < 3; row++ {
rowFrame := ui.NewFrame(fmt.Sprintf("Row%d", row))
for col := 0; col < 3; col++ {
btn := ui.NewButton("X",
ui.NewFrame(fmt.Sprintf("Col%d", col)),
)
btn.Configure(ui.Config{
Height: 20,
BorderStyle: ui.BorderRaised,
})
rowFrame.Pack(btn, ui.Pack{
Anchor: ui.W,
Expand: true,
})
s.Supervisor.Add(btn)
}
rightFrame.Pack(rowFrame, ui.Pack{
Anchor: ui.N, Anchor: ui.N,
Padding: 4, Fill: true,
}) })
}
msg := ui.NewLabel(render.Text{ frame.Pack(ui.NewLabel(render.Text{
Text: "Hello World!", Text: "Hello World!",
Size: 14, Size: 14,
Color: render.Black, Color: render.Black,
}) }), ui.Pack{
msgFrame.Pack(msg, ui.Pack{
Anchor: ui.NW, Anchor: ui.NW,
Padding: 2, Padding: 2,
}) })
frame.Pack(ui.NewLabel(render.Text{
Text: "Like Tk!",
Size: 16,
Color: render.Red,
}), ui.Pack{
Anchor: ui.SE,
Padding: 8,
})
frame.Pack(ui.NewLabel(render.Text{
Text: "Frame widget for pack layouts",
Size: 14,
Color: render.Blue,
}), ui.Pack{
Anchor: ui.SE,
Padding: 8,
})
button1 := ui.NewButton(*ui.NewLabel(render.Text{ // Buttom Frame
btnFrame := ui.NewFrame("btnFrame")
btnFrame.Configure(ui.Config{
Background: render.Grey,
})
window.Pack(btnFrame, ui.Pack{
Anchor: ui.N,
})
button1 := ui.NewButton("Button1", ui.NewLabel(render.Text{
Text: "New Map", Text: "New Map",
Size: 14, Size: 14,
Color: render.Black, Color: render.Black,
@ -91,7 +182,7 @@ func (s *GUITestScene) Setup(d *Doodle) error {
log.Info("Button1 bg: %s", button1.Background()) log.Info("Button1 bg: %s", button1.Background())
button2 := ui.NewButton(*ui.NewLabel(render.Text{ button2 := ui.NewButton("Button2", ui.NewLabel(render.Text{
Text: "New Map", Text: "New Map",
Size: 14, Size: 14,
Color: render.Black, Color: render.Black,
@ -102,12 +193,10 @@ func (s *GUITestScene) Setup(d *Doodle) error {
btnFrame.Pack(button1, ui.Pack{ btnFrame.Pack(button1, ui.Pack{
Anchor: align, Anchor: align,
Padding: 20, Padding: 20,
Fill: true,
}) })
btnFrame.Pack(button2, ui.Pack{ btnFrame.Pack(button2, ui.Pack{
Anchor: align, Anchor: align,
Padding: 20, Padding: 20,
Fill: true,
}) })
s.Supervisor.Add(button1) s.Supervisor.Add(button1)
@ -141,15 +230,17 @@ func (s *GUITestScene) Draw(d *Doodle) error {
}) })
label.Present(d.Engine) label.Present(d.Engine)
s.window.Compute(d.Engine) s.Window.Compute(d.Engine)
s.window.MoveTo(render.Point{ s.Window.MoveTo(render.Point{
X: (d.width / 2) - (s.window.Size().W / 2), X: (d.width / 2) - (s.Window.Size().W / 2),
Y: 100, Y: 100,
}) })
s.window.Present(d.Engine) s.Window.Present(d.Engine)
s.Supervisor.Present(d.Engine) s.Supervisor.Present(d.Engine)
// os.Exit(1)
return nil return nil
} }

View File

@ -21,7 +21,7 @@ func (s *MainScene) Name() string {
func (s *MainScene) Setup(d *Doodle) error { func (s *MainScene) Setup(d *Doodle) error {
s.Supervisor = ui.NewSupervisor() s.Supervisor = ui.NewSupervisor()
frame := ui.NewFrame() frame := ui.NewFrame("frame")
s.frame = frame s.frame = frame
s.frame.Configure(ui.Config{ s.frame.Configure(ui.Config{
// Width: 400, // Width: 400,
@ -32,7 +32,7 @@ func (s *MainScene) Setup(d *Doodle) error {
BorderColor: render.Blue, BorderColor: render.Blue,
}) })
button1 := ui.NewButton(*ui.NewLabel(render.Text{ button1 := ui.NewButton("Button1", ui.NewLabel(render.Text{
Text: "New Map", Text: "New Map",
Size: 14, Size: 14,
Color: render.Black, Color: render.Black,
@ -46,7 +46,7 @@ func (s *MainScene) Setup(d *Doodle) error {
d.NewMap() d.NewMap()
}) })
button2 := ui.NewButton(*ui.NewLabel(render.Text{ button2 := ui.NewButton("Button2", ui.NewLabel(render.Text{
Text: "New Map", Text: "New Map",
Size: 14, Size: 14,
Color: render.Black, Color: render.Black,

View File

@ -125,12 +125,35 @@ type Rect struct {
H int32 H int32
} }
// NewRect creates a rectangle of size `width` and `height`. The X,Y values
// are initialized to zero.
func NewRect(width, height int32) Rect {
return Rect{
W: width,
H: height,
}
}
func (r Rect) String() string { func (r Rect) String() string {
return fmt.Sprintf("Rect<%d,%d,%d,%d>", return fmt.Sprintf("Rect<%d,%d,%d,%d>",
r.X, r.Y, r.W, r.H, r.X, r.Y, r.W, r.H,
) )
} }
// Bigger returns if the given rect is larger than the current one.
func (r Rect) Bigger(other Rect) bool {
// TODO: don't know why this is !
return !(other.X < r.X || // Lefter
other.Y < r.Y || // Higher
other.W > r.W || // Wider
other.H > r.H) // Taller
}
// IsZero returns if the Rect is uninitialized.
func (r Rect) IsZero() bool {
return r.X == 0 && r.Y == 0 && r.W == 0 && r.H == 0
}
// Text holds information for drawing text. // Text holds information for drawing text.
type Text struct { type Text struct {
Text string Text string

View File

@ -65,6 +65,7 @@ func NewShell(d *Doodle) Shell {
"log": log, "log": log,
"RGBA": render.RGBA, "RGBA": render.RGBA,
"Point": render.NewPoint, "Point": render.NewPoint,
"Rect": render.NewRect,
} }
for name, v := range bindings { for name, v := range bindings {
err := s.js.Set(name, v) err := s.js.Set(name, v)

View File

@ -1,6 +1,7 @@
package ui package ui
import ( import (
"errors"
"fmt" "fmt"
"git.kirsle.net/apps/doodle/render" "git.kirsle.net/apps/doodle/render"
@ -10,7 +11,7 @@ import (
// Button is a clickable button. // Button is a clickable button.
type Button struct { type Button struct {
BaseWidget BaseWidget
Label Label child Widget
// Private options. // Private options.
hovering bool hovering bool
@ -18,10 +19,13 @@ type Button struct {
} }
// NewButton creates a new Button. // NewButton creates a new Button.
func NewButton(label Label) *Button { func NewButton(name string, child Widget) *Button {
w := &Button{ w := &Button{
Label: label, child: child,
} }
w.IDFunc(func() string {
return fmt.Sprintf("Button<%s>", name)
})
w.Configure(Config{ w.Configure(Config{
Padding: 4, Padding: 4,
@ -50,33 +54,40 @@ func NewButton(label Label) *Button {
w.SetBorderStyle(BorderRaised) w.SetBorderStyle(BorderRaised)
}) })
w.IDFunc(func() string {
return fmt.Sprintf("Button<%s>", w.Label.Text.Text)
})
return w return w
} }
// SetText quickly changes the text of the label.
func (w *Button) SetText(text string) {
w.Label.Text.Text = text
}
// Compute the size of the button. // Compute the size of the button.
func (w *Button) Compute(e render.Engine) { func (w *Button) Compute(e render.Engine) {
// Compute the size of the inner widget first. // Compute the size of the inner widget first.
w.Label.Compute(e) w.child.Compute(e)
size := w.Label.Size()
// Auto-resize only if we haven't been given a fixed size.
if !w.FixedSize() {
size := w.child.Size()
w.Resize(render.Rect{ w.Resize(render.Rect{
W: size.W + w.BoxThickness(2), W: size.W + w.BoxThickness(2),
H: size.H + w.BoxThickness(2), H: size.H + w.BoxThickness(2),
}) })
}
}
// SetText conveniently sets the button text, for Label children only.
func (w *Button) SetText(text string) error {
if label, ok := w.child.(*Label); ok {
label.Text.Text = text
}
return errors.New("child is not a Label widget")
} }
// Present the button. // Present the button.
func (w *Button) Present(e render.Engine) { func (w *Button) Present(e render.Engine) {
w.Compute(e) w.Compute(e)
P := w.Point() var (
P = w.Point()
S = w.Size()
ChildSize = w.child.Size()
)
// Draw the widget's border and everything. // Draw the widget's border and everything.
w.DrawBox(e) w.DrawBox(e)
@ -87,10 +98,20 @@ func (w *Button) Present(e render.Engine) {
clickOffset++ clickOffset++
} }
// Draw the text label inside. // Where to place the child widget.
w.Label.MoveTo(render.Point{ moveTo := render.Point{
X: P.X + w.BoxThickness(1) + clickOffset, X: P.X + w.BoxThickness(1) + clickOffset,
Y: P.Y + w.BoxThickness(1) + clickOffset, Y: P.Y + w.BoxThickness(1) + clickOffset,
}) }
w.Label.Present(e)
// If we're bigger than we need to be, center the child widget.
if S.Bigger(ChildSize) {
moveTo.X = P.X + (S.W / 2) - (ChildSize.W / 2)
}
_ = S
_ = ChildSize
// Draw the text label inside.
w.child.MoveTo(moveTo)
w.child.Present(e)
} }

View File

@ -2,47 +2,52 @@ package ui
import ( import (
"fmt" "fmt"
"time"
"git.kirsle.net/apps/doodle/render" "git.kirsle.net/apps/doodle/render"
) )
// Frame is a widget that contains other widgets. // Frame is a widget that contains other widgets.
type Frame struct { type Frame struct {
Name string
BaseWidget BaseWidget
packs map[Anchor][]packedWidget packs map[Anchor][]packedWidget
widgets []Widget widgets []Widget
} }
// NewFrame creates a new Frame. // NewFrame creates a new Frame.
func NewFrame() *Frame { func NewFrame(name string) *Frame {
return &Frame{ w := &Frame{
Name: name,
packs: map[Anchor][]packedWidget{}, packs: map[Anchor][]packedWidget{},
widgets: []Widget{}, widgets: []Widget{},
} }
} w.IDFunc(func() string {
return fmt.Sprintf("Frame<%s; %d widgets>",
func (w *Frame) String() string { name,
return fmt.Sprintf("Frame<%d widgets>",
len(w.widgets), len(w.widgets),
) )
})
return w
} }
// 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 ( var (
frameSize = w.Size() 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 maxWidth int32
maxHeight int32 maxHeight int32
visited = []packedWidget{} visited = []packedWidget{}
expanded = []packedWidget{}
) )
// Initialize the dimensions? // Iterate through all anchored directions and compute how much space to
if w.FixedSize() { // reserve to contain all of their widgets.
maxWidth = frameSize.W
maxHeight = frameSize.H
}
for anchor := AnchorMin; anchor <= AnchorMax; anchor++ { for anchor := AnchorMin; anchor <= AnchorMax; anchor++ {
if _, ok := w.packs[anchor]; !ok { if _, ok := w.packs[anchor]; !ok {
continue continue
@ -57,10 +62,10 @@ func (w *Frame) Compute(e render.Engine) {
if anchor.IsSouth() { if anchor.IsSouth() {
y = frameSize.H y = frameSize.H
yDirection = -1 - w.BoxThickness(1) yDirection = -1 - w.BoxThickness(2) // parent + child BoxThickness(1) = 2
} else if anchor == E { } else if anchor == E {
x = frameSize.W x = frameSize.W
xDirection = -1 xDirection = -1 - w.BoxThickness(2)
} }
for _, packedWidget := range w.packs[anchor] { for _, packedWidget := range w.packs[anchor] {
@ -74,25 +79,12 @@ func (w *Frame) Compute(e render.Engine) {
xStep = x * xDirection xStep = x * xDirection
) )
if !w.FixedSize() {
log.Warn("not fixed")
if xStep+size.W+(pack.PadX*2) > maxWidth { if xStep+size.W+(pack.PadX*2) > maxWidth {
var old = maxWidth
maxWidth = xStep + size.W + (pack.PadX * 2) 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 { if yStep+size.H+(pack.PadY*2) > maxHeight {
maxHeight = yStep + size.H + (pack.PadY * 2) maxHeight = yStep + size.H + (pack.PadY * 2)
} }
}
if anchor.IsSouth() { if anchor.IsSouth() {
y -= size.H + (pack.PadY * 2) y -= size.H + (pack.PadY * 2)
@ -114,8 +106,31 @@ func (w *Frame) Compute(e render.Engine) {
} }
visited = append(visited, packedWidget) 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 // Rescan all the widgets in this anchor to re-center them
// in their space. // in their space.
@ -125,38 +140,43 @@ func (w *Frame) Compute(e render.Engine) {
pack = pw.pack pack = pw.pack
point = child.Point() point = child.Point()
size = child.Size() size = child.Size()
resize = size
resized bool resized bool
moved bool moved bool
) )
if pack.Anchor.IsNorth() || pack.Anchor.IsSouth() { if pack.Anchor.IsNorth() || pack.Anchor.IsSouth() {
if pack.FillX && size.W < maxWidth { if pack.FillX && resize.W < frameSize.W {
size.W = maxWidth - w.BoxThickness(2) resize.W = frameSize.W - w.BoxThickness(2)
resized = true resized = true
} }
if size.W < maxWidth { if resize.W < frameSize.W-w.BoxThickness(4) {
if pack.Anchor.IsCenter() { if pack.Anchor.IsCenter() {
point.X = (maxWidth / 2) - (size.W / 2) point.X = (frameSize.W / 2) - (resize.W / 2)
} else if pack.Anchor.IsWest() { } else if pack.Anchor.IsWest() {
point.X = pack.PadX point.X = pack.PadX
} else if pack.Anchor.IsEast() { } else if pack.Anchor.IsEast() {
point.X = maxWidth - size.W - pack.PadX point.X = frameSize.W - resize.W - pack.PadX
} }
moved = true moved = true
} }
} else if pack.Anchor.IsWest() || pack.Anchor.IsEast() { } else if pack.Anchor.IsWest() || pack.Anchor.IsEast() {
if pack.FillY && size.H < maxHeight { if pack.FillY && resize.H < frameSize.H {
size.H = maxHeight - w.BoxThickness(2) resize.H = frameSize.H - w.BoxThickness(2) // BoxThickness(2) for parent + child
// point.Y -= (w.BoxThickness(4) + child.BoxThickness(2))
moved = true
resized = true resized = true
} }
if size.H < maxHeight {
// Vertically align the widgets.
if resize.H < frameSize.H {
if pack.Anchor.IsMiddle() { if pack.Anchor.IsMiddle() {
point.Y = (maxHeight / 2) - (size.H / 2) point.Y = (frameSize.H / 2) - (resize.H / 2)
} else if pack.Anchor.IsNorth() { } else if pack.Anchor.IsNorth() {
point.Y = pack.PadY point.Y = pack.PadY - w.BoxThickness(4)
} else if pack.Anchor.IsSouth() { } else if pack.Anchor.IsSouth() {
point.Y = maxHeight - size.H - pack.PadY point.Y = frameSize.H - resize.H - pack.PadY
} }
moved = true moved = true
} }
@ -164,8 +184,9 @@ func (w *Frame) Compute(e render.Engine) {
log.Error("unsupported pack.Anchor") log.Error("unsupported pack.Anchor")
} }
if resized { if resized && size != resize {
child.Resize(size) child.Resize(resize)
child.Compute(e)
} }
if moved { if moved {
child.MoveTo(point) child.MoveTo(point)
@ -173,10 +194,7 @@ func (w *Frame) Compute(e render.Engine) {
} }
if !w.FixedSize() { if !w.FixedSize() {
w.Resize(render.Rect{ w.Resize(frameSize)
W: maxWidth + w.BoxThickness(2),
H: maxHeight + w.BoxThickness(2),
})
} }
} }
@ -224,7 +242,7 @@ type Pack struct {
Padding int32 // Equal padding on X and Y. Padding int32 // Equal padding on X and Y.
PadX int32 PadX int32
PadY int32 PadY int32
// Expand bool // Widget should grow to fill any remaining space. Expand bool // Widget should grow its allocated space to better fill the parent.
} }
// Anchor is a cardinal direction. // Anchor is a cardinal direction.
@ -299,6 +317,12 @@ func (w *Frame) Pack(child Widget, config ...Pack) {
C.PadX += C.Padding C.PadX += C.Padding
C.PadY += 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{ w.packs[C.Anchor] = append(w.packs[C.Anchor], packedWidget{
widget: child, widget: child,
pack: C, pack: C,

View File

@ -31,10 +31,14 @@ func NewLabel(t render.Text) *Label {
// Compute the size of the label widget. // Compute the size of the label widget.
func (w *Label) Compute(e render.Engine) { func (w *Label) Compute(e render.Engine) {
rect, _ := e.ComputeTextRect(w.Text) rect, _ := e.ComputeTextRect(w.Text)
w.Resize(render.Rect{
if !w.FixedSize() {
w.resizeAuto(render.Rect{
W: rect.W + w.Padding(), W: rect.W + w.Padding(),
H: rect.H + w.Padding(), H: rect.H + w.Padding(),
}) })
}
w.MoveTo(render.Point{ w.MoveTo(render.Point{
X: rect.X + w.BoxThickness(1), X: rect.X + w.BoxThickness(1),
Y: rect.Y + w.BoxThickness(1), Y: rect.Y + w.BoxThickness(1),

View File

@ -10,6 +10,7 @@ type BorderStyle string
// Styles for a widget border. // Styles for a widget border.
const ( const (
BorderNone BorderStyle = ""
BorderSolid BorderStyle = "solid" BorderSolid BorderStyle = "solid"
BorderRaised = "raised" BorderRaised = "raised"
BorderSunken = "sunken" BorderSunken = "sunken"
@ -26,6 +27,7 @@ type Widget interface {
Size() render.Rect // Return the Width and Height of the widget. Size() render.Rect // Return the Width and Height of the widget.
FixedSize() bool // Return whether the size is fixed (true) or automatic (false) FixedSize() bool // Return whether the size is fixed (true) or automatic (false)
Resize(render.Rect) Resize(render.Rect)
ResizeBy(render.Rect)
Handle(string, func(render.Point)) Handle(string, func(render.Point))
Event(string, render.Point) // called internally to trigger an event Event(string, render.Point) // called internally to trigger an event
@ -130,11 +132,15 @@ func (w *BaseWidget) String() string {
// Configure the base widget with all the common properties at once. Any // Configure the base widget with all the common properties at once. Any
// property left as the zero value will not update the widget. // property left as the zero value will not update the widget.
func (w *BaseWidget) Configure(c Config) { func (w *BaseWidget) Configure(c Config) {
if c.Width != 0 && c.Height != 0 { if c.Width != 0 || c.Height != 0 {
w.fixedSize = !c.AutoResize w.fixedSize = !c.AutoResize
if c.Width != 0 {
w.width = c.Width w.width = c.Width
}
if c.Height != 0 {
w.height = c.Height w.height = c.Height
} }
}
if c.Padding != 0 { if c.Padding != 0 {
w.padding = c.Padding w.padding = c.Padding
@ -155,7 +161,7 @@ func (w *BaseWidget) Configure(c Config) {
if c.BorderSize != 0 { if c.BorderSize != 0 {
w.borderSize = c.BorderSize w.borderSize = c.BorderSize
} }
if c.BorderStyle != BorderSolid { if c.BorderStyle != BorderNone {
w.borderStyle = c.BorderStyle w.borderStyle = c.BorderStyle
} }
if c.OutlineSize != 0 { if c.OutlineSize != 0 {
@ -201,6 +207,13 @@ func (w *BaseWidget) Resize(v render.Rect) {
w.height = v.H w.height = v.H
} }
// ResizeBy resizes by a relative amount.
func (w *BaseWidget) ResizeBy(v render.Rect) {
w.fixedSize = true
w.width += v.W
w.height += v.H
}
// resizeAuto sets the size of the widget but doesn't set the fixedSize flag. // resizeAuto sets the size of the widget but doesn't set the fixedSize flag.
func (w *BaseWidget) resizeAuto(v render.Rect) { func (w *BaseWidget) resizeAuto(v render.Rect) {
w.width = v.W w.width = v.W