From fd7b77ffce4a49ed517508748630eb41af0ef74e Mon Sep 17 00:00:00 2001 From: Chris Banes Date: Sun, 10 Dec 2023 19:42:26 +0000 Subject: [PATCH 1/9] Add new messages sample --- .../kotlin/dev/chrisbanes/haze/HazeNode31.kt | 31 ++++--- .../haze/sample/android/ImagesList.kt | 91 +++++++++++++++++++ .../chrisbanes/haze/sample/android/Samples.kt | 1 + .../dev/chrisbanes/haze/sample/ImagesList.kt | 91 +++++++++++++++++++ .../dev/chrisbanes/haze/sample/Samples.kt | 1 + 5 files changed, 204 insertions(+), 11 deletions(-) create mode 100644 sample/android-jetpack/src/main/kotlin/dev/chrisbanes/haze/sample/android/ImagesList.kt create mode 100644 sample/shared/src/commonMain/kotlin/dev/chrisbanes/haze/sample/ImagesList.kt diff --git a/haze-jetpack-compose/src/main/kotlin/dev/chrisbanes/haze/HazeNode31.kt b/haze-jetpack-compose/src/main/kotlin/dev/chrisbanes/haze/HazeNode31.kt index ed7f0274..8d65adfe 100644 --- a/haze-jetpack-compose/src/main/kotlin/dev/chrisbanes/haze/HazeNode31.kt +++ b/haze-jetpack-compose/src/main/kotlin/dev/chrisbanes/haze/HazeNode31.kt @@ -16,6 +16,7 @@ import android.graphics.Shader.TileMode.REPEAT import androidx.annotation.RequiresApi import androidx.compose.ui.geometry.Offset import androidx.compose.ui.geometry.Rect +import androidx.compose.ui.geometry.Size import androidx.compose.ui.graphics.Canvas import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Path @@ -39,9 +40,12 @@ import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.unit.Density import androidx.compose.ui.unit.Dp +import androidx.compose.ui.unit.IntSize import androidx.compose.ui.unit.LayoutDirection +import androidx.compose.ui.unit.toSize import dev.chrisbanes.haze.jetpackcompose.R import kotlin.math.roundToInt +import kotlin.properties.Delegates.observable @RequiresApi(31) internal class HazeNode31( @@ -64,29 +68,34 @@ internal class HazeNode31( private var effectsDirty = true private var effects: List = emptyList() - private var positionInRoot = Offset.Zero - + private var positionInRoot by observable(Offset.Unspecified) { _, _, _ -> + invalidateEffects() + } + private var size by observable(Size.Unspecified) { _, _, _ -> + invalidateEffects() + } private var noiseTexture: Bitmap? = null private var noiseTextureFactor: Float = Float.MIN_VALUE override fun onUpdate() { - effectsDirty = true - invalidateDraw() + invalidateEffects() } override fun onObservedReadsChanged() { + invalidateEffects() + } + + private fun invalidateEffects() { effectsDirty = true invalidateDraw() } override fun onPlaced(coordinates: LayoutCoordinates) { - val newPositionInRoot = coordinates.positionInRoot() - if (positionInRoot != newPositionInRoot) { - positionInRoot = newPositionInRoot + positionInRoot = coordinates.positionInRoot() + } - effectsDirty = true - invalidateDraw() - } + override fun onRemeasured(size: IntSize) { + this.size = size.toSize() } override fun ContentDrawScope.draw() { @@ -173,7 +182,7 @@ internal class HazeNode31( // We expand the area where our effect is applied to. This is necessary so that the blur // effect is applied evenly to all edges. If we don't do this, the blur effect is much less // visible on the edges of the area. - val expandedRect = bounds.inflate(blurRadiusPx) + val expandedRect = bounds //.inflate(blurRadiusPx) val node = RenderNode("blur").apply { setRenderEffect(effect) diff --git a/sample/android-jetpack/src/main/kotlin/dev/chrisbanes/haze/sample/android/ImagesList.kt b/sample/android-jetpack/src/main/kotlin/dev/chrisbanes/haze/sample/android/ImagesList.kt new file mode 100644 index 00000000..7cf9d78c --- /dev/null +++ b/sample/android-jetpack/src/main/kotlin/dev/chrisbanes/haze/sample/android/ImagesList.kt @@ -0,0 +1,91 @@ +// Copyright 2023, Christopher Banes and the Haze project contributors +// SPDX-License-Identifier: Apache-2.0 + +package dev.chrisbanes.haze.sample.android + +import androidx.compose.foundation.Image +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.ArrowBack +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.Icon +import androidx.compose.material3.IconButton +import androidx.compose.material3.LargeTopAppBar +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Scaffold +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.remember +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.layout.ContentScale +import androidx.compose.ui.unit.dp +import com.seiko.imageloader.rememberImagePainter +import dev.chrisbanes.haze.HazeState +import dev.chrisbanes.haze.haze +import dev.chrisbanes.haze.hazeChild + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +fun ImagesList(navigator: Navigator) { + MaterialTheme { + val hazeState = remember { HazeState() } + + Scaffold( + topBar = { + LargeTopAppBar( + title = { Text(text = "Images") }, + navigationIcon = { + IconButton(onClick = navigator::navigateUp) { + Icon(Icons.Default.ArrowBack, null) + } + }, + modifier = Modifier.fillMaxWidth(), + ) + }, + modifier = Modifier.fillMaxSize(), + ) { contentPadding -> + LazyColumn( + verticalArrangement = Arrangement.spacedBy(8.dp), + contentPadding = contentPadding, + modifier = Modifier.fillMaxSize(), + ) { + items(50) { index -> + Box( + modifier = Modifier + .fillParentMaxWidth() + .height(160.dp), + ) { + Image( + painter = rememberImagePainter(rememberRandomSampleImageUrl(width = 800)), + contentScale = ContentScale.Crop, + contentDescription = null, + modifier = Modifier + .haze( + state = hazeState, + backgroundColor = Color(0xFF646464), + tint = Color(0x4D646464), + ) + .fillMaxSize(), + ) + + Text( + "Message $index", + modifier = Modifier + .fillMaxSize(0.8f) + .align(Alignment.Center) + .hazeChild(index, hazeState, RoundedCornerShape(4.dp)), + ) + } + } + } + } + } +} diff --git a/sample/android-jetpack/src/main/kotlin/dev/chrisbanes/haze/sample/android/Samples.kt b/sample/android-jetpack/src/main/kotlin/dev/chrisbanes/haze/sample/android/Samples.kt index 21b51261..97878d79 100644 --- a/sample/android-jetpack/src/main/kotlin/dev/chrisbanes/haze/sample/android/Samples.kt +++ b/sample/android-jetpack/src/main/kotlin/dev/chrisbanes/haze/sample/android/Samples.kt @@ -29,6 +29,7 @@ import androidx.compose.ui.Modifier val Samples = listOf( Sample("Scaffold") { ScaffoldSample(it) }, Sample("Credit Card") { CreditCardSample(it) }, + Sample("Images List") { ImagesList(it) }, ) data class Sample( diff --git a/sample/shared/src/commonMain/kotlin/dev/chrisbanes/haze/sample/ImagesList.kt b/sample/shared/src/commonMain/kotlin/dev/chrisbanes/haze/sample/ImagesList.kt new file mode 100644 index 00000000..d2158889 --- /dev/null +++ b/sample/shared/src/commonMain/kotlin/dev/chrisbanes/haze/sample/ImagesList.kt @@ -0,0 +1,91 @@ +// Copyright 2023, Christopher Banes and the Haze project contributors +// SPDX-License-Identifier: Apache-2.0 + +package dev.chrisbanes.haze.sample + +import androidx.compose.foundation.Image +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.ArrowBack +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.Icon +import androidx.compose.material3.IconButton +import androidx.compose.material3.LargeTopAppBar +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Scaffold +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.remember +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.layout.ContentScale +import androidx.compose.ui.unit.dp +import com.seiko.imageloader.rememberImagePainter +import dev.chrisbanes.haze.HazeState +import dev.chrisbanes.haze.haze +import dev.chrisbanes.haze.hazeChild + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +fun ImagesList(navigator: Navigator) { + MaterialTheme { + val hazeState = remember { HazeState() } + + Scaffold( + topBar = { + LargeTopAppBar( + title = { Text(text = "Images") }, + navigationIcon = { + IconButton(onClick = navigator::navigateUp) { + Icon(Icons.Default.ArrowBack, null) + } + }, + modifier = Modifier.fillMaxWidth(), + ) + }, + modifier = Modifier.fillMaxSize(), + ) { contentPadding -> + LazyColumn( + verticalArrangement = Arrangement.spacedBy(8.dp), + contentPadding = contentPadding, + modifier = Modifier.fillMaxSize(), + ) { + items(50) { index -> + Box( + modifier = Modifier + .fillParentMaxWidth() + .height(160.dp), + ) { + Image( + painter = rememberImagePainter(rememberRandomSampleImageUrl(width = 800)), + contentScale = ContentScale.Crop, + contentDescription = null, + modifier = Modifier + .haze( + state = hazeState, + backgroundColor = Color(0xFF646464), + tint = Color(0x4D646464), + ) + .fillMaxSize(), + ) + + Text( + "Message $index", + modifier = Modifier + .fillMaxSize(0.8f) + .align(Alignment.Center) + .hazeChild(index, hazeState, RoundedCornerShape(4.dp)), + ) + } + } + } + } + } +} diff --git a/sample/shared/src/commonMain/kotlin/dev/chrisbanes/haze/sample/Samples.kt b/sample/shared/src/commonMain/kotlin/dev/chrisbanes/haze/sample/Samples.kt index 4f81408c..396d6237 100644 --- a/sample/shared/src/commonMain/kotlin/dev/chrisbanes/haze/sample/Samples.kt +++ b/sample/shared/src/commonMain/kotlin/dev/chrisbanes/haze/sample/Samples.kt @@ -29,6 +29,7 @@ import androidx.compose.ui.Modifier val Samples = listOf( Sample("Scaffold") { ScaffoldSample(it) }, Sample("Credit Card") { CreditCardSample(it) }, + Sample("Images List") { ImagesList(it) }, ) data class Sample( From 584fc3cd2a1a7f7f895ecd801142812a2e04c2f9 Mon Sep 17 00:00:00 2001 From: Chris Banes Date: Mon, 11 Dec 2023 09:47:02 +0000 Subject: [PATCH 2/9] More tweaks --- .../main/kotlin/dev/chrisbanes/haze/Haze.kt | 14 +++++--- .../kotlin/dev/chrisbanes/haze/HazeNode31.kt | 15 +++++--- .../dev/chrisbanes/haze/HazeNodeBase.kt | 34 ++++++++++++++----- .../dev/chrisbanes/haze/HazeNodeBase.kt | 34 ++++++++++++++----- .../kotlin/dev/chrisbanes/haze/Haze.kt | 12 ++++--- 5 files changed, 77 insertions(+), 32 deletions(-) diff --git a/haze-jetpack-compose/src/main/kotlin/dev/chrisbanes/haze/Haze.kt b/haze-jetpack-compose/src/main/kotlin/dev/chrisbanes/haze/Haze.kt index fbbaadf7..652d758c 100644 --- a/haze-jetpack-compose/src/main/kotlin/dev/chrisbanes/haze/Haze.kt +++ b/haze-jetpack-compose/src/main/kotlin/dev/chrisbanes/haze/Haze.kt @@ -12,6 +12,7 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.geometry.Offset import androidx.compose.ui.geometry.Rect import androidx.compose.ui.geometry.Size +import androidx.compose.ui.geometry.isSpecified import androidx.compose.ui.geometry.isUnspecified import androidx.compose.ui.geometry.toRect import androidx.compose.ui.geometry.translate @@ -61,15 +62,18 @@ class HazeState { internal fun HazeState.addAreasToPath( path: Path, + positionInRoot: Offset, layoutDirection: LayoutDirection, density: Density, ) { + if (positionInRoot.isUnspecified) return + areas.asSequence() - .filterNot { it.isEmpty } + .filter { it.isValid } .forEach { area -> path.addOutline( outline = area.shape.createOutline(area.size, layoutDirection, density), - offset = area.positionInRoot, + offset = area.positionInRoot - positionInRoot, ) } } @@ -91,13 +95,13 @@ class HazeArea { var shape: Shape by mutableStateOf(RectangleShape) internal set - val isEmpty: Boolean get() = size.isEmpty() + val isValid: Boolean + get() = size.isSpecified && positionInRoot.isSpecified && !size.isEmpty() } internal fun HazeArea.boundsInLocal(hazePositionInRoot: Offset): Rect? { - if (size.isUnspecified) return null + if (!isValid) return null if (hazePositionInRoot.isUnspecified) return null - if (positionInRoot.isUnspecified) return null return size.toRect().translate(positionInRoot - hazePositionInRoot) } diff --git a/haze-jetpack-compose/src/main/kotlin/dev/chrisbanes/haze/HazeNode31.kt b/haze-jetpack-compose/src/main/kotlin/dev/chrisbanes/haze/HazeNode31.kt index 8d65adfe..7a8c940f 100644 --- a/haze-jetpack-compose/src/main/kotlin/dev/chrisbanes/haze/HazeNode31.kt +++ b/haze-jetpack-compose/src/main/kotlin/dev/chrisbanes/haze/HazeNode31.kt @@ -68,12 +68,18 @@ internal class HazeNode31( private var effectsDirty = true private var effects: List = emptyList() - private var positionInRoot by observable(Offset.Unspecified) { _, _, _ -> - invalidateEffects() + + private var positionInRoot by observable(Offset.Unspecified) { _, oldValue, newValue -> + if (oldValue != newValue) { + invalidateEffects() + } } - private var size by observable(Size.Unspecified) { _, _, _ -> - invalidateEffects() + private var size by observable(Size.Unspecified) { _, oldValue, newValue -> + if (oldValue != newValue) { + invalidateEffects() + } } + private var noiseTexture: Bitmap? = null private var noiseTextureFactor: Float = Float.MIN_VALUE @@ -92,6 +98,7 @@ internal class HazeNode31( override fun onPlaced(coordinates: LayoutCoordinates) { positionInRoot = coordinates.positionInRoot() + size = coordinates.size.toSize() } override fun onRemeasured(size: IntSize) { diff --git a/haze-jetpack-compose/src/main/kotlin/dev/chrisbanes/haze/HazeNodeBase.kt b/haze-jetpack-compose/src/main/kotlin/dev/chrisbanes/haze/HazeNodeBase.kt index 40a4049c..a05e8cfa 100644 --- a/haze-jetpack-compose/src/main/kotlin/dev/chrisbanes/haze/HazeNodeBase.kt +++ b/haze-jetpack-compose/src/main/kotlin/dev/chrisbanes/haze/HazeNodeBase.kt @@ -4,6 +4,7 @@ package dev.chrisbanes.haze import androidx.compose.ui.geometry.Offset +import androidx.compose.ui.geometry.Size import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Path import androidx.compose.ui.graphics.drawscope.ContentDrawScope @@ -19,7 +20,11 @@ import androidx.compose.ui.node.observeReads import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.unit.Density import androidx.compose.ui.unit.Dp +import androidx.compose.ui.unit.IntSize import androidx.compose.ui.unit.LayoutDirection +import androidx.compose.ui.unit.toSize +import kotlin.properties.Delegates +import kotlin.properties.Delegates.observable /** * On older platforms, we draw a translucent scrim over the content @@ -38,27 +43,38 @@ internal class HazeNodeBase( private val path = Path() private var pathDirty = false - private var positionInRoot = Offset.Zero + + private var positionInRoot by observable(Offset.Unspecified) { _, oldValue, newValue -> + if (oldValue != newValue) { + invalidatePath() + } + } + private var size by observable(Size.Unspecified) { _, oldValue, newValue -> + if (oldValue != newValue) { + invalidatePath() + } + } override fun onUpdate() { invalidateDraw() } override fun onObservedReadsChanged() { - markPathAsDirty() + invalidatePath() } - private fun markPathAsDirty() { + private fun invalidatePath() { pathDirty = true invalidateDraw() } override fun onPlaced(coordinates: LayoutCoordinates) { - val newPositionInRoot = coordinates.positionInRoot() - if (positionInRoot != newPositionInRoot) { - positionInRoot = newPositionInRoot - markPathAsDirty() - } + positionInRoot = coordinates.positionInRoot() + size = coordinates.size.toSize() + } + + override fun onRemeasured(size: IntSize) { + this.size = size.toSize() } override fun ContentDrawScope.draw() { @@ -77,7 +93,7 @@ internal class HazeNodeBase( private fun updatePath(layoutDirection: LayoutDirection, density: Density) { path.reset() - state.addAreasToPath(path, layoutDirection, density) + state.addAreasToPath(path, positionInRoot, layoutDirection, density) pathDirty = false } } diff --git a/haze/src/androidMain/kotlin/dev/chrisbanes/haze/HazeNodeBase.kt b/haze/src/androidMain/kotlin/dev/chrisbanes/haze/HazeNodeBase.kt index 40a4049c..a05e8cfa 100644 --- a/haze/src/androidMain/kotlin/dev/chrisbanes/haze/HazeNodeBase.kt +++ b/haze/src/androidMain/kotlin/dev/chrisbanes/haze/HazeNodeBase.kt @@ -4,6 +4,7 @@ package dev.chrisbanes.haze import androidx.compose.ui.geometry.Offset +import androidx.compose.ui.geometry.Size import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Path import androidx.compose.ui.graphics.drawscope.ContentDrawScope @@ -19,7 +20,11 @@ import androidx.compose.ui.node.observeReads import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.unit.Density import androidx.compose.ui.unit.Dp +import androidx.compose.ui.unit.IntSize import androidx.compose.ui.unit.LayoutDirection +import androidx.compose.ui.unit.toSize +import kotlin.properties.Delegates +import kotlin.properties.Delegates.observable /** * On older platforms, we draw a translucent scrim over the content @@ -38,27 +43,38 @@ internal class HazeNodeBase( private val path = Path() private var pathDirty = false - private var positionInRoot = Offset.Zero + + private var positionInRoot by observable(Offset.Unspecified) { _, oldValue, newValue -> + if (oldValue != newValue) { + invalidatePath() + } + } + private var size by observable(Size.Unspecified) { _, oldValue, newValue -> + if (oldValue != newValue) { + invalidatePath() + } + } override fun onUpdate() { invalidateDraw() } override fun onObservedReadsChanged() { - markPathAsDirty() + invalidatePath() } - private fun markPathAsDirty() { + private fun invalidatePath() { pathDirty = true invalidateDraw() } override fun onPlaced(coordinates: LayoutCoordinates) { - val newPositionInRoot = coordinates.positionInRoot() - if (positionInRoot != newPositionInRoot) { - positionInRoot = newPositionInRoot - markPathAsDirty() - } + positionInRoot = coordinates.positionInRoot() + size = coordinates.size.toSize() + } + + override fun onRemeasured(size: IntSize) { + this.size = size.toSize() } override fun ContentDrawScope.draw() { @@ -77,7 +93,7 @@ internal class HazeNodeBase( private fun updatePath(layoutDirection: LayoutDirection, density: Density) { path.reset() - state.addAreasToPath(path, layoutDirection, density) + state.addAreasToPath(path, positionInRoot, layoutDirection, density) pathDirty = false } } diff --git a/haze/src/commonMain/kotlin/dev/chrisbanes/haze/Haze.kt b/haze/src/commonMain/kotlin/dev/chrisbanes/haze/Haze.kt index fbbaadf7..45f04d4e 100644 --- a/haze/src/commonMain/kotlin/dev/chrisbanes/haze/Haze.kt +++ b/haze/src/commonMain/kotlin/dev/chrisbanes/haze/Haze.kt @@ -12,6 +12,7 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.geometry.Offset import androidx.compose.ui.geometry.Rect import androidx.compose.ui.geometry.Size +import androidx.compose.ui.geometry.isSpecified import androidx.compose.ui.geometry.isUnspecified import androidx.compose.ui.geometry.toRect import androidx.compose.ui.geometry.translate @@ -61,15 +62,16 @@ class HazeState { internal fun HazeState.addAreasToPath( path: Path, + positionInRoot: Offset, layoutDirection: LayoutDirection, density: Density, ) { areas.asSequence() - .filterNot { it.isEmpty } + .filter { it.isValid } .forEach { area -> path.addOutline( outline = area.shape.createOutline(area.size, layoutDirection, density), - offset = area.positionInRoot, + offset = area.positionInRoot - positionInRoot, ) } } @@ -91,13 +93,13 @@ class HazeArea { var shape: Shape by mutableStateOf(RectangleShape) internal set - val isEmpty: Boolean get() = size.isEmpty() + val isValid: Boolean + get() = size.isSpecified && positionInRoot.isSpecified && !size.isEmpty() } internal fun HazeArea.boundsInLocal(hazePositionInRoot: Offset): Rect? { - if (size.isUnspecified) return null + if (!isValid) return null if (hazePositionInRoot.isUnspecified) return null - if (positionInRoot.isUnspecified) return null return size.toRect().translate(positionInRoot - hazePositionInRoot) } From 4ff179bea2d6e2ea3c9c59b22d0311841c037ac9 Mon Sep 17 00:00:00 2001 From: Chris Banes Date: Mon, 11 Dec 2023 10:36:00 +0000 Subject: [PATCH 3/9] Update ImagesList sample --- .../haze/sample/android/ImagesList.kt | 17 +++++++++++------ .../dev/chrisbanes/haze/sample/ImagesList.kt | 17 +++++++++++------ 2 files changed, 22 insertions(+), 12 deletions(-) diff --git a/sample/android-jetpack/src/main/kotlin/dev/chrisbanes/haze/sample/android/ImagesList.kt b/sample/android-jetpack/src/main/kotlin/dev/chrisbanes/haze/sample/android/ImagesList.kt index 7cf9d78c..d3773ee4 100644 --- a/sample/android-jetpack/src/main/kotlin/dev/chrisbanes/haze/sample/android/ImagesList.kt +++ b/sample/android-jetpack/src/main/kotlin/dev/chrisbanes/haze/sample/android/ImagesList.kt @@ -36,8 +36,6 @@ import dev.chrisbanes.haze.hazeChild @Composable fun ImagesList(navigator: Navigator) { MaterialTheme { - val hazeState = remember { HazeState() } - Scaffold( topBar = { LargeTopAppBar( @@ -58,6 +56,8 @@ fun ImagesList(navigator: Navigator) { modifier = Modifier.fillMaxSize(), ) { items(50) { index -> + val hazeState = remember { HazeState() } + Box( modifier = Modifier .fillParentMaxWidth() @@ -76,13 +76,18 @@ fun ImagesList(navigator: Navigator) { .fillMaxSize(), ) - Text( - "Message $index", + Box( modifier = Modifier .fillMaxSize(0.8f) .align(Alignment.Center) - .hazeChild(index, hazeState, RoundedCornerShape(4.dp)), - ) + .hazeChild(key = index, state = hazeState, shape = RoundedCornerShape(4.dp)) + ) { + Text( + "Image $index", + style = MaterialTheme.typography.titleLarge, + modifier = Modifier.align(Alignment.Center) + ) + } } } } diff --git a/sample/shared/src/commonMain/kotlin/dev/chrisbanes/haze/sample/ImagesList.kt b/sample/shared/src/commonMain/kotlin/dev/chrisbanes/haze/sample/ImagesList.kt index d2158889..fff32394 100644 --- a/sample/shared/src/commonMain/kotlin/dev/chrisbanes/haze/sample/ImagesList.kt +++ b/sample/shared/src/commonMain/kotlin/dev/chrisbanes/haze/sample/ImagesList.kt @@ -36,8 +36,6 @@ import dev.chrisbanes.haze.hazeChild @Composable fun ImagesList(navigator: Navigator) { MaterialTheme { - val hazeState = remember { HazeState() } - Scaffold( topBar = { LargeTopAppBar( @@ -58,6 +56,8 @@ fun ImagesList(navigator: Navigator) { modifier = Modifier.fillMaxSize(), ) { items(50) { index -> + val hazeState = remember { HazeState() } + Box( modifier = Modifier .fillParentMaxWidth() @@ -76,13 +76,18 @@ fun ImagesList(navigator: Navigator) { .fillMaxSize(), ) - Text( - "Message $index", + Box( modifier = Modifier .fillMaxSize(0.8f) .align(Alignment.Center) - .hazeChild(index, hazeState, RoundedCornerShape(4.dp)), - ) + .hazeChild(key = index, state = hazeState, shape = RoundedCornerShape(4.dp)) + ) { + Text( + "Image $index", + style = MaterialTheme.typography.titleLarge, + modifier = Modifier.align(Alignment.Center) + ) + } } } } From 33520af1d1882499b41debc5f5e631505a1c7711 Mon Sep 17 00:00:00 2001 From: Chris Banes Date: Mon, 11 Dec 2023 11:29:17 +0000 Subject: [PATCH 4/9] Remove need for key parameter --- .../main/kotlin/dev/chrisbanes/haze/Haze.kt | 28 ++++------------ .../kotlin/dev/chrisbanes/haze/HazeChild.kt | 32 ++++++++----------- .../kotlin/dev/chrisbanes/haze/Haze.kt | 30 ++++++----------- .../kotlin/dev/chrisbanes/haze/HazeChild.kt | 32 ++++++++----------- .../haze/sample/android/CardSample.kt | 2 +- .../haze/sample/android/ImagesList.kt | 2 +- .../haze/sample/android/ScaffoldSample.kt | 4 +-- .../dev/chrisbanes/haze/sample/CardSample.kt | 2 +- .../dev/chrisbanes/haze/sample/ImagesList.kt | 2 +- .../chrisbanes/haze/sample/ScaffoldSample.kt | 4 +-- 10 files changed, 50 insertions(+), 88 deletions(-) diff --git a/haze-jetpack-compose/src/main/kotlin/dev/chrisbanes/haze/Haze.kt b/haze-jetpack-compose/src/main/kotlin/dev/chrisbanes/haze/Haze.kt index 652d758c..d4e562d2 100644 --- a/haze-jetpack-compose/src/main/kotlin/dev/chrisbanes/haze/Haze.kt +++ b/haze-jetpack-compose/src/main/kotlin/dev/chrisbanes/haze/Haze.kt @@ -5,7 +5,7 @@ package dev.chrisbanes.haze import androidx.compose.runtime.Stable import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableStateMapOf +import androidx.compose.runtime.mutableStateListOf import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier @@ -33,30 +33,16 @@ class HazeState { /** * The areas which are blurred by any [Modifier.haze] instances which use this state. */ - private val _areas = mutableStateMapOf() + private val _areas = mutableStateListOf() - val areas: Set get() = _areas.values.toSet() + val areas: List get() = _areas.toList() - fun updateAreaPosition(key: Any, positionInRoot: Offset) { - _areas.getOrPut(key, ::HazeArea).apply { - this.positionInRoot = positionInRoot - } - } - - fun updateAreaSize(key: Any, size: Size) { - _areas.getOrPut(key, ::HazeArea).apply { - this.size = size - } - } - - fun updateAreaShape(key: Any, shape: Shape) { - _areas.getOrPut(key, ::HazeArea).apply { - this.shape = shape - } + fun registerArea(area: HazeArea) { + _areas.add(area) } - fun clearArea(key: Any) { - _areas.remove(key) + fun unregisterArea(area: HazeArea) { + _areas.remove(area) } } diff --git a/haze-jetpack-compose/src/main/kotlin/dev/chrisbanes/haze/HazeChild.kt b/haze-jetpack-compose/src/main/kotlin/dev/chrisbanes/haze/HazeChild.kt index 87773c26..5d917200 100644 --- a/haze-jetpack-compose/src/main/kotlin/dev/chrisbanes/haze/HazeChild.kt +++ b/haze-jetpack-compose/src/main/kotlin/dev/chrisbanes/haze/HazeChild.kt @@ -21,20 +21,17 @@ import androidx.compose.ui.unit.toSize * [Modifier.haze] to blur any content behind the host composable. */ fun Modifier.hazeChild( - key: Any, state: HazeState, shape: Shape = RectangleShape, -): Modifier = this then HazeChildNodeElement(key, state, shape) +): Modifier = this then HazeChildNodeElement(state, shape) private data class HazeChildNodeElement( - val key: Any, val state: HazeState, val shape: Shape, ) : ModifierNodeElement() { - override fun create(): HazeChildNode = HazeChildNode(key, state, shape) + override fun create(): HazeChildNode = HazeChildNode(state, shape) override fun update(node: HazeChildNode) { - node.key = key node.state = state node.shape = shape node.onUpdate() @@ -42,39 +39,36 @@ private data class HazeChildNodeElement( override fun InspectorInfo.inspectableProperties() { name = "HazeChild" - properties["key"] = key properties["shape"] = shape } } private data class HazeChildNode( - var key: Any, var state: HazeState, var shape: Shape, ) : Modifier.Node(), LayoutAwareModifierNode { - override fun onPlaced(coordinates: LayoutCoordinates) { - // After we've been placed, update the state with our new bounds (in root coordinates) - state.updateAreaPosition(key, coordinates.positionInRoot()) - } + + private val area: HazeArea = HazeArea() override fun onAttach() { - state.updateAreaShape(key, shape) + state.registerArea(area) } fun onUpdate() { - state.updateAreaShape(key, shape) + area.shape = shape } - override fun onRemeasured(size: IntSize) { - // After we've been remeasured, update the state with our new size - state.updateAreaSize(key, size.toSize()) + override fun onPlaced(coordinates: LayoutCoordinates) { + // After we've been placed, update the state with our new bounds (in root coordinates) + area.positionInRoot = coordinates.positionInRoot() } - override fun onReset() { - state.clearArea(key) + override fun onRemeasured(size: IntSize) { + // After we've been remeasured, update the state with our new size + area.size = size.toSize() } override fun onDetach() { - state.clearArea(key) + state.unregisterArea(area) } } diff --git a/haze/src/commonMain/kotlin/dev/chrisbanes/haze/Haze.kt b/haze/src/commonMain/kotlin/dev/chrisbanes/haze/Haze.kt index 45f04d4e..d4e562d2 100644 --- a/haze/src/commonMain/kotlin/dev/chrisbanes/haze/Haze.kt +++ b/haze/src/commonMain/kotlin/dev/chrisbanes/haze/Haze.kt @@ -5,7 +5,7 @@ package dev.chrisbanes.haze import androidx.compose.runtime.Stable import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableStateMapOf +import androidx.compose.runtime.mutableStateListOf import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier @@ -33,30 +33,16 @@ class HazeState { /** * The areas which are blurred by any [Modifier.haze] instances which use this state. */ - private val _areas = mutableStateMapOf() + private val _areas = mutableStateListOf() - val areas: Set get() = _areas.values.toSet() + val areas: List get() = _areas.toList() - fun updateAreaPosition(key: Any, positionInRoot: Offset) { - _areas.getOrPut(key, ::HazeArea).apply { - this.positionInRoot = positionInRoot - } - } - - fun updateAreaSize(key: Any, size: Size) { - _areas.getOrPut(key, ::HazeArea).apply { - this.size = size - } + fun registerArea(area: HazeArea) { + _areas.add(area) } - fun updateAreaShape(key: Any, shape: Shape) { - _areas.getOrPut(key, ::HazeArea).apply { - this.shape = shape - } - } - - fun clearArea(key: Any) { - _areas.remove(key) + fun unregisterArea(area: HazeArea) { + _areas.remove(area) } } @@ -66,6 +52,8 @@ internal fun HazeState.addAreasToPath( layoutDirection: LayoutDirection, density: Density, ) { + if (positionInRoot.isUnspecified) return + areas.asSequence() .filter { it.isValid } .forEach { area -> diff --git a/haze/src/commonMain/kotlin/dev/chrisbanes/haze/HazeChild.kt b/haze/src/commonMain/kotlin/dev/chrisbanes/haze/HazeChild.kt index 87773c26..5d917200 100644 --- a/haze/src/commonMain/kotlin/dev/chrisbanes/haze/HazeChild.kt +++ b/haze/src/commonMain/kotlin/dev/chrisbanes/haze/HazeChild.kt @@ -21,20 +21,17 @@ import androidx.compose.ui.unit.toSize * [Modifier.haze] to blur any content behind the host composable. */ fun Modifier.hazeChild( - key: Any, state: HazeState, shape: Shape = RectangleShape, -): Modifier = this then HazeChildNodeElement(key, state, shape) +): Modifier = this then HazeChildNodeElement(state, shape) private data class HazeChildNodeElement( - val key: Any, val state: HazeState, val shape: Shape, ) : ModifierNodeElement() { - override fun create(): HazeChildNode = HazeChildNode(key, state, shape) + override fun create(): HazeChildNode = HazeChildNode(state, shape) override fun update(node: HazeChildNode) { - node.key = key node.state = state node.shape = shape node.onUpdate() @@ -42,39 +39,36 @@ private data class HazeChildNodeElement( override fun InspectorInfo.inspectableProperties() { name = "HazeChild" - properties["key"] = key properties["shape"] = shape } } private data class HazeChildNode( - var key: Any, var state: HazeState, var shape: Shape, ) : Modifier.Node(), LayoutAwareModifierNode { - override fun onPlaced(coordinates: LayoutCoordinates) { - // After we've been placed, update the state with our new bounds (in root coordinates) - state.updateAreaPosition(key, coordinates.positionInRoot()) - } + + private val area: HazeArea = HazeArea() override fun onAttach() { - state.updateAreaShape(key, shape) + state.registerArea(area) } fun onUpdate() { - state.updateAreaShape(key, shape) + area.shape = shape } - override fun onRemeasured(size: IntSize) { - // After we've been remeasured, update the state with our new size - state.updateAreaSize(key, size.toSize()) + override fun onPlaced(coordinates: LayoutCoordinates) { + // After we've been placed, update the state with our new bounds (in root coordinates) + area.positionInRoot = coordinates.positionInRoot() } - override fun onReset() { - state.clearArea(key) + override fun onRemeasured(size: IntSize) { + // After we've been remeasured, update the state with our new size + area.size = size.toSize() } override fun onDetach() { - state.clearArea(key) + state.unregisterArea(area) } } diff --git a/sample/android-jetpack/src/main/kotlin/dev/chrisbanes/haze/sample/android/CardSample.kt b/sample/android-jetpack/src/main/kotlin/dev/chrisbanes/haze/sample/android/CardSample.kt index 09a4ca7b..14b44f01 100644 --- a/sample/android-jetpack/src/main/kotlin/dev/chrisbanes/haze/sample/android/CardSample.kt +++ b/sample/android-jetpack/src/main/kotlin/dev/chrisbanes/haze/sample/android/CardSample.kt @@ -92,7 +92,7 @@ fun CreditCardSample(navigator: Navigator) { } }, ) - .hazeChild("card", state = hazeState, shape = RoundedCornerShape(16.dp)), + .hazeChild(state = hazeState, shape = RoundedCornerShape(16.dp)), ) { Column(Modifier.padding(32.dp)) { Text("Bank of Haze") diff --git a/sample/android-jetpack/src/main/kotlin/dev/chrisbanes/haze/sample/android/ImagesList.kt b/sample/android-jetpack/src/main/kotlin/dev/chrisbanes/haze/sample/android/ImagesList.kt index d3773ee4..97b1603b 100644 --- a/sample/android-jetpack/src/main/kotlin/dev/chrisbanes/haze/sample/android/ImagesList.kt +++ b/sample/android-jetpack/src/main/kotlin/dev/chrisbanes/haze/sample/android/ImagesList.kt @@ -80,7 +80,7 @@ fun ImagesList(navigator: Navigator) { modifier = Modifier .fillMaxSize(0.8f) .align(Alignment.Center) - .hazeChild(key = index, state = hazeState, shape = RoundedCornerShape(4.dp)) + .hazeChild(state = hazeState, shape = RoundedCornerShape(4.dp)) ) { Text( "Image $index", diff --git a/sample/android-jetpack/src/main/kotlin/dev/chrisbanes/haze/sample/android/ScaffoldSample.kt b/sample/android-jetpack/src/main/kotlin/dev/chrisbanes/haze/sample/android/ScaffoldSample.kt index d90cef80..7c8ac719 100644 --- a/sample/android-jetpack/src/main/kotlin/dev/chrisbanes/haze/sample/android/ScaffoldSample.kt +++ b/sample/android-jetpack/src/main/kotlin/dev/chrisbanes/haze/sample/android/ScaffoldSample.kt @@ -63,7 +63,7 @@ fun ScaffoldSample(navigator: Navigator) { }, colors = TopAppBarDefaults.largeTopAppBarColors(Color.Transparent), modifier = Modifier - .hazeChild("app_bar", hazeState) + .hazeChild(hazeState) .fillMaxWidth(), ) }, @@ -79,7 +79,7 @@ fun ScaffoldSample(navigator: Navigator) { selectedIndex, onItemClicked = { selectedIndex = it }, modifier = Modifier - .hazeChild("nav_bar", hazeState) + .hazeChild(hazeState) .fillMaxWidth(), ) } diff --git a/sample/shared/src/commonMain/kotlin/dev/chrisbanes/haze/sample/CardSample.kt b/sample/shared/src/commonMain/kotlin/dev/chrisbanes/haze/sample/CardSample.kt index f8d1e0ad..cc4f00e4 100644 --- a/sample/shared/src/commonMain/kotlin/dev/chrisbanes/haze/sample/CardSample.kt +++ b/sample/shared/src/commonMain/kotlin/dev/chrisbanes/haze/sample/CardSample.kt @@ -94,7 +94,7 @@ fun CreditCardSample(navigator: Navigator) { } }, ) - .hazeChild("card", state = hazeState, shape = RoundedCornerShape(16.dp)), + .hazeChild(state = hazeState, shape = RoundedCornerShape(16.dp)), ) { Column(Modifier.padding(32.dp)) { Text("Bank of Haze") diff --git a/sample/shared/src/commonMain/kotlin/dev/chrisbanes/haze/sample/ImagesList.kt b/sample/shared/src/commonMain/kotlin/dev/chrisbanes/haze/sample/ImagesList.kt index fff32394..ad223518 100644 --- a/sample/shared/src/commonMain/kotlin/dev/chrisbanes/haze/sample/ImagesList.kt +++ b/sample/shared/src/commonMain/kotlin/dev/chrisbanes/haze/sample/ImagesList.kt @@ -80,7 +80,7 @@ fun ImagesList(navigator: Navigator) { modifier = Modifier .fillMaxSize(0.8f) .align(Alignment.Center) - .hazeChild(key = index, state = hazeState, shape = RoundedCornerShape(4.dp)) + .hazeChild(state = hazeState, shape = RoundedCornerShape(4.dp)) ) { Text( "Image $index", diff --git a/sample/shared/src/commonMain/kotlin/dev/chrisbanes/haze/sample/ScaffoldSample.kt b/sample/shared/src/commonMain/kotlin/dev/chrisbanes/haze/sample/ScaffoldSample.kt index ef0ad348..6d1b2f9d 100644 --- a/sample/shared/src/commonMain/kotlin/dev/chrisbanes/haze/sample/ScaffoldSample.kt +++ b/sample/shared/src/commonMain/kotlin/dev/chrisbanes/haze/sample/ScaffoldSample.kt @@ -63,7 +63,7 @@ fun ScaffoldSample(navigator: Navigator) { }, colors = TopAppBarDefaults.largeTopAppBarColors(Color.Transparent), modifier = Modifier - .hazeChild("app_bar", hazeState) + .hazeChild(hazeState) .fillMaxWidth(), ) }, @@ -79,7 +79,7 @@ fun ScaffoldSample(navigator: Navigator) { selectedIndex, onItemClicked = { selectedIndex = it }, modifier = Modifier - .hazeChild("nav_bar", hazeState) + .hazeChild(hazeState) .fillMaxWidth(), ) } From 95d4bf633ab167709dbe4bd4297b7ff0d14c6584 Mon Sep 17 00:00:00 2001 From: Chris Banes Date: Mon, 11 Dec 2023 11:42:55 +0000 Subject: [PATCH 5/9] Fix code style --- .../src/main/kotlin/dev/chrisbanes/haze/HazeNode31.kt | 2 +- .../src/main/kotlin/dev/chrisbanes/haze/HazeNodeBase.kt | 1 - .../androidMain/kotlin/dev/chrisbanes/haze/HazeNodeBase.kt | 1 - .../kotlin/dev/chrisbanes/haze/sample/android/ImagesList.kt | 4 ++-- .../kotlin/dev/chrisbanes/haze/sample/ImagesList.kt | 4 ++-- 5 files changed, 5 insertions(+), 7 deletions(-) diff --git a/haze-jetpack-compose/src/main/kotlin/dev/chrisbanes/haze/HazeNode31.kt b/haze-jetpack-compose/src/main/kotlin/dev/chrisbanes/haze/HazeNode31.kt index 7a8c940f..e7cdc92e 100644 --- a/haze-jetpack-compose/src/main/kotlin/dev/chrisbanes/haze/HazeNode31.kt +++ b/haze-jetpack-compose/src/main/kotlin/dev/chrisbanes/haze/HazeNode31.kt @@ -189,7 +189,7 @@ internal class HazeNode31( // We expand the area where our effect is applied to. This is necessary so that the blur // effect is applied evenly to all edges. If we don't do this, the blur effect is much less // visible on the edges of the area. - val expandedRect = bounds //.inflate(blurRadiusPx) + val expandedRect = bounds // .inflate(blurRadiusPx) val node = RenderNode("blur").apply { setRenderEffect(effect) diff --git a/haze-jetpack-compose/src/main/kotlin/dev/chrisbanes/haze/HazeNodeBase.kt b/haze-jetpack-compose/src/main/kotlin/dev/chrisbanes/haze/HazeNodeBase.kt index a05e8cfa..d67c9a93 100644 --- a/haze-jetpack-compose/src/main/kotlin/dev/chrisbanes/haze/HazeNodeBase.kt +++ b/haze-jetpack-compose/src/main/kotlin/dev/chrisbanes/haze/HazeNodeBase.kt @@ -23,7 +23,6 @@ import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.IntSize import androidx.compose.ui.unit.LayoutDirection import androidx.compose.ui.unit.toSize -import kotlin.properties.Delegates import kotlin.properties.Delegates.observable /** diff --git a/haze/src/androidMain/kotlin/dev/chrisbanes/haze/HazeNodeBase.kt b/haze/src/androidMain/kotlin/dev/chrisbanes/haze/HazeNodeBase.kt index a05e8cfa..d67c9a93 100644 --- a/haze/src/androidMain/kotlin/dev/chrisbanes/haze/HazeNodeBase.kt +++ b/haze/src/androidMain/kotlin/dev/chrisbanes/haze/HazeNodeBase.kt @@ -23,7 +23,6 @@ import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.IntSize import androidx.compose.ui.unit.LayoutDirection import androidx.compose.ui.unit.toSize -import kotlin.properties.Delegates import kotlin.properties.Delegates.observable /** diff --git a/sample/android-jetpack/src/main/kotlin/dev/chrisbanes/haze/sample/android/ImagesList.kt b/sample/android-jetpack/src/main/kotlin/dev/chrisbanes/haze/sample/android/ImagesList.kt index 97b1603b..7d416d10 100644 --- a/sample/android-jetpack/src/main/kotlin/dev/chrisbanes/haze/sample/android/ImagesList.kt +++ b/sample/android-jetpack/src/main/kotlin/dev/chrisbanes/haze/sample/android/ImagesList.kt @@ -80,12 +80,12 @@ fun ImagesList(navigator: Navigator) { modifier = Modifier .fillMaxSize(0.8f) .align(Alignment.Center) - .hazeChild(state = hazeState, shape = RoundedCornerShape(4.dp)) + .hazeChild(state = hazeState, shape = RoundedCornerShape(4.dp)), ) { Text( "Image $index", style = MaterialTheme.typography.titleLarge, - modifier = Modifier.align(Alignment.Center) + modifier = Modifier.align(Alignment.Center), ) } } diff --git a/sample/shared/src/commonMain/kotlin/dev/chrisbanes/haze/sample/ImagesList.kt b/sample/shared/src/commonMain/kotlin/dev/chrisbanes/haze/sample/ImagesList.kt index ad223518..17d35a59 100644 --- a/sample/shared/src/commonMain/kotlin/dev/chrisbanes/haze/sample/ImagesList.kt +++ b/sample/shared/src/commonMain/kotlin/dev/chrisbanes/haze/sample/ImagesList.kt @@ -80,12 +80,12 @@ fun ImagesList(navigator: Navigator) { modifier = Modifier .fillMaxSize(0.8f) .align(Alignment.Center) - .hazeChild(state = hazeState, shape = RoundedCornerShape(4.dp)) + .hazeChild(state = hazeState, shape = RoundedCornerShape(4.dp)), ) { Text( "Image $index", style = MaterialTheme.typography.titleLarge, - modifier = Modifier.align(Alignment.Center) + modifier = Modifier.align(Alignment.Center), ) } } From d0e5a46657cfa555147f0c6ff8e52c73933e35b5 Mon Sep 17 00:00:00 2001 From: Chris Banes Date: Mon, 11 Dec 2023 15:22:40 +0000 Subject: [PATCH 6/9] Fix issue with hazeChilds in Lazy layouts --- .../kotlin/dev/chrisbanes/haze/HazeChild.kt | 31 ++++++++++++++----- .../kotlin/dev/chrisbanes/haze/HazeChild.kt | 31 ++++++++++++++----- 2 files changed, 46 insertions(+), 16 deletions(-) diff --git a/haze-jetpack-compose/src/main/kotlin/dev/chrisbanes/haze/HazeChild.kt b/haze-jetpack-compose/src/main/kotlin/dev/chrisbanes/haze/HazeChild.kt index 5d917200..978446d6 100644 --- a/haze-jetpack-compose/src/main/kotlin/dev/chrisbanes/haze/HazeChild.kt +++ b/haze-jetpack-compose/src/main/kotlin/dev/chrisbanes/haze/HazeChild.kt @@ -11,7 +11,6 @@ import androidx.compose.ui.layout.positionInRoot import androidx.compose.ui.node.LayoutAwareModifierNode import androidx.compose.ui.node.ModifierNodeElement import androidx.compose.ui.platform.InspectorInfo -import androidx.compose.ui.unit.IntSize import androidx.compose.ui.unit.toSize /** @@ -48,27 +47,43 @@ private data class HazeChildNode( var shape: Shape, ) : Modifier.Node(), LayoutAwareModifierNode { - private val area: HazeArea = HazeArea() + private val area: HazeArea by lazy { HazeArea() } + + private var attachedState: HazeState? = null override fun onAttach() { - state.registerArea(area) + attachToHazeState() } fun onUpdate() { + // Propagate any shape changes to the HazeArea area.shape = shape + + if (state != attachedState) { + // The provided HazeState has changed, so we need to detach from the old one, + // and attach to the new one + detachFromHazeState() + attachToHazeState() + } } override fun onPlaced(coordinates: LayoutCoordinates) { // After we've been placed, update the state with our new bounds (in root coordinates) area.positionInRoot = coordinates.positionInRoot() + area.size = coordinates.size.toSize() + } + + override fun onDetach() { + detachFromHazeState() } - override fun onRemeasured(size: IntSize) { - // After we've been remeasured, update the state with our new size - area.size = size.toSize() + private fun attachToHazeState() { + state.registerArea(area) + attachedState = state } - override fun onDetach() { - state.unregisterArea(area) + private fun detachFromHazeState() { + attachedState?.unregisterArea(area) + attachedState = null } } diff --git a/haze/src/commonMain/kotlin/dev/chrisbanes/haze/HazeChild.kt b/haze/src/commonMain/kotlin/dev/chrisbanes/haze/HazeChild.kt index 5d917200..978446d6 100644 --- a/haze/src/commonMain/kotlin/dev/chrisbanes/haze/HazeChild.kt +++ b/haze/src/commonMain/kotlin/dev/chrisbanes/haze/HazeChild.kt @@ -11,7 +11,6 @@ import androidx.compose.ui.layout.positionInRoot import androidx.compose.ui.node.LayoutAwareModifierNode import androidx.compose.ui.node.ModifierNodeElement import androidx.compose.ui.platform.InspectorInfo -import androidx.compose.ui.unit.IntSize import androidx.compose.ui.unit.toSize /** @@ -48,27 +47,43 @@ private data class HazeChildNode( var shape: Shape, ) : Modifier.Node(), LayoutAwareModifierNode { - private val area: HazeArea = HazeArea() + private val area: HazeArea by lazy { HazeArea() } + + private var attachedState: HazeState? = null override fun onAttach() { - state.registerArea(area) + attachToHazeState() } fun onUpdate() { + // Propagate any shape changes to the HazeArea area.shape = shape + + if (state != attachedState) { + // The provided HazeState has changed, so we need to detach from the old one, + // and attach to the new one + detachFromHazeState() + attachToHazeState() + } } override fun onPlaced(coordinates: LayoutCoordinates) { // After we've been placed, update the state with our new bounds (in root coordinates) area.positionInRoot = coordinates.positionInRoot() + area.size = coordinates.size.toSize() + } + + override fun onDetach() { + detachFromHazeState() } - override fun onRemeasured(size: IntSize) { - // After we've been remeasured, update the state with our new size - area.size = size.toSize() + private fun attachToHazeState() { + state.registerArea(area) + attachedState = state } - override fun onDetach() { - state.unregisterArea(area) + private fun detachFromHazeState() { + attachedState?.unregisterArea(area) + attachedState = null } } From 751a65b22a7ea7e3d8cc1a62d6853838f9fe7059 Mon Sep 17 00:00:00 2001 From: Chris Banes Date: Mon, 11 Dec 2023 15:23:27 +0000 Subject: [PATCH 7/9] Uncomment out some code in HazeNode31 --- .../src/main/kotlin/dev/chrisbanes/haze/HazeNode31.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/haze-jetpack-compose/src/main/kotlin/dev/chrisbanes/haze/HazeNode31.kt b/haze-jetpack-compose/src/main/kotlin/dev/chrisbanes/haze/HazeNode31.kt index e7cdc92e..44f9b44b 100644 --- a/haze-jetpack-compose/src/main/kotlin/dev/chrisbanes/haze/HazeNode31.kt +++ b/haze-jetpack-compose/src/main/kotlin/dev/chrisbanes/haze/HazeNode31.kt @@ -189,7 +189,7 @@ internal class HazeNode31( // We expand the area where our effect is applied to. This is necessary so that the blur // effect is applied evenly to all edges. If we don't do this, the blur effect is much less // visible on the edges of the area. - val expandedRect = bounds // .inflate(blurRadiusPx) + val expandedRect = bounds.inflate(blurRadiusPx) val node = RenderNode("blur").apply { setRenderEffect(effect) From f3edfc3955448824ec9a398035cef696dd7ecbe6 Mon Sep 17 00:00:00 2001 From: Chris Banes Date: Mon, 11 Dec 2023 15:33:55 +0000 Subject: [PATCH 8/9] Update API files --- haze-jetpack-compose/api/api.txt | 16 +++++++--------- haze/api/api.txt | 16 +++++++--------- 2 files changed, 14 insertions(+), 18 deletions(-) diff --git a/haze-jetpack-compose/api/api.txt b/haze-jetpack-compose/api/api.txt index e600ad65..32e23dd8 100644 --- a/haze-jetpack-compose/api/api.txt +++ b/haze-jetpack-compose/api/api.txt @@ -6,15 +6,15 @@ package dev.chrisbanes.haze { method public long getPositionInRoot(); method public androidx.compose.ui.graphics.Shape getShape(); method public long getSize(); - method public boolean isEmpty(); - property public final boolean isEmpty; + method public boolean isValid(); + property public final boolean isValid; property public final long positionInRoot; property public final androidx.compose.ui.graphics.Shape shape; property public final long size; } public final class HazeChildKt { - method public static androidx.compose.ui.Modifier hazeChild(androidx.compose.ui.Modifier, Object key, dev.chrisbanes.haze.HazeState state, optional androidx.compose.ui.graphics.Shape shape); + method public static androidx.compose.ui.Modifier hazeChild(androidx.compose.ui.Modifier, dev.chrisbanes.haze.HazeState state, optional androidx.compose.ui.graphics.Shape shape); } public final class HazeDefaults { @@ -33,12 +33,10 @@ package dev.chrisbanes.haze { @androidx.compose.runtime.Stable public final class HazeState { ctor public HazeState(); - method public void clearArea(Object key); - method public java.util.Set getAreas(); - method public void updateAreaPosition(Object key, long positionInRoot); - method public void updateAreaShape(Object key, androidx.compose.ui.graphics.Shape shape); - method public void updateAreaSize(Object key, long size); - property public final java.util.Set areas; + method public java.util.List getAreas(); + method public void registerArea(dev.chrisbanes.haze.HazeArea area); + method public void unregisterArea(dev.chrisbanes.haze.HazeArea area); + property public final java.util.List areas; } } diff --git a/haze/api/api.txt b/haze/api/api.txt index e600ad65..32e23dd8 100644 --- a/haze/api/api.txt +++ b/haze/api/api.txt @@ -6,15 +6,15 @@ package dev.chrisbanes.haze { method public long getPositionInRoot(); method public androidx.compose.ui.graphics.Shape getShape(); method public long getSize(); - method public boolean isEmpty(); - property public final boolean isEmpty; + method public boolean isValid(); + property public final boolean isValid; property public final long positionInRoot; property public final androidx.compose.ui.graphics.Shape shape; property public final long size; } public final class HazeChildKt { - method public static androidx.compose.ui.Modifier hazeChild(androidx.compose.ui.Modifier, Object key, dev.chrisbanes.haze.HazeState state, optional androidx.compose.ui.graphics.Shape shape); + method public static androidx.compose.ui.Modifier hazeChild(androidx.compose.ui.Modifier, dev.chrisbanes.haze.HazeState state, optional androidx.compose.ui.graphics.Shape shape); } public final class HazeDefaults { @@ -33,12 +33,10 @@ package dev.chrisbanes.haze { @androidx.compose.runtime.Stable public final class HazeState { ctor public HazeState(); - method public void clearArea(Object key); - method public java.util.Set getAreas(); - method public void updateAreaPosition(Object key, long positionInRoot); - method public void updateAreaShape(Object key, androidx.compose.ui.graphics.Shape shape); - method public void updateAreaSize(Object key, long size); - property public final java.util.Set areas; + method public java.util.List getAreas(); + method public void registerArea(dev.chrisbanes.haze.HazeArea area); + method public void unregisterArea(dev.chrisbanes.haze.HazeArea area); + property public final java.util.List areas; } } From a50f7f4baf5a369ee0f25ba7edab9a1e8c227e8f Mon Sep 17 00:00:00 2001 From: Chris Banes Date: Mon, 11 Dec 2023 15:35:32 +0000 Subject: [PATCH 9/9] Update docs --- docs/usage.md | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/docs/usage.md b/docs/usage.md index 62eee943..86c55e9d 100644 --- a/docs/usage.md +++ b/docs/usage.md @@ -25,7 +25,7 @@ Box { modifier = Modifier // We use hazeChild on anything where we want the background // blurred. - .hazeChild(key = "app_bar", state = hazeState) + .hazeChild(state = hazeState) .fillMaxWidth(), ) } @@ -40,7 +40,7 @@ Haze has some support for blurring of `Shape`s. Each platform has varying suppor To use a shape, you can just pass it in to `hazeChild`: -``` kotlin hl_lines="11" +``` kotlin hl_lines="10" val hazeState = remember { HazeState() } Box { @@ -49,7 +49,6 @@ Box { LargeTopAppBar( modifier = Modifier .hazeChild( - key = "app_bar", state = hazeState, shape = RoundedCornerShape(16.dp), ), @@ -74,7 +73,7 @@ Scaffold( // Need to make app bar transparent to see the content behind colors = TopAppBarDefaults.largeTopAppBarColors(Color.Transparent), modifier = Modifier - .hazeChild(key = "app_bar", state = hazeState) + .hazeChild(state = hazeState) .fillMaxWidth(), ) { /* todo */ @@ -84,7 +83,7 @@ Scaffold( NavigationBar( containerColor = Color.Transparent, modifier = Modifier - .hazeChild(key = "app_bar", state = hazeState) + .hazeChild(state = hazeState) .fillMaxWidth(), ) { /* todo */