From ae51963ceb87c1065a99fb65a95c4d21b4c4c37f Mon Sep 17 00:00:00 2001 From: Jake Wharton Date: Mon, 27 Mar 2023 23:14:28 -0400 Subject: [PATCH] Shuffle things around for cleaner layering (#164) The 'layout' package is now a standalone widget tree. The 'ui' package is now only composables. The root package contains all the code for integration and Compose. --- .../mosaic/layout/NoContentMeasurePolicy.kt | 5 - .../mosaic/layout/NoContentMeasureScope.kt | 19 ---- .../mosaic/{nodes.kt => layout/Node.kt} | 96 +------------------ .../kotlin/com/jakewharton/mosaic/mosaic.kt | 52 +++++++++- .../com/jakewharton/mosaic/rendering.kt | 1 + .../com/jakewharton/mosaic/ui/Column.kt | 1 - .../mosaic/{layout => ui}/Layout.kt | 33 ++++++- .../kotlin/com/jakewharton/mosaic/ui/Node.kt | 50 ++++++++++ .../kotlin/com/jakewharton/mosaic/ui/Row.kt | 1 - .../com/jakewharton/mosaic/ui/Static.kt | 1 - .../kotlin/com/jakewharton/mosaic/ui/Text.kt | 1 - .../jakewharton/mosaic/DebugRenderingTest.kt | 2 +- .../com/jakewharton/mosaic/LayoutTest.kt | 2 +- .../com/jakewharton/mosaic/NodeApplierTest.kt | 7 +- 14 files changed, 140 insertions(+), 131 deletions(-) delete mode 100644 mosaic-runtime/src/commonMain/kotlin/com/jakewharton/mosaic/layout/NoContentMeasurePolicy.kt delete mode 100644 mosaic-runtime/src/commonMain/kotlin/com/jakewharton/mosaic/layout/NoContentMeasureScope.kt rename mosaic-runtime/src/commonMain/kotlin/com/jakewharton/mosaic/{nodes.kt => layout/Node.kt} (55%) rename mosaic-runtime/src/commonMain/kotlin/com/jakewharton/mosaic/{layout => ui}/Layout.kt (61%) create mode 100644 mosaic-runtime/src/commonMain/kotlin/com/jakewharton/mosaic/ui/Node.kt diff --git a/mosaic-runtime/src/commonMain/kotlin/com/jakewharton/mosaic/layout/NoContentMeasurePolicy.kt b/mosaic-runtime/src/commonMain/kotlin/com/jakewharton/mosaic/layout/NoContentMeasurePolicy.kt deleted file mode 100644 index c77e0e0f..00000000 --- a/mosaic-runtime/src/commonMain/kotlin/com/jakewharton/mosaic/layout/NoContentMeasurePolicy.kt +++ /dev/null @@ -1,5 +0,0 @@ -package com.jakewharton.mosaic.layout - -public fun interface NoContentMeasurePolicy { - public fun NoContentMeasureScope.measure(): MeasureResult -} diff --git a/mosaic-runtime/src/commonMain/kotlin/com/jakewharton/mosaic/layout/NoContentMeasureScope.kt b/mosaic-runtime/src/commonMain/kotlin/com/jakewharton/mosaic/layout/NoContentMeasureScope.kt deleted file mode 100644 index 3a771f67..00000000 --- a/mosaic-runtime/src/commonMain/kotlin/com/jakewharton/mosaic/layout/NoContentMeasureScope.kt +++ /dev/null @@ -1,19 +0,0 @@ -package com.jakewharton.mosaic.layout - -public sealed class NoContentMeasureScope { - public fun layout( - width: Int, - height: Int, - ): MeasureResult { - return LayoutResult(width, height) - } - - private class LayoutResult( - override val width: Int, - override val height: Int, - ) : MeasureResult { - override fun placeChildren() {} - } - - internal companion object : NoContentMeasureScope() -} diff --git a/mosaic-runtime/src/commonMain/kotlin/com/jakewharton/mosaic/nodes.kt b/mosaic-runtime/src/commonMain/kotlin/com/jakewharton/mosaic/layout/Node.kt similarity index 55% rename from mosaic-runtime/src/commonMain/kotlin/com/jakewharton/mosaic/nodes.kt rename to mosaic-runtime/src/commonMain/kotlin/com/jakewharton/mosaic/layout/Node.kt index f537234f..1d67298a 100644 --- a/mosaic-runtime/src/commonMain/kotlin/com/jakewharton/mosaic/nodes.kt +++ b/mosaic-runtime/src/commonMain/kotlin/com/jakewharton/mosaic/layout/Node.kt @@ -1,14 +1,7 @@ -package com.jakewharton.mosaic +package com.jakewharton.mosaic.layout -import androidx.compose.runtime.AbstractApplier -import androidx.compose.runtime.Applier -import androidx.compose.runtime.Composable -import androidx.compose.runtime.ReusableComposeNode -import com.jakewharton.mosaic.layout.Measurable -import com.jakewharton.mosaic.layout.MeasurePolicy -import com.jakewharton.mosaic.layout.MeasureResult -import com.jakewharton.mosaic.layout.MeasureScope -import com.jakewharton.mosaic.layout.Placeable +import com.jakewharton.mosaic.TextCanvas +import com.jakewharton.mosaic.TextSurface import com.jakewharton.mosaic.layout.Placeable.PlacementScope internal fun interface DrawPolicy { @@ -148,87 +141,4 @@ internal class MosaicNode( fun paintStatics(statics: MutableList) = staticPaintPolicy?.run { performPaintStatics(statics) } override fun toString() = debugPolicy.run { renderDebug() } - - companion object { - val Factory: () -> MosaicNode = { - MosaicNode( - measurePolicy = ThrowingPolicy, - drawPolicy = ThrowingPolicy, - staticPaintPolicy = ThrowingPolicy, - debugPolicy = ThrowingPolicy, - ) - } - - fun root(): MosaicNode { - return MosaicNode( - measurePolicy = { measurables -> - var width = 0 - var height = 0 - val placeables = measurables.map { measurable -> - measurable.measure().also { - width = maxOf(width, it.width) - height = maxOf(height, it.height) - } - } - layout(width, height) { - for (placeable in placeables) { - placeable.place(0, 0) - } - } - }, - drawPolicy = null, - staticPaintPolicy = StaticPaintPolicy.Children, - debugPolicy = { - children.joinToString(separator = "\n") - } - ) - } - - private val ThrowingPolicy = object : MeasurePolicy, DrawPolicy, StaticPaintPolicy, DebugPolicy { - override fun MeasureScope.measure(measurables: List) = throw AssertionError() - override fun performDraw(canvas: TextCanvas) = throw AssertionError() - override fun MosaicNode.performPaintStatics(statics: MutableList) = throw AssertionError() - override fun MosaicNode.renderDebug() = throw AssertionError() - } - } -} - -@Composable -internal inline fun Node( - content: @Composable () -> Unit = {}, - measurePolicy: MeasurePolicy, - drawPolicy: DrawPolicy?, - staticPaintPolicy: StaticPaintPolicy?, - debugPolicy: DebugPolicy, -) { - ReusableComposeNode>( - factory = MosaicNode.Factory, - update = { - set(measurePolicy) { this.measurePolicy = measurePolicy } - set(drawPolicy) { this.drawPolicy = drawPolicy } - set(staticPaintPolicy) { this.staticPaintPolicy = staticPaintPolicy } - set(debugPolicy) { this.debugPolicy = debugPolicy } - }, - content = content, - ) -} - -internal class MosaicNodeApplier(root: MosaicNode) : AbstractApplier(root) { - override fun insertTopDown(index: Int, instance: MosaicNode) { - // Ignored, we insert bottom-up. - } - - override fun insertBottomUp(index: Int, instance: MosaicNode) { - current.children.add(index, instance) - } - - override fun remove(index: Int, count: Int) { - current.children.remove(index, count) - } - - override fun move(from: Int, to: Int, count: Int) { - current.children.move(from, to, count) - } - - override fun onClear() {} } diff --git a/mosaic-runtime/src/commonMain/kotlin/com/jakewharton/mosaic/mosaic.kt b/mosaic-runtime/src/commonMain/kotlin/com/jakewharton/mosaic/mosaic.kt index a9a8e92b..00dea670 100644 --- a/mosaic-runtime/src/commonMain/kotlin/com/jakewharton/mosaic/mosaic.kt +++ b/mosaic-runtime/src/commonMain/kotlin/com/jakewharton/mosaic/mosaic.kt @@ -1,10 +1,13 @@ package com.jakewharton.mosaic +import androidx.compose.runtime.AbstractApplier import androidx.compose.runtime.BroadcastFrameClock import androidx.compose.runtime.Composable import androidx.compose.runtime.Composition import androidx.compose.runtime.Recomposer import androidx.compose.runtime.snapshots.Snapshot +import com.jakewharton.mosaic.layout.MosaicNode +import com.jakewharton.mosaic.layout.StaticPaintPolicy import kotlin.time.ExperimentalTime import kotlinx.coroutines.CompletableDeferred import kotlinx.coroutines.CoroutineScope @@ -26,7 +29,7 @@ internal fun mosaicNodes(content: @Composable () -> Unit): MosaicNode { val job = Job() val composeContext = clock + job - val rootNode = MosaicNode.root() + val rootNode = createRootNode() val recomposer = Recomposer(composeContext) val composition = Composition(MosaicNodeApplier(rootNode), recomposer) @@ -64,7 +67,7 @@ public suspend fun runMosaic(body: suspend MosaicScope.() -> Unit): Unit = corou val job = Job(coroutineContext[Job]) val composeContext = coroutineContext + clock + job - val rootNode = MosaicNode.root() + val rootNode = createRootNode() val recomposer = Recomposer(composeContext) val composition = Composition(MosaicNodeApplier(rootNode), recomposer) @@ -136,3 +139,48 @@ public suspend fun runMosaic(body: suspend MosaicScope.() -> Unit): Unit = corou job.cancel() composition.dispose() } + +internal fun createRootNode(): MosaicNode { + return MosaicNode( + measurePolicy = { measurables -> + var width = 0 + var height = 0 + val placeables = measurables.map { measurable -> + measurable.measure().also { + width = maxOf(width, it.width) + height = maxOf(height, it.height) + } + } + layout(width, height) { + for (placeable in placeables) { + placeable.place(0, 0) + } + } + }, + drawPolicy = null, + staticPaintPolicy = StaticPaintPolicy.Children, + debugPolicy = { + children.joinToString(separator = "\n") + } + ) +} + +internal class MosaicNodeApplier(root: MosaicNode) : AbstractApplier(root) { + override fun insertTopDown(index: Int, instance: MosaicNode) { + // Ignored, we insert bottom-up. + } + + override fun insertBottomUp(index: Int, instance: MosaicNode) { + current.children.add(index, instance) + } + + override fun remove(index: Int, count: Int) { + current.children.remove(index, count) + } + + override fun move(from: Int, to: Int, count: Int) { + current.children.move(from, to, count) + } + + override fun onClear() {} +} diff --git a/mosaic-runtime/src/commonMain/kotlin/com/jakewharton/mosaic/rendering.kt b/mosaic-runtime/src/commonMain/kotlin/com/jakewharton/mosaic/rendering.kt index cc542bda..33748668 100644 --- a/mosaic-runtime/src/commonMain/kotlin/com/jakewharton/mosaic/rendering.kt +++ b/mosaic-runtime/src/commonMain/kotlin/com/jakewharton/mosaic/rendering.kt @@ -1,5 +1,6 @@ package com.jakewharton.mosaic +import com.jakewharton.mosaic.layout.MosaicNode import kotlin.time.ExperimentalTime import kotlin.time.TimeMark import kotlin.time.TimeSource diff --git a/mosaic-runtime/src/commonMain/kotlin/com/jakewharton/mosaic/ui/Column.kt b/mosaic-runtime/src/commonMain/kotlin/com/jakewharton/mosaic/ui/Column.kt index 2bbc1eb0..dcacd65f 100644 --- a/mosaic-runtime/src/commonMain/kotlin/com/jakewharton/mosaic/ui/Column.kt +++ b/mosaic-runtime/src/commonMain/kotlin/com/jakewharton/mosaic/ui/Column.kt @@ -3,7 +3,6 @@ package com.jakewharton.mosaic.ui import androidx.compose.runtime.Composable -import com.jakewharton.mosaic.layout.Layout import kotlin.jvm.JvmName @Composable diff --git a/mosaic-runtime/src/commonMain/kotlin/com/jakewharton/mosaic/layout/Layout.kt b/mosaic-runtime/src/commonMain/kotlin/com/jakewharton/mosaic/ui/Layout.kt similarity index 61% rename from mosaic-runtime/src/commonMain/kotlin/com/jakewharton/mosaic/layout/Layout.kt rename to mosaic-runtime/src/commonMain/kotlin/com/jakewharton/mosaic/ui/Layout.kt index 2d03943b..55ca8a98 100644 --- a/mosaic-runtime/src/commonMain/kotlin/com/jakewharton/mosaic/layout/Layout.kt +++ b/mosaic-runtime/src/commonMain/kotlin/com/jakewharton/mosaic/ui/Layout.kt @@ -1,13 +1,38 @@ @file:JvmName("Layout") -package com.jakewharton.mosaic.layout +package com.jakewharton.mosaic.ui import androidx.compose.runtime.Composable -import com.jakewharton.mosaic.DrawPolicy -import com.jakewharton.mosaic.Node -import com.jakewharton.mosaic.StaticPaintPolicy +import com.jakewharton.mosaic.layout.DrawPolicy +import com.jakewharton.mosaic.layout.Measurable +import com.jakewharton.mosaic.layout.MeasurePolicy +import com.jakewharton.mosaic.layout.MeasureResult +import com.jakewharton.mosaic.layout.MeasureScope +import com.jakewharton.mosaic.layout.StaticPaintPolicy import kotlin.jvm.JvmName +internal fun interface NoContentMeasurePolicy { + fun NoContentMeasureScope.measure(): MeasureResult +} + +internal sealed class NoContentMeasureScope { + fun layout( + width: Int, + height: Int, + ): MeasureResult { + return LayoutResult(width, height) + } + + private class LayoutResult( + override val width: Int, + override val height: Int, + ) : MeasureResult { + override fun placeChildren() {} + } + + internal companion object : NoContentMeasureScope() +} + @Composable internal fun Layout( debugInfo: () -> String = { "Layout()" }, diff --git a/mosaic-runtime/src/commonMain/kotlin/com/jakewharton/mosaic/ui/Node.kt b/mosaic-runtime/src/commonMain/kotlin/com/jakewharton/mosaic/ui/Node.kt new file mode 100644 index 00000000..4d6ab9d1 --- /dev/null +++ b/mosaic-runtime/src/commonMain/kotlin/com/jakewharton/mosaic/ui/Node.kt @@ -0,0 +1,50 @@ +package com.jakewharton.mosaic.ui + +import androidx.compose.runtime.Applier +import androidx.compose.runtime.Composable +import androidx.compose.runtime.ReusableComposeNode +import com.jakewharton.mosaic.TextCanvas +import com.jakewharton.mosaic.TextSurface +import com.jakewharton.mosaic.layout.DebugPolicy +import com.jakewharton.mosaic.layout.DrawPolicy +import com.jakewharton.mosaic.layout.Measurable +import com.jakewharton.mosaic.layout.MeasurePolicy +import com.jakewharton.mosaic.layout.MeasureScope +import com.jakewharton.mosaic.layout.MosaicNode +import com.jakewharton.mosaic.layout.StaticPaintPolicy + +@Composable +internal inline fun Node( + content: @Composable () -> Unit = {}, + measurePolicy: MeasurePolicy, + drawPolicy: DrawPolicy?, + staticPaintPolicy: StaticPaintPolicy?, + debugPolicy: DebugPolicy, +) { + ReusableComposeNode>( + factory = NodeFactory, + update = { + set(measurePolicy) { this.measurePolicy = measurePolicy } + set(drawPolicy) { this.drawPolicy = drawPolicy } + set(staticPaintPolicy) { this.staticPaintPolicy = staticPaintPolicy } + set(debugPolicy) { this.debugPolicy = debugPolicy } + }, + content = content, + ) +} + +internal val NodeFactory: () -> MosaicNode = { + MosaicNode( + measurePolicy = ThrowingPolicy, + drawPolicy = ThrowingPolicy, + staticPaintPolicy = ThrowingPolicy, + debugPolicy = ThrowingPolicy, + ) +} + +private val ThrowingPolicy = object : MeasurePolicy, DrawPolicy, StaticPaintPolicy, DebugPolicy { + override fun MeasureScope.measure(measurables: List) = throw AssertionError() + override fun performDraw(canvas: TextCanvas) = throw AssertionError() + override fun MosaicNode.performPaintStatics(statics: MutableList) = throw AssertionError() + override fun MosaicNode.renderDebug() = throw AssertionError() +} diff --git a/mosaic-runtime/src/commonMain/kotlin/com/jakewharton/mosaic/ui/Row.kt b/mosaic-runtime/src/commonMain/kotlin/com/jakewharton/mosaic/ui/Row.kt index af6f7174..80c99577 100644 --- a/mosaic-runtime/src/commonMain/kotlin/com/jakewharton/mosaic/ui/Row.kt +++ b/mosaic-runtime/src/commonMain/kotlin/com/jakewharton/mosaic/ui/Row.kt @@ -3,7 +3,6 @@ package com.jakewharton.mosaic.ui import androidx.compose.runtime.Composable -import com.jakewharton.mosaic.layout.Layout import kotlin.jvm.JvmName @Composable diff --git a/mosaic-runtime/src/commonMain/kotlin/com/jakewharton/mosaic/ui/Static.kt b/mosaic-runtime/src/commonMain/kotlin/com/jakewharton/mosaic/ui/Static.kt index dfd1a273..133e2625 100644 --- a/mosaic-runtime/src/commonMain/kotlin/com/jakewharton/mosaic/ui/Static.kt +++ b/mosaic-runtime/src/commonMain/kotlin/com/jakewharton/mosaic/ui/Static.kt @@ -8,7 +8,6 @@ import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.runtime.snapshots.SnapshotStateList -import com.jakewharton.mosaic.Node import kotlin.jvm.JvmName /** diff --git a/mosaic-runtime/src/commonMain/kotlin/com/jakewharton/mosaic/ui/Text.kt b/mosaic-runtime/src/commonMain/kotlin/com/jakewharton/mosaic/ui/Text.kt index 730c6a5c..d9b9a4c0 100644 --- a/mosaic-runtime/src/commonMain/kotlin/com/jakewharton/mosaic/ui/Text.kt +++ b/mosaic-runtime/src/commonMain/kotlin/com/jakewharton/mosaic/ui/Text.kt @@ -4,7 +4,6 @@ package com.jakewharton.mosaic.ui import androidx.compose.runtime.Composable import androidx.compose.runtime.remember -import com.jakewharton.mosaic.layout.Layout import com.jakewharton.mosaic.text.TextLayout import kotlin.jvm.JvmName diff --git a/mosaic-runtime/src/commonTest/kotlin/com/jakewharton/mosaic/DebugRenderingTest.kt b/mosaic-runtime/src/commonTest/kotlin/com/jakewharton/mosaic/DebugRenderingTest.kt index cb19adff..f7ce7858 100644 --- a/mosaic-runtime/src/commonTest/kotlin/com/jakewharton/mosaic/DebugRenderingTest.kt +++ b/mosaic-runtime/src/commonTest/kotlin/com/jakewharton/mosaic/DebugRenderingTest.kt @@ -1,6 +1,6 @@ package com.jakewharton.mosaic -import com.jakewharton.mosaic.layout.Layout +import com.jakewharton.mosaic.ui.Layout import com.jakewharton.mosaic.ui.Row import com.jakewharton.mosaic.ui.Static import com.jakewharton.mosaic.ui.Text diff --git a/mosaic-runtime/src/commonTest/kotlin/com/jakewharton/mosaic/LayoutTest.kt b/mosaic-runtime/src/commonTest/kotlin/com/jakewharton/mosaic/LayoutTest.kt index 89f412f0..99a768b2 100644 --- a/mosaic-runtime/src/commonTest/kotlin/com/jakewharton/mosaic/LayoutTest.kt +++ b/mosaic-runtime/src/commonTest/kotlin/com/jakewharton/mosaic/LayoutTest.kt @@ -1,8 +1,8 @@ package com.jakewharton.mosaic -import com.jakewharton.mosaic.layout.Layout import com.jakewharton.mosaic.layout.Measurable import com.jakewharton.mosaic.ui.Column +import com.jakewharton.mosaic.ui.Layout import com.jakewharton.mosaic.ui.Row import com.jakewharton.mosaic.ui.Text import kotlin.test.Test diff --git a/mosaic-runtime/src/commonTest/kotlin/com/jakewharton/mosaic/NodeApplierTest.kt b/mosaic-runtime/src/commonTest/kotlin/com/jakewharton/mosaic/NodeApplierTest.kt index 1ae3402b..8ba51d9c 100644 --- a/mosaic-runtime/src/commonTest/kotlin/com/jakewharton/mosaic/NodeApplierTest.kt +++ b/mosaic-runtime/src/commonTest/kotlin/com/jakewharton/mosaic/NodeApplierTest.kt @@ -1,11 +1,14 @@ package com.jakewharton.mosaic import androidx.compose.runtime.Applier +import com.jakewharton.mosaic.layout.DebugPolicy +import com.jakewharton.mosaic.layout.MosaicNode +import com.jakewharton.mosaic.ui.NodeFactory import kotlin.test.Test import kotlin.test.assertEquals class NodeApplierTest { - private val root = MosaicNode.root() + private val root = createRootNode() private val applier = MosaicNodeApplier(root) private fun Applier.insert(index: Int, instance: T) { @@ -185,7 +188,7 @@ class NodeApplierTest { } private fun TextNode(name: String): MosaicNode { - return MosaicNode.Factory().apply { + return NodeFactory().apply { debugPolicy = DebugPolicy { name } } }