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:
parent
2e36d9ca85
commit
cbef5a46cb
|
@ -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)
|
||||||
|
|
159
guitest_scene.go
159
guitest_scene.go
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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
|
||||||
|
|
1
shell.go
1
shell.go
|
@ -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)
|
||||||
|
|
59
ui/button.go
59
ui/button.go
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
116
ui/frame.go
116
ui/frame.go
|
@ -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,
|
||||||
|
|
|
@ -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),
|
||||||
|
|
17
ui/widget.go
17
ui/widget.go
|
@ -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
|
||||||
|
|
Loading…
Reference in New Issue
Block a user