2018-08-02 02:52:09 +00:00
|
|
|
package ui
|
|
|
|
|
2019-04-10 00:35:44 +00:00
|
|
|
import (
|
2019-04-19 20:51:27 +00:00
|
|
|
"fmt"
|
|
|
|
|
2019-04-10 00:35:44 +00:00
|
|
|
"git.kirsle.net/apps/doodle/lib/render"
|
|
|
|
)
|
2018-08-02 02:52:09 +00:00
|
|
|
|
2018-10-08 20:06:42 +00:00
|
|
|
// 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
|
2019-04-19 20:51:27 +00:00
|
|
|
Side Side
|
2018-10-08 20:06:42 +00:00
|
|
|
|
|
|
|
// If the widget is smaller than its allocated space, grow the widget
|
|
|
|
// to fill its space in the Frame.
|
2019-04-19 20:51:27 +00:00
|
|
|
Fill string // "x", "y", "both" or "" for none
|
|
|
|
fillX bool
|
|
|
|
fillY bool
|
2018-10-08 20:06:42 +00:00
|
|
|
|
|
|
|
Padding int32 // Equal padding on X and Y.
|
|
|
|
PadX int32
|
|
|
|
PadY int32
|
|
|
|
Expand bool // Widget should grow its allocated space to better fill the parent.
|
|
|
|
}
|
|
|
|
|
|
|
|
// 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?
|
2019-04-19 20:51:27 +00:00
|
|
|
if _, ok := w.packs[C.Side]; !ok {
|
|
|
|
w.packs[C.Side] = []packedWidget{}
|
2018-10-08 20:06:42 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// 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
|
|
|
|
|
2019-04-19 20:51:27 +00:00
|
|
|
// Cache the full X and Y booleans.
|
|
|
|
C.fillX = C.Fill == FillX || C.Fill == FillBoth
|
|
|
|
C.fillY = C.Fill == FillY || C.Fill == FillBoth
|
2018-10-08 20:06:42 +00:00
|
|
|
|
|
|
|
// Adopt the child widget so it can access the Frame.
|
|
|
|
child.Adopt(w)
|
|
|
|
|
2019-04-19 20:51:27 +00:00
|
|
|
w.packs[C.Side] = append(w.packs[C.Side], packedWidget{
|
2018-10-08 20:06:42 +00:00
|
|
|
widget: child,
|
|
|
|
pack: C,
|
|
|
|
})
|
|
|
|
w.widgets = append(w.widgets, child)
|
|
|
|
}
|
|
|
|
|
2018-08-02 02:52:09 +00:00
|
|
|
// computePacked processes all the Pack layout widgets in the Frame.
|
|
|
|
func (w *Frame) computePacked(e render.Engine) {
|
|
|
|
var (
|
2018-08-05 19:54:57 +00:00
|
|
|
frameSize = w.BoxSize()
|
2018-08-02 02:52:09 +00:00
|
|
|
|
|
|
|
// maxWidth and maxHeight are always the computed minimum dimensions
|
|
|
|
// that the Frame must be to contain all of its children. If the Frame
|
|
|
|
// was configured with an explicit Size, the Frame will be that Size,
|
|
|
|
// but we still calculate how much space the widgets _actually_ take
|
|
|
|
// so we can expand them to fill remaining space in fixed size widgets.
|
|
|
|
maxWidth int32
|
|
|
|
maxHeight int32
|
|
|
|
visited = []packedWidget{}
|
|
|
|
expanded = []packedWidget{}
|
|
|
|
)
|
|
|
|
|
2019-04-19 20:51:27 +00:00
|
|
|
// Iterate through all packed sides and compute how much space to
|
2018-08-02 02:52:09 +00:00
|
|
|
// reserve to contain all of their widgets.
|
2019-04-19 20:51:27 +00:00
|
|
|
for side := SideMin; side <= SideMax; side++ {
|
|
|
|
if _, ok := w.packs[side]; !ok {
|
2018-08-02 02:52:09 +00:00
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
var (
|
|
|
|
x int32
|
|
|
|
y int32
|
|
|
|
yDirection int32 = 1
|
|
|
|
xDirection int32 = 1
|
|
|
|
)
|
|
|
|
|
2019-04-19 20:51:27 +00:00
|
|
|
if side == Bottom { // TODO: these need tuning
|
2018-10-08 17:38:49 +00:00
|
|
|
y = frameSize.H - w.BoxThickness(4)
|
|
|
|
yDirection = -1 * w.BoxThickness(4) // parent + child BoxThickness(1) = 2
|
2019-04-19 20:51:27 +00:00
|
|
|
} else if side == Right {
|
2018-10-08 17:38:49 +00:00
|
|
|
x = frameSize.W - w.BoxThickness(4)
|
|
|
|
xDirection = -1 - w.BoxThickness(4) // - w.BoxThickness(2)
|
2018-08-02 02:52:09 +00:00
|
|
|
}
|
|
|
|
|
2019-04-19 20:51:27 +00:00
|
|
|
for _, packedWidget := range w.packs[side] {
|
Menu Toolbar for Editor + Shell Prompts + Theme
* Added a "menu toolbar" to the top of the Edit Mode with useful buttons
that work: New Level, New Doodad (same thing), Save, Save as, Open.
* Added ability for the dev console to prompt the user for a question,
which opens the console automatically. "Save", "Save as" and "Load"
ask for their filenames this way.
* Started groundwork for theming the app. The palette window is a light
brown with an orange title bar, the Menu Toolbar has a black
background, etc.
* Added support for multiple fonts instead of just monospace. DejaVu
Sans (normal and bold) are used now for most labels and window titles,
respectively. The dev console uses DejaVu Sans Mono as before.
* Update ui.Label to accept PadX and PadY separately instead of only
having the Padding option which did both.
* Improvements to Frame packing algorithm.
* Set the SDL draw mode to BLEND so we can use alpha colors properly,
so now the dev console is semi-translucent.
2018-08-12 00:30:00 +00:00
|
|
|
|
2018-08-02 02:52:09 +00:00
|
|
|
child := packedWidget.widget
|
|
|
|
pack := packedWidget.pack
|
|
|
|
child.Compute(e)
|
Menu Toolbar for Editor + Shell Prompts + Theme
* Added a "menu toolbar" to the top of the Edit Mode with useful buttons
that work: New Level, New Doodad (same thing), Save, Save as, Open.
* Added ability for the dev console to prompt the user for a question,
which opens the console automatically. "Save", "Save as" and "Load"
ask for their filenames this way.
* Started groundwork for theming the app. The palette window is a light
brown with an orange title bar, the Menu Toolbar has a black
background, etc.
* Added support for multiple fonts instead of just monospace. DejaVu
Sans (normal and bold) are used now for most labels and window titles,
respectively. The dev console uses DejaVu Sans Mono as before.
* Update ui.Label to accept PadX and PadY separately instead of only
having the Padding option which did both.
* Improvements to Frame packing algorithm.
* Set the SDL draw mode to BLEND so we can use alpha colors properly,
so now the dev console is semi-translucent.
2018-08-12 00:30:00 +00:00
|
|
|
|
2018-10-08 20:06:42 +00:00
|
|
|
if child.Hidden() {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
Menu Toolbar for Editor + Shell Prompts + Theme
* Added a "menu toolbar" to the top of the Edit Mode with useful buttons
that work: New Level, New Doodad (same thing), Save, Save as, Open.
* Added ability for the dev console to prompt the user for a question,
which opens the console automatically. "Save", "Save as" and "Load"
ask for their filenames this way.
* Started groundwork for theming the app. The palette window is a light
brown with an orange title bar, the Menu Toolbar has a black
background, etc.
* Added support for multiple fonts instead of just monospace. DejaVu
Sans (normal and bold) are used now for most labels and window titles,
respectively. The dev console uses DejaVu Sans Mono as before.
* Update ui.Label to accept PadX and PadY separately instead of only
having the Padding option which did both.
* Improvements to Frame packing algorithm.
* Set the SDL draw mode to BLEND so we can use alpha colors properly,
so now the dev console is semi-translucent.
2018-08-12 00:30:00 +00:00
|
|
|
x += pack.PadX
|
|
|
|
y += pack.PadY
|
|
|
|
|
2018-08-02 02:52:09 +00:00
|
|
|
var (
|
|
|
|
// point = child.Point()
|
Menu Toolbar for Editor + Shell Prompts + Theme
* Added a "menu toolbar" to the top of the Edit Mode with useful buttons
that work: New Level, New Doodad (same thing), Save, Save as, Open.
* Added ability for the dev console to prompt the user for a question,
which opens the console automatically. "Save", "Save as" and "Load"
ask for their filenames this way.
* Started groundwork for theming the app. The palette window is a light
brown with an orange title bar, the Menu Toolbar has a black
background, etc.
* Added support for multiple fonts instead of just monospace. DejaVu
Sans (normal and bold) are used now for most labels and window titles,
respectively. The dev console uses DejaVu Sans Mono as before.
* Update ui.Label to accept PadX and PadY separately instead of only
having the Padding option which did both.
* Improvements to Frame packing algorithm.
* Set the SDL draw mode to BLEND so we can use alpha colors properly,
so now the dev console is semi-translucent.
2018-08-12 00:30:00 +00:00
|
|
|
size = child.Size()
|
2018-08-02 02:52:09 +00:00
|
|
|
yStep = y * yDirection
|
|
|
|
xStep = x * xDirection
|
|
|
|
)
|
|
|
|
|
|
|
|
if xStep+size.W+(pack.PadX*2) > maxWidth {
|
|
|
|
maxWidth = xStep + size.W + (pack.PadX * 2)
|
|
|
|
}
|
|
|
|
if yStep+size.H+(pack.PadY*2) > maxHeight {
|
|
|
|
maxHeight = yStep + size.H + (pack.PadY * 2)
|
|
|
|
}
|
|
|
|
|
2019-04-19 20:51:27 +00:00
|
|
|
if side == Bottom {
|
2018-10-08 17:38:49 +00:00
|
|
|
y -= size.H - pack.PadY
|
2019-04-19 20:51:27 +00:00
|
|
|
} else if side == Right {
|
2018-10-08 17:38:49 +00:00
|
|
|
x -= size.W - pack.PadX
|
2018-08-02 02:52:09 +00:00
|
|
|
}
|
|
|
|
|
2018-08-05 19:54:57 +00:00
|
|
|
child.MoveTo(render.NewPoint(x, y))
|
2018-08-02 02:52:09 +00:00
|
|
|
|
2019-04-19 20:51:27 +00:00
|
|
|
if side == Top {
|
Menu Toolbar for Editor + Shell Prompts + Theme
* Added a "menu toolbar" to the top of the Edit Mode with useful buttons
that work: New Level, New Doodad (same thing), Save, Save as, Open.
* Added ability for the dev console to prompt the user for a question,
which opens the console automatically. "Save", "Save as" and "Load"
ask for their filenames this way.
* Started groundwork for theming the app. The palette window is a light
brown with an orange title bar, the Menu Toolbar has a black
background, etc.
* Added support for multiple fonts instead of just monospace. DejaVu
Sans (normal and bold) are used now for most labels and window titles,
respectively. The dev console uses DejaVu Sans Mono as before.
* Update ui.Label to accept PadX and PadY separately instead of only
having the Padding option which did both.
* Improvements to Frame packing algorithm.
* Set the SDL draw mode to BLEND so we can use alpha colors properly,
so now the dev console is semi-translucent.
2018-08-12 00:30:00 +00:00
|
|
|
y += size.H + pack.PadY
|
2019-04-19 20:51:27 +00:00
|
|
|
} else if side == Left {
|
Menu Toolbar for Editor + Shell Prompts + Theme
* Added a "menu toolbar" to the top of the Edit Mode with useful buttons
that work: New Level, New Doodad (same thing), Save, Save as, Open.
* Added ability for the dev console to prompt the user for a question,
which opens the console automatically. "Save", "Save as" and "Load"
ask for their filenames this way.
* Started groundwork for theming the app. The palette window is a light
brown with an orange title bar, the Menu Toolbar has a black
background, etc.
* Added support for multiple fonts instead of just monospace. DejaVu
Sans (normal and bold) are used now for most labels and window titles,
respectively. The dev console uses DejaVu Sans Mono as before.
* Update ui.Label to accept PadX and PadY separately instead of only
having the Padding option which did both.
* Improvements to Frame packing algorithm.
* Set the SDL draw mode to BLEND so we can use alpha colors properly,
so now the dev console is semi-translucent.
2018-08-12 00:30:00 +00:00
|
|
|
x += size.W + pack.PadX
|
2018-08-02 02:52:09 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
visited = append(visited, packedWidget)
|
2018-10-08 17:38:49 +00:00
|
|
|
if pack.Expand { // TODO: don't fuck with children of fixed size
|
2018-08-02 02:52:09 +00:00
|
|
|
expanded = append(expanded, packedWidget)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// 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)
|
2019-04-19 20:51:27 +00:00
|
|
|
if w.fixedWidth > 0 {
|
|
|
|
computedSize.W = w.fixedWidth
|
|
|
|
}
|
|
|
|
if w.fixedHeight > 0 {
|
|
|
|
computedSize.H = w.fixedHeight
|
|
|
|
}
|
|
|
|
|
2018-08-02 02:52:09 +00:00
|
|
|
if len(expanded) > 0 && !frameSize.IsZero() && frameSize.Bigger(computedSize) {
|
|
|
|
// Divy up the size available.
|
|
|
|
growBy := render.Rect{
|
2018-08-05 19:54:57 +00:00
|
|
|
W: ((frameSize.W - computedSize.W) / int32(len(expanded))), // - w.BoxThickness(2),
|
|
|
|
H: ((frameSize.H - computedSize.H) / int32(len(expanded))), // - w.BoxThickness(2),
|
2018-08-02 02:52:09 +00:00
|
|
|
}
|
|
|
|
for _, pw := range expanded {
|
2019-04-19 20:51:27 +00:00
|
|
|
fmt.Printf("expand %s by %s (comp size %s)\n", pw.widget.ID(), growBy, computedSize)
|
|
|
|
pw.widget.ResizeAuto(growBy)
|
2018-08-02 02:52:09 +00:00
|
|
|
pw.widget.Compute(e)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// If we're not using a fixed Frame size, use the dynamically computed one.
|
|
|
|
if !w.FixedSize() {
|
|
|
|
frameSize = render.NewRect(maxWidth, maxHeight)
|
2018-08-05 19:54:57 +00:00
|
|
|
} 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
|
|
|
|
}
|
2018-08-02 02:52:09 +00:00
|
|
|
}
|
|
|
|
|
2019-04-19 20:51:27 +00:00
|
|
|
// Rescan all the widgets in this side to re-center them
|
2018-08-02 02:52:09 +00:00
|
|
|
// in their space.
|
2018-08-05 19:54:57 +00:00
|
|
|
innerFrameSize := render.NewRect(
|
|
|
|
frameSize.W-w.BoxThickness(2),
|
|
|
|
frameSize.H-w.BoxThickness(2),
|
|
|
|
)
|
2018-08-02 02:52:09 +00:00
|
|
|
for _, pw := range visited {
|
|
|
|
var (
|
|
|
|
child = pw.widget
|
|
|
|
pack = pw.pack
|
|
|
|
point = child.Point()
|
|
|
|
size = child.Size()
|
|
|
|
resize = size
|
|
|
|
resized bool
|
|
|
|
moved bool
|
|
|
|
)
|
|
|
|
|
2019-04-19 20:51:27 +00:00
|
|
|
if pack.Side == Top || pack.Side == Bottom {
|
|
|
|
if pack.fillX && resize.W < innerFrameSize.W {
|
2018-08-05 19:54:57 +00:00
|
|
|
resize.W = innerFrameSize.W - w.BoxThickness(2)
|
2018-08-02 02:52:09 +00:00
|
|
|
resized = true
|
|
|
|
}
|
2018-08-05 19:54:57 +00:00
|
|
|
if resize.W < innerFrameSize.W-w.BoxThickness(4) {
|
2018-08-02 02:52:09 +00:00
|
|
|
if pack.Anchor.IsCenter() {
|
2018-08-05 19:54:57 +00:00
|
|
|
point.X = (innerFrameSize.W / 2) - (resize.W / 2)
|
2018-08-02 02:52:09 +00:00
|
|
|
} else if pack.Anchor.IsWest() {
|
|
|
|
point.X = pack.PadX
|
|
|
|
} else if pack.Anchor.IsEast() {
|
2018-08-05 19:54:57 +00:00
|
|
|
point.X = innerFrameSize.W - resize.W - pack.PadX
|
2018-08-02 02:52:09 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
moved = true
|
|
|
|
}
|
2019-04-19 20:51:27 +00:00
|
|
|
} else if pack.Side == Left || pack.Side == Right {
|
|
|
|
if pack.fillY && resize.H < innerFrameSize.H {
|
2018-08-05 19:54:57 +00:00
|
|
|
resize.H = innerFrameSize.H - w.BoxThickness(2) // BoxThickness(2) for parent + child
|
2018-08-02 02:52:09 +00:00
|
|
|
// point.Y -= (w.BoxThickness(4) + child.BoxThickness(2))
|
|
|
|
moved = true
|
|
|
|
resized = true
|
|
|
|
}
|
|
|
|
|
|
|
|
// Vertically align the widgets.
|
2018-08-05 19:54:57 +00:00
|
|
|
if resize.H < innerFrameSize.H {
|
2018-08-02 02:52:09 +00:00
|
|
|
if pack.Anchor.IsMiddle() {
|
2018-08-05 19:54:57 +00:00
|
|
|
point.Y = (innerFrameSize.H / 2) - (resize.H / 2) - w.BoxThickness(1)
|
2018-08-02 02:52:09 +00:00
|
|
|
} else if pack.Anchor.IsNorth() {
|
|
|
|
point.Y = pack.PadY - w.BoxThickness(4)
|
|
|
|
} else if pack.Anchor.IsSouth() {
|
2018-08-05 19:54:57 +00:00
|
|
|
point.Y = innerFrameSize.H - resize.H - pack.PadY
|
2018-08-02 02:52:09 +00:00
|
|
|
}
|
|
|
|
moved = true
|
|
|
|
}
|
|
|
|
} else {
|
2019-04-19 20:51:27 +00:00
|
|
|
panic("unsupported pack.Side")
|
2018-08-02 02:52:09 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if resized && size != resize {
|
2019-04-19 20:51:27 +00:00
|
|
|
fmt.Printf("fill: resize %s to %s\n", child.ID(), resize)
|
|
|
|
child.ResizeAuto(resize)
|
2018-08-02 02:52:09 +00:00
|
|
|
child.Compute(e)
|
|
|
|
}
|
|
|
|
if moved {
|
|
|
|
child.MoveTo(point)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-08-05 19:54:57 +00:00
|
|
|
// if !w.FixedSize() {
|
2019-04-19 20:51:27 +00:00
|
|
|
w.ResizeAuto(render.NewRect(
|
2018-08-05 19:54:57 +00:00
|
|
|
frameSize.W-w.BoxThickness(2),
|
|
|
|
frameSize.H-w.BoxThickness(2),
|
|
|
|
))
|
|
|
|
// }
|
2018-08-02 02:52:09 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Anchor is a cardinal direction.
|
|
|
|
type Anchor uint8
|
|
|
|
|
2019-04-19 20:51:27 +00:00
|
|
|
// Side of a parent widget to pack children against.
|
|
|
|
type Side uint8
|
|
|
|
|
|
|
|
// Anchor and Side constants.
|
2018-08-02 02:52:09 +00:00
|
|
|
const (
|
|
|
|
Center Anchor = iota
|
|
|
|
N
|
|
|
|
NE
|
|
|
|
E
|
|
|
|
SE
|
|
|
|
S
|
|
|
|
SW
|
|
|
|
W
|
|
|
|
NW
|
2019-04-19 20:51:27 +00:00
|
|
|
|
|
|
|
Top Side = iota
|
|
|
|
Left
|
|
|
|
Right
|
|
|
|
Bottom
|
2018-08-02 02:52:09 +00:00
|
|
|
)
|
|
|
|
|
2019-04-19 20:51:27 +00:00
|
|
|
// Range of Anchor and Side values.
|
2018-08-02 02:52:09 +00:00
|
|
|
const (
|
|
|
|
AnchorMin = Center
|
|
|
|
AnchorMax = NW
|
2019-04-19 20:51:27 +00:00
|
|
|
|
|
|
|
SideMin = Top
|
|
|
|
SideMax = Bottom
|
2018-08-02 02:52:09 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
// 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
|
|
|
|
}
|
|
|
|
|
|
|
|
type packLayout struct {
|
|
|
|
widgets []packedWidget
|
|
|
|
}
|
|
|
|
|
|
|
|
type packedWidget struct {
|
|
|
|
widget Widget
|
|
|
|
pack Pack
|
|
|
|
fill uint8
|
|
|
|
}
|
|
|
|
|
|
|
|
// packedWidget.fill values
|
|
|
|
const (
|
|
|
|
fillNone uint8 = iota
|
|
|
|
fillX
|
|
|
|
fillY
|
|
|
|
fillBoth
|
|
|
|
)
|