diff --git a/haze/screenshots/android/HazeScreenshotTest.creditCard_sourceContentChange[28]_blue.png b/haze/screenshots/android/HazeScreenshotTest.creditCard_sourceContentChange[28]_blue.png new file mode 100644 index 00000000..515560bd Binary files /dev/null and b/haze/screenshots/android/HazeScreenshotTest.creditCard_sourceContentChange[28]_blue.png differ diff --git a/haze/screenshots/android/HazeScreenshotTest.creditCard_sourceContentChange[28]_red.png b/haze/screenshots/android/HazeScreenshotTest.creditCard_sourceContentChange[28]_red.png new file mode 100644 index 00000000..e05c1b90 Binary files /dev/null and b/haze/screenshots/android/HazeScreenshotTest.creditCard_sourceContentChange[28]_red.png differ diff --git a/haze/screenshots/android/HazeScreenshotTest.creditCard_sourceContentChange[28]_yellow.png b/haze/screenshots/android/HazeScreenshotTest.creditCard_sourceContentChange[28]_yellow.png new file mode 100644 index 00000000..d8564fa2 Binary files /dev/null and b/haze/screenshots/android/HazeScreenshotTest.creditCard_sourceContentChange[28]_yellow.png differ diff --git a/haze/screenshots/android/HazeScreenshotTest.creditCard_sourceContentChange[32]_blue.png b/haze/screenshots/android/HazeScreenshotTest.creditCard_sourceContentChange[32]_blue.png new file mode 100644 index 00000000..3721b49b Binary files /dev/null and b/haze/screenshots/android/HazeScreenshotTest.creditCard_sourceContentChange[32]_blue.png differ diff --git a/haze/screenshots/android/HazeScreenshotTest.creditCard_sourceContentChange[32]_red.png b/haze/screenshots/android/HazeScreenshotTest.creditCard_sourceContentChange[32]_red.png new file mode 100644 index 00000000..5ae25d8c Binary files /dev/null and b/haze/screenshots/android/HazeScreenshotTest.creditCard_sourceContentChange[32]_red.png differ diff --git a/haze/screenshots/android/HazeScreenshotTest.creditCard_sourceContentChange[32]_yellow.png b/haze/screenshots/android/HazeScreenshotTest.creditCard_sourceContentChange[32]_yellow.png new file mode 100644 index 00000000..bc871b93 Binary files /dev/null and b/haze/screenshots/android/HazeScreenshotTest.creditCard_sourceContentChange[32]_yellow.png differ diff --git a/haze/screenshots/android/HazeScreenshotTest.creditCard_sourceContentChange_blue.png b/haze/screenshots/android/HazeScreenshotTest.creditCard_sourceContentChange_blue.png new file mode 100644 index 00000000..77a238a7 Binary files /dev/null and b/haze/screenshots/android/HazeScreenshotTest.creditCard_sourceContentChange_blue.png differ diff --git a/haze/screenshots/android/HazeScreenshotTest.creditCard_sourceContentChange_red.png b/haze/screenshots/android/HazeScreenshotTest.creditCard_sourceContentChange_red.png new file mode 100644 index 00000000..41f0b795 Binary files /dev/null and b/haze/screenshots/android/HazeScreenshotTest.creditCard_sourceContentChange_red.png differ diff --git a/haze/screenshots/android/HazeScreenshotTest.creditCard_sourceContentChange_yellow.png b/haze/screenshots/android/HazeScreenshotTest.creditCard_sourceContentChange_yellow.png new file mode 100644 index 00000000..2aaf5b9e Binary files /dev/null and b/haze/screenshots/android/HazeScreenshotTest.creditCard_sourceContentChange_yellow.png differ diff --git a/haze/screenshots/desktop/HazeScreenshotTest.creditCard_sourceContentChange_blue.png b/haze/screenshots/desktop/HazeScreenshotTest.creditCard_sourceContentChange_blue.png new file mode 100644 index 00000000..1031b834 Binary files /dev/null and b/haze/screenshots/desktop/HazeScreenshotTest.creditCard_sourceContentChange_blue.png differ diff --git a/haze/screenshots/desktop/HazeScreenshotTest.creditCard_sourceContentChange_red.png b/haze/screenshots/desktop/HazeScreenshotTest.creditCard_sourceContentChange_red.png new file mode 100644 index 00000000..59bc395b Binary files /dev/null and b/haze/screenshots/desktop/HazeScreenshotTest.creditCard_sourceContentChange_red.png differ diff --git a/haze/screenshots/desktop/HazeScreenshotTest.creditCard_sourceContentChange_yellow.png b/haze/screenshots/desktop/HazeScreenshotTest.creditCard_sourceContentChange_yellow.png new file mode 100644 index 00000000..02d2147e Binary files /dev/null and b/haze/screenshots/desktop/HazeScreenshotTest.creditCard_sourceContentChange_yellow.png differ diff --git a/haze/src/commonMain/kotlin/dev/chrisbanes/haze/Haze.kt b/haze/src/commonMain/kotlin/dev/chrisbanes/haze/Haze.kt index a4793e77..7d81f110 100644 --- a/haze/src/commonMain/kotlin/dev/chrisbanes/haze/Haze.kt +++ b/haze/src/commonMain/kotlin/dev/chrisbanes/haze/Haze.kt @@ -72,7 +72,7 @@ class HazeArea { /** * The content [GraphicsLayer]. */ - var contentLayer: GraphicsLayer? = null + var contentLayer: GraphicsLayer? by mutableStateOf(null) internal set internal val bounds: Rect? get() = when { diff --git a/haze/src/commonMain/kotlin/dev/chrisbanes/haze/HazeEffectNode.kt b/haze/src/commonMain/kotlin/dev/chrisbanes/haze/HazeEffectNode.kt index 049d731c..63ec572d 100644 --- a/haze/src/commonMain/kotlin/dev/chrisbanes/haze/HazeEffectNode.kt +++ b/haze/src/commonMain/kotlin/dev/chrisbanes/haze/HazeEffectNode.kt @@ -341,40 +341,38 @@ class HazeEffectNode( val bg = resolveBackgroundColor() require(bg.isSpecified) { "backgroundColor not specified. Please provide a color." } - // We don't want to observe any state here. Anything we state we read should be observed - // be triggered from `dirtyTracker` only - Snapshot.withoutReadObservation { - val bounds = Rect(positionOnScreen, size) - - layer.record(scaledSize.roundToIntSize()) { - drawRect(bg) - - clipRect { - scale(scale = scaleFactor, pivot = Offset.Zero) { - translate(offset = -positionOnScreen) { - for (area in areas) { - require(!area.contentDrawing) { - "Modifier.haze nodes can not draw Modifier.hazeChild nodes. " + - "This should not happen if you are providing correct values for zIndex on Modifier.haze. " + - "Alternatively you can use can `canDrawArea` to to filter out parent areas." - } - - val areaBounds = area.bounds - if (areaBounds == null || !bounds.overlaps(areaBounds)) { - log(TAG) { "Area does not overlap us. Skipping... $area" } - continue - } - - translate(area.positionOnScreen.orZero) { - // Draw the content into our effect layer - area.contentLayer - ?.takeUnless { it.isReleased } - ?.takeUnless { it.size.width <= 0 || it.size.height <= 0 } - ?.let { - log(TAG) { "Drawing HazeArea GraphicsLayer: $it" } - drawLayer(it) - } - } + val bounds = Rect(positionOnScreen, size) + + layer.record(scaledSize.roundToIntSize()) { + drawRect(bg) + + clipRect { + scale(scale = scaleFactor, pivot = Offset.Zero) { + translate(offset = -positionOnScreen) { + for (area in areas) { + require(!area.contentDrawing) { + "Modifier.haze nodes can not draw Modifier.hazeChild nodes. " + + "This should not happen if you are providing correct values for zIndex on Modifier.haze. " + + "Alternatively you can use can `canDrawArea` to to filter out parent areas." + } + + val areaBounds = Snapshot.withoutReadObservation { area.bounds } + if (areaBounds == null || !bounds.overlaps(areaBounds)) { + log(TAG) { "Area does not overlap us. Skipping... $area" } + continue + } + + val position = Snapshot.withoutReadObservation { area.positionOnScreen.orZero } + translate(position) { + // Draw the content into our effect layer. We do want to observe this via snapshot + // state + area.contentLayer + ?.takeUnless { it.isReleased } + ?.takeUnless { it.size.width <= 0 || it.size.height <= 0 } + ?.let { + log(TAG) { "Drawing HazeArea GraphicsLayer: $it" } + drawLayer(it) + } } } } diff --git a/haze/src/screenshotTest/kotlin/dev/chrisbanes/haze/HazeScreenshotTest.kt b/haze/src/screenshotTest/kotlin/dev/chrisbanes/haze/HazeScreenshotTest.kt index 579d3496..30464bd0 100644 --- a/haze/src/screenshotTest/kotlin/dev/chrisbanes/haze/HazeScreenshotTest.kt +++ b/haze/src/screenshotTest/kotlin/dev/chrisbanes/haze/HazeScreenshotTest.kt @@ -256,6 +256,39 @@ class HazeScreenshotTest : ScreenshotTest() { captureRoot() } + /** + * This test does not currently produce the correct output on Skia platforms. + * It works correctly when run on device, etc. It seems to be a timing setup thing in tests. + * + * My working theory is that state updates are ran immediately in the CMP UI tests, which + * breaks how dependent graphics layers are invalidated. In non-tests, state updates are deferred + * until the next 'pass'. + * + * This is being re-worked in CMP 1.8, so there's little point in investigating this too much: + * https://youtrack.jetbrains.com/issue/CMP-6703 + */ + @Test + fun creditCard_sourceContentChange() = runScreenshotTest { + var backgroundColors by mutableStateOf(listOf(Color.Blue, Color.Cyan)) + + setContent { + ScreenshotTheme { + CreditCardSample(backgroundColors = backgroundColors, tint = DefaultTint) + } + } + + waitForIdle() + captureRoot("blue") + + backgroundColors = listOf(Color.Yellow, Color.hsl(0.4f, 0.94f, 0.58f)) + waitForIdle() + captureRoot("yellow") + + backgroundColors = listOf(Color.Red, Color.hsl(0.06f, 0.69f, 0.35f)) + waitForIdle() + captureRoot("red") + } + companion object { val DefaultTint = HazeTint(Color.White.copy(alpha = 0.1f)) val OverrideStyle = HazeStyle(tints = listOf(HazeTint(Color.Red.copy(alpha = 0.5f)))) diff --git a/haze/src/screenshotTest/kotlin/dev/chrisbanes/haze/ScreenshotTestContent.kt b/haze/src/screenshotTest/kotlin/dev/chrisbanes/haze/ScreenshotTestContent.kt index baecb0db..9269e817 100644 --- a/haze/src/screenshotTest/kotlin/dev/chrisbanes/haze/ScreenshotTestContent.kt +++ b/haze/src/screenshotTest/kotlin/dev/chrisbanes/haze/ScreenshotTestContent.kt @@ -29,6 +29,7 @@ import androidx.compose.ui.unit.dp @Composable internal fun CreditCardSample( + backgroundColors: List = listOf(Color.Blue, Color.Cyan), style: HazeStyle = HazeStyle.Unspecified, tint: HazeTint = HazeTint.Unspecified, blurRadius: Dp = 8.dp, @@ -52,7 +53,7 @@ internal fun CreditCardSample( Spacer( Modifier .fillMaxSize() - .background(brush = Brush.linearGradient(colors = listOf(Color.Blue, Color.Cyan))), + .background(brush = Brush.linearGradient(colors = backgroundColors)), ) Text( diff --git a/sample/shared/src/commonMain/kotlin/dev/chrisbanes/haze/sample/ImageItem.kt b/sample/shared/src/commonMain/kotlin/dev/chrisbanes/haze/sample/ImageItem.kt index 2347b882..55052529 100644 --- a/sample/shared/src/commonMain/kotlin/dev/chrisbanes/haze/sample/ImageItem.kt +++ b/sample/shared/src/commonMain/kotlin/dev/chrisbanes/haze/sample/ImageItem.kt @@ -68,6 +68,6 @@ fun randomSampleImageUrl( ): String = "https://picsum.photos/seed/$seed/$width/$height" @Composable -fun rememberRandomSampleImageUrl(index: Int = -1): String = rememberSaveable { +fun rememberRandomSampleImageUrl(index: Int = -1): String = rememberSaveable(index) { precannedImageUrls.getOrNull(index) ?: randomSampleImageUrl() } diff --git a/sample/shared/src/commonMain/kotlin/dev/chrisbanes/haze/sample/ListOverImage.kt b/sample/shared/src/commonMain/kotlin/dev/chrisbanes/haze/sample/ListOverImage.kt index ff4d8b51..744cf9e8 100644 --- a/sample/shared/src/commonMain/kotlin/dev/chrisbanes/haze/sample/ListOverImage.kt +++ b/sample/shared/src/commonMain/kotlin/dev/chrisbanes/haze/sample/ListOverImage.kt @@ -13,6 +13,7 @@ import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material.icons.Icons import androidx.compose.material.icons.automirrored.filled.ArrowBack +import androidx.compose.material.icons.filled.Refresh import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.Icon import androidx.compose.material3.IconButton @@ -21,7 +22,10 @@ import androidx.compose.material3.Scaffold import androidx.compose.material3.Text import androidx.compose.material3.TopAppBar import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableIntStateOf import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip @@ -38,6 +42,8 @@ import dev.chrisbanes.haze.materials.HazeMaterials @OptIn(ExperimentalMaterial3Api::class, ExperimentalHazeMaterialsApi::class) @Composable fun ListOverImage(navigator: Navigator) { + var imageIndex by remember { mutableIntStateOf(0) } + MaterialTheme { Scaffold( topBar = { @@ -48,6 +54,11 @@ fun ListOverImage(navigator: Navigator) { Icon(Icons.AutoMirrored.Filled.ArrowBack, null) } }, + actions = { + IconButton(onClick = { imageIndex++ }) { + Icon(Icons.Default.Refresh, "Refresh background button") + } + }, modifier = Modifier.fillMaxWidth(), ) }, @@ -57,7 +68,7 @@ fun ListOverImage(navigator: Navigator) { Box { AsyncImage( - model = rememberRandomSampleImageUrl(0), + model = rememberRandomSampleImageUrl(imageIndex), contentScale = ContentScale.Crop, contentDescription = null, modifier = Modifier