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 } 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...) { clipped := can.Clip(tile) if this.transparent() && this.parent != nil { this.parent.drawBackgroundPart(clipped) } if clipped == nil { continue } pen := clipped.Pen() pen.Fill(this.color) pen.Texture(this.texture) pen.Rectangle(this.innerClippingBounds) } } func (this *containerBox) drawBackgroundPart (can canvas.Canvas) { if can == nil { return } pen := can.Pen() pen.Fill(this.color) pen.Texture(this.texture) if this.transparent() && this.parent != nil { this.parent.drawBackgroundPart(can) } pen.Rectangle(this.innerClippingBounds) } 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 }