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 com.jakewharton.mosaic.TextCanvas
import androidx.compose.runtime.Applier import com.jakewharton.mosaic.TextSurface
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.layout.Placeable.PlacementScope import com.jakewharton.mosaic.layout.Placeable.PlacementScope
internal fun interface DrawPolicy { internal fun interface DrawPolicy {
@ -148,87 +141,4 @@ internal class MosaicNode(
fun paintStatics(statics: MutableList<TextSurface>) = staticPaintPolicy?.run { performPaintStatics(statics) } fun paintStatics(statics: MutableList<TextSurface>) = staticPaintPolicy?.run { performPaintStatics(statics) }
override fun toString() = debugPolicy.run { renderDebug() } 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 package com.jakewharton.mosaic
import androidx.compose.runtime.AbstractApplier
import androidx.compose.runtime.BroadcastFrameClock import androidx.compose.runtime.BroadcastFrameClock
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.Composition import androidx.compose.runtime.Composition
import androidx.compose.runtime.Recomposer import androidx.compose.runtime.Recomposer
import androidx.compose.runtime.snapshots.Snapshot import androidx.compose.runtime.snapshots.Snapshot
import com.jakewharton.mosaic.layout.MosaicNode
import com.jakewharton.mosaic.layout.StaticPaintPolicy
import kotlin.time.ExperimentalTime import kotlin.time.ExperimentalTime
import kotlinx.coroutines.CompletableDeferred import kotlinx.coroutines.CompletableDeferred
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
@ -26,7 +29,7 @@ internal fun mosaicNodes(content: @Composable () -> Unit): MosaicNode {
val job = Job() val job = Job()
val composeContext = clock + job val composeContext = clock + job
val rootNode = MosaicNode.root() val rootNode = createRootNode()
val recomposer = Recomposer(composeContext) val recomposer = Recomposer(composeContext)
val composition = Composition(MosaicNodeApplier(rootNode), recomposer) 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 job = Job(coroutineContext[Job])
val composeContext = coroutineContext + clock + job val composeContext = coroutineContext + clock + job
val rootNode = MosaicNode.root() val rootNode = createRootNode()
val recomposer = Recomposer(composeContext) val recomposer = Recomposer(composeContext)
val composition = Composition(MosaicNodeApplier(rootNode), recomposer) val composition = Composition(MosaicNodeApplier(rootNode), recomposer)
@ -136,3 +139,48 @@ public suspend fun runMosaic(body: suspend MosaicScope.() -> Unit): Unit = corou
job.cancel() job.cancel()
composition.dispose() 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 package com.jakewharton.mosaic
import com.jakewharton.mosaic.layout.MosaicNode
import kotlin.time.ExperimentalTime import kotlin.time.ExperimentalTime
import kotlin.time.TimeMark import kotlin.time.TimeMark
import kotlin.time.TimeSource import kotlin.time.TimeSource

View File

@ -3,7 +3,6 @@
package com.jakewharton.mosaic.ui package com.jakewharton.mosaic.ui
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import com.jakewharton.mosaic.layout.Layout
import kotlin.jvm.JvmName import kotlin.jvm.JvmName
@Composable @Composable

View File

@ -1,13 +1,38 @@
@file:JvmName("Layout") @file:JvmName("Layout")
package com.jakewharton.mosaic.layout package com.jakewharton.mosaic.ui
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import com.jakewharton.mosaic.DrawPolicy import com.jakewharton.mosaic.layout.DrawPolicy
import com.jakewharton.mosaic.Node import com.jakewharton.mosaic.layout.Measurable
import com.jakewharton.mosaic.StaticPaintPolicy 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 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 @Composable
internal fun Layout( internal fun Layout(
debugInfo: () -> String = { "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 package com.jakewharton.mosaic.ui
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import com.jakewharton.mosaic.layout.Layout
import kotlin.jvm.JvmName import kotlin.jvm.JvmName
@Composable @Composable

View File

@ -8,7 +8,6 @@ import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue import androidx.compose.runtime.setValue
import androidx.compose.runtime.snapshots.SnapshotStateList import androidx.compose.runtime.snapshots.SnapshotStateList
import com.jakewharton.mosaic.Node
import kotlin.jvm.JvmName import kotlin.jvm.JvmName
/** /**

View File

@ -4,7 +4,6 @@ package com.jakewharton.mosaic.ui
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import com.jakewharton.mosaic.layout.Layout
import com.jakewharton.mosaic.text.TextLayout import com.jakewharton.mosaic.text.TextLayout
import kotlin.jvm.JvmName import kotlin.jvm.JvmName

View File

@ -1,6 +1,6 @@
package com.jakewharton.mosaic 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.Row
import com.jakewharton.mosaic.ui.Static import com.jakewharton.mosaic.ui.Static
import com.jakewharton.mosaic.ui.Text import com.jakewharton.mosaic.ui.Text

View File

@ -1,8 +1,8 @@
package com.jakewharton.mosaic package com.jakewharton.mosaic
import com.jakewharton.mosaic.layout.Layout
import com.jakewharton.mosaic.layout.Measurable import com.jakewharton.mosaic.layout.Measurable
import com.jakewharton.mosaic.ui.Column import com.jakewharton.mosaic.ui.Column
import com.jakewharton.mosaic.ui.Layout
import com.jakewharton.mosaic.ui.Row import com.jakewharton.mosaic.ui.Row
import com.jakewharton.mosaic.ui.Text import com.jakewharton.mosaic.ui.Text
import kotlin.test.Test import kotlin.test.Test

View File

@ -1,11 +1,14 @@
package com.jakewharton.mosaic package com.jakewharton.mosaic
import androidx.compose.runtime.Applier 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.Test
import kotlin.test.assertEquals import kotlin.test.assertEquals
class NodeApplierTest { class NodeApplierTest {
private val root = MosaicNode.root() private val root = createRootNode()
private val applier = MosaicNodeApplier(root) private val applier = MosaicNodeApplier(root)
private fun <T> Applier<T>.insert(index: Int, instance: T) { private fun <T> Applier<T>.insert(index: Int, instance: T) {
@ -185,7 +188,7 @@ class NodeApplierTest {
} }
private fun TextNode(name: String): MosaicNode { private fun TextNode(name: String): MosaicNode {
return MosaicNode.Factory().apply { return NodeFactory().apply {
debugPolicy = DebugPolicy { name } debugPolicy = DebugPolicy { name }
} }
} }