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() | ||||
| 	} | ||||
| } | ||||
|  | ||||
							
								
								
									
										80
									
								
								box.go
									
									
									
									
									
								
							
							
						
						
									
										80
									
								
								box.go
									
									
									
									
									
								
							| @ -15,6 +15,10 @@ type box struct { | ||||
| 
 | ||||
| 	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, | ||||
| 	} | ||||
| 	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 () { | ||||
|  | ||||
							
								
								
									
										23
									
								
								system.go
									
									
									
									
									
								
							
							
						
						
									
										23
									
								
								system.go
									
									
									
									
									
								
							| @ -40,13 +40,17 @@ type parent interface { | ||||
| 
 | ||||
| type anyBox interface { | ||||
| 	tomo.Box | ||||
| 	canvas.Drawer | ||||
| 	 | ||||
| 	doDraw           () | ||||
| 	doLayout         () | ||||
| 	doMinimumSize    () | ||||
| 	contentMinimum   () image.Point | ||||
| 	setParent        (parent) | ||||
| 	flushActionQueue () | ||||
| 	recursiveRedo    () | ||||
| 	canBeFocused     () bool | ||||
| 	boxUnder         (image.Point) anyBox | ||||
| 	recalculateMinimumSize () | ||||
| 
 | ||||
| 	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() | ||||
| 	} | ||||
|  | ||||
							
								
								
									
										23
									
								
								textbox.go
									
									
									
									
									
								
							
							
						
						
									
										23
									
								
								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()) | ||||
| @ -284,12 +282,7 @@ func (this *textBox) recalculateMinimumSize () { | ||||
| 		 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 () { | ||||
|  | ||||
							
								
								
									
										23
									
								
								window.go
									
									
									
									
									
								
							
							
						
						
									
										23
									
								
								window.go
									
									
									
									
									
								
							| @ -46,9 +46,17 @@ type window struct { | ||||
| 	focused anyBox | ||||
| 	hovered anyBox | ||||
| 
 | ||||
| 	needDraw   boxSet | ||||
| 	// 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 { } | ||||
| func (window *window) doMinimumSize () { | ||||
| 	window.minimumClean = true | ||||
| 
 | ||||
| 	size := image.Point { } | ||||
| 	if window.root != nil { | ||||
| 		rootMinimum = window.root.MinimumSize() | ||||
| 	} | ||||
| 	window.setMinimumSize(rootMinimum) | ||||
| 		size = window.root.MinimumSize() | ||||
| 	} | ||||
| 	 | ||||
| func (window *window) setMinimumSize (size image.Point) { | ||||
| 	if size.X < 8 { size.X = 8 } | ||||
| 	if size.Y < 8 { size.Y = 8 } | ||||
| 	icccm.WmNormalHintsSet ( | ||||
|  | ||||
		Reference in New Issue
	
	Block a user