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:
parent
316456ef03
commit
8624a28ea9
|
@ -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,
|
||||
}
|
||||
|
|
|
@ -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{})
|
||||
}
|
||||
|
||||
|
|
|
@ -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
116
editor_ui.go
Normal 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
|
||||
}
|
|
@ -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{
|
||||
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{
|
||||
btn := ui.NewButton("dummy "+label, ui.NewLabel(ui.Label{
|
||||
Text: label,
|
||||
Size: 12,
|
||||
Color: render.Black,
|
||||
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",
|
||||
(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{
|
||||
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{
|
||||
ui.NewLabel(ui.Label{
|
||||
Text: "Toggle Debug Overlay",
|
||||
Size: 14,
|
||||
Color: render.Black,
|
||||
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{
|
||||
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{
|
||||
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{
|
||||
button1 := ui.NewButton("Button1", ui.NewLabel(ui.Label{
|
||||
Text: "New Map",
|
||||
Size: 14,
|
||||
Color: render.Black,
|
||||
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{
|
||||
button2 := ui.NewButton("Button2", ui.NewLabel(ui.Label{
|
||||
Text: "New Map",
|
||||
Size: 14,
|
||||
Color: render.Black,
|
||||
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{
|
||||
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
|
||||
}
|
||||
|
|
|
@ -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,50 +24,28 @@ 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{
|
||||
button1 := ui.NewButton("Button1", ui.NewLabel(ui.Label{
|
||||
Text: "New Map",
|
||||
Size: 14,
|
||||
Color: render.Black,
|
||||
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{
|
||||
button2 := ui.NewButton("Button2", ui.NewLabel(ui.Label{
|
||||
Text: "New Map",
|
||||
Size: 14,
|
||||
Color: render.Black,
|
||||
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,
|
||||
Anchor: ui.N,
|
||||
Fill: true,
|
||||
})
|
||||
frame.Pack(button2, ui.Pack{
|
||||
Anchor: align,
|
||||
Padding: 12,
|
||||
Anchor: ui.N,
|
||||
PadY: 12,
|
||||
Fill: true,
|
||||
})
|
||||
|
||||
|
@ -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{
|
||||
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
|
||||
}
|
||||
|
|
|
@ -159,6 +159,7 @@ type Text struct {
|
|||
Text string
|
||||
Size int
|
||||
Color Color
|
||||
Padding int32
|
||||
Stroke Color // Stroke color (if not zero)
|
||||
Shadow Color // Drop shadow color (if not zero)
|
||||
}
|
||||
|
|
11
ui/button.go
11
ui/button.go
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -32,7 +32,6 @@ func NewCheckButton(name string, boolVar *bool, child Widget) *CheckButton {
|
|||
}
|
||||
|
||||
w.Configure(Config{
|
||||
Padding: 4,
|
||||
BorderSize: 2,
|
||||
BorderStyle: borderStyle,
|
||||
OutlineSize: 1,
|
||||
|
|
13
ui/frame.go
13
ui/frame.go
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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().
|
||||
|
|
51
ui/label.go
51
ui/label.go
|
@ -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,
|
||||
})
|
||||
}
|
||||
|
|
|
@ -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())
|
||||
}
|
||||
}
|
||||
|
||||
|
|
56
ui/widget.go
56
ui/widget.go
|
@ -26,6 +26,7 @@ type Widget interface {
|
|||
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)
|
||||
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.
|
||||
|
|
Loading…
Reference in New Issue
Block a user