mirror of
https://github.com/JakeWharton/mosaic.git
synced 2025-11-01 03:43:31 +08:00
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:
@ -1,5 +0,0 @@
|
|||||||
package com.jakewharton.mosaic.layout
|
|
||||||
|
|
||||||
public fun interface NoContentMeasurePolicy {
|
|
||||||
public fun NoContentMeasureScope.measure(): MeasureResult
|
|
||||||
}
|
|
||||||
@ -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()
|
|
||||||
}
|
|
||||||
@ -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() {}
|
|
||||||
}
|
}
|
||||||
@ -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() {}
|
||||||
|
}
|
||||||
|
|||||||
@ -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
|
||||||
|
|||||||
@ -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
|
||||||
|
|||||||
@ -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()" },
|
||||||
@ -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()
|
||||||
|
}
|
||||||
@ -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
|
||||||
|
|||||||
@ -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
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@ -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
|
||||||
|
|
||||||
|
|||||||
@ -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
|
||||||
|
|||||||
@ -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
|
||||||
|
|||||||
@ -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 }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user