/
Node.kt
173 lines (148 loc) · 4.53 KB
/
Node.kt
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
package com.jakewharton.mosaic.layout
import com.jakewharton.mosaic.TextCanvas
import com.jakewharton.mosaic.TextSurface
import com.jakewharton.mosaic.layout.Placeable.PlacementScope
import com.jakewharton.mosaic.modifier.Modifier
internal fun interface DebugPolicy {
fun MosaicNode.renderDebug(): String
}
internal abstract class MosaicNodeLayer : Measurable, Placeable(), PlacementScope, MeasureScope {
abstract fun drawTo(canvas: TextCanvas)
}
internal abstract class AbstractMosaicNodeLayer(
private val next: MosaicNodeLayer?,
private val isStatic: Boolean,
) : MosaicNodeLayer() {
private var measureResult: MeasureResult = NotMeasured
final override val width get() = measureResult.width
final override val height get() = measureResult.height
override fun measure() = apply {
measureResult = doMeasure()
}
protected open fun doMeasure(): MeasureResult {
val placeable = next!!.measure()
return object : MeasureResult {
override val width: Int get() = placeable.width
override val height: Int get() = placeable.height
override fun placeChildren() {
placeable.place(0, 0)
}
}
}
final override var x = 0
private set
final override var y = 0
private set
final override fun placeAt(x: Int, y: Int) {
// If this layer belongs to a static node, ignore the placement coordinates from the parent.
// We reset the coordinate system to draw at 0,0 since static drawing will be on a canvas
// sized to this node's width and height.
if (!isStatic) {
this.x = x
this.y = y
}
measureResult.placeChildren()
}
override fun drawTo(canvas: TextCanvas) {
next?.drawTo(canvas)
}
}
internal object NotMeasured : MeasureResult {
override val width get() = 0
override val height get() = 0
override fun placeChildren() = throw UnsupportedOperationException("Not measured")
}
internal class MosaicNode(
var measurePolicy: MeasurePolicy,
var debugPolicy: DebugPolicy,
val onStaticDraw: (() -> Unit)?,
) : Measurable {
val isStatic get() = onStaticDraw != null
val children = mutableListOf<MosaicNode>()
private val bottomLayer: MosaicNodeLayer = BottomLayer(this)
var topLayer: MosaicNodeLayer = bottomLayer
var modifiers: Modifier = Modifier
set(value) {
topLayer = value.foldOut(bottomLayer) { element, lowerLayer ->
when (element) {
is LayoutModifier -> LayoutLayer(element, lowerLayer)
is DrawModifier -> DrawLayer(element, lowerLayer)
else -> lowerLayer
}
}
field = value
}
override fun measure(): Placeable = topLayer.apply { measure() }
val width: Int get() = topLayer.width
val height: Int get() = topLayer.height
val x: Int get() = topLayer.x
val y: Int get() = topLayer.y
fun measureAndPlace() {
val placeable = measure()
topLayer.run { placeable.place(0, 0) }
}
/**
* Draw this node to a [TextSurface].
* A call to [measureAndPlace] must precede calls to this function.
*/
fun paint(): TextSurface {
val surface = TextSurface(width, height)
topLayer.drawTo(surface)
return surface
}
/**
* Append any static [TextSurfaces][TextSurface] to [statics].
* A call to [measureAndPlace] must precede calls to this function.
*/
fun paintStatics(statics: MutableList<TextSurface>) {
for (child in children) {
if (isStatic) {
statics += child.paint()
}
child.paintStatics(statics)
}
onStaticDraw?.invoke()
}
override fun toString() = debugPolicy.run { renderDebug() }
}
private class BottomLayer(
private val node: MosaicNode,
) : AbstractMosaicNodeLayer(null, node.isStatic) {
override fun doMeasure(): MeasureResult {
return node.measurePolicy.run { measure(node.children) }
}
override fun drawTo(canvas: TextCanvas) {
for (child in node.children) {
if (child.width != 0 && child.height != 0) {
child.topLayer.drawTo(canvas)
}
}
}
}
private class LayoutLayer(
private val element: LayoutModifier,
private val lowerLayer: MosaicNodeLayer,
) : AbstractMosaicNodeLayer(lowerLayer, false) {
override fun doMeasure(): MeasureResult {
return element.run { measure(lowerLayer) }
}
}
private class DrawLayer(
private val element: DrawModifier,
private val lowerLayer: MosaicNodeLayer,
) : AbstractMosaicNodeLayer(lowerLayer, false) {
override fun drawTo(canvas: TextCanvas) {
val oldX = canvas.translationX
val oldY = canvas.translationY
canvas.translationX = x
canvas.translationY = y
val scope = object : TextCanvasDrawScope(canvas, width, height), ContentDrawScope {
override fun drawContent() {
lowerLayer.drawTo(canvas)
}
}
element.run { scope.draw() }
canvas.translationX = oldX
canvas.translationY = oldY
}
}