Vertical layout partially works
This commit is contained in:
parent
9e16f7b532
commit
986315d5db
@ -34,8 +34,10 @@ func (ent *entity) unlink () {
|
|||||||
delete(ent.window.system.drawingInvalid, child)
|
delete(ent.window.system.drawingInvalid, child)
|
||||||
return true
|
return true
|
||||||
})
|
})
|
||||||
|
|
||||||
delete(ent.window.system.drawingInvalid, ent)
|
if ent.window != nil {
|
||||||
|
delete(ent.window.system.drawingInvalid, ent)
|
||||||
|
}
|
||||||
ent.parent = nil
|
ent.parent = nil
|
||||||
ent.window = nil
|
ent.window = nil
|
||||||
}
|
}
|
||||||
|
@ -98,7 +98,7 @@ func (system *system) afterEvent () {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (system *system) layout (entity *entity, force bool) {
|
func (system *system) layout (entity *entity, force bool) {
|
||||||
if entity == nil { return }
|
if entity == nil || !entity.isContainer { return }
|
||||||
if entity.layoutInvalid == true || force {
|
if entity.layoutInvalid == true || force {
|
||||||
entity.element.(tomo.Container).Layout()
|
entity.element.(tomo.Container).Layout()
|
||||||
entity.layoutInvalid = false
|
entity.layoutInvalid = false
|
||||||
|
@ -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
178
elements/containers/vbox.go
Normal 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)
|
||||||
|
}
|
@ -1,9 +1,8 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import "git.tebibyte.media/sashakoshka/tomo"
|
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"
|
||||||
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/elements/containers"
|
||||||
import _ "git.tebibyte.media/sashakoshka/tomo/backends/all"
|
import _ "git.tebibyte.media/sashakoshka/tomo/backends/all"
|
||||||
|
|
||||||
@ -15,8 +14,7 @@ func run () {
|
|||||||
window, _ := tomo.NewWindow(tomo.Bounds(0, 0, 128, 128))
|
window, _ := tomo.NewWindow(tomo.Bounds(0, 0, 128, 128))
|
||||||
window.SetTitle("vertical stack")
|
window.SetTitle("vertical stack")
|
||||||
|
|
||||||
container := containers.NewContainer(layouts.Vertical { true, true })
|
container := containers.NewVBox(true, true)
|
||||||
window.Adopt(container)
|
|
||||||
|
|
||||||
label := elements.NewLabel("it is a label hehe", true)
|
label := elements.NewLabel("it is a label hehe", true)
|
||||||
button := elements.NewButton("drawing pad")
|
button := elements.NewButton("drawing pad")
|
||||||
@ -24,7 +22,7 @@ func run () {
|
|||||||
button.OnClick (func () {
|
button.OnClick (func () {
|
||||||
container.DisownAll()
|
container.DisownAll()
|
||||||
container.Adopt(elements.NewLabel("Draw here:", false), false)
|
container.Adopt(elements.NewLabel("Draw here:", false), false)
|
||||||
container.Adopt(testing.NewMouse(), true)
|
// container.Adopt(testing.NewMouse(), true)
|
||||||
container.Adopt(okButton, false)
|
container.Adopt(okButton, false)
|
||||||
okButton.Focus()
|
okButton.Focus()
|
||||||
})
|
})
|
||||||
@ -35,6 +33,7 @@ func run () {
|
|||||||
container.Adopt(okButton, false)
|
container.Adopt(okButton, false)
|
||||||
okButton.Focus()
|
okButton.Focus()
|
||||||
|
|
||||||
|
window.Adopt(container)
|
||||||
window.OnClose(tomo.Stop)
|
window.OnClose(tomo.Stop)
|
||||||
window.Show()
|
window.Show()
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user