diff --git a/docs/migrating-0.9.md b/docs/migrating-0.9.md index 69d48724..422b3fe7 100644 --- a/docs/migrating-0.9.md +++ b/docs/migrating-0.9.md @@ -54,11 +54,11 @@ FooAppBar( ) ``` -#### Default style functionality on Modifier.haze has been removed +#### Default style functionality on Modifier.haze has been moved -- **What:** In previous versions, there was a `style` parameter on `Modifier.haze`, which has been removed in v0.9. -- **Migration:** Move all styling to `Modifier.hazeChild` calls. -- **Why:** Previously `Modifier.haze` to be the source of truth for styling, as it was responsible for all drawing. With the changes listed below, drawing is now the responsibility of the children themselves, therefore it makes little sense to invert the responsibility. +- **What:** In previous versions, there was a `style` parameter on `Modifier.haze`, which has been moved in v0.9. +- **Migration:** Use the new [LocalHazeStyle](api/haze/dev.chrisbanes.haze/-local-haze-style.html) composition local instead. +- **Why:** Composition locals are used throughout styling frameworks, so this is a better API going forward. #### HazeArea has been removed diff --git a/haze/api/api.txt b/haze/api/api.txt index 7bed3de7..7264435e 100644 --- a/haze/api/api.txt +++ b/haze/api/api.txt @@ -1,37 +1,83 @@ // Signature format: 4.0 package dev.chrisbanes.haze { + @kotlin.RequiresOptIn(message="Experimental Haze API", level=kotlin.RequiresOptIn.Level.WARNING) public @interface ExperimentalHazeApi { + } + public final class HazeChildKt { method @Deprecated public static androidx.compose.ui.Modifier hazeChild(androidx.compose.ui.Modifier, dev.chrisbanes.haze.HazeState state, androidx.compose.ui.graphics.Shape shape, dev.chrisbanes.haze.HazeStyle style); - method public static androidx.compose.ui.Modifier hazeChild(androidx.compose.ui.Modifier, dev.chrisbanes.haze.HazeState state, dev.chrisbanes.haze.HazeStyle style); - method public static androidx.compose.ui.Modifier hazeChild(androidx.compose.ui.Modifier, dev.chrisbanes.haze.HazeState state, kotlin.jvm.functions.Function1 block); + method @androidx.compose.runtime.Stable public static androidx.compose.ui.Modifier hazeChild(androidx.compose.ui.Modifier, dev.chrisbanes.haze.HazeState state, optional dev.chrisbanes.haze.HazeStyle style, optional kotlin.jvm.functions.Function1? block); + } + + @dev.chrisbanes.haze.ExperimentalHazeApi public final class HazeChildNode extends androidx.compose.ui.Modifier.Node implements androidx.compose.ui.node.CompositionLocalConsumerModifierNode androidx.compose.ui.node.DrawModifierNode androidx.compose.ui.node.GlobalPositionAwareModifierNode dev.chrisbanes.haze.HazeChildScope androidx.compose.ui.node.ObserverModifierNode { + ctor public HazeChildNode(dev.chrisbanes.haze.HazeState state, optional dev.chrisbanes.haze.HazeStyle style, optional kotlin.jvm.functions.Function1? block); + method public void draw(androidx.compose.ui.graphics.drawscope.ContentDrawScope); + method public float getAlpha(); + method public long getBackgroundColor(); + method public kotlin.jvm.functions.Function1? getBlock(); + method public float getBlurRadius(); + method public dev.chrisbanes.haze.HazeTint getFallbackTint(); + method public androidx.compose.ui.graphics.Brush? getMask(); + method public float getNoiseFactor(); + method public dev.chrisbanes.haze.HazeProgressive? getProgressive(); + method public dev.chrisbanes.haze.HazeState getState(); + method public dev.chrisbanes.haze.HazeStyle getStyle(); + method public java.util.List getTints(); + method public void onGloballyPositioned(androidx.compose.ui.layout.LayoutCoordinates coordinates); + method public void onObservedReadsChanged(); + method public void setAlpha(float); + method public void setBackgroundColor(long); + method public void setBlock(kotlin.jvm.functions.Function1?); + method public void setBlurRadius(float); + method public void setFallbackTint(dev.chrisbanes.haze.HazeTint); + method public void setMask(androidx.compose.ui.graphics.Brush?); + method public void setNoiseFactor(float); + method public void setProgressive(dev.chrisbanes.haze.HazeProgressive?); + method public void setState(dev.chrisbanes.haze.HazeState); + method public void setStyle(dev.chrisbanes.haze.HazeStyle); + method public void setTints(java.util.List); + property public float alpha; + property public long backgroundColor; + property public final kotlin.jvm.functions.Function1? block; + property public float blurRadius; + property public dev.chrisbanes.haze.HazeTint fallbackTint; + property public androidx.compose.ui.graphics.Brush? mask; + property public float noiseFactor; + property public dev.chrisbanes.haze.HazeProgressive? progressive; + property public boolean shouldAutoInvalidate; + property public final dev.chrisbanes.haze.HazeState state; + property public dev.chrisbanes.haze.HazeStyle style; + property public java.util.List tints; + field public static final String TAG = "HazeChild"; } public interface HazeChildScope { - method public void applyStyle(dev.chrisbanes.haze.HazeStyle style); method public float getAlpha(); method public long getBackgroundColor(); method public float getBlurRadius(); - method public dev.chrisbanes.haze.HazeTint? getFallbackTint(); + method public dev.chrisbanes.haze.HazeTint getFallbackTint(); method public androidx.compose.ui.graphics.Brush? getMask(); method public float getNoiseFactor(); method public dev.chrisbanes.haze.HazeProgressive? getProgressive(); + method public dev.chrisbanes.haze.HazeStyle getStyle(); method public java.util.List getTints(); method public void setAlpha(float); method public void setBackgroundColor(long); method public void setBlurRadius(float); - method public void setFallbackTint(dev.chrisbanes.haze.HazeTint?); + method public void setFallbackTint(dev.chrisbanes.haze.HazeTint); method public void setMask(androidx.compose.ui.graphics.Brush?); method public void setNoiseFactor(float); method public void setProgressive(dev.chrisbanes.haze.HazeProgressive?); + method public void setStyle(dev.chrisbanes.haze.HazeStyle); method public void setTints(java.util.List); property public abstract float alpha; property public abstract long backgroundColor; property public abstract float blurRadius; - property public abstract dev.chrisbanes.haze.HazeTint? fallbackTint; + property public abstract dev.chrisbanes.haze.HazeTint fallbackTint; property public abstract androidx.compose.ui.graphics.Brush? mask; property public abstract float noiseFactor; property public abstract dev.chrisbanes.haze.HazeProgressive? progressive; + property public abstract dev.chrisbanes.haze.HazeStyle style; property public abstract java.util.List tints; } @@ -47,7 +93,18 @@ package dev.chrisbanes.haze { } public final class HazeKt { - method public static androidx.compose.ui.Modifier haze(androidx.compose.ui.Modifier, dev.chrisbanes.haze.HazeState state); + method @androidx.compose.runtime.Stable public static androidx.compose.ui.Modifier haze(androidx.compose.ui.Modifier, dev.chrisbanes.haze.HazeState state); + } + + @dev.chrisbanes.haze.ExperimentalHazeApi public final class HazeNode extends androidx.compose.ui.Modifier.Node implements androidx.compose.ui.node.CompositionLocalConsumerModifierNode androidx.compose.ui.node.DrawModifierNode androidx.compose.ui.node.GlobalPositionAwareModifierNode { + ctor public HazeNode(dev.chrisbanes.haze.HazeState state); + method public void draw(androidx.compose.ui.graphics.drawscope.ContentDrawScope); + method public dev.chrisbanes.haze.HazeState getState(); + method public void onGloballyPositioned(androidx.compose.ui.layout.LayoutCoordinates coordinates); + method public void setState(dev.chrisbanes.haze.HazeState); + property public boolean shouldAutoInvalidate; + property public final dev.chrisbanes.haze.HazeState state; + field public static final String TAG = "HazeNode"; } public sealed interface HazeProgressive { @@ -95,22 +152,22 @@ package dev.chrisbanes.haze { } @androidx.compose.runtime.Immutable public final class HazeStyle { - ctor public HazeStyle(optional long backgroundColor, optional dev.chrisbanes.haze.HazeTint? tint, optional float blurRadius, optional float noiseFactor, optional dev.chrisbanes.haze.HazeTint? fallbackTint); - ctor public HazeStyle(optional long backgroundColor, optional java.util.List tints, optional float blurRadius, optional float noiseFactor, optional dev.chrisbanes.haze.HazeTint? fallbackTint); + ctor public HazeStyle(optional long backgroundColor, optional dev.chrisbanes.haze.HazeTint? tint, optional float blurRadius, optional float noiseFactor, optional dev.chrisbanes.haze.HazeTint fallbackTint); + ctor public HazeStyle(optional long backgroundColor, optional java.util.List tints, optional float blurRadius, optional float noiseFactor, optional dev.chrisbanes.haze.HazeTint fallbackTint); method public long component1-0d7_KjU(); method public java.util.List component2(); method public float component3-D9Ej5fM(); method public float component4(); - method public dev.chrisbanes.haze.HazeTint? component5(); - method public dev.chrisbanes.haze.HazeStyle copy-cq6XJ1M(long backgroundColor, java.util.List tints, float blurRadius, float noiseFactor, dev.chrisbanes.haze.HazeTint? fallbackTint); + method public dev.chrisbanes.haze.HazeTint component5(); + method public dev.chrisbanes.haze.HazeStyle copy-cq6XJ1M(long backgroundColor, java.util.List tints, float blurRadius, float noiseFactor, dev.chrisbanes.haze.HazeTint fallbackTint); method public long getBackgroundColor(); method public float getBlurRadius(); - method public dev.chrisbanes.haze.HazeTint? getFallbackTint(); + method public dev.chrisbanes.haze.HazeTint getFallbackTint(); method public float getNoiseFactor(); method public java.util.List getTints(); property public final long backgroundColor; property public final float blurRadius; - property public final dev.chrisbanes.haze.HazeTint? fallbackTint; + property public final dev.chrisbanes.haze.HazeTint fallbackTint; property public final float noiseFactor; property public final java.util.List tints; field public static final dev.chrisbanes.haze.HazeStyle.Companion Companion; @@ -121,6 +178,11 @@ package dev.chrisbanes.haze { property public final dev.chrisbanes.haze.HazeStyle Unspecified; } + public final class HazeStyleKt { + method public static androidx.compose.runtime.ProvidableCompositionLocal getLocalHazeStyle(); + property public static final androidx.compose.runtime.ProvidableCompositionLocal LocalHazeStyle; + } + @androidx.compose.runtime.Stable public final class HazeTint { ctor public HazeTint(long color, optional int blendMode); method public long component1-0d7_KjU(); @@ -128,8 +190,16 @@ package dev.chrisbanes.haze { method public dev.chrisbanes.haze.HazeTint copy-xETnrds(long color, int blendMode); method public int getBlendMode(); method public long getColor(); + method public boolean isSpecified(); property public final int blendMode; property public final long color; + property public final boolean isSpecified; + field public static final dev.chrisbanes.haze.HazeTint.Companion Companion; + } + + public static final class HazeTint.Companion { + method public dev.chrisbanes.haze.HazeTint getUnspecified(); + property public final dev.chrisbanes.haze.HazeTint Unspecified; } } diff --git a/haze/build.gradle.kts b/haze/build.gradle.kts index 605fe3a7..85bd2462 100644 --- a/haze/build.gradle.kts +++ b/haze/build.gradle.kts @@ -102,6 +102,12 @@ kotlin { } } +tasks.withType>().configureEach { + compilerOptions { + optIn.add("dev.chrisbanes.haze.ExperimentalHazeApi") + } +} + tasks.withType().configureEach { compilerOptions { freeCompilerArgs.add("-Xcontext-receivers") diff --git a/haze/screenshots/android/HazeScreenshotTest.creditCard_compositionLocalStyle.png b/haze/screenshots/android/HazeScreenshotTest.creditCard_compositionLocalStyle.png new file mode 100644 index 00000000..e8609deb Binary files /dev/null and b/haze/screenshots/android/HazeScreenshotTest.creditCard_compositionLocalStyle.png differ diff --git a/haze/screenshots/android/HazeScreenshotTest.creditCard_compositionLocalStyle[28].png b/haze/screenshots/android/HazeScreenshotTest.creditCard_compositionLocalStyle[28].png new file mode 100644 index 00000000..d440aad7 Binary files /dev/null and b/haze/screenshots/android/HazeScreenshotTest.creditCard_compositionLocalStyle[28].png differ diff --git a/haze/screenshots/android/HazeScreenshotTest.creditCard_style.png b/haze/screenshots/android/HazeScreenshotTest.creditCard_style.png new file mode 100644 index 00000000..e8609deb Binary files /dev/null and b/haze/screenshots/android/HazeScreenshotTest.creditCard_style.png differ diff --git a/haze/screenshots/android/HazeScreenshotTest.creditCard_style[28].png b/haze/screenshots/android/HazeScreenshotTest.creditCard_style[28].png new file mode 100644 index 00000000..d440aad7 Binary files /dev/null and b/haze/screenshots/android/HazeScreenshotTest.creditCard_style[28].png differ diff --git a/haze/screenshots/desktop/HazeScreenshotTest.creditCard_compositionLocalStyle.png b/haze/screenshots/desktop/HazeScreenshotTest.creditCard_compositionLocalStyle.png new file mode 100644 index 00000000..cfdd7405 Binary files /dev/null and b/haze/screenshots/desktop/HazeScreenshotTest.creditCard_compositionLocalStyle.png differ diff --git a/haze/screenshots/desktop/HazeScreenshotTest.creditCard_style.png b/haze/screenshots/desktop/HazeScreenshotTest.creditCard_style.png new file mode 100644 index 00000000..cfdd7405 Binary files /dev/null and b/haze/screenshots/desktop/HazeScreenshotTest.creditCard_style.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 d7b45f71..10816da6 100644 --- a/haze/src/commonMain/kotlin/dev/chrisbanes/haze/Haze.kt +++ b/haze/src/commonMain/kotlin/dev/chrisbanes/haze/Haze.kt @@ -3,23 +3,19 @@ package dev.chrisbanes.haze -import androidx.compose.runtime.Immutable import androidx.compose.runtime.Stable import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import androidx.compose.ui.geometry.Offset -import androidx.compose.ui.graphics.BlendMode import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.isSpecified import androidx.compose.ui.graphics.layer.GraphicsLayer -import androidx.compose.ui.graphics.takeOrElse import androidx.compose.ui.node.ModifierNodeElement import androidx.compose.ui.platform.InspectorInfo import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp -import androidx.compose.ui.unit.takeOrElse @Stable class HazeState { @@ -42,12 +38,14 @@ class HazeState { } /** - * Draw content within the provided [HazeState.areas] blurred in a 'glassmorphism' style. + * Draw background content for [hazeChild] child nodes, which will be drawn with a blur + * in a 'glassmorphism' style. * * When running on Android 12 devices (and newer), usage of this API renders the corresponding composable * into a separate graphics layer. On older Android platforms, a translucent scrim will be drawn * instead. */ +@Stable fun Modifier.haze(state: HazeState): Modifier = this then HazeNodeElement(state) /** @@ -123,65 +121,3 @@ internal data class HazeNodeElement( name = "haze" } } - -/** - * A holder for the style properties used by Haze. - * - * Can be set via [Modifier.haze] and [Modifier.hazeChild]. - * - * @property backgroundColor Color to draw behind the blurred content. Ideally should be opaque - * so that the original content is not visible behind. Typically this would be - * `MaterialTheme.colorScheme.surface` or similar. - * @property tints The [HazeTint]s to apply to the blurred content. - * @property blurRadius Radius of the blur. - * @property noiseFactor Amount of noise applied to the content, in the range `0f` to `1f`. - * Anything outside of that range will be clamped. - * @property fallbackTint The [HazeTint] to use when Haze uses the fallback scrim functionality. - * In this scenario, the tints provided in [tints] are ignored. - */ -@Immutable -data class HazeStyle( - val backgroundColor: Color = Color.Unspecified, - val tints: List = emptyList(), - val blurRadius: Dp = Dp.Unspecified, - val noiseFactor: Float = -1f, - val fallbackTint: HazeTint? = tints.firstOrNull()?.boostForFallback(blurRadius), -) { - constructor( - backgroundColor: Color = Color.Unspecified, - tint: HazeTint? = null, - blurRadius: Dp = Dp.Unspecified, - noiseFactor: Float = -1f, - fallbackTint: HazeTint? = tint?.boostForFallback(blurRadius), - ) : this(backgroundColor, listOfNotNull(tint), blurRadius, noiseFactor, fallbackTint) - - companion object { - val Unspecified: HazeStyle = HazeStyle(tints = emptyList()) - } -} - -@Stable -data class HazeTint( - val color: Color, - val blendMode: BlendMode = BlendMode.SrcOver, -) - -/** - * Resolves the style which should be used by renderers. The style returned from here - * is guaranteed to contains specified values. - */ -internal fun resolveStyle( - default: HazeStyle, - child: HazeStyle, -): HazeStyle = HazeStyle( - tints = child.tints.takeIf { it.isNotEmpty() } ?: default.tints, - blurRadius = child.blurRadius.takeOrElse { default.blurRadius }.takeOrElse { 0.dp }, - noiseFactor = child.noiseFactor.takeOrElse { default.noiseFactor }.takeOrElse { 0f }, - backgroundColor = child.backgroundColor - .takeOrElse { default.backgroundColor } - .takeOrElse { Color.Unspecified }, - fallbackTint = child.fallbackTint ?: default.fallbackTint, -) - -private inline fun Float.takeOrElse(block: () -> Float): Float = - if (this in 0f..1f) this else block() diff --git a/haze/src/commonMain/kotlin/dev/chrisbanes/haze/HazeChild.kt b/haze/src/commonMain/kotlin/dev/chrisbanes/haze/HazeChild.kt index fd03b7d4..8db63e99 100644 --- a/haze/src/commonMain/kotlin/dev/chrisbanes/haze/HazeChild.kt +++ b/haze/src/commonMain/kotlin/dev/chrisbanes/haze/HazeChild.kt @@ -3,6 +3,7 @@ package dev.chrisbanes.haze +import androidx.compose.runtime.Stable import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.graphics.Brush @@ -18,6 +19,18 @@ interface HazeChildScope { */ var alpha: Float + /** + * Style set on this specific [HazeChildNode]. + * + * There are precedence rules to how each styling property is applied. The order of precedence + * for each property are as follows: + * + * - Property value set in [HazeChildScope], if specified. + * - Value set here in [HazeChildScope.style], if specified. + * - Value set in the [LocalHazeStyle] composition local. + */ + var style: HazeStyle + /** * Optional alpha mask which allows effects such as fading via a * [Brush.verticalGradient] or similar. This is only applied when [progressive] is null. @@ -31,29 +44,59 @@ interface HazeChildScope { * Color to draw behind the blurred content. Ideally should be opaque * so that the original content is not visible behind. Typically this would be * `MaterialTheme.colorScheme.surface` or similar. + * + * There are precedence rules to how this styling property is applied: + * + * - This property value, if specified. + * - [HazeStyle.backgroundColor] value set in [style], if specified. + * - [HazeStyle.backgroundColor] value set in the [LocalHazeStyle] composition local. */ var backgroundColor: Color /** * The [HazeTint]s to apply to the blurred content. + * + * There are precedence rules to how this styling property is applied: + * + * - This property value, if not empty. + * - [HazeStyle.tints] value set in [style], if not empty. + * - [HazeStyle.tints] value set in the [LocalHazeStyle] composition local. */ var tints: List /** * Radius of the blur. + * + * There are precedence rules to how this styling property is applied: + * + * - This property value, if specified. + * - [HazeStyle.blurRadius] value set in [style], if specified. + * - [HazeStyle.blurRadius] value set in the [LocalHazeStyle] composition local. */ var blurRadius: Dp /** * Amount of noise applied to the content, in the range `0f` to `1f`. * Anything outside of that range will be clamped. + * + * There are precedence rules to how this styling property is applied: + * + * - This property value, if in the range 0f..1f. + * - [HazeStyle.noiseFactor] value set in [style], if in the range 0f..1f. + * - [HazeStyle.noiseFactor] value set in the [LocalHazeStyle] composition local. */ var noiseFactor: Float /** - * The [HazeTint] to use when Haze uses the fallback scrim functionality. + * The [HazeTint] to use when Haze uses the fallback scrim functionality. + * + * There are precedence rules to how this styling property is applied: + * + * - This property value, if specified + * - [HazeStyle.fallbackTint] value set in [style], if specified. + * - [HazeStyle.fallbackTint] value set in the [LocalHazeStyle] composition local. */ - var fallbackTint: HazeTint? + var fallbackTint: HazeTint /** * Parameters for enabling a progressive (or gradient) blur effect, or null for a uniform @@ -65,11 +108,6 @@ interface HazeChildScope { * visual finesse. */ var progressive: HazeProgressive? - - /** - * Apply the given [HazeStyle] to this block. - */ - fun applyStyle(style: HazeStyle) } /** @@ -102,34 +140,26 @@ fun Modifier.hazeChild( * * @param style The [HazeStyle] to use on this content. Any specified values in the given * style will override that value from the default style, provided to [haze]. - */ -fun Modifier.hazeChild( - state: HazeState, - style: HazeStyle, -): Modifier = hazeChild(state) { applyStyle(style) } - -/** - * Mark this composable as being a Haze child composable. - * - * This will update the given [HazeState] whenever the layout is placed, enabling any layouts using - * [Modifier.haze] to blur any content behind the host composable. - * * @param block block on HazeChildScope where you define the styling and visual properties. */ +@Stable fun Modifier.hazeChild( state: HazeState, - block: (HazeChildScope.() -> Unit), -): Modifier = this then HazeChildNodeElement(state, block) + style: HazeStyle = HazeStyle.Unspecified, + block: (HazeChildScope.() -> Unit)? = null, +): Modifier = this then HazeChildNodeElement(state, style, block) private data class HazeChildNodeElement( val state: HazeState, - val block: HazeChildScope.() -> Unit, + val style: HazeStyle = HazeStyle.Unspecified, + val block: (HazeChildScope.() -> Unit)? = null, ) : ModifierNodeElement() { - override fun create(): HazeChildNode = HazeChildNode(state, block) + override fun create(): HazeChildNode = HazeChildNode(state, style, block) override fun update(node: HazeChildNode) { node.state = state + node.style = style node.block = block node.update() } diff --git a/haze/src/commonMain/kotlin/dev/chrisbanes/haze/HazeChildNode.kt b/haze/src/commonMain/kotlin/dev/chrisbanes/haze/HazeChildNode.kt index 6217d5c3..fc252117 100644 --- a/haze/src/commonMain/kotlin/dev/chrisbanes/haze/HazeChildNode.kt +++ b/haze/src/commonMain/kotlin/dev/chrisbanes/haze/HazeChildNode.kt @@ -22,6 +22,7 @@ import androidx.compose.ui.graphics.drawscope.clipRect import androidx.compose.ui.graphics.isSpecified import androidx.compose.ui.graphics.layer.GraphicsLayer import androidx.compose.ui.graphics.layer.drawLayer +import androidx.compose.ui.graphics.takeOrElse import androidx.compose.ui.graphics.withSaveLayer import androidx.compose.ui.layout.LayoutCoordinates import androidx.compose.ui.layout.positionInWindow @@ -45,20 +46,168 @@ import kotlin.math.hypot import kotlin.math.max import kotlin.math.min -internal class HazeChildNode( +/** + * The [Modifier.Node] implementation used by [Modifier.hazeChild]. + * + * This is public API in order to aid custom extensible modifiers, _but_ we reserve the right + * to be able to change the API in the future, hence why it is marked as experimental forever. + */ +@ExperimentalHazeApi +class HazeChildNode( var state: HazeState, - var block: HazeChildScope.() -> Unit, + style: HazeStyle = HazeStyle.Unspecified, + var block: (HazeChildScope.() -> Unit)? = null, ) : Modifier.Node(), CompositionLocalConsumerModifierNode, GlobalPositionAwareModifierNode, ObserverModifierNode, - DrawModifierNode { - - private val effect by lazy(::ReusableHazeEffect) + DrawModifierNode, + HazeChildScope { override val shouldAutoInvalidate: Boolean = false - fun update() { + private val paint by lazy { Paint() } + + private var renderEffect: RenderEffect? = null + private var renderEffectDirty: Boolean = true + private var positionChanged: Boolean = true + private var drawParametersDirty: Boolean = true + private var progressiveDirty: Boolean = true + + internal var compositionLocalStyle: HazeStyle = HazeStyle.Unspecified + set(value) { + if (field != value) { + log(TAG) { "LocalHazeStyle changed. Current: $field. New: $value" } + field = value + renderEffectDirty = true + } + } + + override var style: HazeStyle = style + set(value) { + if (field != value) { + log(TAG) { "style changed. Current: $field. New: $value" } + field = value + renderEffectDirty = true + } + } + + private var positionInContent: Offset = Offset.Unspecified + set(value) { + if (value != field) { + log(TAG) { "positionInContent changed. Current: $field. New: $value" } + positionChanged = true + field = value + } + } + + private val isValid: Boolean + get() = size.isSpecified && layerSize.isSpecified + + private var size: Size = Size.Unspecified + set(value) { + if (value != field) { + log(TAG) { "size changed. Current: $field. New: $value" } + // We use the size for crop rects/brush sizing + renderEffectDirty = true + field = value + } + } + + private var layerSize: Size = Size.Unspecified + set(value) { + if (value != field) { + log(TAG) { "layerSize changed. Current: $field. New: $value" } + renderEffectDirty = true + field = value + } + } + + private val layerOffset: Offset + get() = when { + isValid -> { + Offset( + x = (layerSize.width - size.width) / 2f, + y = (layerSize.height - size.height) / 2f, + ) + } + + else -> Offset.Zero + } + + override var blurRadius: Dp = HazeDefaults.blurRadius + set(value) { + if (value != field) { + log(TAG) { "blurRadius changed. Current: $field. New: $value" } + renderEffectDirty = true + field = value + } + } + + override var noiseFactor: Float = HazeDefaults.noiseFactor + set(value) { + if (value != field) { + log(TAG) { "noiseFactor changed. Current: $field. New: $value" } + renderEffectDirty = true + field = value + } + } + + override var mask: Brush? = null + set(value) { + if (value != field) { + log(TAG) { "mask changed. Current: $field. New: $value" } + renderEffectDirty = true + field = value + } + } + + override var backgroundColor: Color = Color.Unspecified + set(value) { + if (value != field) { + log(TAG) { "backgroundColor changed. Current: $field. New: $value" } + renderEffectDirty = true + field = value + } + } + + override var tints: List = emptyList() + set(value) { + if (value != field) { + log(TAG) { "tints changed. Current: $field. New: $value" } + renderEffectDirty = true + field = value + } + } + + override var fallbackTint: HazeTint = HazeTint.Unspecified + set(value) { + if (value != field) { + log(TAG) { "fallbackTint changed. Current: $field. New: $value" } + renderEffectDirty = true + field = value + } + } + + override var alpha: Float = 1f + set(value) { + if (value != field) { + log(TAG) { "alpha changed. Current $field. New: $value" } + drawParametersDirty = true + field = value + } + } + + override var progressive: HazeProgressive? = null + set(value) { + if (value != field) { + log(TAG) { "progressive changed. Current $field. New: $value" } + progressiveDirty = true + field = value + } + } + + internal fun update() { onObservedReadsChanged() } @@ -69,17 +218,20 @@ internal class HazeChildNode( override fun onObservedReadsChanged() { observeReads { updateEffect() + compositionLocalStyle = currentValueOf(LocalHazeStyle) } } override fun onGloballyPositioned(coordinates: LayoutCoordinates) { log(TAG) { "onGloballyPositioned: positionInWindow=${coordinates.positionInWindow()}" } - effect.positionInContent = coordinates.positionInWindow() + - calculateWindowOffset() - state.positionOnScreen - effect.size = coordinates.size.toSize() + positionInContent = + coordinates.positionInWindow() + calculateWindowOffset() - state.positionOnScreen + size = coordinates.size.toSize() - val blurRadiusPx = with(currentValueOf(LocalDensity)) { effect.blurRadius.toPx() } - effect.layerSize = effect.size.expand(blurRadiusPx * 2) + val blurRadiusPx = with(currentValueOf(LocalDensity)) { + resolvedBlurRadius.takeOrElse { 0.dp }.toPx() + } + layerSize = size.expand(blurRadiusPx * 2) updateEffect() } @@ -91,7 +243,7 @@ internal class HazeChildNode( "Layout nodes using Modifier.haze and Modifier.hazeChild can not be descendants of each other" } - if (!effect.isValid) { + if (!isValid) { // If we don't have any effects, just call drawContent and return early drawContent() log(TAG) { "-> HazeChild. end draw()" } @@ -99,7 +251,7 @@ internal class HazeChildNode( } // First we need to make sure that the effects are updated (if necessary) - effect.onPreDraw(drawContext.density) + onPreDraw(drawContext.density) if (useGraphicLayers()) { val contentLayer = state.contentLayer @@ -113,7 +265,7 @@ internal class HazeChildNode( // Finally we draw the content drawContent() - effect.onPostDraw() + onPostDraw() log(TAG) { "-> HazeChild. end draw()" } } @@ -121,9 +273,9 @@ internal class HazeChildNode( private fun updateEffect() { // Invalidate if any of the effects triggered an invalidation, or we now have zero // effects but were previously showing some - block(effect) + block?.invoke(this) - if (effect.needInvalidation) { + if (needInvalidation()) { log(TAG) { "invalidateDraw called, due to effect needing invalidation" } invalidateDraw() } @@ -138,33 +290,33 @@ internal class HazeChildNode( // The layer size is usually than the bounds. This is so that we include enough // content around the edges to keep the blurring uniform. Without the extra border, // the blur will naturally fade out at the edges. - val inflatedSize = effect.layerSize + val inflatedSize = layerSize // This is the topLeft in the inflated bounds where the real are should be at [0,0] - val inflatedOffset = effect.layerOffset + val inflatedOffset = layerOffset clippedContentLayer.record(inflatedSize.roundToIntSize()) { - require(effect.backgroundColor.isSpecified) { + val bg = resolvedBackgroundColor + require(bg.isSpecified) { "backgroundColor not specified. Please provide a color." } - drawRect(effect.backgroundColor) + drawRect(bg) - translate(inflatedOffset - effect.positionInContent) { + translate(inflatedOffset - positionInContent) { // Draw the content into our effect layer drawLayer(contentLayer) } } - val progressive = effect.progressive - if (progressive is HazeProgressive.LinearGradient && useGraphicLayers()) { + val p = progressive + if (p is HazeProgressive.LinearGradient && useGraphicLayers()) { drawLinearGradientProgressiveEffect( - effect = effect, - progressive = progressive, + progressive = p, innerDrawOffset = -inflatedOffset, contentLayer = clippedContentLayer, ) } else { - clippedContentLayer.renderEffect = effect.renderEffect - clippedContentLayer.alpha = effect.alpha + clippedContentLayer.renderEffect = renderEffect + clippedContentLayer.alpha = alpha clipRect(right = size.width, bottom = size.height) { translate(-inflatedOffset) { @@ -179,18 +331,31 @@ internal class HazeChildNode( } private fun DrawScope.drawEffectWithScrim() { - // Maybe we can do this progressive too? - drawFallbackEffect( - alpha = effect.alpha, - blurRadius = effect.blurRadius, - tints = effect.tints, - fallbackTint = effect.fallbackTint, - mask = effect.mask, - ) + val scrimTint = resolvedFallbackTint.takeIf { it.isSpecified } + ?: resolvedTints.firstOrNull()?.boostForFallback(resolvedBlurRadius.takeOrElse { 0.dp }) + + fun scrim() { + if (scrimTint != null) { + val m = mask + if (m != null) { + drawRect(brush = m, colorFilter = ColorFilter.tint(scrimTint.color)) + } else { + drawRect(color = scrimTint.color, blendMode = scrimTint.blendMode) + } + } + } + + if (alpha != 1f) { + paint.alpha = alpha + drawContext.canvas.withSaveLayer(size.toRect(), paint) { + scrim() + } + } else { + scrim() + } } private fun DrawScope.drawLinearGradientProgressiveEffect( - effect: ReusableHazeEffect, progressive: HazeProgressive.LinearGradient, innerDrawOffset: Offset, contentLayer: GraphicsLayer, @@ -207,7 +372,7 @@ internal class HazeChildNode( // We use a calculation of 48dp per step, which is a good balance between // quality vs performance val stepHeightPx = with(drawContext.density) { 48.dp.toPx() } - val length = calculateLength(progressive.start, progressive.end, effect.size) + val length = calculateLength(progressive.start, progressive.end, size) steps = ceil(length / stepHeightPx).toInt().coerceAtLeast(2) } @@ -248,17 +413,18 @@ internal class HazeChildNode( "maskStops=${maskStops.map { it.first to it.second.alpha }}" } - val blurRadiusPx = with(drawContext.density) { effect.blurRadiusOrZero.toPx() } - val boundsInLayer = Rect(effect.layerOffset, effect.size) + val boundsInLayer = Rect(layerOffset, size) - layer.alpha = effect.alpha + layer.alpha = alpha layer.renderEffect = createRenderEffect( - blurRadiusPx = intensity * blurRadiusPx, - noiseFactor = effect.noiseFactor, - tints = effect.tints, + blurRadiusPx = with(drawContext.density) { + intensity * resolvedBlurRadius.takeOrElse { 0.dp }.toPx() + }, + noiseFactor = resolvedNoiseFactor, + tints = resolvedTints, tintAlphaModulate = intensity, boundsInLayer = boundsInLayer, // cache this - layerSize = effect.layerSize, + layerSize = layerSize, mask = Brush.linearGradient( *maskStops.toTypedArray(), start = progressive.start, @@ -278,29 +444,39 @@ internal class HazeChildNode( } } - private fun ReusableHazeEffect.onPreDraw(density: Density) { + private fun onPreDraw(density: Density) { if (renderEffectDirty) { renderEffect = createRenderEffect( - blurRadiusPx = with(density) { blurRadiusOrZero.toPx() }, // cache this - noiseFactor = noiseFactor, - tints = tints, + blurRadiusPx = with(density) { + resolvedBlurRadius.takeOrElse { 0.dp }.toPx() + }, + noiseFactor = resolvedNoiseFactor, + tints = resolvedTints, boundsInLayer = Rect(layerOffset, size), // cache this layerSize = layerSize, mask = mask, ) renderEffectDirty = false } - // We don't update the path here as we may not need it. Let draw request it - // via getUpdatedPath if it needs it } - private fun ReusableHazeEffect.onPostDraw() { + private fun onPostDraw() { drawParametersDirty = false progressiveDirty = false positionChanged = false } - internal companion object { + private fun needInvalidation(): Boolean { + log(TAG) { + "needInvalidation. renderEffectDirty=$renderEffectDirty, " + + "drawParametersDirty=$drawParametersDirty, " + + "progressiveDirty=$progressiveDirty" + + "positionChanged=$positionChanged" + } + return renderEffectDirty || drawParametersDirty || progressiveDirty || positionChanged + } + + private companion object { const val TAG = "HazeChild" } } @@ -424,183 +600,36 @@ internal expect fun HazeChildNode.createRenderEffect( mask: Brush? = null, ): RenderEffect? -internal class ReusableHazeEffect : HazeChildScope { - var renderEffect: RenderEffect? = null - var renderEffectDirty: Boolean = true - var positionChanged: Boolean = true - var drawParametersDirty: Boolean = true - var progressiveDirty: Boolean = true - - var positionInContent: Offset = Offset.Unspecified - set(value) { - if (value != field) { - log("ReusableHazeEffect") { "positionInContent changed. Current: $field. New: $value" } - positionChanged = true - field = value - } - } - - val isValid: Boolean - get() = size.isSpecified && layerSize.isSpecified - - var size: Size = Size.Unspecified - set(value) { - if (value != field) { - log("ReusableHazeEffect") { "size changed. Current: $field. New: $value" } - // We use the size for crop rects/brush sizing - renderEffectDirty = true - field = value - } - } - - var layerSize: Size = Size.Unspecified - set(value) { - if (value != field) { - log("ReusableHazeEffect") { "layerSize changed. Current: $field. New: $value" } - renderEffectDirty = true - field = value - } - } - - val layerOffset: Offset - get() = when { - isValid -> { - Offset( - x = (layerSize.width - size.width) / 2f, - y = (layerSize.height - size.height) / 2f, - ) - } - - else -> Offset.Zero - } - - override var blurRadius: Dp = HazeDefaults.blurRadius - set(value) { - if (value != field) { - log("ReusableHazeEffect") { "blurRadius changed. Current: $field. New: $value" } - renderEffectDirty = true - field = value - } - } - - override var noiseFactor: Float = HazeDefaults.noiseFactor - set(value) { - if (value != field) { - log("ReusableHazeEffect") { "noiseFactor changed. Current: $field. New: $value" } - renderEffectDirty = true - field = value - } - } - - override var mask: Brush? = null - set(value) { - if (value != field) { - log("ReusableHazeEffect") { "mask changed. Current: $field. New: $value" } - renderEffectDirty = true - field = value - } - } +internal val HazeChildNode.resolvedBackgroundColor: Color + get() = backgroundColor + .takeOrElse { style.backgroundColor } + .takeOrElse { compositionLocalStyle.backgroundColor } - override var backgroundColor: Color = Color.Unspecified +internal val HazeChildNode.resolvedBlurRadius: Dp + get() = blurRadius + .takeOrElse { style.blurRadius } + .takeOrElse { compositionLocalStyle.blurRadius } - override var tints: List = emptyList() - set(value) { - if (value != field) { - log("ReusableHazeEffect") { "tints changed. Current: $field. New: $value" } - renderEffectDirty = true - field = value - } - } +internal val HazeChildNode.resolvedTints: List + get() = tints.takeIf { it.isNotEmpty() } + ?: style.tints.takeIf { it.isNotEmpty() } + ?: compositionLocalStyle.tints.takeIf { it.isNotEmpty() } + ?: emptyList() - override var fallbackTint: HazeTint? = null - set(value) { - if (value != field) { - log("ReusableHazeEffect") { "fallbackTint changed. Current: $field. New: $value" } - renderEffectDirty = true - field = value - } - } +internal val HazeChildNode.resolvedFallbackTint: HazeTint + get() = fallbackTint.takeIf { it.isSpecified } + ?: style.fallbackTint.takeIf { it.isSpecified } + ?: compositionLocalStyle.fallbackTint - override var alpha: Float = 1f - set(value) { - if (value != field) { - log("ReusableHazeEffect") { "alpha changed. Current $field. New: $value" } - drawParametersDirty = true - field = value - } - } - - override var progressive: HazeProgressive? = null - set(value) { - if (value != field) { - log("ReusableHazeEffect") { "progressive changed. Current $field. New: $value" } - progressiveDirty = true - field = value - } - } - - override fun applyStyle(style: HazeStyle) { - noiseFactor = style.noiseFactor - blurRadius = style.blurRadius - tints = style.tints - fallbackTint = style.fallbackTint - backgroundColor = style.backgroundColor - } -} - -internal val ReusableHazeEffect.blurRadiusOrZero: Dp - get() = blurRadius.takeOrElse { 0.dp } - -internal val ReusableHazeEffect.needInvalidation: Boolean - get() { - log("ReusableHazeEffect") { - "needInvalidation. renderEffectDirty=$renderEffectDirty, " + - "drawParametersDirty=$drawParametersDirty, " + - "progressiveDirty=$progressiveDirty" + - "positionChanged=$positionChanged" - } - return renderEffectDirty || drawParametersDirty || progressiveDirty || positionChanged - } +internal val HazeChildNode.resolvedNoiseFactor: Float + get() = noiseFactor + .takeOrElse { style.noiseFactor } + .takeOrElse { compositionLocalStyle.noiseFactor } private fun Size.expand(expansion: Float): Size { return Size(width = width + expansion, height = height + expansion) } -private fun DrawScope.drawFallbackEffect( - alpha: Float, - blurRadius: Dp, - tints: List, - fallbackTint: HazeTint?, - mask: Brush?, -) { - val tint = fallbackTint - ?.takeIf { it.color.isSpecified } - ?: tints.firstOrNull()?.boostForFallback(blurRadius.takeOrElse { 0.dp }) - - log(HazeChildNode.TAG) { "drawEffect. Drawing effect with scrim: tint=$tint, mask=$mask, alpha=$alpha" } - - fun scrim() { - if (tint != null) { - if (mask != null) { - drawRect(brush = mask, colorFilter = ColorFilter.tint(tint.color)) - } else { - drawRect(color = tint.color, blendMode = tint.blendMode) - } - } - } - - if (alpha != 1f) { - val paint = Paint().apply { - this.alpha = alpha - } - drawContext.canvas.withSaveLayer(size.toRect(), paint) { - scrim() - } - } else { - scrim() - } -} - private fun calculateLength( start: Offset, end: Offset, diff --git a/haze/src/commonMain/kotlin/dev/chrisbanes/haze/HazeNode.kt b/haze/src/commonMain/kotlin/dev/chrisbanes/haze/HazeNode.kt index 0de7bbb3..0186fabb 100644 --- a/haze/src/commonMain/kotlin/dev/chrisbanes/haze/HazeNode.kt +++ b/haze/src/commonMain/kotlin/dev/chrisbanes/haze/HazeNode.kt @@ -18,7 +18,17 @@ import androidx.compose.ui.platform.LocalGraphicsContext import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.takeOrElse -internal class HazeNode( +@RequiresOptIn(message = "Experimental Haze API", level = RequiresOptIn.Level.WARNING) +annotation class ExperimentalHazeApi + +/** + * The [Modifier.Node] implementation used by [Modifier.haze]. + * + * This is public API in order to aid custom extensible modifiers, _but_ we reserve the right + * to be able to change the API in the future, hence why it is marked as experimental forever. + */ +@ExperimentalHazeApi +class HazeNode( var state: HazeState, ) : Modifier.Node(), CompositionLocalConsumerModifierNode, diff --git a/haze/src/commonMain/kotlin/dev/chrisbanes/haze/HazeStyle.kt b/haze/src/commonMain/kotlin/dev/chrisbanes/haze/HazeStyle.kt new file mode 100644 index 00000000..3db1adb4 --- /dev/null +++ b/haze/src/commonMain/kotlin/dev/chrisbanes/haze/HazeStyle.kt @@ -0,0 +1,83 @@ +// Copyright 2024, Christopher Banes and the Haze project contributors +// SPDX-License-Identifier: Apache-2.0 + +package dev.chrisbanes.haze + +import androidx.compose.runtime.Immutable +import androidx.compose.runtime.ProvidableCompositionLocal +import androidx.compose.runtime.Stable +import androidx.compose.runtime.compositionLocalOf +import androidx.compose.ui.graphics.BlendMode +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.isSpecified +import androidx.compose.ui.unit.Dp + +/** + * A [ProvidableCompositionLocal] which provides the default [HazeStyle] for all [hazeChild] + * layout nodes placed within this composition local's content. + * + * There are precedence rules to how each styling property is applied. The order of precedence + * for each property are as follows: + * + * - Value set in [HazeChildScope], if specified. + * - Value set in style provided to [hazeChild] (or [HazeChildScope.style]), if specified. + * - Value set in this composition local. + */ +val LocalHazeStyle: ProvidableCompositionLocal = compositionLocalOf { HazeStyle.Unspecified } + +/** + * A holder for the style properties used by Haze. + * + * Can be set via [haze] and [hazeChild]. + * + * @property backgroundColor Color to draw behind the blurred content. Ideally should be opaque + * so that the original content is not visible behind. Typically this would be + * `MaterialTheme.colorScheme.surface` or similar. + * @property tints The [HazeTint]s to apply to the blurred content. + * @property blurRadius Radius of the blur. + * @property noiseFactor Amount of noise applied to the content, in the range `0f` to `1f`. + * Anything outside of that range will be clamped. + * @property fallbackTint The [HazeTint] to use when Haze uses the fallback scrim functionality. + * In this scenario, the tints provided in [tints] are ignored. + */ +@Immutable +data class HazeStyle( + val backgroundColor: Color = Color.Unspecified, + val tints: List = emptyList(), + val blurRadius: Dp = Dp.Unspecified, + val noiseFactor: Float = -1f, + val fallbackTint: HazeTint = HazeTint.Unspecified, +) { + constructor( + backgroundColor: Color = Color.Unspecified, + tint: HazeTint? = null, + blurRadius: Dp = Dp.Unspecified, + noiseFactor: Float = -1f, + fallbackTint: HazeTint = HazeTint.Unspecified, + ) : this( + backgroundColor = backgroundColor, + tints = listOfNotNull(tint), + blurRadius = blurRadius, + noiseFactor = noiseFactor, + fallbackTint = fallbackTint, + ) + + companion object { + val Unspecified: HazeStyle = HazeStyle(tints = emptyList()) + } +} + +@Stable +data class HazeTint( + val color: Color, + val blendMode: BlendMode = BlendMode.SrcOver, +) { + companion object { + val Unspecified: HazeTint = HazeTint(Color.Unspecified) + } + + val isSpecified: Boolean get() = color.isSpecified +} + +internal inline fun Float.takeOrElse(block: () -> Float): Float = + if (this in 0f..1f) this else block() diff --git a/haze/src/screenshotTest/kotlin/dev/chrisbanes/haze/HazeScreenshotTest.kt b/haze/src/screenshotTest/kotlin/dev/chrisbanes/haze/HazeScreenshotTest.kt index 0291d1fb..d04ca5c5 100644 --- a/haze/src/screenshotTest/kotlin/dev/chrisbanes/haze/HazeScreenshotTest.kt +++ b/haze/src/screenshotTest/kotlin/dev/chrisbanes/haze/HazeScreenshotTest.kt @@ -4,6 +4,7 @@ package dev.chrisbanes.haze import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.setValue @@ -20,7 +21,29 @@ class HazeScreenshotTest : ScreenshotTest() { fun creditCard() = runScreenshotTest { setContent { ScreenshotTheme { - CreditCardSample() + CreditCardSample(tint = DefaultTint) + } + } + captureRoot() + } + + @Test + fun creditCard_style() = runScreenshotTest { + setContent { + ScreenshotTheme { + CreditCardSample(style = OverrideStyle) + } + } + captureRoot() + } + + @Test + fun creditCard_compositionLocalStyle() = runScreenshotTest { + setContent { + ScreenshotTheme { + CompositionLocalProvider(LocalHazeStyle provides OverrideStyle) { + CreditCardSample() + } } } captureRoot() @@ -41,6 +64,7 @@ class HazeScreenshotTest : ScreenshotTest() { setContent { ScreenshotTheme { CreditCardSample( + tint = DefaultTint, mask = Brush.verticalGradient( 0f to Color.Transparent, 0.5f to Color.Black, @@ -56,7 +80,7 @@ class HazeScreenshotTest : ScreenshotTest() { fun creditCard_alpha() = runScreenshotTest { setContent { ScreenshotTheme { - CreditCardSample(alpha = 0.5f) + CreditCardSample(tint = DefaultTint, alpha = 0.5f) } } captureRoot() @@ -67,6 +91,7 @@ class HazeScreenshotTest : ScreenshotTest() { setContent { ScreenshotTheme { CreditCardSample( + tint = DefaultTint, progressive = HazeProgressive.horizontalGradient(), ) } @@ -79,6 +104,7 @@ class HazeScreenshotTest : ScreenshotTest() { setContent { ScreenshotTheme { CreditCardSample( + tint = DefaultTint, progressive = HazeProgressive.verticalGradient(), ) } @@ -136,7 +162,7 @@ class HazeScreenshotTest : ScreenshotTest() { setContent { ScreenshotTheme { - CreditCardSample(enabled = enabled) + CreditCardSample(tint = DefaultTint, enabled = enabled) } } @@ -155,9 +181,14 @@ class HazeScreenshotTest : ScreenshotTest() { private fun roundedCornerTest(roundedCornerShape: RoundedCornerShape) = runScreenshotTest { setContent { ScreenshotTheme { - CreditCardSample(shape = roundedCornerShape) + CreditCardSample(tint = DefaultTint, shape = roundedCornerShape) } } captureRoot() } + + 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 74600c85..70870e67 100644 --- a/haze/src/screenshotTest/kotlin/dev/chrisbanes/haze/ScreenshotTestContent.kt +++ b/haze/src/screenshotTest/kotlin/dev/chrisbanes/haze/ScreenshotTestContent.kt @@ -26,7 +26,8 @@ import androidx.compose.ui.unit.dp @Composable internal fun CreditCardSample( - tint: HazeTint = HazeTint(Color.White.copy(alpha = 0.1f)), + style: HazeStyle = HazeStyle.Unspecified, + tint: HazeTint = HazeTint.Unspecified, shape: RoundedCornerShape = RoundedCornerShape(16.dp), enabled: Boolean = true, mask: Brush? = null, @@ -68,9 +69,10 @@ internal fun CreditCardSample( when { enabled -> { Modifier.hazeChild(state = hazeState) { + this.style = style backgroundColor = surfaceColor noiseFactor = HazeDefaults.noiseFactor - tints = listOfNotNull(tint) + tints = listOfNotNull(tint.takeIf(HazeTint::isSpecified)) blurRadius = 8.dp this.mask = mask this.alpha = alpha 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 3c488b12..da41373c 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 @@ -74,7 +74,7 @@ fun ScaffoldSample(navigator: Navigator, useProgressive: Boolean) { ), modifier = Modifier .hazeChild(hazeState) { - applyStyle(style) + this.style = style if (useProgressive) { progressive = HazeProgressive.verticalGradient(startIntensity = 1f, endIntensity = 0f) }