Added support for horizontal layouts

This commit is contained in:
Sasha Koshka 2023-04-15 19:14:44 -04:00
parent 0cd7fb9be9
commit 0a21f605fb
3 changed files with 113 additions and 55 deletions

View File

@ -11,27 +11,40 @@ type scratchEntry struct {
minimum float64
}
type VBox struct {
entity tomo.ContainerEntity
scratch map[tomo.Element] scratchEntry
theme theme.Wrapped
padding bool
margin bool
// 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 {
entity tomo.ContainerEntity
scratch map[tomo.Element] scratchEntry
theme theme.Wrapped
padding bool
margin bool
vertical bool
}
func NewVBox (padding, margin bool) (element *VBox) {
element = &VBox { padding: padding, margin: margin }
// NewHBox creates a new horizontal box.
func NewHBox (padding, margin bool) (element *Box) {
element = &Box { padding: padding, margin: margin }
element.scratch = make(map[tomo.Element] scratchEntry)
element.theme.Case = tomo.C("tomo", "vBox")
element.theme.Case = tomo.C("tomo", "box")
element.entity = tomo.NewEntity(element).(tomo.ContainerEntity)
return
}
func (element *VBox) Entity () tomo.Entity {
// NewHBox creates a new vertical box.
func NewVBox (padding, margin bool) (element *Box) {
element = NewHBox(padding, margin)
element.vertical = true
return
}
func (element *Box) Entity () tomo.Entity {
return element.entity
}
func (element *VBox) Draw (destination canvas.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()
@ -43,14 +56,20 @@ func (element *VBox) Draw (destination canvas.Canvas) {
}
}
func (element *VBox) Layout () {
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()
expandingElementHeight := freeSpace / nExpanding
expandingElementSize := freeSpace / nExpanding
// set the size and position of each element
x := float64(bounds.Min.X)
@ -58,23 +77,31 @@ func (element *VBox) Layout () {
for index := 0; index < element.entity.CountChildren(); index ++ {
entry := element.scratch[element.entity.Child(index)]
var height float64; if entry.expand {
height = expandingElementHeight
var size float64; if entry.expand {
size = expandingElementSize
} else {
height = entry.minimum
size = entry.minimum
}
element.entity.PlaceChild (index, tomo.Bounds (
int(x), int(y),
bounds.Dx(), int(height)))
y += height
if element.margin { y += float64(margin.Y) }
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 }
}
}
}
func (element *VBox) Adopt (child tomo.Element, expand bool) {
func (element *Box) Adopt (child tomo.Element, expand bool) {
element.entity.Adopt(child)
element.scratch[child] = scratchEntry { expand: expand }
element.updateMinimumSize()
@ -82,7 +109,7 @@ func (element *VBox) Adopt (child tomo.Element, expand bool) {
element.entity.InvalidateLayout()
}
func (element *VBox) Disown (child tomo.Element) {
func (element *Box) Disown (child tomo.Element) {
index := element.entity.IndexOf(child)
if index < 0 { return }
element.entity.Disown(index)
@ -92,7 +119,7 @@ func (element *VBox) Disown (child tomo.Element) {
element.entity.InvalidateLayout()
}
func (element *VBox) DisownAll () {
func (element *Box) DisownAll () {
func () {
for index := 0; index < element.entity.CountChildren(); index ++ {
index := index
@ -105,18 +132,18 @@ func (element *VBox) DisownAll () {
element.entity.InvalidateLayout()
}
func (element *VBox) HandleChildMinimumSizeChange (child tomo.Element) {
func (element *Box) HandleChildMinimumSizeChange (child tomo.Element) {
element.updateMinimumSize()
element.entity.Invalidate()
element.entity.InvalidateLayout()
}
func (element *VBox) DrawBackground (destination canvas.Canvas) {
func (element *Box) DrawBackground (destination canvas.Canvas) {
element.entity.DrawBackground(destination)
}
// SetTheme sets the element's theme.
func (element *VBox) SetTheme (theme tomo.Theme) {
func (element *Box) SetTheme (theme tomo.Theme) {
if theme == element.theme.Theme { return }
element.theme.Theme = theme
element.updateMinimumSize()
@ -124,10 +151,21 @@ func (element *VBox) SetTheme (theme tomo.Theme) {
element.entity.InvalidateLayout()
}
func (element *VBox) freeSpace () (space float64, nExpanding float64) {
func (element *Box) freeSpace () (space float64, nExpanding float64) {
margin := element.theme.Margin(tomo.PatternBackground)
padding := element.theme.Padding(tomo.PatternBackground)
space = float64(element.entity.Bounds().Dy())
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 {
@ -141,34 +179,50 @@ func (element *VBox) freeSpace () (space float64, nExpanding float64) {
space -= float64(padding.Vertical())
}
if element.margin {
space -= float64(margin.Y * (len(element.scratch) - 1))
space -= float64(marginSize * (len(element.scratch) - 1))
}
return
}
func (element *VBox) updateMinimumSize () {
func (element *Box) updateMinimumSize () {
margin := element.theme.Margin(tomo.PatternBackground)
padding := element.theme.Padding(tomo.PatternBackground)
var width, height int
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)
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.minimum = float64(childHeight)
entry.minimum = float64(childSize)
element.scratch[key] = entry
if childWidth > width {
width = childWidth
if childBreadth > breadth {
breadth = childBreadth
}
height += childHeight
size += childSize
if element.margin && index > 0 {
height += margin.Y
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()

View File

@ -1,7 +1,7 @@
package main
import "git.tebibyte.media/sashakoshka/tomo"
// import "git.tebibyte.media/sashakoshka/tomo/popups"
import "git.tebibyte.media/sashakoshka/tomo/popups"
import "git.tebibyte.media/sashakoshka/tomo/elements"
import "git.tebibyte.media/sashakoshka/tomo/elements/containers"
import _ "git.tebibyte.media/sashakoshka/tomo/backends/all"
@ -34,11 +34,11 @@ func run () {
vsync := elements.NewCheckbox("Enable vsync", false)
vsync.OnToggle (func () {
if vsync.Value() {
// popups.NewDialog (
// popups.DialogKindInfo,
// window,
// "Ha!",
// "That doesn't do anything.")
popups.NewDialog (
popups.DialogKindInfo,
window,
"Ha!",
"That doesn't do anything.")
}
})
container.Adopt(vsync, false)

View File

@ -2,7 +2,6 @@ package popups
import "image"
import "git.tebibyte.media/sashakoshka/tomo"
import "git.tebibyte.media/sashakoshka/tomo/layouts"
import "git.tebibyte.media/sashakoshka/tomo/elements"
import "git.tebibyte.media/sashakoshka/tomo/elements/containers"
@ -16,6 +15,8 @@ const (
DialogKindError
)
// TODO: add ability to have an icon for buttons
// Button represents a dialog response button.
type Button struct {
// Name contains the text to display on the button.
@ -42,11 +43,11 @@ func NewDialog (
window, _ = parent.NewModal(image.Rectangle { })
}
window.SetTitle(title)
box := containers.NewVBox(true, true)
messageRow := containers.NewHBox(false, true)
controlRow := containers.NewHBox(false, true)
container := containers.NewContainer(layouts.Dialog { true, true })
window.Adopt(container)
messageContainer := containers.NewContainer(layouts.Horizontal { true, false })
iconId := tomo.IconInformation
switch kind {
case DialogKindInfo: iconId = tomo.IconInformation
@ -55,15 +56,15 @@ func NewDialog (
case DialogKindError: iconId = tomo.IconError
}
messageContainer.Adopt(elements.NewIcon(iconId, tomo.IconSizeLarge), false)
messageContainer.Adopt(elements.NewLabel(message, false), true)
container.Adopt(messageContainer, true)
messageRow.Adopt(elements.NewIcon(iconId, tomo.IconSizeLarge), false)
messageRow.Adopt(elements.NewLabel(message, false), true)
controlRow.Adopt(elements.NewSpacer(false), true)
if len(buttons) == 0 {
button := elements.NewButton("OK")
button.SetIcon(tomo.IconYes)
button.OnClick(window.Close)
container.Adopt(button, false)
controlRow.Adopt(button, false)
button.Focus()
} else {
var button *elements.Button
@ -74,11 +75,14 @@ func NewDialog (
buttonDescriptor.OnPress()
window.Close()
})
container.Adopt(button, false)
controlRow.Adopt(button, false)
}
button.Focus()
}
box.Adopt(messageRow, true)
box.Adopt(controlRow, false)
window.Adopt(box)
window.Show()
return
}