WIP Labels
This commit is contained in:
parent
11df6cbda9
commit
2e36d9ca85
|
@ -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(&MainScene{})
|
d.Goto(&GUITestScene{})
|
||||||
|
// d.Goto(&MainScene{})
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Info("Enter Main Loop")
|
log.Info("Enter Main Loop")
|
||||||
|
|
159
guitest_scene.go
Normal file
159
guitest_scene.go
Normal file
|
@ -0,0 +1,159 @@
|
||||||
|
package doodle
|
||||||
|
|
||||||
|
import (
|
||||||
|
"git.kirsle.net/apps/doodle/events"
|
||||||
|
"git.kirsle.net/apps/doodle/render"
|
||||||
|
"git.kirsle.net/apps/doodle/ui"
|
||||||
|
)
|
||||||
|
|
||||||
|
// GUITestScene implements the main menu of Doodle.
|
||||||
|
type GUITestScene struct {
|
||||||
|
Supervisor *ui.Supervisor
|
||||||
|
frame *ui.Frame
|
||||||
|
window *ui.Frame
|
||||||
|
}
|
||||||
|
|
||||||
|
// Name of the scene.
|
||||||
|
func (s *GUITestScene) Name() string {
|
||||||
|
return "Main"
|
||||||
|
}
|
||||||
|
|
||||||
|
// Setup the scene.
|
||||||
|
func (s *GUITestScene) Setup(d *Doodle) error {
|
||||||
|
s.Supervisor = ui.NewSupervisor()
|
||||||
|
|
||||||
|
window := ui.NewFrame()
|
||||||
|
s.window = window
|
||||||
|
window.Configure(ui.Config{
|
||||||
|
Width: 400,
|
||||||
|
Height: 400,
|
||||||
|
Background: render.Grey,
|
||||||
|
BorderStyle: ui.BorderRaised,
|
||||||
|
BorderSize: 2,
|
||||||
|
})
|
||||||
|
|
||||||
|
titleBar := ui.NewLabel(render.Text{
|
||||||
|
Text: "Alert",
|
||||||
|
Size: 12,
|
||||||
|
Color: render.White,
|
||||||
|
Stroke: render.Black,
|
||||||
|
})
|
||||||
|
titleBar.Configure(ui.Config{
|
||||||
|
Background: render.Blue,
|
||||||
|
OutlineSize: 1,
|
||||||
|
OutlineColor: render.Black,
|
||||||
|
})
|
||||||
|
window.Pack(titleBar, ui.Pack{
|
||||||
|
Anchor: ui.N,
|
||||||
|
FillX: true,
|
||||||
|
})
|
||||||
|
|
||||||
|
msgFrame := ui.NewFrame()
|
||||||
|
msgFrame.Configure(ui.Config{
|
||||||
|
Background: render.Grey,
|
||||||
|
BorderStyle: ui.BorderRaised,
|
||||||
|
BorderSize: 1,
|
||||||
|
})
|
||||||
|
window.Pack(msgFrame, ui.Pack{
|
||||||
|
Anchor: ui.N,
|
||||||
|
Fill: true,
|
||||||
|
Padding: 4,
|
||||||
|
})
|
||||||
|
|
||||||
|
btnFrame := ui.NewFrame()
|
||||||
|
btnFrame.Configure(ui.Config{
|
||||||
|
Background: render.DarkRed,
|
||||||
|
})
|
||||||
|
window.Pack(btnFrame, ui.Pack{
|
||||||
|
Anchor: ui.N,
|
||||||
|
Padding: 4,
|
||||||
|
})
|
||||||
|
|
||||||
|
msg := ui.NewLabel(render.Text{
|
||||||
|
Text: "Hello World!",
|
||||||
|
Size: 14,
|
||||||
|
Color: render.Black,
|
||||||
|
})
|
||||||
|
msgFrame.Pack(msg, ui.Pack{
|
||||||
|
Anchor: ui.NW,
|
||||||
|
Padding: 2,
|
||||||
|
})
|
||||||
|
|
||||||
|
button1 := ui.NewButton(*ui.NewLabel(render.Text{
|
||||||
|
Text: "New Map",
|
||||||
|
Size: 14,
|
||||||
|
Color: render.Black,
|
||||||
|
}))
|
||||||
|
button1.SetBackground(render.Blue)
|
||||||
|
button1.Handle("Click", func(p render.Point) {
|
||||||
|
d.NewMap()
|
||||||
|
})
|
||||||
|
|
||||||
|
log.Info("Button1 bg: %s", button1.Background())
|
||||||
|
|
||||||
|
button2 := ui.NewButton(*ui.NewLabel(render.Text{
|
||||||
|
Text: "New Map",
|
||||||
|
Size: 14,
|
||||||
|
Color: render.Black,
|
||||||
|
}))
|
||||||
|
button2.SetText("Load Map")
|
||||||
|
|
||||||
|
var align = ui.W
|
||||||
|
btnFrame.Pack(button1, ui.Pack{
|
||||||
|
Anchor: align,
|
||||||
|
Padding: 20,
|
||||||
|
Fill: true,
|
||||||
|
})
|
||||||
|
btnFrame.Pack(button2, ui.Pack{
|
||||||
|
Anchor: align,
|
||||||
|
Padding: 20,
|
||||||
|
Fill: true,
|
||||||
|
})
|
||||||
|
|
||||||
|
s.Supervisor.Add(button1)
|
||||||
|
s.Supervisor.Add(button2)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Loop the editor scene.
|
||||||
|
func (s *GUITestScene) Loop(d *Doodle, ev *events.State) error {
|
||||||
|
s.Supervisor.Loop(ev)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Draw the pixels on this frame.
|
||||||
|
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.Compute(d.Engine)
|
||||||
|
label.MoveTo(render.Point{
|
||||||
|
X: (d.width / 2) - (label.Size().W / 2),
|
||||||
|
Y: 40,
|
||||||
|
})
|
||||||
|
label.Present(d.Engine)
|
||||||
|
|
||||||
|
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)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Destroy the scene.
|
||||||
|
func (s *GUITestScene) Destroy() error {
|
||||||
|
return nil
|
||||||
|
}
|
|
@ -9,6 +9,7 @@ import (
|
||||||
// MainScene implements the main menu of Doodle.
|
// MainScene implements the main menu of Doodle.
|
||||||
type MainScene struct {
|
type MainScene struct {
|
||||||
Supervisor *ui.Supervisor
|
Supervisor *ui.Supervisor
|
||||||
|
frame *ui.Frame
|
||||||
}
|
}
|
||||||
|
|
||||||
// Name of the scene.
|
// Name of the scene.
|
||||||
|
@ -20,16 +21,27 @@ 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()
|
||||||
|
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(*ui.NewLabel(render.Text{
|
button1 := ui.NewButton(*ui.NewLabel(render.Text{
|
||||||
Text: "New Map",
|
Text: "New Map",
|
||||||
Size: 14,
|
Size: 14,
|
||||||
Color: render.Black,
|
Color: render.Black,
|
||||||
}))
|
}))
|
||||||
button1.Compute(d.Engine)
|
// button1.Compute(d.Engine)
|
||||||
button1.MoveTo(render.Point{
|
// button1.MoveTo(render.Point{
|
||||||
X: (d.width / 2) - (button1.Size().W / 2),
|
// X: (d.width / 2) - (button1.Size().W / 2),
|
||||||
Y: 200,
|
// Y: 200,
|
||||||
})
|
// })
|
||||||
button1.Handle("Click", func(p render.Point) {
|
button1.Handle("Click", func(p render.Point) {
|
||||||
d.NewMap()
|
d.NewMap()
|
||||||
})
|
})
|
||||||
|
@ -40,10 +52,22 @@ func (s *MainScene) Setup(d *Doodle) error {
|
||||||
Color: render.Black,
|
Color: render.Black,
|
||||||
}))
|
}))
|
||||||
button2.SetText("Load Map")
|
button2.SetText("Load Map")
|
||||||
button2.Compute(d.Engine)
|
// button2.Compute(d.Engine)
|
||||||
button2.MoveTo(render.Point{
|
// button2.MoveTo(render.Point{
|
||||||
X: (d.width / 2) - (button2.Size().W / 2),
|
// X: (d.width / 2) - (button2.Size().W / 2),
|
||||||
Y: 260,
|
// Y: 260,
|
||||||
|
// })
|
||||||
|
|
||||||
|
var align = ui.E
|
||||||
|
frame.Pack(button1, ui.Pack{
|
||||||
|
Anchor: align,
|
||||||
|
Padding: 12,
|
||||||
|
Fill: true,
|
||||||
|
})
|
||||||
|
frame.Pack(button2, ui.Pack{
|
||||||
|
Anchor: align,
|
||||||
|
Padding: 12,
|
||||||
|
Fill: true,
|
||||||
})
|
})
|
||||||
|
|
||||||
s.Supervisor.Add(button1)
|
s.Supervisor.Add(button1)
|
||||||
|
@ -77,6 +101,13 @@ func (s *MainScene) Draw(d *Doodle) error {
|
||||||
})
|
})
|
||||||
label.Present(d.Engine)
|
label.Present(d.Engine)
|
||||||
|
|
||||||
|
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.Supervisor.Present(d.Engine)
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
|
|
@ -89,6 +89,16 @@ func (c Color) Add(r, g, b, a int32) Color {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Lighten a color value.
|
||||||
|
func (c Color) Lighten(v int32) Color {
|
||||||
|
return c.Add(v, v, v, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Darken a color value.
|
||||||
|
func (c Color) Darken(v int32) Color {
|
||||||
|
return c.Add(-v, -v, -v, 0)
|
||||||
|
}
|
||||||
|
|
||||||
// Point holds an X,Y coordinate value.
|
// Point holds an X,Y coordinate value.
|
||||||
type Point struct {
|
type Point struct {
|
||||||
X int32
|
X int32
|
||||||
|
|
20
ui/button.go
20
ui/button.go
|
@ -1,6 +1,8 @@
|
||||||
package ui
|
package ui
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
"git.kirsle.net/apps/doodle/render"
|
"git.kirsle.net/apps/doodle/render"
|
||||||
"git.kirsle.net/apps/doodle/ui/theme"
|
"git.kirsle.net/apps/doodle/ui/theme"
|
||||||
)
|
)
|
||||||
|
@ -21,12 +23,14 @@ func NewButton(label Label) *Button {
|
||||||
Label: label,
|
Label: label,
|
||||||
}
|
}
|
||||||
|
|
||||||
w.SetPadding(4)
|
w.Configure(Config{
|
||||||
w.SetBorderSize(2)
|
Padding: 4,
|
||||||
w.SetBorderStyle(BorderRaised)
|
BorderSize: 2,
|
||||||
w.SetOutlineSize(1)
|
BorderStyle: BorderRaised,
|
||||||
w.SetOutlineColor(theme.ButtonOutlineColor)
|
OutlineSize: 1,
|
||||||
w.SetBackground(theme.ButtonBackgroundColor)
|
OutlineColor: theme.ButtonOutlineColor,
|
||||||
|
Background: theme.ButtonBackgroundColor,
|
||||||
|
})
|
||||||
|
|
||||||
w.Handle("MouseOver", func(p render.Point) {
|
w.Handle("MouseOver", func(p render.Point) {
|
||||||
w.hovering = true
|
w.hovering = true
|
||||||
|
@ -46,6 +50,10 @@ 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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
325
ui/frame.go
Normal file
325
ui/frame.go
Normal file
|
@ -0,0 +1,325 @@
|
||||||
|
package ui
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"git.kirsle.net/apps/doodle/render"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Frame is a widget that contains other widgets.
|
||||||
|
type Frame struct {
|
||||||
|
BaseWidget
|
||||||
|
packs map[Anchor][]packedWidget
|
||||||
|
widgets []Widget
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewFrame creates a new Frame.
|
||||||
|
func NewFrame() *Frame {
|
||||||
|
return &Frame{
|
||||||
|
packs: map[Anchor][]packedWidget{},
|
||||||
|
widgets: []Widget{},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *Frame) String() string {
|
||||||
|
return fmt.Sprintf("Frame<%d widgets>",
|
||||||
|
len(w.widgets),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Compute the size of the Frame.
|
||||||
|
func (w *Frame) Compute(e render.Engine) {
|
||||||
|
var (
|
||||||
|
frameSize = w.Size()
|
||||||
|
maxWidth int32
|
||||||
|
maxHeight int32
|
||||||
|
visited = []packedWidget{}
|
||||||
|
)
|
||||||
|
|
||||||
|
// Initialize the dimensions?
|
||||||
|
if w.FixedSize() {
|
||||||
|
maxWidth = frameSize.W
|
||||||
|
maxHeight = frameSize.H
|
||||||
|
}
|
||||||
|
|
||||||
|
for anchor := AnchorMin; anchor <= AnchorMax; anchor++ {
|
||||||
|
if _, ok := w.packs[anchor]; !ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
x int32
|
||||||
|
y int32
|
||||||
|
yDirection int32 = 1
|
||||||
|
xDirection int32 = 1
|
||||||
|
)
|
||||||
|
|
||||||
|
if anchor.IsSouth() {
|
||||||
|
y = frameSize.H
|
||||||
|
yDirection = -1 - w.BoxThickness(1)
|
||||||
|
} else if anchor == E {
|
||||||
|
x = frameSize.W
|
||||||
|
xDirection = -1
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, packedWidget := range w.packs[anchor] {
|
||||||
|
child := packedWidget.widget
|
||||||
|
pack := packedWidget.pack
|
||||||
|
child.Compute(e)
|
||||||
|
var (
|
||||||
|
// point = child.Point()
|
||||||
|
size = child.Size()
|
||||||
|
yStep = y * yDirection
|
||||||
|
xStep = x * xDirection
|
||||||
|
)
|
||||||
|
|
||||||
|
if !w.FixedSize() {
|
||||||
|
log.Warn("not fixed")
|
||||||
|
if xStep+size.W+(pack.PadX*2) > maxWidth {
|
||||||
|
var old = maxWidth
|
||||||
|
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 {
|
||||||
|
maxHeight = yStep + size.H + (pack.PadY * 2)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if anchor.IsSouth() {
|
||||||
|
y -= size.H + (pack.PadY * 2)
|
||||||
|
}
|
||||||
|
if anchor.IsEast() {
|
||||||
|
x -= size.W + (pack.PadX * 2)
|
||||||
|
}
|
||||||
|
|
||||||
|
child.MoveTo(render.Point{
|
||||||
|
X: x + pack.PadX,
|
||||||
|
Y: y + pack.PadY,
|
||||||
|
})
|
||||||
|
|
||||||
|
if anchor.IsNorth() {
|
||||||
|
y += size.H + (pack.PadY * 2)
|
||||||
|
}
|
||||||
|
if anchor == W {
|
||||||
|
x += size.W + (pack.PadX * 2)
|
||||||
|
}
|
||||||
|
|
||||||
|
visited = append(visited, packedWidget)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Rescan all the widgets in this anchor to re-center them
|
||||||
|
// in their space.
|
||||||
|
for _, pw := range visited {
|
||||||
|
var (
|
||||||
|
child = pw.widget
|
||||||
|
pack = pw.pack
|
||||||
|
point = child.Point()
|
||||||
|
size = child.Size()
|
||||||
|
resized bool
|
||||||
|
moved bool
|
||||||
|
)
|
||||||
|
|
||||||
|
if pack.Anchor.IsNorth() || pack.Anchor.IsSouth() {
|
||||||
|
if pack.FillX && size.W < maxWidth {
|
||||||
|
size.W = maxWidth - w.BoxThickness(2)
|
||||||
|
resized = true
|
||||||
|
}
|
||||||
|
if size.W < maxWidth {
|
||||||
|
if pack.Anchor.IsCenter() {
|
||||||
|
point.X = (maxWidth / 2) - (size.W / 2)
|
||||||
|
} else if pack.Anchor.IsWest() {
|
||||||
|
point.X = pack.PadX
|
||||||
|
} else if pack.Anchor.IsEast() {
|
||||||
|
point.X = maxWidth - size.W - pack.PadX
|
||||||
|
}
|
||||||
|
|
||||||
|
moved = true
|
||||||
|
}
|
||||||
|
} else if pack.Anchor.IsWest() || pack.Anchor.IsEast() {
|
||||||
|
if pack.FillY && size.H < maxHeight {
|
||||||
|
size.H = maxHeight - w.BoxThickness(2)
|
||||||
|
resized = true
|
||||||
|
}
|
||||||
|
if size.H < maxHeight {
|
||||||
|
if pack.Anchor.IsMiddle() {
|
||||||
|
point.Y = (maxHeight / 2) - (size.H / 2)
|
||||||
|
} else if pack.Anchor.IsNorth() {
|
||||||
|
point.Y = pack.PadY
|
||||||
|
} else if pack.Anchor.IsSouth() {
|
||||||
|
point.Y = maxHeight - size.H - pack.PadY
|
||||||
|
}
|
||||||
|
moved = true
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
log.Error("unsupported pack.Anchor")
|
||||||
|
}
|
||||||
|
|
||||||
|
if resized {
|
||||||
|
child.Resize(size)
|
||||||
|
}
|
||||||
|
if moved {
|
||||||
|
child.MoveTo(point)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !w.FixedSize() {
|
||||||
|
w.Resize(render.Rect{
|
||||||
|
W: maxWidth + w.BoxThickness(2),
|
||||||
|
H: maxHeight + w.BoxThickness(2),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Present the Frame.
|
||||||
|
func (w *Frame) Present(e render.Engine) {
|
||||||
|
var (
|
||||||
|
P = w.Point()
|
||||||
|
S = w.Size()
|
||||||
|
)
|
||||||
|
|
||||||
|
// Draw the widget's border and everything.
|
||||||
|
w.DrawBox(e)
|
||||||
|
|
||||||
|
// Draw the background color.
|
||||||
|
e.DrawBox(w.Background(), render.Rect{
|
||||||
|
X: P.X + w.BoxThickness(1),
|
||||||
|
Y: P.Y + w.BoxThickness(1),
|
||||||
|
W: S.W - w.BoxThickness(2),
|
||||||
|
H: S.H - w.BoxThickness(2),
|
||||||
|
})
|
||||||
|
|
||||||
|
// Draw the widgets.
|
||||||
|
for _, child := range w.widgets {
|
||||||
|
p := child.Point()
|
||||||
|
child.MoveTo(render.NewPoint(
|
||||||
|
P.X+p.X+w.BoxThickness(1),
|
||||||
|
P.Y+p.Y+w.BoxThickness(1),
|
||||||
|
))
|
||||||
|
child.Present(e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pack provides configuration fields for Frame.Pack().
|
||||||
|
type Pack struct {
|
||||||
|
// Side of the parent to anchor the position to, like N, SE, W. Default
|
||||||
|
// is Center.
|
||||||
|
Anchor Anchor
|
||||||
|
|
||||||
|
// If the widget is smaller than its allocated space, grow the widget
|
||||||
|
// to fill its space in the Frame.
|
||||||
|
Fill bool
|
||||||
|
FillX bool
|
||||||
|
FillY bool
|
||||||
|
|
||||||
|
Padding int32 // Equal padding on X and Y.
|
||||||
|
PadX int32
|
||||||
|
PadY int32
|
||||||
|
// Expand bool // Widget should grow to fill any remaining space.
|
||||||
|
}
|
||||||
|
|
||||||
|
// Anchor is a cardinal direction.
|
||||||
|
type Anchor uint8
|
||||||
|
|
||||||
|
// Anchor values.
|
||||||
|
const (
|
||||||
|
Center Anchor = iota
|
||||||
|
N
|
||||||
|
NE
|
||||||
|
E
|
||||||
|
SE
|
||||||
|
S
|
||||||
|
SW
|
||||||
|
W
|
||||||
|
NW
|
||||||
|
)
|
||||||
|
|
||||||
|
// Range of Anchor values.
|
||||||
|
const (
|
||||||
|
AnchorMin = Center
|
||||||
|
AnchorMax = NW
|
||||||
|
)
|
||||||
|
|
||||||
|
// IsNorth returns if the anchor is N, NE or NW.
|
||||||
|
func (a Anchor) IsNorth() bool {
|
||||||
|
return a == N || a == NE || a == NW
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsSouth returns if the anchor is S, SE or SW.
|
||||||
|
func (a Anchor) IsSouth() bool {
|
||||||
|
return a == S || a == SE || a == SW
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsEast returns if the anchor is E, NE or SE.
|
||||||
|
func (a Anchor) IsEast() bool {
|
||||||
|
return a == E || a == NE || a == SE
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsWest returns if the anchor is W, NW or SW.
|
||||||
|
func (a Anchor) IsWest() bool {
|
||||||
|
return a == W || a == NW || a == SW
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsCenter returns if the anchor is Center, N or S, to determine
|
||||||
|
// whether to align text as centered for North/South anchors.
|
||||||
|
func (a Anchor) IsCenter() bool {
|
||||||
|
return a == Center || a == N || a == S
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsMiddle returns if the anchor is Center, E or W, to determine
|
||||||
|
// whether to align text as middled for East/West anchors.
|
||||||
|
func (a Anchor) IsMiddle() bool {
|
||||||
|
return a == Center || a == W || a == E
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pack a widget along a side of the frame.
|
||||||
|
func (w *Frame) Pack(child Widget, config ...Pack) {
|
||||||
|
var C Pack
|
||||||
|
if len(config) > 0 {
|
||||||
|
C = config[0]
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initialize the pack list for this anchor?
|
||||||
|
if _, ok := w.packs[C.Anchor]; !ok {
|
||||||
|
w.packs[C.Anchor] = []packedWidget{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Padding: if the user only provided Padding add it to both
|
||||||
|
// the X and Y value. If the user additionally provided the X
|
||||||
|
// and Y value, it will add to the base padding as you'd expect.
|
||||||
|
C.PadX += C.Padding
|
||||||
|
C.PadY += C.Padding
|
||||||
|
|
||||||
|
w.packs[C.Anchor] = append(w.packs[C.Anchor], packedWidget{
|
||||||
|
widget: child,
|
||||||
|
pack: C,
|
||||||
|
})
|
||||||
|
w.widgets = append(w.widgets, child)
|
||||||
|
}
|
||||||
|
|
||||||
|
type packLayout struct {
|
||||||
|
widgets []packedWidget
|
||||||
|
}
|
||||||
|
|
||||||
|
type packedWidget struct {
|
||||||
|
widget Widget
|
||||||
|
pack Pack
|
||||||
|
fill uint8
|
||||||
|
}
|
||||||
|
|
||||||
|
// packedWidget.fill values
|
||||||
|
const (
|
||||||
|
fillNone uint8 = iota
|
||||||
|
fillX
|
||||||
|
fillY
|
||||||
|
fillBoth
|
||||||
|
)
|
38
ui/label.go
38
ui/label.go
|
@ -1,6 +1,10 @@
|
||||||
package ui
|
package ui
|
||||||
|
|
||||||
import "git.kirsle.net/apps/doodle/render"
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"git.kirsle.net/apps/doodle/render"
|
||||||
|
)
|
||||||
|
|
||||||
// Label is a simple text label widget.
|
// Label is a simple text label widget.
|
||||||
type Label struct {
|
type Label struct {
|
||||||
|
@ -12,20 +16,40 @@ type Label struct {
|
||||||
|
|
||||||
// NewLabel creates a new label.
|
// NewLabel creates a new label.
|
||||||
func NewLabel(t render.Text) *Label {
|
func NewLabel(t render.Text) *Label {
|
||||||
return &Label{
|
w := &Label{
|
||||||
Text: t,
|
Text: t,
|
||||||
}
|
}
|
||||||
|
w.Configure(Config{
|
||||||
|
Padding: 4,
|
||||||
|
})
|
||||||
|
w.IDFunc(func() string {
|
||||||
|
return fmt.Sprintf("Label<%s>", w.Text.Text)
|
||||||
|
})
|
||||||
|
return w
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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, err := e.ComputeTextRect(w.Text)
|
rect, _ := e.ComputeTextRect(w.Text)
|
||||||
w.Resize(rect)
|
w.Resize(render.Rect{
|
||||||
_ = rect
|
W: rect.W + w.Padding(),
|
||||||
_ = err
|
H: rect.H + w.Padding(),
|
||||||
|
})
|
||||||
|
w.MoveTo(render.Point{
|
||||||
|
X: rect.X + w.BoxThickness(1),
|
||||||
|
Y: rect.Y + w.BoxThickness(1),
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// Present the label widget.
|
// Present the label widget.
|
||||||
func (w *Label) Present(e render.Engine) {
|
func (w *Label) Present(e render.Engine) {
|
||||||
e.DrawText(w.Text, w.Point())
|
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,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
14
ui/log.go
Normal file
14
ui/log.go
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
package ui
|
||||||
|
|
||||||
|
import "github.com/kirsle/golog"
|
||||||
|
|
||||||
|
var log *golog.Logger
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
log = golog.GetLogger("ui")
|
||||||
|
log.Configure(&golog.Config{
|
||||||
|
Level: golog.DebugLevel,
|
||||||
|
Theme: golog.DarkTheme,
|
||||||
|
Colors: golog.ExtendedColor,
|
||||||
|
})
|
||||||
|
}
|
|
@ -84,7 +84,8 @@ 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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -7,4 +7,6 @@ var (
|
||||||
ButtonBackgroundColor = render.RGBA(200, 200, 200, 255)
|
ButtonBackgroundColor = render.RGBA(200, 200, 200, 255)
|
||||||
ButtonHoverColor = render.RGBA(200, 255, 255, 255)
|
ButtonHoverColor = render.RGBA(200, 255, 255, 255)
|
||||||
ButtonOutlineColor = render.Black
|
ButtonOutlineColor = render.Black
|
||||||
|
|
||||||
|
BorderColorOffset int32 = 40
|
||||||
)
|
)
|
||||||
|
|
178
ui/widget.go
178
ui/widget.go
|
@ -2,6 +2,7 @@ package ui
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"git.kirsle.net/apps/doodle/render"
|
"git.kirsle.net/apps/doodle/render"
|
||||||
|
"git.kirsle.net/apps/doodle/ui/theme"
|
||||||
)
|
)
|
||||||
|
|
||||||
// BorderStyle options for widget.SetBorderStyle()
|
// BorderStyle options for widget.SetBorderStyle()
|
||||||
|
@ -16,10 +17,14 @@ const (
|
||||||
|
|
||||||
// Widget is a user interface element.
|
// Widget is a user interface element.
|
||||||
type Widget interface {
|
type Widget interface {
|
||||||
|
ID() string // Get the widget's string ID.
|
||||||
|
IDFunc(func() string) // Set a function that returns the widget's ID.
|
||||||
|
String() string
|
||||||
Point() render.Point
|
Point() render.Point
|
||||||
MoveTo(render.Point)
|
MoveTo(render.Point)
|
||||||
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)
|
||||||
Resize(render.Rect)
|
Resize(render.Rect)
|
||||||
|
|
||||||
Handle(string, func(render.Point))
|
Handle(string, func(render.Point))
|
||||||
|
@ -56,9 +61,34 @@ type Widget interface {
|
||||||
Present(render.Engine)
|
Present(render.Engine)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Config holds common base widget configs for quick configuration.
|
||||||
|
type Config struct {
|
||||||
|
// Size management. If you provide a non-zero value for Width and Height,
|
||||||
|
// the widget will be resized and the "fixedSize" flag is set, meaning it
|
||||||
|
// will not re-compute its size dynamically. To set the size while also
|
||||||
|
// keeping the auto-resize property, pass AutoResize=true too. This is
|
||||||
|
// mainly used internally when widgets are calculating their automatic sizes.
|
||||||
|
AutoResize bool
|
||||||
|
Width int32
|
||||||
|
Height int32
|
||||||
|
Padding int32
|
||||||
|
PadX int32
|
||||||
|
PadY int32
|
||||||
|
Background render.Color
|
||||||
|
Foreground render.Color
|
||||||
|
BorderSize int32
|
||||||
|
BorderStyle BorderStyle
|
||||||
|
BorderColor render.Color
|
||||||
|
OutlineSize int32
|
||||||
|
OutlineColor render.Color
|
||||||
|
}
|
||||||
|
|
||||||
// BaseWidget holds common functionality for all widgets, such as managing
|
// BaseWidget holds common functionality for all widgets, such as managing
|
||||||
// their widths and heights.
|
// their widths and heights.
|
||||||
type BaseWidget struct {
|
type BaseWidget struct {
|
||||||
|
id string
|
||||||
|
idFunc func() string
|
||||||
|
fixedSize bool
|
||||||
width int32
|
width int32
|
||||||
height int32
|
height int32
|
||||||
point render.Point
|
point render.Point
|
||||||
|
@ -73,6 +103,66 @@ type BaseWidget struct {
|
||||||
handlers map[string][]func(render.Point)
|
handlers map[string][]func(render.Point)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SetID sets a string name for your widget, helpful for debugging purposes.
|
||||||
|
func (w *BaseWidget) SetID(id string) {
|
||||||
|
w.id = id
|
||||||
|
}
|
||||||
|
|
||||||
|
// ID returns the ID that the widget calls itself by.
|
||||||
|
func (w *BaseWidget) ID() string {
|
||||||
|
if w.idFunc == nil {
|
||||||
|
w.IDFunc(func() string {
|
||||||
|
return "Widget<Untitled>"
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return w.idFunc()
|
||||||
|
}
|
||||||
|
|
||||||
|
// IDFunc sets an ID function.
|
||||||
|
func (w *BaseWidget) IDFunc(fn func() string) {
|
||||||
|
w.idFunc = fn
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *BaseWidget) String() string {
|
||||||
|
return w.ID()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Configure the base widget with all the common properties at once. Any
|
||||||
|
// property left as the zero value will not update the widget.
|
||||||
|
func (w *BaseWidget) Configure(c Config) {
|
||||||
|
if c.Width != 0 && c.Height != 0 {
|
||||||
|
w.fixedSize = !c.AutoResize
|
||||||
|
w.width = c.Width
|
||||||
|
w.height = c.Height
|
||||||
|
}
|
||||||
|
|
||||||
|
if c.Padding != 0 {
|
||||||
|
w.padding = c.Padding
|
||||||
|
}
|
||||||
|
if c.Background != render.Invisible {
|
||||||
|
w.background = c.Background
|
||||||
|
}
|
||||||
|
if c.Foreground != render.Invisible {
|
||||||
|
w.foreground = c.Foreground
|
||||||
|
}
|
||||||
|
if c.BorderColor != render.Invisible {
|
||||||
|
w.borderColor = c.BorderColor
|
||||||
|
}
|
||||||
|
if c.OutlineColor != render.Invisible {
|
||||||
|
w.outlineColor = c.OutlineColor
|
||||||
|
}
|
||||||
|
|
||||||
|
if c.BorderSize != 0 {
|
||||||
|
w.borderSize = c.BorderSize
|
||||||
|
}
|
||||||
|
if c.BorderStyle != BorderSolid {
|
||||||
|
w.borderStyle = c.BorderStyle
|
||||||
|
}
|
||||||
|
if c.OutlineSize != 0 {
|
||||||
|
w.outlineSize = c.OutlineSize
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Point returns the X,Y position of the widget on the window.
|
// Point returns the X,Y position of the widget on the window.
|
||||||
func (w *BaseWidget) Point() render.Point {
|
func (w *BaseWidget) Point() render.Point {
|
||||||
return w.point
|
return w.point
|
||||||
|
@ -98,8 +188,21 @@ func (w *BaseWidget) Size() render.Rect {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 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 {
|
||||||
|
return w.fixedSize
|
||||||
|
}
|
||||||
|
|
||||||
// Resize sets the size of the widget to the .W and .H attributes of a rect.
|
// Resize sets the size of the widget to the .W and .H attributes of a rect.
|
||||||
func (w *BaseWidget) Resize(v render.Rect) {
|
func (w *BaseWidget) Resize(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.
|
||||||
|
func (w *BaseWidget) resizeAuto(v render.Rect) {
|
||||||
w.width = v.W
|
w.width = v.W
|
||||||
w.height = v.H
|
w.height = v.H
|
||||||
}
|
}
|
||||||
|
@ -121,8 +224,8 @@ func (w *BaseWidget) DrawBox(e render.Engine) {
|
||||||
outline = w.OutlineSize()
|
outline = w.OutlineSize()
|
||||||
border = w.BorderSize()
|
border = w.BorderSize()
|
||||||
borderColor = w.BorderColor()
|
borderColor = w.BorderColor()
|
||||||
highlight = borderColor.Add(20, 20, 20, 0)
|
highlight = borderColor.Lighten(theme.BorderColorOffset)
|
||||||
shadow = borderColor.Add(-20, -20, -20, 0)
|
shadow = borderColor.Darken(theme.BorderColorOffset)
|
||||||
color render.Color
|
color render.Color
|
||||||
box = render.Rect{
|
box = render.Rect{
|
||||||
X: P.X,
|
X: P.X,
|
||||||
|
@ -132,46 +235,67 @@ func (w *BaseWidget) DrawBox(e render.Engine) {
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if borderColor == render.Invisible {
|
||||||
|
borderColor = render.Red
|
||||||
|
}
|
||||||
|
|
||||||
// Draw the outline layer as the full size of the widget.
|
// Draw the outline layer as the full size of the widget.
|
||||||
e.DrawBox(w.OutlineColor(), render.Rect{
|
if outline > 0 && w.OutlineColor() != render.Invisible {
|
||||||
X: P.X - outline,
|
e.DrawBox(w.OutlineColor(), render.Rect{
|
||||||
Y: P.Y - outline,
|
X: P.X,
|
||||||
W: S.W + (outline * 2),
|
Y: P.Y,
|
||||||
H: S.H + (outline * 2),
|
W: S.W,
|
||||||
})
|
H: S.H,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
box.X += outline
|
||||||
|
box.Y += outline
|
||||||
|
box.W -= outline * 2
|
||||||
|
box.H -= outline * 2
|
||||||
|
|
||||||
// Highlight on the top left edge.
|
// Highlight on the top left edge.
|
||||||
if w.BorderStyle() == BorderRaised {
|
if border > 0 {
|
||||||
color = highlight
|
if w.BorderStyle() == BorderRaised {
|
||||||
} else if w.BorderStyle() == BorderSunken {
|
color = highlight
|
||||||
color = shadow
|
} else if w.BorderStyle() == BorderSunken {
|
||||||
} else {
|
color = shadow
|
||||||
color = borderColor
|
} else {
|
||||||
|
color = borderColor
|
||||||
|
}
|
||||||
|
e.DrawBox(color, box)
|
||||||
}
|
}
|
||||||
e.DrawBox(color, box)
|
|
||||||
box.W = S.W
|
|
||||||
|
|
||||||
// Shadow on the bottom right edge.
|
// Shadow on the bottom right edge.
|
||||||
box.X += border
|
box.X += border
|
||||||
box.Y += border
|
box.Y += border
|
||||||
box.W -= border
|
box.W -= border
|
||||||
box.H -= border
|
box.H -= border
|
||||||
if w.BorderStyle() == BorderRaised {
|
if w.BorderSize() > 0 {
|
||||||
color = shadow
|
if w.BorderStyle() == BorderRaised {
|
||||||
} else if w.BorderStyle() == BorderSunken {
|
color = shadow
|
||||||
color = highlight
|
} else if w.BorderStyle() == BorderSunken {
|
||||||
} else {
|
color = highlight
|
||||||
color = borderColor
|
} else {
|
||||||
|
color = borderColor
|
||||||
|
}
|
||||||
|
e.DrawBox(color, box)
|
||||||
}
|
}
|
||||||
e.DrawBox(color.Add(-20, -20, -20, 0), box)
|
|
||||||
|
|
||||||
// Background color of the button.
|
// Background color of the button.
|
||||||
box.W -= border
|
box.W -= border
|
||||||
box.H -= border
|
box.H -= border
|
||||||
// if w.hovering {
|
if w.Background() != render.Invisible {
|
||||||
// e.DrawBox(render.Yellow, box)
|
e.DrawBox(w.Background(), box)
|
||||||
// } else {
|
}
|
||||||
e.DrawBox(color, 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.
|
// Padding returns the padding width.
|
||||||
|
|
Loading…
Reference in New Issue
Block a user