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.
This commit is contained in:
Jake Wharton
2023-03-27 23:14:28 -04:00
committed by GitHub
parent 40be11f82c
commit ae51963ceb
14 changed files with 140 additions and 131 deletions

View File

@ -1,5 +0,0 @@
package com.jakewharton.mosaic.layout
public fun interface NoContentMeasurePolicy {
public fun NoContentMeasureScope.measure(): MeasureResult
}

View File

@ -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()
}

View File

@ -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<TextSurface>) = 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<Measurable>) = throw AssertionError()
override fun performDraw(canvas: TextCanvas) = throw AssertionError()
override fun MosaicNode.performPaintStatics(statics: MutableList<TextSurface>) = 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<MosaicNode, Applier<Any>>(
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<MosaicNode>(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() {}
}

View File

@ -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<MosaicNode>(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() {}
}

View File

@ -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

View File

@ -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

View File

@ -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()" },

View File

@ -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<MosaicNode, Applier<Any>>(
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<Measurable>) = throw AssertionError()
override fun performDraw(canvas: TextCanvas) = throw AssertionError()
override fun MosaicNode.performPaintStatics(statics: MutableList<TextSurface>) = throw AssertionError()
override fun MosaicNode.renderDebug() = throw AssertionError()
}

View File

@ -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

View File

@ -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
/**

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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 <T> Applier<T>.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 }
}
}