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.
chunks
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
import "git.kirsle.net/apps/doodle/render"
import (
"git.kirsle.net/apps/doodle/render"
)
// Shell related variables.
var (
// TODO: why not renders transparent
ShellBackgroundColor = render.Color{0, 10, 20, 128}
ShellBackgroundColor = render.RGBA(0, 10, 20, 128)
ShellForegroundColor = render.White
ShellPadding int32 = 8
ShellFontSize = 16
@ -15,3 +17,10 @@ var (
// Ticks that a flashed message persists for.
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.
if d.Scene == nil {
d.Goto(&GUITestScene{})
// d.Goto(&GUITestScene{})
d.NewMap()
// d.Goto(&MainScene{})
}

View File

@ -20,6 +20,8 @@ type EditorScene struct {
Filename string
Canvas render.Grid
UI *EditorUI
// History of all the pixels placed by the user.
pixelHistory []level.Pixel
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.UI = NewEditorUI(d)
d.Flash("Editor Mode. Press 'P' to play this map.")
if s.pixelHistory == nil {
@ -72,6 +76,8 @@ func (s *EditorScene) Setup(d *Doodle) error {
// Loop the editor scene.
func (s *EditorScene) Loop(d *Doodle, ev *events.State) error {
s.UI.Loop(ev)
// Taking a screenshot?
if ev.ScreenshotKey.Pressed() {
log.Info("Taking a screenshot")
@ -131,6 +137,7 @@ func (s *EditorScene) Loop(d *Doodle, ev *events.State) error {
// Draw the current frame.
func (s *EditorScene) Draw(d *Doodle) error {
s.canvas.Draw(d.Engine)
s.UI.Present(d.Engine)
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 (
"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"
@ -37,11 +38,13 @@ func (s *GUITestScene) Setup(d *Doodle) error {
})
// Title Bar
titleBar := ui.NewLabel(render.Text{
Text: "Widget Toolkit",
Size: 12,
Color: render.White,
Stroke: render.DarkBlue,
titleBar := ui.NewLabel(ui.Label{
Text: "Widget Toolkit",
Font: render.Text{
Size: 12,
Color: render.White,
Stroke: render.DarkBlue,
},
})
titleBar.Configure(ui.Config{
Background: render.Blue,
@ -76,15 +79,17 @@ func (s *GUITestScene) Setup(d *Doodle) error {
// 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,
btn := ui.NewButton("dummy "+label, ui.NewLabel(ui.Label{
Text: label,
Font: balance.StatusFont,
}))
btn.Handle("Click", func(p render.Point) {
d.Flash("%s clicked", btn)
})
s.Supervisor.Add(btn)
leftFrame.Pack(btn, ui.Pack{
Anchor: ui.N,
Fill: true,
FillX: true,
PadY: 2,
})
}
@ -98,6 +103,7 @@ func (s *GUITestScene) Setup(d *Doodle) error {
body.Pack(frame, ui.Pack{
Anchor: ui.W,
Expand: true,
Fill: true,
})
// Right Frame
@ -116,19 +122,28 @@ func (s *GUITestScene) Setup(d *Doodle) error {
// A grid of buttons.
for row := 0; row < 3; 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++ {
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)
(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)),
)
btn.Configure(ui.Config{
Height: 20,
BorderStyle: ui.BorderRaised,
})
btn.Handle("Click", func(p render.Point) {
d.Flash("%s clicked", btn)
})
rowFrame.Pack(btn, ui.Pack{
Anchor: ui.W,
Expand: true,
FillX: true,
})
s.Supervisor.Add(btn)
})(row, col, rowFrame)
}
rightFrame.Pack(rowFrame, ui.Pack{
Anchor: ui.N,
@ -136,10 +151,12 @@ func (s *GUITestScene) Setup(d *Doodle) error {
})
}
frame.Pack(ui.NewLabel(render.Text{
Text: "Hello World!",
Size: 14,
Color: render.Black,
frame.Pack(ui.NewLabel(ui.Label{
Text: "Hello World!",
Font: render.Text{
Size: 14,
Color: render.Black,
},
}), ui.Pack{
Anchor: ui.NW,
Padding: 2,
@ -147,10 +164,9 @@ func (s *GUITestScene) Setup(d *Doodle) error {
cb := ui.NewCheckbox("Overlay",
&DebugOverlay,
ui.NewLabel(render.Text{
Text: "Toggle Debug Overlay",
Size: 14,
Color: render.Black,
ui.NewLabel(ui.Label{
Text: "Toggle Debug Overlay",
Font: balance.StatusFont,
}),
)
frame.Pack(cb, ui.Pack{
@ -158,18 +174,22 @@ func (s *GUITestScene) Setup(d *Doodle) error {
Padding: 4,
})
cb.Supervise(s.Supervisor)
frame.Pack(ui.NewLabel(render.Text{
Text: "Like Tk!",
Size: 16,
Color: render.Red,
frame.Pack(ui.NewLabel(ui.Label{
Text: "Like Tk!",
Font: render.Text{
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,
frame.Pack(ui.NewLabel(ui.Label{
Text: "Frame widget for pack layouts",
Font: render.Text{
Size: 14,
Color: render.Blue,
},
}), ui.Pack{
Anchor: ui.SE,
Padding: 8,
@ -184,10 +204,9 @@ func (s *GUITestScene) Setup(d *Doodle) error {
Anchor: ui.N,
})
button1 := ui.NewButton("Button1", ui.NewLabel(render.Text{
Text: "New Map",
Size: 14,
Color: render.Black,
button1 := ui.NewButton("Button1", ui.NewLabel(ui.Label{
Text: "New Map",
Font: balance.StatusFont,
}))
button1.SetBackground(render.Blue)
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())
button2 := ui.NewButton("Button2", ui.NewLabel(render.Text{
Text: "New Map",
Size: 14,
Color: render.Black,
button2 := ui.NewButton("Button2", ui.NewLabel(ui.Label{
Text: "New Map",
Font: balance.StatusFont,
}))
button2.Handle("Click", func(p render.Point) {
d.Flash("Button2 clicked")
})
button2.SetText("Load Map")
var align = ui.W
@ -230,30 +251,28 @@ func (s *GUITestScene) Draw(d *Doodle) error {
// Clear the canvas and fill it with white.
d.Engine.Clear(render.White)
label := ui.NewLabel(render.Text{
Text: "GUITest Doodle v" + Version,
Size: 26,
Color: render.Pink,
Stroke: render.SkyBlue,
Shadow: render.Black,
label := ui.NewLabel(ui.Label{
Text: "GUITest Doodle v" + Version,
Font: render.Text{
Size: 26,
Color: render.Pink,
Stroke: render.SkyBlue,
Shadow: render.Black,
},
})
label.Compute(d.Engine)
label.MoveTo(render.Point{
X: (d.width / 2) - (label.Size().W / 2),
Y: 40,
})
label.Present(d.Engine)
label.Present(d.Engine, label.Point())
s.Window.Compute(d.Engine)
s.Window.MoveTo(render.Point{
X: (d.width / 2) - (s.Window.Size().W / 2),
Y: 100,
})
s.Window.Present(d.Engine)
s.Supervisor.Present(d.Engine)
// os.Exit(1)
s.Window.Present(d.Engine, s.Window.Point())
return nil
}

View File

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

View File

@ -156,11 +156,12 @@ func (r Rect) IsZero() bool {
// Text holds information for drawing text.
type Text struct {
Text string
Size int
Color Color
Stroke Color // Stroke color (if not zero)
Shadow Color // Drop shadow color (if not zero)
Text string
Size int
Color Color
Padding int32
Stroke Color // Stroke color (if not zero)
Shadow Color // Drop shadow color (if not zero)
}
func (t Text) String() string {

View File

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

View File

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

View File

@ -46,14 +46,13 @@ func (w *Frame) Compute(e render.Engine) {
}
// Present the Frame.
func (w *Frame) Present(e render.Engine) {
func (w *Frame) Present(e render.Engine, P render.Point) {
var (
P = w.Point()
S = w.Size()
)
// Draw the widget's border and everything.
w.DrawBox(e)
w.DrawBox(e, P)
// Draw the background color.
e.DrawBox(w.Background(), render.Rect{
@ -65,11 +64,13 @@ func (w *Frame) Present(e render.Engine) {
// Draw the widgets.
for _, child := range w.widgets {
// child.Compute(e)
p := child.Point()
child.MoveTo(render.NewPoint(
moveTo := render.NewPoint(
P.X+p.X+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.
func (w *Frame) computePacked(e render.Engine) {
var (
frameSize = w.Size()
frameSize = w.BoxSize()
// maxWidth and maxHeight are always the computed minimum dimensions
// 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
} else if anchor == E {
x = frameSize.W
xDirection = -1 - w.BoxThickness(2)
xDirection = -1 // - w.BoxThickness(2)
}
for _, packedWidget := range w.packs[anchor] {
@ -46,7 +46,7 @@ func (w *Frame) computePacked(e render.Engine) {
child.Compute(e)
var (
// point = child.Point()
size = child.Size()
size = child.BoxSize()
yStep = y * yDirection
xStep = x * xDirection
)
@ -65,10 +65,7 @@ func (w *Frame) computePacked(e render.Engine) {
x -= size.W + (pack.PadX * 2)
}
child.MoveTo(render.Point{
X: x + pack.PadX,
Y: y + pack.PadY,
})
child.MoveTo(render.NewPoint(x, y))
if anchor.IsNorth() {
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) {
// 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),
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)
@ -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 !w.FixedSize() {
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
// in their space.
innerFrameSize := render.NewRect(
frameSize.W-w.BoxThickness(2),
frameSize.H-w.BoxThickness(2),
)
for _, pw := range visited {
var (
child = pw.widget
@ -117,38 +126,42 @@ func (w *Frame) computePacked(e render.Engine) {
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.FillX && resize.W < frameSize.W {
resize.W = frameSize.W - w.BoxThickness(2)
if pack.FillX && resize.W < innerFrameSize.W {
resize.W = innerFrameSize.W - w.BoxThickness(2)
resized = true
}
if resize.W < frameSize.W-w.BoxThickness(4) {
if resize.W < innerFrameSize.W-w.BoxThickness(4) {
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() {
point.X = pack.PadX
} else if pack.Anchor.IsEast() {
point.X = frameSize.W - resize.W - pack.PadX
point.X = innerFrameSize.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
if pack.FillY && resize.H < innerFrameSize.H {
resize.H = innerFrameSize.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 resize.H < innerFrameSize.H {
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() {
point.Y = pack.PadY - w.BoxThickness(4)
} else if pack.Anchor.IsSouth() {
point.Y = frameSize.H - resize.H - pack.PadY
point.Y = innerFrameSize.H - resize.H - pack.PadY
}
moved = true
}
@ -157,6 +170,7 @@ func (w *Frame) computePacked(e render.Engine) {
}
if resized && size != resize {
// log.Debug("%s/%s: resize to: %s", w, child, resize)
child.Resize(resize)
child.Compute(e)
}
@ -165,9 +179,12 @@ func (w *Frame) computePacked(e render.Engine) {
}
}
if !w.FixedSize() {
w.Resize(frameSize)
}
// if !w.FixedSize() {
w.Resize(render.NewRect(
frameSize.W-w.BoxThickness(2),
frameSize.H-w.BoxThickness(2),
))
// }
}
// Pack provides configuration fields for Frame.Pack().

View File

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

View File

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

View File

@ -24,8 +24,9 @@ type Widget interface {
Point() render.Point
MoveTo(render.Point)
MoveBy(render.Point)
Size() render.Rect // Return the Width and Height of the widget.
FixedSize() bool // Return whether the size is fixed (true) or automatic (false)
Size() render.Rect // Return the Width and Height of the widget.
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)
ResizeBy(render.Rect)
@ -34,11 +35,11 @@ type Widget interface {
// Thickness of the padding + border + outline.
BoxThickness(multiplier int32) int32
DrawBox(render.Engine)
DrawBox(render.Engine, render.Point)
// Widget configuration getters.
Padding() int32 // Padding
SetPadding(int32) //
Margin() int32 // Margin away from other widgets
SetMargin(int32) //
Background() render.Color // Background color
SetBackground(render.Color) //
Foreground() render.Color // Foreground color
@ -60,7 +61,7 @@ type Widget interface {
Compute(render.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.
@ -73,9 +74,9 @@ type Config struct {
AutoResize bool
Width int32
Height int32
Padding int32
PadX int32
PadY int32
Margin int32
MarginX int32
MarginY int32
Background render.Color
Foreground render.Color
BorderSize int32
@ -94,7 +95,7 @@ type BaseWidget struct {
width int32
height int32
point render.Point
padding int32
margin int32
background render.Color
foreground render.Color
borderStyle BorderStyle
@ -142,8 +143,8 @@ func (w *BaseWidget) Configure(c Config) {
}
}
if c.Padding != 0 {
w.padding = c.Padding
if c.Margin != 0 {
w.margin = c.Margin
}
if c.Background != render.Invisible {
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
// (true) or if it automatically resizes based on its contents (false).
func (w *BaseWidget) FixedSize() bool {
@ -226,13 +236,12 @@ func (w *BaseWidget) BoxThickness(m int32) int32 {
if m == 0 {
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.
func (w *BaseWidget) DrawBox(e render.Engine) {
func (w *BaseWidget) DrawBox(e render.Engine, P render.Point) {
var (
P = w.Point()
S = w.Size()
outline = w.OutlineSize()
border = w.BorderSize()
@ -300,25 +309,16 @@ func (w *BaseWidget) DrawBox(e render.Engine) {
if w.Background() != render.Invisible {
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.
func (w *BaseWidget) Padding() int32 {
return w.padding
// Margin returns the margin width.
func (w *BaseWidget) Margin() int32 {
return w.margin
}
// SetPadding sets the padding width.
func (w *BaseWidget) SetPadding(v int32) {
w.padding = v
// SetMargin sets the margin width.
func (w *BaseWidget) SetMargin(v int32) {
w.margin = v
}
// Background returns the background color.