Vertical layout partially works

This commit is contained in:
Sasha Koshka 2023-04-15 18:09:49 -04:00
parent 9e16f7b532
commit 986315d5db
8 changed files with 187 additions and 266 deletions

View File

@ -34,8 +34,10 @@ func (ent *entity) unlink () {
delete(ent.window.system.drawingInvalid, child)
return true
})
delete(ent.window.system.drawingInvalid, ent)
if ent.window != nil {
delete(ent.window.system.drawingInvalid, ent)
}
ent.parent = nil
ent.window = nil
}

View File

@ -98,7 +98,7 @@ func (system *system) afterEvent () {
}
func (system *system) layout (entity *entity, force bool) {
if entity == nil { return }
if entity == nil || !entity.isContainer { return }
if entity.layoutInvalid == true || force {
entity.element.(tomo.Container).Layout()
entity.layoutInvalid = false

View File

@ -1,258 +0,0 @@
package containers
import "image"
import "git.tebibyte.media/sashakoshka/tomo"
import "git.tebibyte.media/sashakoshka/tomo/input"
import "git.tebibyte.media/sashakoshka/tomo/canvas"
import "git.tebibyte.media/sashakoshka/tomo/elements/core"
import "git.tebibyte.media/sashakoshka/tomo/default/theme"
import "git.tebibyte.media/sashakoshka/tomo/default/config"
// Container is an element capable of containg other elements, and arranging
// them in a layout.
type Container struct {
*core.Core
*core.Propagator
core core.CoreControl
layout tomo.Layout
children []tomo.LayoutEntry
warping bool
config config.Wrapped
theme theme.Wrapped
onFocusRequest func () (granted bool)
onFocusMotionRequest func (input.KeynavDirection) (granted bool)
}
// NewContainer creates a new container.
func NewContainer (layout tomo.Layout) (element *Container) {
element = &Container { }
element.theme.Case = tomo.C("tomo", "container")
element.Core, element.core = core.NewCore(element, element.redoAll)
element.Propagator = core.NewPropagator(element, element.core)
element.SetLayout(layout)
return
}
// SetLayout sets the layout of this container.
func (element *Container) SetLayout (layout tomo.Layout) {
element.layout = layout
element.updateMinimumSize()
if element.core.HasImage() {
element.redoAll()
element.core.DamageAll()
}
}
// Adopt adds a new child element to the container. If expand is set to true,
// the element will expand (instead of contract to its minimum size), in
// whatever way is defined by the current layout.
func (element *Container) Adopt (child tomo.Element, expand bool) {
if child0, ok := child.(tomo.Themeable); ok {
child0.SetTheme(element.theme.Theme)
}
if child0, ok := child.(tomo.Configurable); ok {
child0.SetConfig(element.config.Config)
}
child.SetParent(element)
// add child
element.children = append (element.children, tomo.LayoutEntry {
Element: child,
Expand: expand,
})
// refresh stale data
element.updateMinimumSize()
if element.core.HasImage() && !element.warping {
element.redoAll()
element.core.DamageAll()
}
}
// Warp runs the specified callback, deferring all layout and rendering updates
// until the callback has finished executing. This allows for aplications to
// perform batch gui updates without flickering and stuff.
func (element *Container) Warp (callback func ()) {
if element.warping {
callback()
return
}
element.warping = true
callback()
element.warping = false
// TODO: create some sort of task list so we don't do a full recalculate
// and redraw every time, because although that is the most likely use
// case, it is not the only one.
if element.core.HasImage() {
element.redoAll()
element.core.DamageAll()
}
}
// Disown removes the given child from the container if it is contained within
// it.
func (element *Container) Disown (child tomo.Element) {
for index, entry := range element.children {
if entry.Element == child {
element.clearChildEventHandlers(entry.Element)
element.children = append (
element.children[:index],
element.children[index + 1:]...)
break
}
}
element.updateMinimumSize()
if element.core.HasImage() && !element.warping {
element.redoAll()
element.core.DamageAll()
}
}
func (element *Container) clearChildEventHandlers (child tomo.Element) {
child.DrawTo(nil, image.Rectangle { }, nil)
child.SetParent(nil)
if child, ok := child.(tomo.Focusable); ok {
if child.Focused() {
child.HandleUnfocus()
}
}
}
// DisownAll removes all child elements from the container at once.
func (element *Container) DisownAll () {
for _, entry := range element.children {
element.clearChildEventHandlers(entry.Element)
}
element.children = nil
element.updateMinimumSize()
if element.core.HasImage() && !element.warping {
element.redoAll()
element.core.DamageAll()
}
}
// Children returns a slice containing this element's children.
func (element *Container) Children () (children []tomo.Element) {
children = make([]tomo.Element, len(element.children))
for index, entry := range element.children {
children[index] = entry.Element
}
return
}
// CountChildren returns the amount of children contained within this element.
func (element *Container) CountChildren () (count int) {
return len(element.children)
}
// Child returns the child at the specified index. If the index is out of
// bounds, this method will return nil.
func (element *Container) Child (index int) (child tomo.Element) {
if index < 0 || index > len(element.children) { return }
return element.children[index].Element
}
// ChildAt returns the child that contains the specified x and y coordinates. If
// there are no children at the coordinates, this method will return nil.
func (element *Container) ChildAt (point image.Point) (child tomo.Element) {
for _, entry := range element.children {
if point.In(entry.Bounds) {
child = entry.Element
}
}
return
}
func (element *Container) redoAll () {
if !element.core.HasImage() { return }
// remove child canvasses so that any operations done in here will not
// cause a child to draw to a wack ass canvas.
for _, entry := range element.children {
entry.DrawTo(nil, entry.Bounds, nil)
}
// do a layout
element.doLayout()
// draw a background
rocks := make([]image.Rectangle, len(element.children))
for index, entry := range element.children {
rocks[index] = entry.Bounds
}
element.core.DrawBackgroundBoundsShatter (
element.theme.Pattern(tomo.PatternBackground, tomo.State { }),
element.Bounds(),
rocks...)
// cut our canvas up and give peices to child elements
for _, entry := range element.children {
entry.DrawTo (
canvas.Cut(element.core, entry.Bounds),
entry.Bounds, func (region image.Rectangle) {
element.core.DamageRegion(region)
})
}
}
func (element *Container) Window () tomo.Window {
return element.core.Window()
}
// NotifyMinimumSizeChange notifies the container that the minimum size of a
// child element has changed.
func (element *Container) NotifyMinimumSizeChange (child tomo.Element) {
element.updateMinimumSize()
element.redoAll()
element.core.DamageAll()
}
// DrawBackground draws a portion of the container's background pattern within
// the specified bounds. The container will not push these changes.
func (element *Container) DrawBackground (bounds image.Rectangle) {
element.core.DrawBackgroundBounds (
element.theme.Pattern(tomo.PatternBackground, tomo.State { }),
bounds)
}
// SetTheme sets the element's theme.
func (element *Container) SetTheme (new tomo.Theme) {
if new == element.theme.Theme { return }
element.theme.Theme = new
element.Propagator.SetTheme(new)
element.updateMinimumSize()
element.redoAll()
}
// SetConfig sets the element's configuration.
func (element *Container) SetConfig (new tomo.Config) {
if new == element.config.Config { return }
element.Propagator.SetConfig(new)
element.updateMinimumSize()
element.redoAll()
}
func (element *Container) updateMinimumSize () {
margin := element.theme.Margin(tomo.PatternBackground)
padding := element.theme.Padding(tomo.PatternBackground)
width, height := element.layout.MinimumSize (
element.children, margin, padding)
element.core.SetMinimumSize(width, height)
}
func (element *Container) doLayout () {
margin := element.theme.Margin(tomo.PatternBackground)
padding := element.theme.Padding(tomo.PatternBackground)
element.layout.Arrange (
element.children, margin,
padding, element.Bounds())
}

178
elements/containers/vbox.go Normal file
View File

@ -0,0 +1,178 @@
package containers
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"
type scratchEntry struct {
expand bool
minimum float64
}
type VBox struct {
entity tomo.ContainerEntity
scratch map[tomo.Element] scratchEntry
theme theme.Wrapped
padding bool
margin bool
}
func NewVBox (padding, margin bool) (element *VBox) {
element = &VBox { padding: padding, margin: margin }
element.scratch = make(map[tomo.Element] scratchEntry)
element.theme.Case = tomo.C("tomo", "vBox")
element.entity = tomo.NewEntity(element).(tomo.ContainerEntity)
return
}
func (element *VBox) Entity () tomo.Entity {
return element.entity
}
func (element *VBox) 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))
}
}
func (element *VBox) Layout () {
margin := element.theme.Margin(tomo.PatternBackground)
padding := element.theme.Padding(tomo.PatternBackground)
bounds := element.entity.Bounds()
if element.padding { bounds = padding.Apply(bounds) }
freeSpace, nExpanding := element.freeSpace()
expandingElementHeight := 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 height float64; if entry.expand {
height = expandingElementHeight
} else {
height = 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) }
}
}
func (element *VBox) Adopt (child tomo.Element, expand bool) {
element.entity.Adopt(child)
element.scratch[child] = scratchEntry { expand: expand }
element.updateMinimumSize()
element.entity.Invalidate()
element.entity.InvalidateLayout()
}
func (element *VBox) Disown (child tomo.Element) {
index := element.entity.IndexOf(child)
if index < 0 { return }
element.entity.Disown(index)
delete(element.scratch, child)
element.updateMinimumSize()
element.entity.Invalidate()
element.entity.InvalidateLayout()
}
func (element *VBox) DisownAll () {
func () {
for index := 0; index < element.entity.CountChildren(); index ++ {
index := index
defer element.entity.Disown(index)
}
} ()
element.scratch = make(map[tomo.Element] scratchEntry)
element.updateMinimumSize()
element.entity.Invalidate()
element.entity.InvalidateLayout()
}
func (element *VBox) HandleChildMinimumSizeChange (child tomo.Element) {
element.updateMinimumSize()
element.entity.Invalidate()
element.entity.InvalidateLayout()
}
func (element *VBox) DrawBackground (destination canvas.Canvas) {
element.entity.DrawBackground(destination)
}
// SetTheme sets the element's theme.
func (element *VBox) SetTheme (theme tomo.Theme) {
if theme == element.theme.Theme { return }
element.theme.Theme = theme
element.updateMinimumSize()
element.entity.Invalidate()
element.entity.InvalidateLayout()
}
func (element *VBox) freeSpace () (space float64, nExpanding float64) {
margin := element.theme.Margin(tomo.PatternBackground)
padding := element.theme.Padding(tomo.PatternBackground)
space = float64(element.entity.Bounds().Dy())
for _, entry := range element.scratch {
if entry.expand {
nExpanding ++;
} else {
space -= float64(entry.minimum)
}
}
if element.padding {
space -= float64(padding.Vertical())
}
if element.margin {
space -= float64(margin.Y * len(element.scratch) - 1)
}
return
}
func (element *VBox) updateMinimumSize () {
margin := element.theme.Margin(tomo.PatternBackground)
padding := element.theme.Padding(tomo.PatternBackground)
var width, height int
for index := 0; index < element.entity.CountChildren(); index ++ {
childWidth, childHeight := element.entity.ChildMinimumSize(index)
key := element.entity.Child(index)
entry := element.scratch[key]
entry.minimum = float64(childHeight)
element.scratch[key] = entry
if childWidth > width {
width = childWidth
}
height += childHeight
if element.margin && index > 0 {
height += margin.Y
}
}
if element.padding {
width += padding.Horizontal()
height += padding.Vertical()
}
element.entity.SetMinimumSize(width, height)
}

View File

@ -1,9 +1,8 @@
package main
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/testing"
// import "git.tebibyte.media/sashakoshka/tomo/elements/testing"
import "git.tebibyte.media/sashakoshka/tomo/elements/containers"
import _ "git.tebibyte.media/sashakoshka/tomo/backends/all"
@ -15,8 +14,7 @@ func run () {
window, _ := tomo.NewWindow(tomo.Bounds(0, 0, 128, 128))
window.SetTitle("vertical stack")
container := containers.NewContainer(layouts.Vertical { true, true })
window.Adopt(container)
container := containers.NewVBox(true, true)
label := elements.NewLabel("it is a label hehe", true)
button := elements.NewButton("drawing pad")
@ -24,7 +22,7 @@ func run () {
button.OnClick (func () {
container.DisownAll()
container.Adopt(elements.NewLabel("Draw here:", false), false)
container.Adopt(testing.NewMouse(), true)
// container.Adopt(testing.NewMouse(), true)
container.Adopt(okButton, false)
okButton.Focus()
})
@ -35,6 +33,7 @@ func run () {
container.Adopt(okButton, false)
okButton.Focus()
window.Adopt(container)
window.OnClose(tomo.Stop)
window.Show()
}