From 9b92367009bd3e6072aea114f036d0af3855f9ca Mon Sep 17 00:00:00 2001 From: Seiko Date: Tue, 31 Oct 2023 17:20:36 +0800 Subject: [PATCH] Use NodeElement instead of composed (#13) --- .../dev/chrisbanes/haze/HazePlatform.kt | 134 ++++++++++++++---- 1 file changed, 105 insertions(+), 29 deletions(-) diff --git a/haze/src/skikoMain/kotlin/dev/chrisbanes/haze/HazePlatform.kt b/haze/src/skikoMain/kotlin/dev/chrisbanes/haze/HazePlatform.kt index 1185cdb1..e245e269 100644 --- a/haze/src/skikoMain/kotlin/dev/chrisbanes/haze/HazePlatform.kt +++ b/haze/src/skikoMain/kotlin/dev/chrisbanes/haze/HazePlatform.kt @@ -3,15 +3,22 @@ package dev.chrisbanes.haze -import androidx.compose.runtime.remember import androidx.compose.ui.Modifier -import androidx.compose.ui.composed import androidx.compose.ui.geometry.Rect import androidx.compose.ui.graphics.BlurEffect import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.RenderEffect import androidx.compose.ui.graphics.asComposeRenderEffect -import androidx.compose.ui.graphics.graphicsLayer +import androidx.compose.ui.layout.Measurable +import androidx.compose.ui.layout.MeasureResult +import androidx.compose.ui.layout.MeasureScope +import androidx.compose.ui.node.CompositionLocalConsumerModifierNode +import androidx.compose.ui.node.LayoutModifierNode +import androidx.compose.ui.node.ModifierNodeElement +import androidx.compose.ui.node.currentValueOf +import androidx.compose.ui.platform.InspectorInfo import androidx.compose.ui.platform.LocalDensity +import androidx.compose.ui.unit.Constraints import androidx.compose.ui.unit.Dp import org.jetbrains.skia.FilterTileMode import org.jetbrains.skia.ImageFilter @@ -77,40 +84,109 @@ internal actual fun Modifier.haze( backgroundColor: Color, tint: Color, blurRadius: Dp, -): Modifier = composed { - val blurRadiusPx = with(LocalDensity.current) { blurRadius.toPx() } - val blur = remember(blurRadius) { +): Modifier = this then HazeNodeElement( + areas = areas, + tint = tint, + blurRadius = blurRadius, +) + +private data class HazeNodeElement( + val areas: List, + val tint: Color, + val blurRadius: Dp, +) : ModifierNodeElement() { + override fun create(): HazeNode { + return HazeNode( + areas = areas, + tint = tint, + blurRadius = blurRadius, + ) + } + + override fun update(node: HazeNode) { + node.update( + areas = areas, + tint = tint, + blurRadius = blurRadius, + ) + } + + override fun InspectorInfo.inspectableProperties() { + name = "haze" + properties["areas"] = areas + properties["tint"] = tint + properties["blurRadius"] = blurRadius + } +} + +private class HazeNode( + private var areas: List, + private var tint: Color, + private val blurRadius: Dp, +) : Modifier.Node(), LayoutModifierNode, CompositionLocalConsumerModifierNode { + + private var blurFilter: ImageFilter? = null + + override fun onAttach() { + super.onAttach() + blurFilter = createBlurImageFilter(blurRadius) + } + + fun update( + areas: List, + tint: Color, + blurRadius: Dp, + ) { + this.areas = areas + this.tint = tint + blurFilter = createBlurImageFilter(blurRadius) + } + + override fun MeasureScope.measure( + measurable: Measurable, + constraints: Constraints, + ): MeasureResult { + val placeable = measurable.measure(constraints) + return layout(placeable.width, placeable.height) { + placeable.placeWithLayer(0, 0, layerBlock = { + renderEffect = createBlurRenderEffect() + }) + } + } + + private fun createBlurImageFilter(blurRadius: Dp): ImageFilter { + val blurRadiusPx = with(currentValueOf(LocalDensity)) { + blurRadius.toPx() + } val sigma = BlurEffect.convertRadiusToSigma(blurRadiusPx) - ImageFilter.makeBlur( + return ImageFilter.makeBlur( sigmaX = sigma, sigmaY = sigma, mode = FilterTileMode.DECAL, ) } - graphicsLayer( - renderEffect = remember(areas, tint, blur) { - areas.asSequence() - .filterNot { it.isEmpty } - .map { area -> - val compositeShaderBuilder = RuntimeShaderBuilder(RUNTIME_SHADER).apply { - uniform("rectangle", area.left, area.top, area.right, area.bottom) - uniform("color", tint.red, tint.green, tint.blue, 1f) - uniform("colorShift", tint.alpha) - - child("noise", NOISE_SHADER) - } - - ImageFilter.makeRuntimeShader( - runtimeShaderBuilder = compositeShaderBuilder, - shaderNames = arrayOf("content", "blur"), - inputs = arrayOf(null, blur), - ) + private fun createBlurRenderEffect(): RenderEffect? { + return areas.asSequence() + .filterNot { it.isEmpty } + .map { area -> + val compositeShaderBuilder = RuntimeShaderBuilder(RUNTIME_SHADER).apply { + uniform("rectangle", area.left, area.top, area.right, area.bottom) + uniform("color", tint.red, tint.green, tint.blue, 1f) + uniform("colorShift", tint.alpha) + + child("noise", NOISE_SHADER) } - .toList() - .flatten()?.asComposeRenderEffect() - }, - ) + + ImageFilter.makeRuntimeShader( + runtimeShaderBuilder = compositeShaderBuilder, + shaderNames = arrayOf("content", "blur"), + inputs = arrayOf(null, blurFilter), + ) + } + .toList() + .flatten()?.asComposeRenderEffect() + } } private fun Collection.flatten(): ImageFilter? = when {