diff --git a/backend.go b/backend.go index 5a1f17c..901b3e0 100644 --- a/backend.go +++ b/backend.go @@ -48,9 +48,7 @@ func (backend *Backend) Run () error { case <- pingQuit: return nil // FIXME: if we exited due to an error say so } - for _, window := range backend.windows { - window.afterEvent() - } + backend.afterEvent() } } @@ -78,3 +76,9 @@ func (backend *Backend) Do (callback func ()) { func (backend *Backend) assert () { if backend == nil { panic("nil backend") } } + +func (backend *Backend) afterEvent () { + for _, window := range backend.windows { + window.afterEvent() + } +} diff --git a/box.go b/box.go index e364aac..015dc86 100644 --- a/box.go +++ b/box.go @@ -13,8 +13,12 @@ type box struct { parent parent outer anyBox - bounds image.Rectangle - minSize image.Point + bounds image.Rectangle + minSize image.Point + userMinSize image.Point + + minSizeQueued bool + focusQueued *bool padding tomo.Inset border []tomo.Border @@ -45,13 +49,23 @@ type box struct { } } -func (backend *Backend) NewBox() tomo.Box { +func (backend *Backend) newBox (outer anyBox) *box { box := &box { backend: backend, color: color.White, + outer: outer, + drawer: outer, } - box.drawer = box - box.outer = box + if outer == nil { + box.drawer = box + box.outer = box + } + box.invalidateMinimum() + return box +} + +func (backend *Backend) NewBox() tomo.Box { + box := backend.newBox(nil) return box } @@ -105,23 +119,20 @@ func (this *box) SetColor (c color.Color) { func (this *box) SetBorder (border ...tomo.Border) { this.border = border this.invalidateLayout() - this.recalculateMinimumSize() + this.invalidateMinimum() } func (this *box) SetMinimumSize (size image.Point) { - if this.minSize == size { return } - this.minSize = size - - if this.parent != nil { - this.parent.notifyMinimumSizeChange(this) - } + if this.userMinSize == size { return } + this.userMinSize = size + this.invalidateMinimum() } func (this *box) SetPadding (padding tomo.Inset) { if this.padding == padding { return } this.padding = padding this.invalidateLayout() - this.recalculateMinimumSize() + this.invalidateMinimum() } func (this *box) SetDNDData (dat data.Data) { @@ -133,9 +144,11 @@ func (this *box) SetDNDAccept (types ...data.Mime) { } func (this *box) SetFocused (focused bool) { - if this.parent == nil { return } - window := this.parent.window() - if window == nil { return } + if this.parent == nil || this.parent.window () == nil { + focusedCopy := focused + this.focusQueued = &focusedCopy + return + } if !this.focusable { return } if this.Focused () && !focused { @@ -251,6 +264,30 @@ func (this *box) drawBorders (can canvas.Canvas) { } } +func (this *box) contentMinimum () image.Point { + var minimum image.Point + minimum.X += this.padding.Horizontal() + minimum.Y += this.padding.Vertical() + borderSum := this.borderSum() + minimum.X += borderSum.Horizontal() + minimum.Y += borderSum.Vertical() + return minimum +} + +func (this *box) doMinimumSize () { + this.minSize = this.outer.contentMinimum() + if this.minSize.X < this.userMinSize.X { + this.minSize.X = this.userMinSize.X + } + if this.minSize.Y < this.userMinSize.Y { + this.minSize.Y = this.userMinSize.Y + } + + if this.parent != nil { + this.parent.notifyMinimumSizeChange(this) + } +} + func (this *box) doDraw () { if this.canvas == nil { return } if this.drawer != nil { @@ -273,6 +310,17 @@ func (this *box) setParent (parent parent) { this.parent = parent } +func (this *box) flushActionQueue () { + if this.parent == nil || this.parent.window() == nil { return } + + if this.minSizeQueued { + this.invalidateMinimum() + } + if this.focusQueued != nil { + this.SetFocused(*this.focusQueued) + } +} + func (this *box) recursiveRedo () { this.doLayout() this.doDraw() @@ -288,10 +336,12 @@ func (this *box) invalidateDraw () { this.parent.window().invalidateDraw(this.outer) } -func (this *box) recalculateMinimumSize () { - if this.outer != anyBox(this) { - this.outer.recalculateMinimumSize() +func (this *box) invalidateMinimum () { + if this.parent == nil || this.parent.window() == nil { + this.minSizeQueued = true + return } + this.parent.window().invalidateMinimum(this.outer) } func (this *box) canBeFocused () bool { diff --git a/canvasbox.go b/canvasbox.go index fcabfb8..384fab4 100644 --- a/canvasbox.go +++ b/canvasbox.go @@ -8,11 +8,9 @@ type canvasBox struct { } func (backend *Backend) NewCanvasBox () tomo.CanvasBox { - box := &canvasBox { - box: backend.NewBox().(*box), - } - box.outer = box - return box + this := &canvasBox { } + this.box = backend.newBox(this) + return this } func (this *canvasBox) Box () tomo.Box { diff --git a/containerbox.go b/containerbox.go index 232ac50..6e61e9b 100644 --- a/containerbox.go +++ b/containerbox.go @@ -24,13 +24,9 @@ type containerBox struct { } func (backend *Backend) NewContainerBox() tomo.ContainerBox { - box := &containerBox { - box: backend.NewBox().(*box), - propagateEvents: true, - } - box.drawer = box - box.outer = box - return box + this := &containerBox { propagateEvents: true } + this.box = backend.newBox(this) + return this } func (this *containerBox) SetOverflow (horizontal, vertical bool) { @@ -69,7 +65,7 @@ func (this *containerBox) SetGap (gap image.Point) { if this.gap == gap { return } this.gap = gap this.invalidateLayout() - this.recalculateMinimumSize() + this.invalidateMinimum() } func (this *containerBox) Add (child tomo.Object) { @@ -77,9 +73,10 @@ func (this *containerBox) Add (child tomo.Object) { if indexOf(this.children, tomo.Box(box)) > -1 { return } box.setParent(this) + box.flushActionQueue() this.children = append(this.children, box) this.invalidateLayout() - this.recalculateMinimumSize() + this.invalidateMinimum() } func (this *containerBox) Delete (child tomo.Object) { @@ -90,7 +87,7 @@ func (this *containerBox) Delete (child tomo.Object) { box.setParent(nil) this.children = remove(this.children, index) this.invalidateLayout() - this.recalculateMinimumSize() + this.invalidateMinimum() } func (this *containerBox) Insert (child, before tomo.Object) { @@ -104,7 +101,7 @@ func (this *containerBox) Insert (child, before tomo.Object) { box.setParent(this) this.children = insert(this.children, index, tomo.Box(box)) this.invalidateLayout() - this.recalculateMinimumSize() + this.invalidateMinimum() } func (this *containerBox) Clear () { @@ -113,7 +110,7 @@ func (this *containerBox) Clear () { } this.children = nil this.invalidateLayout() - this.recalculateMinimumSize() + this.invalidateMinimum() } func (this *containerBox) Length () int { @@ -130,7 +127,7 @@ func (this *containerBox) At (index int) tomo.Object { func (this *containerBox) SetLayout (layout tomo.Layout) { this.layout = layout this.invalidateLayout() - this.recalculateMinimumSize() + this.invalidateMinimum() } func (this *containerBox) Draw (can canvas.Canvas) { @@ -148,6 +145,13 @@ func (this *containerBox) Draw (can canvas.Canvas) { } } +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() @@ -158,8 +162,7 @@ func (this *containerBox) canvas () canvas.Canvas { } func (this *containerBox) notifyMinimumSizeChange (child anyBox) { - this.recalculateMinimumSize() - + this.invalidateMinimum() size := child.MinimumSize() bounds := child.Bounds() if bounds.Dx() < size.X || bounds.Dy() < size.Y { @@ -179,18 +182,15 @@ func (this *containerBox) layoutHints () tomo.LayoutHints { } } -func (this *containerBox) recalculateMinimumSize () { - if this.layout == nil { - this.SetMinimumSize(image.Point { }) - return +func (this *containerBox) contentMinimum () image.Point { + minimum := this.box.contentMinimum() + if this.layout != nil { + minimum = minimum.Add ( + this.layout.MinimumSize ( + this.layoutHints(), + this.children)) } - 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) + return minimum } func (this *containerBox) doLayout () { diff --git a/system.go b/system.go index 2c7c57f..2422360 100644 --- a/system.go +++ b/system.go @@ -40,13 +40,17 @@ type parent interface { type anyBox interface { tomo.Box - doDraw () - doLayout () - setParent (parent) - recursiveRedo () - canBeFocused () bool - boxUnder (image.Point) anyBox - recalculateMinimumSize () + canvas.Drawer + + doDraw () + doLayout () + doMinimumSize () + contentMinimum () image.Point + setParent (parent) + flushActionQueue () + recursiveRedo () + canBeFocused () bool + boxUnder (image.Point) anyBox propagate (func (anyBox) bool) bool propagateAlt (func (anyBox) bool) bool @@ -83,10 +87,11 @@ func (window *window) SetRoot (root tomo.Object) { } else { box := assertAnyBox(root.GetBox()) box.setParent(window) + box.flushActionQueue() window.invalidateLayout(box) window.root = box } - window.recalculateMinimumSize() + window.minimumClean = false } func (window *window) window () *window { @@ -98,7 +103,11 @@ func (window *window) canvas () canvas.Canvas { } func (window *window) notifyMinimumSizeChange (anyBox) { - window.recalculateMinimumSize() + window.minimumClean = false +} + +func (window *window) invalidateMinimum (box anyBox) { + window.needMinimum.Add(box) } func (window *window) invalidateDraw (box anyBox) { @@ -106,8 +115,6 @@ func (window *window) invalidateDraw (box anyBox) { } func (window *window) invalidateLayout (box anyBox) { - // TODO: use a priority queue for this and have the value be the amount - // of parents a box has window.needLayout.Add(box) window.invalidateDraw(box) } @@ -214,6 +221,12 @@ func (window *window) afterEvent () { return } + for len(window.needMinimum) > 0 { + window.needMinimum.Pop().doMinimumSize() + } + if !window.minimumClean { + window.doMinimumSize() + } for len(window.needLayout) > 0 { window.needLayout.Pop().doLayout() } diff --git a/textbox.go b/textbox.go index 2b751db..00f2f13 100644 --- a/textbox.go +++ b/textbox.go @@ -39,14 +39,12 @@ type textBox struct { } func (backend *Backend) NewTextBox() tomo.TextBox { - box := &textBox { - box: backend.NewBox().(*box), + this := &textBox { textColor: color.Black, dotColor: color.RGBA { B: 255, G: 255, A: 255 }, } - box.box.drawer = box - box.outer = box - return box + this.box = backend.newBox(this) + return this } func (this *textBox) SetOverflow (horizontal, vertical bool) { @@ -74,7 +72,7 @@ func (this *textBox) SetText (text string) { if this.text == text { return } this.text = text this.drawer.SetText([]rune(text)) - this.recalculateMinimumSize() + this.invalidateMinimum() this.invalidateLayout() } @@ -88,14 +86,14 @@ func (this *textBox) SetFace (face font.Face) { if this.face == face { return } this.face = face this.drawer.SetFace(face) - this.recalculateMinimumSize() + this.invalidateMinimum() this.invalidateLayout() } func (this *textBox) SetWrap (wrap bool) { if this.wrap == wrap { return } this.drawer.SetWrap(wrap) - this.recalculateMinimumSize() + this.invalidateMinimum() this.invalidateLayout() } @@ -271,7 +269,7 @@ func (this *textBox) normalizedLayoutBoundsSpace () image.Rectangle { return bounds.Sub(bounds.Min) } -func (this *textBox) recalculateMinimumSize () { +func (this *textBox) contentMinimum () image.Point { minimum := image.Pt ( this.drawer.Em().Round(), this.drawer.LineHeight().Round()) @@ -283,13 +281,8 @@ func (this *textBox) recalculateMinimumSize () { if !this.vOverflow { minimum.Y = textSize.Y } - - minimum.X += this.padding.Horizontal() - minimum.Y += this.padding.Vertical() - borderSum := this.borderSum() - minimum.X += borderSum.Horizontal() - minimum.Y += borderSum.Vertical() - this.SetMinimumSize(minimum) + + return minimum.Add(this.box.contentMinimum()) } func (this *textBox) doLayout () { diff --git a/window.go b/window.go index 0932d18..ba7aae0 100644 --- a/window.go +++ b/window.go @@ -46,9 +46,17 @@ type window struct { focused anyBox hovered anyBox - needDraw boxSet - needLayout boxSet - needRedo bool + // TODO: needMinimum and needLayout should be priority queues. for the + // minimums, we need to start at the deeper parts of the layout tree and + // go upward towards the top. for the layouts, we need to start at the + // top of the layout tree and progressively go deeper. this will + // eliminate redundant layout calculations. + + needMinimum boxSet + needLayout boxSet + needDraw boxSet + needRedo bool + minimumClean bool } func (backend *Backend) NewWindow ( @@ -130,7 +138,7 @@ func (backend *Backend) newWindow ( // Connect(backend.x, window.xWindow.Id) window.metrics.bounds = bounds - window.setMinimumSize(image.Pt(8, 8)) + window.doMinimumSize() backend.windows[window.xWindow.Id] = window @@ -353,15 +361,14 @@ func (window *window) pushRegion (region image.Rectangle) { subCanvas.(*xcanvas.Canvas).Push(window.xWindow.Id) } -func (window *window) recalculateMinimumSize () { - rootMinimum := image.Point { } - if window.root != nil { - rootMinimum = window.root.MinimumSize() - } - window.setMinimumSize(rootMinimum) -} +func (window *window) doMinimumSize () { + window.minimumClean = true -func (window *window) setMinimumSize (size image.Point) { + size := image.Point { } + if window.root != nil { + size = window.root.MinimumSize() + } + if size.X < 8 { size.X = 8 } if size.Y < 8 { size.Y = 8 } icccm.WmNormalHintsSet (