Add StatusBar to Editor Mode, Iterate on UI Toolkit

* Added `BoxSize()` to Widget that reports the full box size including
  borders and margin.
* The Frame uses the `BoxSize()` of widgets to position them.
  Reintroduces some padding issues (boxes on the GUI Test stick out of
  bounds a bit) but is on the right track.
* Renamed `Padding` to `Margin` on the Widget object, since the Margin
  is taken into consideration along with Outline and Border in computing
  the widget's BoxSize.
* Restructured the Label widget to take a Text or TextVariable property
  and the font settings (render.Text) are in a new `Font` property.
This commit is contained in:
Noah 2018-08-05 12:54:57 -07:00
parent 316456ef03
commit 8624a28ea9
14 changed files with 355 additions and 196 deletions

View File

@ -1,11 +1,13 @@
package balance package balance
import "git.kirsle.net/apps/doodle/render" import (
"git.kirsle.net/apps/doodle/render"
)
// Shell related variables. // Shell related variables.
var ( var (
// TODO: why not renders transparent // TODO: why not renders transparent
ShellBackgroundColor = render.Color{0, 10, 20, 128} ShellBackgroundColor = render.RGBA(0, 10, 20, 128)
ShellForegroundColor = render.White ShellForegroundColor = render.White
ShellPadding int32 = 8 ShellPadding int32 = 8
ShellFontSize = 16 ShellFontSize = 16
@ -15,3 +17,10 @@ var (
// Ticks that a flashed message persists for. // Ticks that a flashed message persists for.
FlashTTL uint64 = 400 FlashTTL uint64 = 400
) )
// StatusFont is the font for the status bar.
var StatusFont = render.Text{
Size: 12,
Padding: 4,
Color: render.Black,
}

View File

@ -63,7 +63,8 @@ func (d *Doodle) Run() error {
// Set up the default scene. // Set up the default scene.
if d.Scene == nil { if d.Scene == nil {
d.Goto(&GUITestScene{}) // d.Goto(&GUITestScene{})
d.NewMap()
// d.Goto(&MainScene{}) // d.Goto(&MainScene{})
} }

View File

@ -20,6 +20,8 @@ type EditorScene struct {
Filename string Filename string
Canvas render.Grid Canvas render.Grid
UI *EditorUI
// History of all the pixels placed by the user. // History of all the pixels placed by the user.
pixelHistory []level.Pixel pixelHistory []level.Pixel
lastPixel *level.Pixel // last pixel placed while mouse down and dragging lastPixel *level.Pixel // last pixel placed while mouse down and dragging
@ -56,6 +58,8 @@ func (s *EditorScene) Setup(d *Doodle) error {
s.Canvas = nil s.Canvas = nil
} }
s.UI = NewEditorUI(d)
d.Flash("Editor Mode. Press 'P' to play this map.") d.Flash("Editor Mode. Press 'P' to play this map.")
if s.pixelHistory == nil { if s.pixelHistory == nil {
@ -72,6 +76,8 @@ func (s *EditorScene) Setup(d *Doodle) error {
// Loop the editor scene. // Loop the editor scene.
func (s *EditorScene) Loop(d *Doodle, ev *events.State) error { func (s *EditorScene) Loop(d *Doodle, ev *events.State) error {
s.UI.Loop(ev)
// Taking a screenshot? // Taking a screenshot?
if ev.ScreenshotKey.Pressed() { if ev.ScreenshotKey.Pressed() {
log.Info("Taking a screenshot") log.Info("Taking a screenshot")
@ -131,6 +137,7 @@ func (s *EditorScene) Loop(d *Doodle, ev *events.State) error {
// Draw the current frame. // Draw the current frame.
func (s *EditorScene) Draw(d *Doodle) error { func (s *EditorScene) Draw(d *Doodle) error {
s.canvas.Draw(d.Engine) s.canvas.Draw(d.Engine)
s.UI.Present(d.Engine)
return nil return nil
} }

116
editor_ui.go Normal file
View File

@ -0,0 +1,116 @@
package doodle
import (
"fmt"
"git.kirsle.net/apps/doodle/balance"
"git.kirsle.net/apps/doodle/events"
"git.kirsle.net/apps/doodle/render"
"git.kirsle.net/apps/doodle/ui"
)
// EditorUI manages the user interface for the Editor Scene.
type EditorUI struct {
d *Doodle
// Variables
StatusMouseText string
// Widgets
Supervisor *ui.Supervisor
StatusBar *ui.Frame
}
// NewEditorUI initializes the Editor UI.
func NewEditorUI(d *Doodle) *EditorUI {
u := &EditorUI{
d: d,
Supervisor: ui.NewSupervisor(),
StatusMouseText: ".",
}
u.StatusBar = u.SetupStatusBar(d)
return u
}
// Loop to process events and update the UI.
func (u *EditorUI) Loop(ev *events.State) {
u.StatusMouseText = fmt.Sprintf("Mouse: (%d,%d)",
ev.CursorX.Now,
ev.CursorY.Now,
)
u.StatusBar.Compute(u.d.Engine)
u.Supervisor.Loop(ev)
}
// Present the UI to the screen.
func (u *EditorUI) Present(e render.Engine) {
u.StatusBar.Present(e, u.StatusBar.Point())
}
// SetupStatusBar sets up the status bar widget along the bottom of the window.
func (u *EditorUI) SetupStatusBar(d *Doodle) *ui.Frame {
frame := ui.NewFrame("Status Bar")
frame.Configure(ui.Config{
BorderStyle: ui.BorderRaised,
Background: render.Grey,
BorderSize: 2,
Width: d.width,
})
cursorLabel := ui.NewLabel(ui.Label{
TextVariable: &u.StatusMouseText,
Font: balance.StatusFont,
})
cursorLabel.Configure(ui.Config{
Background: render.Grey,
BorderStyle: ui.BorderSunken,
BorderColor: render.Grey,
BorderSize: 1,
})
cursorLabel.Compute(d.Engine)
frame.Pack(cursorLabel, ui.Pack{
Anchor: ui.W,
})
filenameLabel := ui.NewLabel(ui.Label{
Text: "Filename: untitled.map",
Font: balance.StatusFont,
})
filenameLabel.Configure(ui.Config{
Background: render.Grey,
BorderStyle: ui.BorderSunken,
BorderColor: render.Grey,
BorderSize: 1,
})
filenameLabel.Compute(d.Engine)
frame.Pack(filenameLabel, ui.Pack{
Anchor: ui.W,
})
extraLabel := ui.NewLabel(ui.Label{
Text: "blah",
Font: balance.StatusFont,
})
extraLabel.Configure(ui.Config{
Background: render.Grey,
BorderStyle: ui.BorderSunken,
BorderColor: render.Grey,
BorderSize: 1,
})
extraLabel.Compute(d.Engine)
frame.Pack(extraLabel, ui.Pack{
Anchor: ui.E,
})
frame.Resize(render.Rect{
W: d.width,
H: cursorLabel.BoxSize().H + frame.BoxThickness(1),
})
frame.Compute(d.Engine)
frame.MoveTo(render.Point{
X: 0,
Y: d.height - frame.Size().H,
})
return frame
}

View File

@ -3,6 +3,7 @@ package doodle
import ( import (
"fmt" "fmt"
"git.kirsle.net/apps/doodle/balance"
"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"
@ -37,11 +38,13 @@ func (s *GUITestScene) Setup(d *Doodle) error {
}) })
// Title Bar // Title Bar
titleBar := ui.NewLabel(render.Text{ titleBar := ui.NewLabel(ui.Label{
Text: "Widget Toolkit", Text: "Widget Toolkit",
Font: render.Text{
Size: 12, Size: 12,
Color: render.White, Color: render.White,
Stroke: render.DarkBlue, Stroke: render.DarkBlue,
},
}) })
titleBar.Configure(ui.Config{ titleBar.Configure(ui.Config{
Background: render.Blue, Background: render.Blue,
@ -76,15 +79,17 @@ func (s *GUITestScene) Setup(d *Doodle) error {
// Some left frame buttons. // Some left frame buttons.
for _, label := range []string{"New", "Edit", "Play", "Help"} { for _, label := range []string{"New", "Edit", "Play", "Help"} {
btn := ui.NewButton("dummy "+label, ui.NewLabel(render.Text{ btn := ui.NewButton("dummy "+label, ui.NewLabel(ui.Label{
Text: label, Text: label,
Size: 12, Font: balance.StatusFont,
Color: render.Black,
})) }))
btn.Handle("Click", func(p render.Point) {
d.Flash("%s clicked", btn)
})
s.Supervisor.Add(btn) s.Supervisor.Add(btn)
leftFrame.Pack(btn, ui.Pack{ leftFrame.Pack(btn, ui.Pack{
Anchor: ui.N, Anchor: ui.N,
Fill: true, FillX: true,
PadY: 2, PadY: 2,
}) })
} }
@ -98,6 +103,7 @@ func (s *GUITestScene) Setup(d *Doodle) error {
body.Pack(frame, ui.Pack{ body.Pack(frame, ui.Pack{
Anchor: ui.W, Anchor: ui.W,
Expand: true, Expand: true,
Fill: true,
}) })
// Right Frame // Right Frame
@ -116,19 +122,28 @@ func (s *GUITestScene) Setup(d *Doodle) error {
// A grid of buttons. // A grid of buttons.
for row := 0; row < 3; row++ { for row := 0; row < 3; row++ {
rowFrame := ui.NewFrame(fmt.Sprintf("Row%d", row)) rowFrame := ui.NewFrame(fmt.Sprintf("Row%d", row))
rowFrame.Configure(ui.Config{
Background: render.RGBA(0, uint8((row*20)+120), 0, 255),
})
for col := 0; col < 3; col++ { for col := 0; col < 3; col++ {
btn := ui.NewButton("X", (func(row, col int, frame *ui.Frame) {
btn := ui.NewButton(fmt.Sprintf("Grid Button %d:%d", col, row),
ui.NewFrame(fmt.Sprintf("Col%d", col)), ui.NewFrame(fmt.Sprintf("Col%d", col)),
) )
btn.Configure(ui.Config{ btn.Configure(ui.Config{
Height: 20, Height: 20,
BorderStyle: ui.BorderRaised, BorderStyle: ui.BorderRaised,
}) })
btn.Handle("Click", func(p render.Point) {
d.Flash("%s clicked", btn)
})
rowFrame.Pack(btn, ui.Pack{ rowFrame.Pack(btn, ui.Pack{
Anchor: ui.W, Anchor: ui.W,
Expand: true, Expand: true,
FillX: true,
}) })
s.Supervisor.Add(btn) s.Supervisor.Add(btn)
})(row, col, rowFrame)
} }
rightFrame.Pack(rowFrame, ui.Pack{ rightFrame.Pack(rowFrame, ui.Pack{
Anchor: ui.N, Anchor: ui.N,
@ -136,10 +151,12 @@ func (s *GUITestScene) Setup(d *Doodle) error {
}) })
} }
frame.Pack(ui.NewLabel(render.Text{ frame.Pack(ui.NewLabel(ui.Label{
Text: "Hello World!", Text: "Hello World!",
Font: render.Text{
Size: 14, Size: 14,
Color: render.Black, Color: render.Black,
},
}), ui.Pack{ }), ui.Pack{
Anchor: ui.NW, Anchor: ui.NW,
Padding: 2, Padding: 2,
@ -147,10 +164,9 @@ func (s *GUITestScene) Setup(d *Doodle) error {
cb := ui.NewCheckbox("Overlay", cb := ui.NewCheckbox("Overlay",
&DebugOverlay, &DebugOverlay,
ui.NewLabel(render.Text{ ui.NewLabel(ui.Label{
Text: "Toggle Debug Overlay", Text: "Toggle Debug Overlay",
Size: 14, Font: balance.StatusFont,
Color: render.Black,
}), }),
) )
frame.Pack(cb, ui.Pack{ frame.Pack(cb, ui.Pack{
@ -158,18 +174,22 @@ func (s *GUITestScene) Setup(d *Doodle) error {
Padding: 4, Padding: 4,
}) })
cb.Supervise(s.Supervisor) cb.Supervise(s.Supervisor)
frame.Pack(ui.NewLabel(render.Text{ frame.Pack(ui.NewLabel(ui.Label{
Text: "Like Tk!", Text: "Like Tk!",
Font: render.Text{
Size: 16, Size: 16,
Color: render.Red, Color: render.Red,
},
}), ui.Pack{ }), ui.Pack{
Anchor: ui.SE, Anchor: ui.SE,
Padding: 8, Padding: 8,
}) })
frame.Pack(ui.NewLabel(render.Text{ frame.Pack(ui.NewLabel(ui.Label{
Text: "Frame widget for pack layouts", Text: "Frame widget for pack layouts",
Font: render.Text{
Size: 14, Size: 14,
Color: render.Blue, Color: render.Blue,
},
}), ui.Pack{ }), ui.Pack{
Anchor: ui.SE, Anchor: ui.SE,
Padding: 8, Padding: 8,
@ -184,10 +204,9 @@ func (s *GUITestScene) Setup(d *Doodle) error {
Anchor: ui.N, Anchor: ui.N,
}) })
button1 := ui.NewButton("Button1", ui.NewLabel(render.Text{ button1 := ui.NewButton("Button1", ui.NewLabel(ui.Label{
Text: "New Map", Text: "New Map",
Size: 14, Font: balance.StatusFont,
Color: render.Black,
})) }))
button1.SetBackground(render.Blue) button1.SetBackground(render.Blue)
button1.Handle("Click", func(p render.Point) { button1.Handle("Click", func(p render.Point) {
@ -196,11 +215,13 @@ func (s *GUITestScene) Setup(d *Doodle) error {
log.Info("Button1 bg: %s", button1.Background()) log.Info("Button1 bg: %s", button1.Background())
button2 := ui.NewButton("Button2", ui.NewLabel(render.Text{ button2 := ui.NewButton("Button2", ui.NewLabel(ui.Label{
Text: "New Map", Text: "New Map",
Size: 14, Font: balance.StatusFont,
Color: render.Black,
})) }))
button2.Handle("Click", func(p render.Point) {
d.Flash("Button2 clicked")
})
button2.SetText("Load Map") button2.SetText("Load Map")
var align = ui.W var align = ui.W
@ -230,30 +251,28 @@ func (s *GUITestScene) Draw(d *Doodle) error {
// Clear the canvas and fill it with white. // Clear the canvas and fill it with white.
d.Engine.Clear(render.White) d.Engine.Clear(render.White)
label := ui.NewLabel(render.Text{ label := ui.NewLabel(ui.Label{
Text: "GUITest Doodle v" + Version, Text: "GUITest Doodle v" + Version,
Font: render.Text{
Size: 26, Size: 26,
Color: render.Pink, Color: render.Pink,
Stroke: render.SkyBlue, Stroke: render.SkyBlue,
Shadow: render.Black, Shadow: render.Black,
},
}) })
label.Compute(d.Engine) label.Compute(d.Engine)
label.MoveTo(render.Point{ label.MoveTo(render.Point{
X: (d.width / 2) - (label.Size().W / 2), X: (d.width / 2) - (label.Size().W / 2),
Y: 40, Y: 40,
}) })
label.Present(d.Engine) label.Present(d.Engine, label.Point())
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.Window.Point())
s.Supervisor.Present(d.Engine)
// os.Exit(1)
return nil return nil
} }

View File

@ -1,6 +1,7 @@
package doodle package doodle
import ( import (
"git.kirsle.net/apps/doodle/balance"
"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"
@ -23,50 +24,28 @@ func (s *MainScene) Setup(d *Doodle) error {
frame := ui.NewFrame("frame") frame := ui.NewFrame("frame")
s.frame = frame s.frame = frame
s.frame.Configure(ui.Config{
// Width: 400,
// Height: 200,
Background: render.Purple,
BorderStyle: ui.BorderSolid,
BorderSize: 1,
BorderColor: render.Blue,
})
button1 := ui.NewButton("Button1", ui.NewLabel(render.Text{ button1 := ui.NewButton("Button1", ui.NewLabel(ui.Label{
Text: "New Map", Text: "New Map",
Size: 14, Font: balance.StatusFont,
Color: render.Black,
})) }))
// button1.Compute(d.Engine)
// button1.MoveTo(render.Point{
// X: (d.width / 2) - (button1.Size().W / 2),
// Y: 200,
// })
button1.Handle("Click", func(p render.Point) { button1.Handle("Click", func(p render.Point) {
d.NewMap() d.NewMap()
}) })
button2 := ui.NewButton("Button2", ui.NewLabel(render.Text{ button2 := ui.NewButton("Button2", ui.NewLabel(ui.Label{
Text: "New Map", Text: "New Map",
Size: 14, Font: balance.StatusFont,
Color: render.Black,
})) }))
button2.SetText("Load Map") button2.SetText("Load Map")
// button2.Compute(d.Engine)
// button2.MoveTo(render.Point{
// X: (d.width / 2) - (button2.Size().W / 2),
// Y: 260,
// })
var align = ui.E
frame.Pack(button1, ui.Pack{ frame.Pack(button1, ui.Pack{
Anchor: align, Anchor: ui.N,
Padding: 12,
Fill: true, Fill: true,
}) })
frame.Pack(button2, ui.Pack{ frame.Pack(button2, ui.Pack{
Anchor: align, Anchor: ui.N,
Padding: 12, PadY: 12,
Fill: true, Fill: true,
}) })
@ -87,28 +66,28 @@ func (s *MainScene) Draw(d *Doodle) error {
// Clear the canvas and fill it with white. // Clear the canvas and fill it with white.
d.Engine.Clear(render.White) d.Engine.Clear(render.White)
label := ui.NewLabel(render.Text{ label := ui.NewLabel(ui.Label{
Text: "Doodle v" + Version, Text: "Doodle v" + Version,
Font: render.Text{
Size: 26, Size: 26,
Color: render.Pink, Color: render.Pink,
Stroke: render.SkyBlue, Stroke: render.SkyBlue,
Shadow: render.Black, Shadow: render.Black,
},
}) })
label.Compute(d.Engine) label.Compute(d.Engine)
label.MoveTo(render.Point{ label.MoveTo(render.Point{
X: (d.width / 2) - (label.Size().W / 2), X: (d.width / 2) - (label.Size().W / 2),
Y: 120, Y: 120,
}) })
label.Present(d.Engine) label.Present(d.Engine, label.Point())
s.frame.Compute(d.Engine) s.frame.Compute(d.Engine)
s.frame.MoveTo(render.Point{ s.frame.MoveTo(render.Point{
X: (d.width / 2) - (s.frame.Size().W / 2), X: (d.width / 2) - (s.frame.Size().W / 2),
Y: 200, Y: 200,
}) })
s.frame.Present(d.Engine) s.frame.Present(d.Engine, s.frame.Point())
s.Supervisor.Present(d.Engine)
return nil return nil
} }

View File

@ -159,6 +159,7 @@ type Text struct {
Text string Text string
Size int Size int
Color Color Color Color
Padding int32
Stroke Color // Stroke color (if not zero) Stroke Color // Stroke color (if not zero)
Shadow Color // Drop shadow color (if not zero) Shadow Color // Drop shadow color (if not zero)
} }

View File

@ -28,7 +28,6 @@ func NewButton(name string, child Widget) *Button {
}) })
w.Configure(Config{ w.Configure(Config{
Padding: 4,
BorderSize: 2, BorderSize: 2,
BorderStyle: BorderRaised, BorderStyle: BorderRaised,
OutlineSize: 1, OutlineSize: 1,
@ -75,22 +74,21 @@ func (w *Button) Compute(e render.Engine) {
// SetText conveniently sets the button text, for Label children only. // SetText conveniently sets the button text, for Label children only.
func (w *Button) SetText(text string) error { func (w *Button) SetText(text string) error {
if label, ok := w.child.(*Label); ok { if label, ok := w.child.(*Label); ok {
label.Text.Text = text label.Text = text
} }
return errors.New("child is not a Label widget") 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, P render.Point) {
w.Compute(e) w.Compute(e)
var ( var (
P = w.Point()
S = w.Size() S = w.Size()
ChildSize = w.child.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, P)
// Offset further if we are currently sunken. // Offset further if we are currently sunken.
var clickOffset int32 var clickOffset int32
@ -112,6 +110,5 @@ func (w *Button) Present(e render.Engine) {
_ = ChildSize _ = ChildSize
// Draw the text label inside. // Draw the text label inside.
w.child.MoveTo(moveTo) w.child.Present(e, moveTo)
w.child.Present(e)
} }

View File

@ -32,7 +32,6 @@ func NewCheckButton(name string, boolVar *bool, child Widget) *CheckButton {
} }
w.Configure(Config{ w.Configure(Config{
Padding: 4,
BorderSize: 2, BorderSize: 2,
BorderStyle: borderStyle, BorderStyle: borderStyle,
OutlineSize: 1, OutlineSize: 1,

View File

@ -46,14 +46,13 @@ func (w *Frame) Compute(e render.Engine) {
} }
// Present the Frame. // Present the Frame.
func (w *Frame) Present(e render.Engine) { func (w *Frame) Present(e render.Engine, P render.Point) {
var ( var (
P = w.Point()
S = w.Size() S = w.Size()
) )
// Draw the widget's border and everything. // Draw the widget's border and everything.
w.DrawBox(e) w.DrawBox(e, P)
// Draw the background color. // Draw the background color.
e.DrawBox(w.Background(), render.Rect{ e.DrawBox(w.Background(), render.Rect{
@ -65,11 +64,13 @@ func (w *Frame) Present(e render.Engine) {
// Draw the widgets. // Draw the widgets.
for _, child := range w.widgets { for _, child := range w.widgets {
// child.Compute(e)
p := child.Point() p := child.Point()
child.MoveTo(render.NewPoint( moveTo := render.NewPoint(
P.X+p.X+w.BoxThickness(1), P.X+p.X+w.BoxThickness(1),
P.Y+p.Y+w.BoxThickness(1), P.Y+p.Y+w.BoxThickness(1),
)) )
child.Present(e) child.MoveTo(moveTo)
child.Present(e, moveTo)
} }
} }

View File

@ -5,7 +5,7 @@ import "git.kirsle.net/apps/doodle/render"
// computePacked processes all the Pack layout widgets in the Frame. // computePacked processes all the Pack layout widgets in the Frame.
func (w *Frame) computePacked(e render.Engine) { func (w *Frame) computePacked(e render.Engine) {
var ( var (
frameSize = w.Size() frameSize = w.BoxSize()
// maxWidth and maxHeight are always the computed minimum dimensions // maxWidth and maxHeight are always the computed minimum dimensions
// that the Frame must be to contain all of its children. If the Frame // that the Frame must be to contain all of its children. If the Frame
@ -37,7 +37,7 @@ func (w *Frame) computePacked(e render.Engine) {
yDirection = -1 - w.BoxThickness(2) // parent + child BoxThickness(1) = 2 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 - w.BoxThickness(2) xDirection = -1 // - w.BoxThickness(2)
} }
for _, packedWidget := range w.packs[anchor] { for _, packedWidget := range w.packs[anchor] {
@ -46,7 +46,7 @@ func (w *Frame) computePacked(e render.Engine) {
child.Compute(e) child.Compute(e)
var ( var (
// point = child.Point() // point = child.Point()
size = child.Size() size = child.BoxSize()
yStep = y * yDirection yStep = y * yDirection
xStep = x * xDirection xStep = x * xDirection
) )
@ -65,10 +65,7 @@ func (w *Frame) computePacked(e render.Engine) {
x -= size.W + (pack.PadX * 2) x -= size.W + (pack.PadX * 2)
} }
child.MoveTo(render.Point{ child.MoveTo(render.NewPoint(x, y))
X: x + pack.PadX,
Y: y + pack.PadY,
})
if anchor.IsNorth() { if anchor.IsNorth() {
y += size.H + (pack.PadY * 2) y += size.H + (pack.PadY * 2)
@ -90,8 +87,8 @@ func (w *Frame) computePacked(e render.Engine) {
if len(expanded) > 0 && !frameSize.IsZero() && frameSize.Bigger(computedSize) { if len(expanded) > 0 && !frameSize.IsZero() && frameSize.Bigger(computedSize) {
// Divy up the size available. // Divy up the size available.
growBy := render.Rect{ growBy := render.Rect{
W: ((frameSize.W - computedSize.W) / int32(len(expanded))) - w.BoxThickness(2), W: ((frameSize.W - computedSize.W) / int32(len(expanded))), // - w.BoxThickness(2),
H: ((frameSize.H - computedSize.H) / int32(len(expanded))) - w.BoxThickness(2), H: ((frameSize.H - computedSize.H) / int32(len(expanded))), // - w.BoxThickness(2),
} }
for _, pw := range expanded { for _, pw := range expanded {
pw.widget.ResizeBy(growBy) pw.widget.ResizeBy(growBy)
@ -102,10 +99,22 @@ func (w *Frame) computePacked(e render.Engine) {
// If we're not using a fixed Frame size, use the dynamically computed one. // If we're not using a fixed Frame size, use the dynamically computed one.
if !w.FixedSize() { if !w.FixedSize() {
frameSize = render.NewRect(maxWidth, maxHeight) frameSize = render.NewRect(maxWidth, maxHeight)
} else {
// If either of the sizes were left zero, use the dynamically computed one.
if frameSize.W == 0 {
frameSize.W = maxWidth
}
if frameSize.H == 0 {
frameSize.H = 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.
innerFrameSize := render.NewRect(
frameSize.W-w.BoxThickness(2),
frameSize.H-w.BoxThickness(2),
)
for _, pw := range visited { for _, pw := range visited {
var ( var (
child = pw.widget child = pw.widget
@ -117,38 +126,42 @@ func (w *Frame) computePacked(e render.Engine) {
moved bool moved bool
) )
if w.String() == "Frame<Row0; 3 widgets>" {
log.Debug("%s>%s: pack.FillX=%d resize=%s innerFrameSize=%s", w, child, pack.FillX, resize, innerFrameSize)
}
if pack.Anchor.IsNorth() || pack.Anchor.IsSouth() { if pack.Anchor.IsNorth() || pack.Anchor.IsSouth() {
if pack.FillX && resize.W < frameSize.W { if pack.FillX && resize.W < innerFrameSize.W {
resize.W = frameSize.W - w.BoxThickness(2) resize.W = innerFrameSize.W - w.BoxThickness(2)
resized = true resized = true
} }
if resize.W < frameSize.W-w.BoxThickness(4) { if resize.W < innerFrameSize.W-w.BoxThickness(4) {
if pack.Anchor.IsCenter() { if pack.Anchor.IsCenter() {
point.X = (frameSize.W / 2) - (resize.W / 2) point.X = (innerFrameSize.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 = frameSize.W - resize.W - pack.PadX point.X = innerFrameSize.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 && resize.H < frameSize.H { if pack.FillY && resize.H < innerFrameSize.H {
resize.H = frameSize.H - w.BoxThickness(2) // BoxThickness(2) for parent + child resize.H = innerFrameSize.H - w.BoxThickness(2) // BoxThickness(2) for parent + child
// point.Y -= (w.BoxThickness(4) + child.BoxThickness(2)) // point.Y -= (w.BoxThickness(4) + child.BoxThickness(2))
moved = true moved = true
resized = true resized = true
} }
// Vertically align the widgets. // Vertically align the widgets.
if resize.H < frameSize.H { if resize.H < innerFrameSize.H {
if pack.Anchor.IsMiddle() { if pack.Anchor.IsMiddle() {
point.Y = (frameSize.H / 2) - (resize.H / 2) point.Y = (innerFrameSize.H / 2) - (resize.H / 2) - w.BoxThickness(1)
} else if pack.Anchor.IsNorth() { } else if pack.Anchor.IsNorth() {
point.Y = pack.PadY - w.BoxThickness(4) point.Y = pack.PadY - w.BoxThickness(4)
} else if pack.Anchor.IsSouth() { } else if pack.Anchor.IsSouth() {
point.Y = frameSize.H - resize.H - pack.PadY point.Y = innerFrameSize.H - resize.H - pack.PadY
} }
moved = true moved = true
} }
@ -157,6 +170,7 @@ func (w *Frame) computePacked(e render.Engine) {
} }
if resized && size != resize { if resized && size != resize {
// log.Debug("%s/%s: resize to: %s", w, child, resize)
child.Resize(resize) child.Resize(resize)
child.Compute(e) child.Compute(e)
} }
@ -165,9 +179,12 @@ func (w *Frame) computePacked(e render.Engine) {
} }
} }
if !w.FixedSize() { // if !w.FixedSize() {
w.Resize(frameSize) w.Resize(render.NewRect(
} frameSize.W-w.BoxThickness(2),
frameSize.H-w.BoxThickness(2),
))
// }
} }
// Pack provides configuration fields for Frame.Pack(). // Pack provides configuration fields for Frame.Pack().

View File

@ -9,33 +9,48 @@ import (
// Label is a simple text label widget. // Label is a simple text label widget.
type Label struct { type Label struct {
BaseWidget BaseWidget
// Configurable fields for the constructor.
Text string
TextVariable *string
Font render.Text
width int32 width int32
height int32 height int32
Text render.Text
} }
// NewLabel creates a new label. // NewLabel creates a new label.
func NewLabel(t render.Text) *Label { func NewLabel(c Label) *Label {
w := &Label{ w := &Label{
Text: t, Text: c.Text,
TextVariable: c.TextVariable,
Font: c.Font,
} }
w.Configure(Config{
Padding: 4,
})
w.IDFunc(func() string { w.IDFunc(func() string {
return fmt.Sprintf("Label<%s>", w.Text.Text) return fmt.Sprintf("Label<%s>", w.text().Text)
}) })
return w return w
} }
// text returns the label's displayed text, coming from the TextVariable if
// available or else the Text attribute instead.
func (w *Label) text() render.Text {
if w.TextVariable != nil {
w.Font.Text = *w.TextVariable
return w.Font
}
w.Font.Text = w.Text
return w.Font
}
// 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())
if !w.FixedSize() { if !w.FixedSize() {
w.resizeAuto(render.Rect{ w.resizeAuto(render.Rect{
W: rect.W + w.Padding(), W: rect.W + (w.Font.Padding * 2),
H: rect.H + w.Padding(), H: rect.H + (w.Font.Padding * 2),
}) })
} }
@ -46,14 +61,12 @@ func (w *Label) Compute(e render.Engine) {
} }
// Present the label widget. // Present the label widget.
func (w *Label) Present(e render.Engine) { func (w *Label) Present(e render.Engine, P render.Point) {
var ( border := w.BoxThickness(1)
P = w.Point()
border = w.BoxThickness(1) w.DrawBox(e, P)
) e.DrawText(w.text(), render.Point{
w.DrawBox(e) X: P.X + border + w.Font.Padding,
e.DrawText(w.Text, render.Point{ Y: P.Y + border + w.Font.Padding,
X: P.X + border,
Y: P.Y + border,
}) })
} }

View File

@ -20,6 +20,7 @@ type Supervisor struct {
// NewSupervisor creates a supervisor. // NewSupervisor creates a supervisor.
func NewSupervisor() *Supervisor { func NewSupervisor() *Supervisor {
return &Supervisor{ return &Supervisor{
widgets: []Widget{},
hovering: map[int]interface{}{}, hovering: map[int]interface{}{},
clicked: map[int]interface{}{}, clicked: map[int]interface{}{},
} }
@ -84,8 +85,7 @@ func (s *Supervisor) Present(e render.Engine) {
defer s.lock.RUnlock() defer s.lock.RUnlock()
for _, w := range s.widgets { for _, w := range s.widgets {
// w.Present(e) w.Present(e, w.Point())
_ = w
} }
} }

View File

@ -26,6 +26,7 @@ type Widget interface {
MoveBy(render.Point) MoveBy(render.Point)
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)
BoxSize() render.Rect // Return the full size including the border and outline.
Resize(render.Rect) Resize(render.Rect)
ResizeBy(render.Rect) ResizeBy(render.Rect)
@ -34,11 +35,11 @@ type Widget interface {
// Thickness of the padding + border + outline. // Thickness of the padding + border + outline.
BoxThickness(multiplier int32) int32 BoxThickness(multiplier int32) int32
DrawBox(render.Engine) DrawBox(render.Engine, render.Point)
// Widget configuration getters. // Widget configuration getters.
Padding() int32 // Padding Margin() int32 // Margin away from other widgets
SetPadding(int32) // SetMargin(int32) //
Background() render.Color // Background color Background() render.Color // Background color
SetBackground(render.Color) // SetBackground(render.Color) //
Foreground() render.Color // Foreground color Foreground() render.Color // Foreground color
@ -60,7 +61,7 @@ type Widget interface {
Compute(render.Engine) Compute(render.Engine)
// Render the final widget onto the drawing engine. // Render the final widget onto the drawing engine.
Present(render.Engine) Present(render.Engine, render.Point)
} }
// Config holds common base widget configs for quick configuration. // Config holds common base widget configs for quick configuration.
@ -73,9 +74,9 @@ type Config struct {
AutoResize bool AutoResize bool
Width int32 Width int32
Height int32 Height int32
Padding int32 Margin int32
PadX int32 MarginX int32
PadY int32 MarginY int32
Background render.Color Background render.Color
Foreground render.Color Foreground render.Color
BorderSize int32 BorderSize int32
@ -94,7 +95,7 @@ type BaseWidget struct {
width int32 width int32
height int32 height int32
point render.Point point render.Point
padding int32 margin int32
background render.Color background render.Color
foreground render.Color foreground render.Color
borderStyle BorderStyle borderStyle BorderStyle
@ -142,8 +143,8 @@ func (w *BaseWidget) Configure(c Config) {
} }
} }
if c.Padding != 0 { if c.Margin != 0 {
w.padding = c.Padding w.margin = c.Margin
} }
if c.Background != render.Invisible { if c.Background != render.Invisible {
w.background = c.Background w.background = c.Background
@ -194,6 +195,15 @@ func (w *BaseWidget) Size() render.Rect {
} }
} }
// BoxSize returns the full rendered size of the widget including its box
// thickness (border, padding and outline).
func (w *BaseWidget) BoxSize() render.Rect {
return render.Rect{
W: w.width + w.BoxThickness(2),
H: w.height + w.BoxThickness(2),
}
}
// FixedSize returns whether the widget's size has been hard-coded by the user // FixedSize returns whether the widget's size has been hard-coded by the user
// (true) or if it automatically resizes based on its contents (false). // (true) or if it automatically resizes based on its contents (false).
func (w *BaseWidget) FixedSize() bool { func (w *BaseWidget) FixedSize() bool {
@ -226,13 +236,12 @@ func (w *BaseWidget) BoxThickness(m int32) int32 {
if m == 0 { if m == 0 {
m = 1 m = 1
} }
return (w.Padding() * m) + (w.BorderSize() * m) + (w.OutlineSize() * m) return (w.Margin() * m) + (w.BorderSize() * m) + (w.OutlineSize() * m)
} }
// DrawBox draws the border and outline. // DrawBox draws the border and outline.
func (w *BaseWidget) DrawBox(e render.Engine) { func (w *BaseWidget) DrawBox(e render.Engine, P render.Point) {
var ( var (
P = w.Point()
S = w.Size() S = w.Size()
outline = w.OutlineSize() outline = w.OutlineSize()
border = w.BorderSize() border = w.BorderSize()
@ -300,25 +309,16 @@ func (w *BaseWidget) DrawBox(e render.Engine) {
if w.Background() != render.Invisible { if w.Background() != render.Invisible {
e.DrawBox(w.Background(), box) e.DrawBox(w.Background(), box)
} }
// log.Info("Widget %s background color: %s", w, w.Background())
// XXX: color effective area
// box.X += w.Padding()
// box.Y += w.Padding()
// box.W -= w.Padding() * 2
// box.H -= w.Padding() * 2
// e.DrawBox(render.RGBA(0, 255, 255, 153), box)
} }
// Padding returns the padding width. // Margin returns the margin width.
func (w *BaseWidget) Padding() int32 { func (w *BaseWidget) Margin() int32 {
return w.padding return w.margin
} }
// SetPadding sets the padding width. // SetMargin sets the margin width.
func (w *BaseWidget) SetPadding(v int32) { func (w *BaseWidget) SetMargin(v int32) {
w.padding = v w.margin = v
} }
// Background returns the background color. // Background returns the background color.