Last active
June 26, 2022 01:38
-
-
Save zach-klippenstein/1f41043b8c0d4a5b84f6d3b2d6185a63 to your computer and use it in GitHub Desktop.
Proof-of-concept of a composable that draws information about all its children on top of them.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
@Composable fun App() { | |
DebugBounds { | |
Column(Modifier.background(Color.White).fillMaxSize()) { | |
BasicText("Some text") | |
Spacer(Modifier.size(10.dp)) | |
BasicText("More text") | |
Spacer(Modifier.size(5.dp)) | |
BasicText("Button", Modifier | |
.clickable { } | |
.background(Color.Blue, RoundedCornerShape(3.dp)) | |
.padding(8.dp) | |
) | |
} | |
} | |
} | |
@Composable fun DebugBounds(content: @Composable () -> Unit) { | |
val recomposeScope = currentRecomposeScope | |
val boundsTracker = remember { BoundsTracker(recomposeScope) } | |
ObserveLayoutInfos( | |
onLayoutInfoInserted = boundsTracker::onLayoutInfoInserted, | |
modifier = Modifier.drawWithCache { | |
with(boundsTracker) { | |
draw() | |
} | |
}, | |
content = content | |
) | |
} | |
private class BoundsTracker(private val recomposeScope: RecomposeScope) { | |
private val infos = mutableListOf<LayoutInfo>() | |
fun onLayoutInfoInserted(layoutInfo: LayoutInfo) { | |
infos += layoutInfo | |
recomposeScope.invalidate() | |
} | |
fun CacheDrawScope.draw(): DrawResult { | |
val textPaint = Paint().apply { | |
color = android.graphics.Color.RED | |
textSize = 24.sp.toPx() | |
} | |
return onDrawWithContent { | |
drawContent() | |
drawBounds(textPaint) | |
} | |
} | |
private fun DrawScope.drawBounds(textPaint: Paint) { | |
infos.forEach { info -> | |
if (info.isPlaced && info.isAttached) { | |
val bounds = info.coordinates.boundsInRoot() | |
drawRect(Color.Red, topLeft = bounds.topLeft, size = bounds.size, style = Stroke(1f)) | |
drawContext.canvas.nativeCanvas.apply { | |
drawText( | |
"${bounds.width.roundToInt()}×${bounds.height.roundToInt()}", | |
bounds.left + 1f, | |
bounds.top - textPaint.fontMetrics.top - 1f, | |
textPaint | |
) | |
} | |
} | |
} | |
} | |
} | |
@OptIn(ExperimentalComposeApi::class, ComposeCompilerApi::class) | |
@Composable private fun ObserveLayoutInfos( | |
onLayoutInfoInserted: (LayoutInfo) -> Unit, | |
modifier: Modifier = Modifier, | |
content: @Composable () -> Unit | |
) { | |
BoxWithConstraints(modifier) { | |
val composer = currentComposer | |
val context = rememberCompositionContext() | |
val subComposition = remember { ObservedComposition(composer.applier, context) } | |
subComposition.observingApplier.onLayoutInfoInserted = onLayoutInfoInserted | |
subComposition.composition.setContent { | |
Box(Modifier.sizeIn(maxWidth = maxWidth, maxHeight = maxHeight)) { | |
content() | |
} | |
} | |
} | |
} | |
@OptIn(ExperimentalComposeApi::class) | |
private class ObservedComposition<T>( | |
applier: Applier<T>, | |
parent: CompositionContext | |
) : RememberObserver { | |
val observingApplier = LayoutInfoObservingApplier(applier) | |
val composition = Composition(applier = observingApplier, parent = parent) | |
override fun onAbandoned() { | |
composition.dispose() | |
} | |
override fun onRemembered() { | |
// noop | |
} | |
override fun onForgotten() { | |
composition.dispose() | |
} | |
} | |
private class LayoutInfoObservingApplier<T>( | |
private val wrapped: Applier<T> | |
) : Applier<T> by wrapped { | |
var onLayoutInfoInserted: (LayoutInfo) -> Unit = {} | |
override fun insertBottomUp( | |
index: Int, | |
instance: T | |
) { | |
wrapped.insertBottomUp(index, instance) | |
if (instance is LayoutInfo) { | |
onLayoutInfoInserted(instance) | |
} | |
} | |
override fun insertTopDown( | |
index: Int, | |
instance: T | |
) { | |
wrapped.insertTopDown(index, instance) | |
if (instance is LayoutInfo) { | |
onLayoutInfoInserted(instance) | |
} | |
} | |
} |
Author
zach-klippenstein
commented
Mar 5, 2021
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment