Sasha Koshka
ff8875535d
Boxes that need their minimum size to be updated now use a map like for layout and drawing. Size set with MinimumSize is now treated as separate from the content size and the larger size is used.
245 lines
5.7 KiB
Go
245 lines
5.7 KiB
Go
package x
|
|
|
|
import "image"
|
|
import "git.tebibyte.media/tomo/tomo"
|
|
import "git.tebibyte.media/tomo/tomo/event"
|
|
import "git.tebibyte.media/tomo/tomo/canvas"
|
|
|
|
type containerBox struct {
|
|
*box
|
|
|
|
hOverflow, vOverflow bool
|
|
hAlign, vAlign tomo.Align
|
|
contentBounds image.Rectangle
|
|
scroll image.Point
|
|
|
|
gap image.Point
|
|
children []tomo.Box
|
|
layout tomo.Layout
|
|
propagateEvents bool
|
|
|
|
on struct {
|
|
contentBoundsChange event.FuncBroadcaster
|
|
}
|
|
}
|
|
|
|
func (backend *Backend) NewContainerBox() tomo.ContainerBox {
|
|
this := &containerBox { propagateEvents: true }
|
|
this.box = backend.newBox(this)
|
|
return this
|
|
}
|
|
|
|
func (this *containerBox) SetOverflow (horizontal, vertical bool) {
|
|
if this.hOverflow == horizontal && this.vOverflow == vertical { return }
|
|
this.hOverflow = horizontal
|
|
this.vOverflow = vertical
|
|
this.invalidateLayout()
|
|
}
|
|
|
|
func (this *containerBox) SetAlign (x, y tomo.Align) {
|
|
if this.hAlign == x && this.vAlign == y { return }
|
|
this.hAlign = x
|
|
this.vAlign = y
|
|
this.invalidateLayout()
|
|
}
|
|
|
|
func (this *containerBox) ContentBounds () image.Rectangle {
|
|
return this.contentBounds
|
|
}
|
|
|
|
func (this *containerBox) ScrollTo (point image.Point) {
|
|
// TODO: constrain scroll
|
|
this.scroll = point
|
|
this.invalidateLayout()
|
|
}
|
|
|
|
func (this *containerBox) OnContentBoundsChange (callback func()) event.Cookie {
|
|
return this.on.contentBoundsChange.Connect(callback)
|
|
}
|
|
|
|
func (this *containerBox) SetPropagateEvents (propagate bool) {
|
|
this.propagateEvents = propagate
|
|
}
|
|
|
|
func (this *containerBox) SetGap (gap image.Point) {
|
|
if this.gap == gap { return }
|
|
this.gap = gap
|
|
this.invalidateLayout()
|
|
this.invalidateMinimum()
|
|
}
|
|
|
|
func (this *containerBox) Add (child tomo.Object) {
|
|
box := assertAnyBox(child.GetBox())
|
|
if indexOf(this.children, tomo.Box(box)) > -1 { return }
|
|
|
|
box.setParent(this)
|
|
box.flushActionQueue()
|
|
this.children = append(this.children, box)
|
|
this.invalidateLayout()
|
|
this.invalidateMinimum()
|
|
}
|
|
|
|
func (this *containerBox) Delete (child tomo.Object) {
|
|
box := assertAnyBox(child.GetBox())
|
|
index := indexOf(this.children, tomo.Box(box))
|
|
if index < 0 { return }
|
|
|
|
box.setParent(nil)
|
|
this.children = remove(this.children, index)
|
|
this.invalidateLayout()
|
|
this.invalidateMinimum()
|
|
}
|
|
|
|
func (this *containerBox) Insert (child, before tomo.Object) {
|
|
box := assertAnyBox(child.GetBox())
|
|
if indexOf(this.children, tomo.Box(box)) > -1 { return }
|
|
|
|
beforeBox := assertAnyBox(before.GetBox())
|
|
index := indexOf(this.children, tomo.Box(beforeBox))
|
|
if index < 0 { return }
|
|
|
|
box.setParent(this)
|
|
this.children = insert(this.children, index, tomo.Box(box))
|
|
this.invalidateLayout()
|
|
this.invalidateMinimum()
|
|
}
|
|
|
|
func (this *containerBox) Clear () {
|
|
for _, box := range this.children {
|
|
box.(anyBox).setParent(nil)
|
|
}
|
|
this.children = nil
|
|
this.invalidateLayout()
|
|
this.invalidateMinimum()
|
|
}
|
|
|
|
func (this *containerBox) Length () int {
|
|
return len(this.children)
|
|
}
|
|
|
|
func (this *containerBox) At (index int) tomo.Object {
|
|
if index < 0 || index >= len(this.children) {
|
|
return nil
|
|
}
|
|
return this.children[index]
|
|
}
|
|
|
|
func (this *containerBox) SetLayout (layout tomo.Layout) {
|
|
this.layout = layout
|
|
this.invalidateLayout()
|
|
this.invalidateMinimum()
|
|
}
|
|
|
|
func (this *containerBox) Draw (can canvas.Canvas) {
|
|
if can == nil { return }
|
|
this.drawBorders(can)
|
|
pen := can.Pen()
|
|
pen.Fill(this.color)
|
|
|
|
rocks := make([]image.Rectangle, len(this.children))
|
|
for index, box := range this.children {
|
|
rocks[index] = box.Bounds()
|
|
}
|
|
for _, tile := range canvas.Shatter(this.bounds, rocks...) {
|
|
pen.Rectangle(tile)
|
|
}
|
|
}
|
|
|
|
func (this *containerBox) flushActionQueue () {
|
|
for _, box := range this.children {
|
|
box.(anyBox).flushActionQueue()
|
|
}
|
|
this.box.flushActionQueue()
|
|
}
|
|
|
|
func (this *containerBox) window () *window {
|
|
if this.parent == nil { return nil }
|
|
return this.parent.window()
|
|
}
|
|
|
|
func (this *containerBox) canvas () canvas.Canvas {
|
|
return this.box.canvas
|
|
}
|
|
|
|
func (this *containerBox) notifyMinimumSizeChange (child anyBox) {
|
|
this.invalidateMinimum()
|
|
size := child.MinimumSize()
|
|
bounds := child.Bounds()
|
|
if bounds.Dx() < size.X || bounds.Dy() < size.Y {
|
|
this.invalidateLayout()
|
|
}
|
|
}
|
|
|
|
func (this *containerBox) layoutHints () tomo.LayoutHints {
|
|
innerBounds := this.InnerBounds().Sub(this.scroll)
|
|
return tomo.LayoutHints {
|
|
Bounds: innerBounds,
|
|
OverflowX: this.hOverflow,
|
|
OverflowY: this.vOverflow,
|
|
AlignX: this.hAlign,
|
|
AlignY: this.vAlign,
|
|
Gap: this.gap,
|
|
}
|
|
}
|
|
|
|
func (this *containerBox) contentMinimum () image.Point {
|
|
minimum := this.box.contentMinimum()
|
|
if this.layout != nil {
|
|
minimum = minimum.Add (
|
|
this.layout.MinimumSize (
|
|
this.layoutHints(),
|
|
this.children))
|
|
}
|
|
return minimum
|
|
}
|
|
|
|
func (this *containerBox) doLayout () {
|
|
this.box.doLayout()
|
|
previousContentBounds := this.contentBounds
|
|
if this.layout != nil {
|
|
this.layout.Arrange(this.layoutHints(), this.children)
|
|
}
|
|
if previousContentBounds != this.contentBounds {
|
|
this.on.contentBoundsChange.Broadcast()
|
|
}
|
|
}
|
|
|
|
func (this *containerBox) recursiveRedo () {
|
|
this.doLayout()
|
|
this.doDraw()
|
|
for _, child := range this.children {
|
|
child.(anyBox).recursiveRedo()
|
|
}
|
|
}
|
|
|
|
func (this *containerBox) boxUnder (point image.Point) anyBox {
|
|
if this.propagateEvents {
|
|
for _, box := range this.children {
|
|
candidate := box.(anyBox).boxUnder(point)
|
|
if candidate != nil { return candidate }
|
|
}
|
|
}
|
|
|
|
return this.box.boxUnder(point)
|
|
}
|
|
|
|
func (this *containerBox) propagate (callback func (anyBox) bool) bool {
|
|
for _, box := range this.children {
|
|
box := box.(anyBox)
|
|
if !box.propagate(callback) { return false }
|
|
}
|
|
|
|
return callback(this)
|
|
}
|
|
|
|
func (this *containerBox) propagateAlt (callback func (anyBox) bool) bool {
|
|
if !callback(this) { return false}
|
|
|
|
for _, box := range this.children {
|
|
box := box.(anyBox)
|
|
if !box.propagateAlt(callback) { return false }
|
|
}
|
|
|
|
return true
|
|
}
|