223 lines
6.0 KiB
Go
223 lines
6.0 KiB
Go
package elements
|
|
|
|
import "image"
|
|
import "git.tebibyte.media/sashakoshka/tomo"
|
|
import "git.tebibyte.media/sashakoshka/tomo/canvas"
|
|
import "git.tebibyte.media/sashakoshka/tomo/shatter"
|
|
import "git.tebibyte.media/sashakoshka/tomo/default/theme"
|
|
|
|
// Space is a list of spacing configurations that can be passed to some
|
|
// containers.
|
|
type Space int; const (
|
|
SpaceNone Space = 0
|
|
SpacePadding Space = 1
|
|
SpaceMargin Space = 2
|
|
SpaceBoth Space = SpacePadding | SpaceMargin
|
|
)
|
|
|
|
// Includes returns whether a spacing value has been or'd with another spacing
|
|
// value.
|
|
func (space Space) Includes (sub Space) bool {
|
|
return (space & sub) > 0
|
|
}
|
|
|
|
// Box is a container that lays out its children horizontally or vertically.
|
|
// Child elements can be set to contract to their minimum size, or expand to
|
|
// fill remaining space. Boxes can be nested and used together to create more
|
|
// complex layouts.
|
|
type Box struct {
|
|
container
|
|
theme theme.Wrapped
|
|
padding bool
|
|
margin bool
|
|
vertical bool
|
|
}
|
|
|
|
// NewHBox creates a new horizontal box.
|
|
func NewHBox (space Space, children ...tomo.Element) (element *Box) {
|
|
element = &Box {
|
|
padding: space.Includes(SpacePadding),
|
|
margin: space.Includes(SpaceMargin),
|
|
}
|
|
element.entity = tomo.NewEntity(element).(tomo.ContainerEntity)
|
|
element.minimumSize = element.updateMinimumSize
|
|
element.init()
|
|
element.theme.Case = tomo.C("tomo", "box")
|
|
element.Adopt(children...)
|
|
return
|
|
}
|
|
|
|
// NewHBox creates a new vertical box.
|
|
func NewVBox (space Space, children ...tomo.Element) (element *Box) {
|
|
element = &Box {
|
|
padding: space.Includes(SpacePadding),
|
|
margin: space.Includes(SpaceMargin),
|
|
vertical: true,
|
|
}
|
|
element.entity = tomo.NewEntity(element).(tomo.ContainerEntity)
|
|
element.minimumSize = element.updateMinimumSize
|
|
element.init()
|
|
element.theme.Case = tomo.C("tomo", "box")
|
|
element.Adopt(children...)
|
|
return
|
|
}
|
|
|
|
// Draw causes the element to draw to the specified destination canvas.
|
|
func (element *Box) Draw (destination canvas.Canvas) {
|
|
rocks := make([]image.Rectangle, element.entity.CountChildren())
|
|
for index := 0; index < element.entity.CountChildren(); index ++ {
|
|
rocks[index] = element.entity.Child(index).Entity().Bounds()
|
|
}
|
|
|
|
tiles := shatter.Shatter(element.entity.Bounds(), rocks...)
|
|
for _, tile := range tiles {
|
|
element.entity.DrawBackground(canvas.Cut(destination, tile))
|
|
}
|
|
}
|
|
|
|
// Layout causes this element to perform a layout operation.
|
|
func (element *Box) Layout () {
|
|
margin := element.theme.Margin(tomo.PatternBackground)
|
|
padding := element.theme.Padding(tomo.PatternBackground)
|
|
bounds := element.entity.Bounds()
|
|
if element.padding { bounds = padding.Apply(bounds) }
|
|
|
|
var marginSize float64; if element.vertical {
|
|
marginSize = float64(margin.Y)
|
|
} else {
|
|
marginSize = float64(margin.X)
|
|
}
|
|
|
|
freeSpace, nExpanding := element.freeSpace()
|
|
expandingElementSize := freeSpace / nExpanding
|
|
|
|
// set the size and position of each element
|
|
x := float64(bounds.Min.X)
|
|
y := float64(bounds.Min.Y)
|
|
for index := 0; index < element.entity.CountChildren(); index ++ {
|
|
entry := element.scratch[element.entity.Child(index)]
|
|
|
|
var size float64; if entry.expand {
|
|
size = expandingElementSize
|
|
} else {
|
|
size = entry.minSize
|
|
}
|
|
|
|
var childBounds image.Rectangle; if element.vertical {
|
|
childBounds = tomo.Bounds(int(x), int(y), bounds.Dx(), int(size))
|
|
} else {
|
|
childBounds = tomo.Bounds(int(x), int(y), int(size), bounds.Dy())
|
|
}
|
|
element.entity.PlaceChild(index, childBounds)
|
|
|
|
if element.vertical {
|
|
y += size
|
|
if element.margin { y += marginSize }
|
|
} else {
|
|
x += size
|
|
if element.margin { x += marginSize }
|
|
}
|
|
}
|
|
}
|
|
|
|
// AdoptExpand adds one or more elements to the box. These elements will be
|
|
// expanded to fill in empty space.
|
|
func (element *Box) AdoptExpand (children ...tomo.Element) {
|
|
element.adopt(true, children...)
|
|
}
|
|
|
|
// DrawBackground draws this element's background pattern to the specified
|
|
// destination canvas.
|
|
func (element *Box) DrawBackground (destination canvas.Canvas) {
|
|
element.entity.DrawBackground(destination)
|
|
}
|
|
|
|
// SetTheme sets the element's theme.
|
|
func (element *Box) SetTheme (theme tomo.Theme) {
|
|
if theme == element.theme.Theme { return }
|
|
element.theme.Theme = theme
|
|
element.updateMinimumSize()
|
|
element.entity.Invalidate()
|
|
element.entity.InvalidateLayout()
|
|
}
|
|
|
|
func (element *Box) freeSpace () (space float64, nExpanding float64) {
|
|
margin := element.theme.Margin(tomo.PatternBackground)
|
|
padding := element.theme.Padding(tomo.PatternBackground)
|
|
|
|
var marginSize int; if element.vertical {
|
|
marginSize = margin.Y
|
|
} else {
|
|
marginSize = margin.X
|
|
}
|
|
|
|
if element.vertical {
|
|
space = float64(element.entity.Bounds().Dy())
|
|
} else {
|
|
space = float64(element.entity.Bounds().Dx())
|
|
}
|
|
|
|
for _, entry := range element.scratch {
|
|
if entry.expand {
|
|
nExpanding ++;
|
|
} else {
|
|
space -= float64(entry.minSize)
|
|
}
|
|
}
|
|
|
|
if element.padding {
|
|
space -= float64(padding.Vertical())
|
|
}
|
|
if element.margin {
|
|
space -= float64(marginSize * (len(element.scratch) - 1))
|
|
}
|
|
|
|
return
|
|
}
|
|
|
|
func (element *Box) updateMinimumSize () {
|
|
margin := element.theme.Margin(tomo.PatternBackground)
|
|
padding := element.theme.Padding(tomo.PatternBackground)
|
|
var breadth, size int
|
|
var marginSize int; if element.vertical {
|
|
marginSize = margin.Y
|
|
} else {
|
|
marginSize = margin.X
|
|
}
|
|
|
|
for index := 0; index < element.entity.CountChildren(); index ++ {
|
|
childWidth, childHeight := element.entity.ChildMinimumSize(index)
|
|
var childBreadth, childSize int; if element.vertical {
|
|
childBreadth, childSize = childWidth, childHeight
|
|
} else {
|
|
childBreadth, childSize = childHeight, childWidth
|
|
}
|
|
|
|
key := element.entity.Child(index)
|
|
entry := element.scratch[key]
|
|
entry.minSize = float64(childSize)
|
|
element.scratch[key] = entry
|
|
|
|
if childBreadth > breadth {
|
|
breadth = childBreadth
|
|
}
|
|
size += childSize
|
|
if element.margin && index > 0 {
|
|
size += marginSize
|
|
}
|
|
}
|
|
|
|
var width, height int; if element.vertical {
|
|
width, height = breadth, size
|
|
} else {
|
|
width, height = size, breadth
|
|
}
|
|
|
|
if element.padding {
|
|
width += padding.Horizontal()
|
|
height += padding.Vertical()
|
|
}
|
|
|
|
element.entity.SetMinimumSize(width, height)
|
|
}
|