WIP: Broken UI - Reworking Frame Pack
This commit is contained in:
parent
fb8f4b1029
commit
8fb579e66e
9
lib/ui/constants.go
Normal file
9
lib/ui/constants.go
Normal file
|
@ -0,0 +1,9 @@
|
|||
package ui
|
||||
|
||||
// Constants for the UI toolkit.
|
||||
const (
|
||||
// Pack.Fill values.
|
||||
FillBoth = "both"
|
||||
FillX = "x"
|
||||
FillY = "y"
|
||||
)
|
104
lib/ui/eg/layout/main.go
Normal file
104
lib/ui/eg/layout/main.go
Normal file
|
@ -0,0 +1,104 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"git.kirsle.net/apps/doodle/lib/render"
|
||||
"git.kirsle.net/apps/doodle/lib/render/sdl"
|
||||
"git.kirsle.net/apps/doodle/lib/ui"
|
||||
)
|
||||
|
||||
var TargetFPS = 1000 / 60
|
||||
|
||||
func main() {
|
||||
engine := sdl.New("Test Layout GUI", 1024, 768)
|
||||
|
||||
if err := engine.Setup(); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
super := ui.NewSupervisor()
|
||||
|
||||
window := ui.NewWindow("Test Window")
|
||||
window.Configure(ui.Config{
|
||||
Width: 750,
|
||||
Height: 400,
|
||||
Background: render.Grey,
|
||||
})
|
||||
window.MoveTo(render.NewPoint(80, 80))
|
||||
|
||||
leftPanel := ui.NewFrame("Left Panel")
|
||||
leftPanel.Configure(ui.Config{
|
||||
// AutoResize: true,
|
||||
Width: 200,
|
||||
Background: render.SkyBlue,
|
||||
BorderStyle: ui.BorderRaised,
|
||||
BorderSize: 2,
|
||||
})
|
||||
window.Pack(leftPanel, ui.Pack{
|
||||
Side: ui.Left,
|
||||
Fill: ui.FillY,
|
||||
Expand: true,
|
||||
})
|
||||
|
||||
body := ui.NewFrame("Body Panel")
|
||||
body.Configure(ui.Config{
|
||||
Background: render.RGBA(255, 0, 0, 64),
|
||||
BorderStyle: ui.BorderSunken,
|
||||
BorderSize: 2,
|
||||
})
|
||||
window.Pack(body, ui.Pack{
|
||||
Side: ui.Left,
|
||||
Expand: true,
|
||||
})
|
||||
|
||||
label1 := ui.NewLabel(ui.Label{
|
||||
Text: "Hello world!",
|
||||
Font: render.Text{
|
||||
Size: 24,
|
||||
Color: render.Red,
|
||||
Stroke: render.Purple,
|
||||
},
|
||||
})
|
||||
body.Pack(label1, ui.Pack{
|
||||
Side: ui.Top,
|
||||
})
|
||||
|
||||
window.Frame().SetBackground(render.Yellow)
|
||||
window.Compute(engine)
|
||||
// window.Present(engine, window.Point())
|
||||
|
||||
super.Add(window)
|
||||
super.MainLoop(engine)
|
||||
//
|
||||
for true {
|
||||
|
||||
start := time.Now()
|
||||
engine.Clear(render.White)
|
||||
|
||||
// poll for events
|
||||
ev, err := engine.Poll()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
// escape key to close the window
|
||||
if ev.EscapeKey.Now {
|
||||
break
|
||||
}
|
||||
|
||||
super.Loop(ev)
|
||||
window.Compute(engine)
|
||||
window.Present(engine, window.Point())
|
||||
engine.Present()
|
||||
|
||||
// Delay to maintain the target frames per second.
|
||||
var delay uint32
|
||||
elapsed := time.Now().Sub(start)
|
||||
tmp := elapsed / time.Millisecond
|
||||
if TargetFPS-int(tmp) > 0 { // make sure it won't roll under
|
||||
delay = uint32(TargetFPS - int(tmp))
|
||||
}
|
||||
engine.Delay(delay)
|
||||
}
|
||||
}
|
|
@ -10,7 +10,7 @@ import (
|
|||
type Frame struct {
|
||||
Name string
|
||||
BaseWidget
|
||||
packs map[Anchor][]packedWidget
|
||||
packs map[Side][]packedWidget
|
||||
widgets []Widget
|
||||
}
|
||||
|
||||
|
@ -18,7 +18,7 @@ type Frame struct {
|
|||
func NewFrame(name string) *Frame {
|
||||
w := &Frame{
|
||||
Name: name,
|
||||
packs: map[Anchor][]packedWidget{},
|
||||
packs: map[Side][]packedWidget{},
|
||||
widgets: []Widget{},
|
||||
}
|
||||
w.IDFunc(func() string {
|
||||
|
@ -32,7 +32,7 @@ func NewFrame(name string) *Frame {
|
|||
// Setup ensures all the Frame's data is initialized and not null.
|
||||
func (w *Frame) Setup() {
|
||||
if w.packs == nil {
|
||||
w.packs = map[Anchor][]packedWidget{}
|
||||
w.packs = map[Side][]packedWidget{}
|
||||
}
|
||||
if w.widgets == nil {
|
||||
w.widgets = []Widget{}
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
package ui
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"git.kirsle.net/apps/doodle/lib/render"
|
||||
)
|
||||
|
||||
|
@ -9,12 +11,13 @@ type Pack struct {
|
|||
// Side of the parent to anchor the position to, like N, SE, W. Default
|
||||
// is Center.
|
||||
Anchor Anchor
|
||||
Side Side
|
||||
|
||||
// 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
|
||||
Fill string // "x", "y", "both" or "" for none
|
||||
fillX bool
|
||||
fillY bool
|
||||
|
||||
Padding int32 // Equal padding on X and Y.
|
||||
PadX int32
|
||||
|
@ -30,8 +33,8 @@ func (w *Frame) Pack(child Widget, config ...Pack) {
|
|||
}
|
||||
|
||||
// Initialize the pack list for this anchor?
|
||||
if _, ok := w.packs[C.Anchor]; !ok {
|
||||
w.packs[C.Anchor] = []packedWidget{}
|
||||
if _, ok := w.packs[C.Side]; !ok {
|
||||
w.packs[C.Side] = []packedWidget{}
|
||||
}
|
||||
|
||||
// Padding: if the user only provided Padding add it to both
|
||||
|
@ -40,16 +43,14 @@ func (w *Frame) Pack(child Widget, config ...Pack) {
|
|||
C.PadX += C.Padding
|
||||
C.PadY += C.Padding
|
||||
|
||||
// Fill: true implies both directions.
|
||||
if C.Fill {
|
||||
C.FillX = true
|
||||
C.FillY = true
|
||||
}
|
||||
// Cache the full X and Y booleans.
|
||||
C.fillX = C.Fill == FillX || C.Fill == FillBoth
|
||||
C.fillY = C.Fill == FillY || C.Fill == FillBoth
|
||||
|
||||
// Adopt the child widget so it can access the Frame.
|
||||
child.Adopt(w)
|
||||
|
||||
w.packs[C.Anchor] = append(w.packs[C.Anchor], packedWidget{
|
||||
w.packs[C.Side] = append(w.packs[C.Side], packedWidget{
|
||||
widget: child,
|
||||
pack: C,
|
||||
})
|
||||
|
@ -72,10 +73,10 @@ func (w *Frame) computePacked(e render.Engine) {
|
|||
expanded = []packedWidget{}
|
||||
)
|
||||
|
||||
// Iterate through all anchored directions and compute how much space to
|
||||
// Iterate through all packed sides and compute how much space to
|
||||
// reserve to contain all of their widgets.
|
||||
for anchor := AnchorMin; anchor <= AnchorMax; anchor++ {
|
||||
if _, ok := w.packs[anchor]; !ok {
|
||||
for side := SideMin; side <= SideMax; side++ {
|
||||
if _, ok := w.packs[side]; !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
|
@ -86,15 +87,15 @@ func (w *Frame) computePacked(e render.Engine) {
|
|||
xDirection int32 = 1
|
||||
)
|
||||
|
||||
if anchor.IsSouth() { // TODO: these need tuning
|
||||
if side == Bottom { // TODO: these need tuning
|
||||
y = frameSize.H - w.BoxThickness(4)
|
||||
yDirection = -1 * w.BoxThickness(4) // parent + child BoxThickness(1) = 2
|
||||
} else if anchor == E {
|
||||
} else if side == Right {
|
||||
x = frameSize.W - w.BoxThickness(4)
|
||||
xDirection = -1 - w.BoxThickness(4) // - w.BoxThickness(2)
|
||||
}
|
||||
|
||||
for _, packedWidget := range w.packs[anchor] {
|
||||
for _, packedWidget := range w.packs[side] {
|
||||
|
||||
child := packedWidget.widget
|
||||
pack := packedWidget.pack
|
||||
|
@ -121,19 +122,17 @@ func (w *Frame) computePacked(e render.Engine) {
|
|||
maxHeight = yStep + size.H + (pack.PadY * 2)
|
||||
}
|
||||
|
||||
if anchor.IsSouth() {
|
||||
if side == Bottom {
|
||||
y -= size.H - pack.PadY
|
||||
}
|
||||
if anchor.IsEast() {
|
||||
} else if side == Right {
|
||||
x -= size.W - pack.PadX
|
||||
}
|
||||
|
||||
child.MoveTo(render.NewPoint(x, y))
|
||||
|
||||
if anchor.IsNorth() {
|
||||
if side == Top {
|
||||
y += size.H + pack.PadY
|
||||
}
|
||||
if anchor == W {
|
||||
} else if side == Left {
|
||||
x += size.W + pack.PadX
|
||||
}
|
||||
|
||||
|
@ -147,6 +146,13 @@ func (w *Frame) computePacked(e render.Engine) {
|
|||
// If we have extra space in the Frame and any expanding widgets, let the
|
||||
// expanding widgets grow and share the remaining space.
|
||||
computedSize := render.NewRect(maxWidth, maxHeight)
|
||||
if w.fixedWidth > 0 {
|
||||
computedSize.W = w.fixedWidth
|
||||
}
|
||||
if w.fixedHeight > 0 {
|
||||
computedSize.H = w.fixedHeight
|
||||
}
|
||||
|
||||
if len(expanded) > 0 && !frameSize.IsZero() && frameSize.Bigger(computedSize) {
|
||||
// Divy up the size available.
|
||||
growBy := render.Rect{
|
||||
|
@ -154,7 +160,8 @@ func (w *Frame) computePacked(e render.Engine) {
|
|||
H: ((frameSize.H - computedSize.H) / int32(len(expanded))), // - w.BoxThickness(2),
|
||||
}
|
||||
for _, pw := range expanded {
|
||||
pw.widget.ResizeBy(growBy)
|
||||
fmt.Printf("expand %s by %s (comp size %s)\n", pw.widget.ID(), growBy, computedSize)
|
||||
pw.widget.ResizeAuto(growBy)
|
||||
pw.widget.Compute(e)
|
||||
}
|
||||
}
|
||||
|
@ -172,7 +179,7 @@ func (w *Frame) computePacked(e render.Engine) {
|
|||
}
|
||||
}
|
||||
|
||||
// Rescan all the widgets in this anchor to re-center them
|
||||
// Rescan all the widgets in this side to re-center them
|
||||
// in their space.
|
||||
innerFrameSize := render.NewRect(
|
||||
frameSize.W-w.BoxThickness(2),
|
||||
|
@ -189,8 +196,8 @@ func (w *Frame) computePacked(e render.Engine) {
|
|||
moved bool
|
||||
)
|
||||
|
||||
if pack.Anchor.IsNorth() || pack.Anchor.IsSouth() {
|
||||
if pack.FillX && resize.W < innerFrameSize.W {
|
||||
if pack.Side == Top || pack.Side == Bottom {
|
||||
if pack.fillX && resize.W < innerFrameSize.W {
|
||||
resize.W = innerFrameSize.W - w.BoxThickness(2)
|
||||
resized = true
|
||||
}
|
||||
|
@ -205,8 +212,8 @@ func (w *Frame) computePacked(e render.Engine) {
|
|||
|
||||
moved = true
|
||||
}
|
||||
} else if pack.Anchor.IsWest() || pack.Anchor.IsEast() {
|
||||
if pack.FillY && resize.H < innerFrameSize.H {
|
||||
} else if pack.Side == Left || pack.Side == Right {
|
||||
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
|
||||
|
@ -225,11 +232,12 @@ func (w *Frame) computePacked(e render.Engine) {
|
|||
moved = true
|
||||
}
|
||||
} else {
|
||||
panic("unsupported pack.Anchor")
|
||||
panic("unsupported pack.Side")
|
||||
}
|
||||
|
||||
if resized && size != resize {
|
||||
child.Resize(resize)
|
||||
fmt.Printf("fill: resize %s to %s\n", child.ID(), resize)
|
||||
child.ResizeAuto(resize)
|
||||
child.Compute(e)
|
||||
}
|
||||
if moved {
|
||||
|
@ -238,7 +246,7 @@ func (w *Frame) computePacked(e render.Engine) {
|
|||
}
|
||||
|
||||
// if !w.FixedSize() {
|
||||
w.Resize(render.NewRect(
|
||||
w.ResizeAuto(render.NewRect(
|
||||
frameSize.W-w.BoxThickness(2),
|
||||
frameSize.H-w.BoxThickness(2),
|
||||
))
|
||||
|
@ -248,7 +256,10 @@ func (w *Frame) computePacked(e render.Engine) {
|
|||
// Anchor is a cardinal direction.
|
||||
type Anchor uint8
|
||||
|
||||
// Anchor values.
|
||||
// Side of a parent widget to pack children against.
|
||||
type Side uint8
|
||||
|
||||
// Anchor and Side constants.
|
||||
const (
|
||||
Center Anchor = iota
|
||||
N
|
||||
|
@ -259,12 +270,20 @@ const (
|
|||
SW
|
||||
W
|
||||
NW
|
||||
|
||||
Top Side = iota
|
||||
Left
|
||||
Right
|
||||
Bottom
|
||||
)
|
||||
|
||||
// Range of Anchor values.
|
||||
// Range of Anchor and Side values.
|
||||
const (
|
||||
AnchorMin = Center
|
||||
AnchorMax = NW
|
||||
|
||||
SideMin = Top
|
||||
SideMax = Bottom
|
||||
)
|
||||
|
||||
// IsNorth returns if the anchor is N, NE or NW.
|
||||
|
|
|
@ -91,7 +91,7 @@ func (w *Label) Compute(e render.Engine) {
|
|||
)
|
||||
|
||||
if !w.FixedSize() {
|
||||
w.resizeAuto(render.Rect{
|
||||
w.ResizeAuto(render.Rect{
|
||||
W: maxRect.W + (padX * 2),
|
||||
H: maxRect.H + (padY * 2),
|
||||
})
|
||||
|
|
|
@ -3,6 +3,7 @@ package ui
|
|||
import (
|
||||
"errors"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"git.kirsle.net/apps/doodle/lib/events"
|
||||
"git.kirsle.net/apps/doodle/lib/render"
|
||||
|
@ -29,12 +30,13 @@ const (
|
|||
// interaction events such as mouse hovers and clicks in their general
|
||||
// vicinity.
|
||||
type Supervisor struct {
|
||||
lock sync.RWMutex
|
||||
serial int // ID number of each widget added in order
|
||||
widgets map[int]WidgetSlot // map of widget ID to WidgetSlot
|
||||
hovering map[int]interface{} // map of widgets under the cursor
|
||||
clicked map[int]interface{} // map of widgets being clicked
|
||||
dd *DragDrop
|
||||
lock sync.RWMutex
|
||||
targetFPS int
|
||||
serial int // ID number of each widget added in order
|
||||
widgets map[int]WidgetSlot // map of widget ID to WidgetSlot
|
||||
hovering map[int]interface{} // map of widgets under the cursor
|
||||
clicked map[int]interface{} // map of widgets being clicked
|
||||
dd *DragDrop
|
||||
}
|
||||
|
||||
// WidgetSlot holds a widget with a unique ID number in a sorted list.
|
||||
|
@ -46,10 +48,11 @@ type WidgetSlot struct {
|
|||
// NewSupervisor creates a supervisor.
|
||||
func NewSupervisor() *Supervisor {
|
||||
return &Supervisor{
|
||||
widgets: map[int]WidgetSlot{},
|
||||
hovering: map[int]interface{}{},
|
||||
clicked: map[int]interface{}{},
|
||||
dd: NewDragDrop(),
|
||||
targetFPS: 1000 / 60,
|
||||
widgets: map[int]WidgetSlot{},
|
||||
hovering: map[int]interface{}{},
|
||||
clicked: map[int]interface{}{},
|
||||
dd: NewDragDrop(),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -75,6 +78,43 @@ var (
|
|||
ErrStopPropagation = errors.New("stop all event propagation")
|
||||
)
|
||||
|
||||
// MainLoop starts the UI main loop, for UI-only applications.
|
||||
func (s *Supervisor) MainLoop(e render.Engine) error {
|
||||
for true {
|
||||
start := time.Now()
|
||||
e.Clear(render.Green)
|
||||
|
||||
// Poll for events.
|
||||
ev, err := e.Poll()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// TODO: escape key to exit the main loop
|
||||
if ev.EscapeKey.Now {
|
||||
return nil
|
||||
}
|
||||
|
||||
s.Loop(ev)
|
||||
|
||||
// Render the widgets under our care.
|
||||
s.Present(e)
|
||||
|
||||
// Commit the pixels to screen.
|
||||
e.Present()
|
||||
|
||||
// Delay to maintain the target FPS.
|
||||
var delay uint32
|
||||
elapsed := time.Now().Sub(start)
|
||||
tmp := elapsed / time.Millisecond
|
||||
if s.targetFPS-int(tmp) > 0 {
|
||||
delay = uint32(s.targetFPS - int(tmp))
|
||||
}
|
||||
e.Delay(delay)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Loop to check events and pass them to managed widgets.
|
||||
//
|
||||
// Useful errors returned by this may be:
|
||||
|
@ -211,12 +251,15 @@ func (s *Supervisor) Present(e render.Engine) {
|
|||
}
|
||||
|
||||
// Add a widget to be supervised.
|
||||
func (s *Supervisor) Add(w Widget) {
|
||||
func (s *Supervisor) Add(w ...Widget) {
|
||||
s.lock.Lock()
|
||||
s.widgets[s.serial] = WidgetSlot{
|
||||
id: s.serial,
|
||||
widget: w,
|
||||
|
||||
for _, child := range w {
|
||||
s.widgets[s.serial] = WidgetSlot{
|
||||
id: s.serial,
|
||||
widget: child,
|
||||
}
|
||||
s.serial++
|
||||
}
|
||||
s.serial++
|
||||
s.lock.Unlock()
|
||||
}
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
package ui
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"git.kirsle.net/apps/doodle/lib/render"
|
||||
"git.kirsle.net/apps/doodle/lib/ui/theme"
|
||||
)
|
||||
|
@ -24,11 +26,13 @@ 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)
|
||||
BoxSize() render.Rect // Return the full size including the border and outline.
|
||||
Size() render.Rect // Return the Width and Height of the widget.
|
||||
FixedSize() bool // Return whether the size is fixed (true) or automatic (false)
|
||||
FixedSizes() (int32, int32) // Return whether the W and H are fixed in size
|
||||
BoxSize() render.Rect // Return the full size including the border and outline.
|
||||
Resize(render.Rect)
|
||||
ResizeBy(render.Rect)
|
||||
ResizeAuto(render.Rect)
|
||||
Rect() render.Rect // Return the full absolute rect combining the Size() and Point()
|
||||
|
||||
Handle(Event, func(render.Point))
|
||||
|
@ -107,6 +111,8 @@ type BaseWidget struct {
|
|||
hidden bool
|
||||
width int32
|
||||
height int32
|
||||
fixedWidth int32 // values manually configured by the user,
|
||||
fixedHeight int32 // and do not change
|
||||
point render.Point
|
||||
margin int32
|
||||
background render.Color
|
||||
|
@ -152,9 +158,11 @@ func (w *BaseWidget) Configure(c Config) {
|
|||
w.fixedSize = !c.AutoResize
|
||||
if c.Width != 0 {
|
||||
w.width = c.Width
|
||||
w.fixedWidth = w.width
|
||||
}
|
||||
if c.Height != 0 {
|
||||
w.height = c.Height
|
||||
w.fixedHeight = w.height
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -235,6 +243,12 @@ func (w *BaseWidget) FixedSize() bool {
|
|||
return w.fixedSize
|
||||
}
|
||||
|
||||
// FixedSizes returns whether the widget's Width or Height were manually set
|
||||
// to hard-coded values, respectively.
|
||||
func (w *BaseWidget) FixedSizes() (int32, int32) {
|
||||
return w.fixedWidth, w.fixedHeight
|
||||
}
|
||||
|
||||
// Resize sets the size of the widget to the .W and .H attributes of a rect.
|
||||
func (w *BaseWidget) Resize(v render.Rect) {
|
||||
w.fixedSize = true
|
||||
|
@ -249,10 +263,17 @@ func (w *BaseWidget) ResizeBy(v render.Rect) {
|
|||
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.height = v.H
|
||||
// ResizeAuto sets the size of the widget but doesn't set the fixedSize flag.
|
||||
func (w *BaseWidget) ResizeAuto(v render.Rect) {
|
||||
if w.fixedWidth == 0 {
|
||||
w.width = v.W
|
||||
}
|
||||
if w.fixedHeight == 0 {
|
||||
w.height = v.H
|
||||
}
|
||||
fmt.Printf("ResizeAuto(%s): fixed size=%d,%d new size=%s\n",
|
||||
w.ID(), w.fixedWidth, w.fixedHeight, w.Size(),
|
||||
)
|
||||
}
|
||||
|
||||
// BoxThickness returns the full sum of the padding, border and outline.
|
||||
|
|
|
@ -50,8 +50,8 @@ func NewWindow(title string) *Window {
|
|||
Background: render.Blue,
|
||||
})
|
||||
w.body.Pack(titleBar, Pack{
|
||||
Anchor: N,
|
||||
Fill: true,
|
||||
Side: Top,
|
||||
Fill: FillX,
|
||||
})
|
||||
w.titleBar = titleBar
|
||||
|
||||
|
@ -61,8 +61,8 @@ func NewWindow(title string) *Window {
|
|||
Background: render.Grey,
|
||||
})
|
||||
w.body.Pack(content, Pack{
|
||||
Anchor: N,
|
||||
Fill: true,
|
||||
Side: Top,
|
||||
Fill: FillBoth,
|
||||
})
|
||||
w.content = content
|
||||
|
||||
|
@ -81,6 +81,11 @@ func (w *Window) TitleBar() *Label {
|
|||
return w.titleBar
|
||||
}
|
||||
|
||||
// Frame returns the content frame of the window.
|
||||
func (w *Window) Frame() *Frame {
|
||||
return w.content
|
||||
}
|
||||
|
||||
// Configure the widget. Color and style changes are passed down to the inner
|
||||
// content frame of the window.
|
||||
func (w *Window) Configure(C Config) {
|
||||
|
@ -100,7 +105,23 @@ func (w *Window) ConfigureTitle(C Config) {
|
|||
|
||||
// Compute the window.
|
||||
func (w *Window) Compute(e render.Engine) {
|
||||
var size = w.Size()
|
||||
|
||||
w.body.Compute(e)
|
||||
|
||||
// Assign a manual Height to the title bar using its naturally computed
|
||||
// height, but leave the Width empty so the frame packer can stretch it
|
||||
// horizontally.
|
||||
w.titleBar.Configure(Config{
|
||||
Height: w.titleBar.Size().H,
|
||||
})
|
||||
|
||||
// Shrink down the content frame to leave room for the title bar.
|
||||
w.content.Resize(render.Rect{
|
||||
W: size.W - w.BoxThickness(2) - w.titleBar.BoxThickness(2),
|
||||
H: size.H - w.titleBar.Size().H - w.BoxThickness(4) -
|
||||
((w.titleBar.Font.Padding + w.titleBar.Font.PadY) * 2),
|
||||
})
|
||||
}
|
||||
|
||||
// Present the window.
|
||||
|
|
|
@ -16,7 +16,7 @@ type GUITestScene struct {
|
|||
|
||||
// Private widgets.
|
||||
Frame *ui.Frame
|
||||
Window *ui.Frame
|
||||
Window *ui.Window
|
||||
}
|
||||
|
||||
// Name of the scene.
|
||||
|
@ -28,8 +28,7 @@ func (s *GUITestScene) Name() string {
|
|||
func (s *GUITestScene) Setup(d *Doodle) error {
|
||||
s.Supervisor = ui.NewSupervisor()
|
||||
|
||||
window := ui.NewFrame("window")
|
||||
s.Window = window
|
||||
window := ui.NewWindow("Widget Toolkit")
|
||||
window.Configure(ui.Config{
|
||||
Width: 750,
|
||||
Height: 450,
|
||||
|
@ -37,33 +36,7 @@ func (s *GUITestScene) Setup(d *Doodle) error {
|
|||
BorderStyle: ui.BorderRaised,
|
||||
BorderSize: 2,
|
||||
})
|
||||
|
||||
// Title Bar
|
||||
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,
|
||||
})
|
||||
window.Pack(titleBar, ui.Pack{
|
||||
Anchor: ui.N,
|
||||
Fill: true,
|
||||
})
|
||||
|
||||
// Window Body
|
||||
body := ui.NewFrame("Window Body")
|
||||
body.Configure(ui.Config{
|
||||
Background: render.Yellow,
|
||||
})
|
||||
window.Pack(body, ui.Pack{
|
||||
Anchor: ui.N,
|
||||
Expand: true,
|
||||
})
|
||||
s.Window = window
|
||||
|
||||
// Left Frame
|
||||
leftFrame := ui.NewFrame("Left Frame")
|
||||
|
@ -73,9 +46,9 @@ func (s *GUITestScene) Setup(d *Doodle) error {
|
|||
BorderSize: 4,
|
||||
Width: 100,
|
||||
})
|
||||
body.Pack(leftFrame, ui.Pack{
|
||||
Anchor: ui.W,
|
||||
FillY: true,
|
||||
window.Pack(leftFrame, ui.Pack{
|
||||
Side: ui.Left,
|
||||
FillY: true,
|
||||
})
|
||||
|
||||
// Some left frame buttons.
|
||||
|
@ -89,9 +62,9 @@ func (s *GUITestScene) Setup(d *Doodle) error {
|
|||
})
|
||||
s.Supervisor.Add(btn)
|
||||
leftFrame.Pack(btn, ui.Pack{
|
||||
Anchor: ui.N,
|
||||
FillX: true,
|
||||
PadY: 2,
|
||||
Side: ui.Top,
|
||||
FillX: true,
|
||||
PadY: 2,
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -101,8 +74,8 @@ func (s *GUITestScene) Setup(d *Doodle) error {
|
|||
Background: render.White,
|
||||
BorderSize: 0,
|
||||
})
|
||||
body.Pack(frame, ui.Pack{
|
||||
Anchor: ui.W,
|
||||
window.Pack(frame, ui.Pack{
|
||||
Side: ui.Left,
|
||||
Expand: true,
|
||||
Fill: true,
|
||||
})
|
||||
|
@ -115,9 +88,9 @@ func (s *GUITestScene) Setup(d *Doodle) error {
|
|||
BorderSize: 2,
|
||||
Width: 80,
|
||||
})
|
||||
body.Pack(rightFrame, ui.Pack{
|
||||
Anchor: ui.W,
|
||||
Fill: true,
|
||||
window.Pack(rightFrame, ui.Pack{
|
||||
Side: ui.Right,
|
||||
Fill: true,
|
||||
})
|
||||
|
||||
// A grid of buttons.
|
||||
|
@ -139,7 +112,7 @@ func (s *GUITestScene) Setup(d *Doodle) error {
|
|||
d.Flash("%s clicked", btn)
|
||||
})
|
||||
rowFrame.Pack(btn, ui.Pack{
|
||||
Anchor: ui.W,
|
||||
Side: ui.Left,
|
||||
Expand: true,
|
||||
FillX: true,
|
||||
})
|
||||
|
@ -147,8 +120,8 @@ func (s *GUITestScene) Setup(d *Doodle) error {
|
|||
})(row, col, rowFrame)
|
||||
}
|
||||
rightFrame.Pack(rowFrame, ui.Pack{
|
||||
Anchor: ui.N,
|
||||
Fill: true,
|
||||
Side: ui.Top,
|
||||
Fill: true,
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -160,7 +133,8 @@ func (s *GUITestScene) Setup(d *Doodle) error {
|
|||
Color: render.Black,
|
||||
},
|
||||
}), ui.Pack{
|
||||
Anchor: ui.NW,
|
||||
Side: ui.Top,
|
||||
Anchor: ui.W,
|
||||
Padding: 2,
|
||||
})
|
||||
|
||||
|
@ -172,7 +146,8 @@ func (s *GUITestScene) Setup(d *Doodle) error {
|
|||
}),
|
||||
)
|
||||
frame.Pack(cb, ui.Pack{
|
||||
Anchor: ui.NW,
|
||||
Side: ui.Top,
|
||||
Anchor: ui.W,
|
||||
Padding: 4,
|
||||
})
|
||||
cb.Supervise(s.Supervisor)
|
||||
|
@ -184,7 +159,8 @@ func (s *GUITestScene) Setup(d *Doodle) error {
|
|||
Color: render.Red,
|
||||
},
|
||||
}), ui.Pack{
|
||||
Anchor: ui.SE,
|
||||
Side: ui.Bottom,
|
||||
Anchor: ui.E,
|
||||
Padding: 8,
|
||||
})
|
||||
frame.Pack(ui.NewLabel(ui.Label{
|
||||
|
@ -194,7 +170,8 @@ func (s *GUITestScene) Setup(d *Doodle) error {
|
|||
Color: render.Blue,
|
||||
},
|
||||
}), ui.Pack{
|
||||
Anchor: ui.SE,
|
||||
Side: ui.Bottom,
|
||||
Anchor: ui.E,
|
||||
Padding: 8,
|
||||
})
|
||||
|
||||
|
@ -204,7 +181,7 @@ func (s *GUITestScene) Setup(d *Doodle) error {
|
|||
Background: render.Grey,
|
||||
})
|
||||
window.Pack(btnFrame, ui.Pack{
|
||||
Anchor: ui.N,
|
||||
Side: ui.Top,
|
||||
})
|
||||
|
||||
button1 := ui.NewButton("Button1", ui.NewLabel(ui.Label{
|
||||
|
@ -228,13 +205,12 @@ func (s *GUITestScene) Setup(d *Doodle) error {
|
|||
})
|
||||
})
|
||||
|
||||
var align = ui.W
|
||||
btnFrame.Pack(button1, ui.Pack{
|
||||
Anchor: align,
|
||||
Side: ui.Left,
|
||||
Padding: 20,
|
||||
})
|
||||
btnFrame.Pack(button2, ui.Pack{
|
||||
Anchor: align,
|
||||
Side: ui.Left,
|
||||
Padding: 20,
|
||||
})
|
||||
|
||||
|
|
Loading…
Reference in New Issue
Block a user