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 contentBounds image.Rectangle scroll image.Point gap tomo.Gap children []anyBox 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) 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 tomo.Gap) { if this.gap == gap { return } this.gap = gap this.invalidateLayout() } func (this *containerBox) Add (child tomo.Object) { box := assertAnyBox(child.Box()) if indexOf(this.children, box) > -1 { return } box.setParent(this) this.children = append(this.children, box) this.invalidateLayout() } func (this *containerBox) Delete (child tomo.Object) { box := assertAnyBox(child.Box()) index := indexOf(this.children, box) if index < 0 { return } box.setParent(nil) this.children = remove(this.children, index) this.invalidateLayout() } func (this *containerBox) Insert (child, before tomo.Object) { box := assertAnyBox(child.Box()) if indexOf(this.children, box) > -1 { return } beforeBox := assertAnyBox(before.Box()) index := indexOf(this.children, beforeBox) if index < 0 { return } box.setParent(this) this.children = insert(this.children, index, box) this.invalidateLayout() } func (this *containerBox) Clear () { for _, box := range this.children { box.setParent(nil) } this.children = nil this.invalidateLayout() } 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() } func (this *containerBox) Draw (can canvas.Canvas) { 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() } // TODO: use shatter algorithm here to optimize amount of pixels drawn // and not draw over child boxes 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) doLayout () { this.box.doLayout() // TODO: possibly store all children as tomo.Box-es and don't allocate a // slice here previousContentBounds := this.contentBounds boxes := make([]tomo.Box, len(this.children)) for index, box := range this.children { boxes[index] = box } if this.layout != nil { // TODO maybe we should pass more information into Arrange such // as overflow information and scroll this.layout.Arrange ( this.InnerBounds().Sub(this.scroll), this.gap, boxes) } if previousContentBounds != this.contentBounds { this.on.contentBoundsChange.Broadcast() } } func (this *containerBox) recursiveRedo () { this.doLayout() this.doDraw() for _, child := range this.children { child.recursiveRedo() } }