Skip to content

Commit

Permalink
Style improvements for 0.9.x (#360)
Browse files Browse the repository at this point in the history
This re-introduces a more global way to set styling properties, but this
time via a new `LocalHazeStyle` composition local. As we now have 3
different sources for styling information, we now have documented
precedence rules.

Also made the `Modifier.Node` implementations public, for more custom
use cases.

To quickly illustrate all of the ways to set styling properties:

### LocalHazeStyle

```kotlin
CompositionLocalProvider(LocalHazeStyle provides fooStyle) {
    Box(
       Modifier.hazeChild(state = hazeState)
    )
}
```

### style on hazeChild

```kotlin
Box(
   Modifier.hazeChild(state = hazeState, style = fooStyle)
)

// or... (these are equivalent)

Box(
    Modifier.hazeChild(state = hazeState) {
        style = fooStyle
    }
)
```

### Individual styling properties

```kotlin
Box(
    Modifier.hazeChild(state = hazeState) {
        tints = listOf(...)
    }
)
```

Fixes #340
  • Loading branch information
chrisbanes authored Oct 15, 2024
1 parent b90d504 commit 9c411e7
Show file tree
Hide file tree
Showing 17 changed files with 532 additions and 335 deletions.
8 changes: 4 additions & 4 deletions docs/migrating-0.9.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
96 changes: 83 additions & 13 deletions haze/api/api.txt
Original file line number Diff line number Diff line change
@@ -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<? super dev.chrisbanes.haze.HazeChildScope,kotlin.Unit> 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<? super dev.chrisbanes.haze.HazeChildScope,kotlin.Unit>? 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<? super dev.chrisbanes.haze.HazeChildScope,kotlin.Unit>? 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<dev.chrisbanes.haze.HazeChildScope,kotlin.Unit>? 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<dev.chrisbanes.haze.HazeTint> 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<? super dev.chrisbanes.haze.HazeChildScope,kotlin.Unit>?);
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<dev.chrisbanes.haze.HazeTint>);
property public float alpha;
property public long backgroundColor;
property public final kotlin.jvm.functions.Function1<dev.chrisbanes.haze.HazeChildScope,kotlin.Unit>? 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<dev.chrisbanes.haze.HazeTint> 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<dev.chrisbanes.haze.HazeTint> 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<dev.chrisbanes.haze.HazeTint>);
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<dev.chrisbanes.haze.HazeTint> tints;
}

Expand All @@ -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 {
Expand Down Expand Up @@ -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<dev.chrisbanes.haze.HazeTint> 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<dev.chrisbanes.haze.HazeTint> tints, optional float blurRadius, optional float noiseFactor, optional dev.chrisbanes.haze.HazeTint fallbackTint);
method public long component1-0d7_KjU();
method public java.util.List<dev.chrisbanes.haze.HazeTint> 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<dev.chrisbanes.haze.HazeTint> 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<dev.chrisbanes.haze.HazeTint> 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<dev.chrisbanes.haze.HazeTint> 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<dev.chrisbanes.haze.HazeTint> tints;
field public static final dev.chrisbanes.haze.HazeStyle.Companion Companion;
Expand All @@ -121,15 +178,28 @@ package dev.chrisbanes.haze {
property public final dev.chrisbanes.haze.HazeStyle Unspecified;
}

public final class HazeStyleKt {
method public static androidx.compose.runtime.ProvidableCompositionLocal<dev.chrisbanes.haze.HazeStyle> getLocalHazeStyle();
property public static final androidx.compose.runtime.ProvidableCompositionLocal<dev.chrisbanes.haze.HazeStyle> LocalHazeStyle;
}

@androidx.compose.runtime.Stable public final class HazeTint {
ctor public HazeTint(long color, optional int blendMode);
method public long component1-0d7_KjU();
method public int component2-0nO6VwU();
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;
}

}
Expand Down
6 changes: 6 additions & 0 deletions haze/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,12 @@ kotlin {
}
}

tasks.withType<org.jetbrains.kotlin.gradle.tasks.KotlinCompilationTask<*>>().configureEach {
compilerOptions {
optIn.add("dev.chrisbanes.haze.ExperimentalHazeApi")
}
}

tasks.withType<org.jetbrains.kotlin.gradle.tasks.KotlinCompile>().configureEach {
compilerOptions {
freeCompilerArgs.add("-Xcontext-receivers")
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
70 changes: 3 additions & 67 deletions haze/src/commonMain/kotlin/dev/chrisbanes/haze/Haze.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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)

/**
Expand Down Expand Up @@ -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<HazeTint> = 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()
Loading

0 comments on commit 9c411e7

Please sign in to comment.