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 on struct { contentBoundsChange event.FuncBroadcaster } } func (backend *Backend) NewContainerBox() tomo.ContainerBox { box := &containerBox { box: backend.NewBox().(*box), } box.drawer = box box.outer = box return box } func (this *containerBox) Box () tomo.Box { 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) SetGap (gap image.Point) { if this.gap == gap { return } this.gap = gap this.invalidateLayout() this.recalculateMinimumSize() } func (this *containerBox) Add (child tomo.Object) { box := assertAnyBox(child.Box()) if indexOf(this.children, tomo.Box(box)) > -1 { return } box.setParent(this) this.children = append(this.children, box) this.invalidateLayout() this.recalculateMinimumSize() } func (this *containerBox) Delete (child tomo.Object) { box := assertAnyBox(child.Box()) index := indexOf(this.children, tomo.Box(box)) if index < 0 { return } box.setParent(nil) this.children = remove(this.children, index) this.invalidateLayout() this.recalculateMinimumSize() } func (this *containerBox) Insert (child, before tomo.Object) { box := assertAnyBox(child.Box()) if indexOf(this.children, tomo.Box(box)) > -1 { return } beforeBox := assertAnyBox(before.Box()) 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.recalculateMinimumSize() } func (this *containerBox) Clear () { for _, box := range this.children { box.(anyBox).setParent(nil) } this.children = nil this.invalidateLayout() this.recalculateMinimumSize() } 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.recalculateMinimumSize() } func (this *containerBox) Draw (can canvas.Canvas) { if can == nil { return } this.drawBorders(can) pen := can.Pen() pen.Fill(this.color) // TODO: do this in doLayout and save the result 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) 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.recalculateMinimumSize() 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) recalculateMinimumSize () { if this.layout == nil { this.SetMinimumSize(image.Point { }) return } minimum := this.layout.MinimumSize(this.layoutHints(), this.children) minimum.X += this.padding.Horizontal() minimum.Y += this.padding.Vertical() borderSum := this.borderSum() minimum.X += borderSum.Horizontal() minimum.Y += borderSum.Vertical() this.SetMinimumSize(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() } }