Skip to content

Commit

Permalink
Use NodeElement instead of composed (#13)
Browse files Browse the repository at this point in the history
  • Loading branch information
qdsfdhvh authored Oct 31, 2023
1 parent 03f2088 commit 9b92367
Showing 1 changed file with 105 additions and 29 deletions.
134 changes: 105 additions & 29 deletions haze/src/skikoMain/kotlin/dev/chrisbanes/haze/HazePlatform.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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<Rect>,
val tint: Color,
val blurRadius: Dp,
) : ModifierNodeElement<HazeNode>() {
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<Rect>,
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<Rect>,
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<ImageFilter>.flatten(): ImageFilter? = when {
Expand Down

0 comments on commit 9b92367

Please sign in to comment.