Changed the way minimum sizes are calculated
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.
This commit is contained in:
parent
00629a863d
commit
ff8875535d
10
backend.go
10
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()
|
||||
}
|
||||
}
|
||||
|
88
box.go
88
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 {
|
||||
|
@ -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 {
|
||||
|
@ -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 () {
|
||||
|
35
system.go
35
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()
|
||||
}
|
||||
|
25
textbox.go
25
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 () {
|
||||
|
31
window.go
31
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 (
|
||||
|
Reference in New Issue
Block a user