mirror of
				https://github.com/JakeWharton/mosaic.git
				synced 2025-10-31 18:58:37 +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
	 Jake Wharton
					Jake Wharton