Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion docs/architecture.md
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ haze (core)
```

Future effects will follow the same pattern:
- **haze-liquidglass** - Refraction-based liquid glass effect
- **haze-liquidglass** - Refraction-based liquid glass effect with rounded-shape SDF and optional chromatic aberration
- Custom third-party effect modules

## Effect Registration Pattern
Expand Down
47 changes: 47 additions & 0 deletions docs/effects/liquid-glass.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
# Liquid Glass

A refraction-driven glass effect inspired by iOS “liquid” glass. It combines refraction, depth blur, tint, Fresnel/ambient lift, and specular highlights with optional rounded shapes and dispersion.

## Parameters
- **tint**: Glass tint (defaults to white 12% alpha).
- **refractionStrength**: Distortion strength `0..1` (default 0.7).
- **refractionHeight**: Fraction of the shortest side that participates in refraction (default 0.25). Lower values pull the effect toward the edges; higher values push it deeper into the interior.
- **depth / blurRadius**: Blend refracted content with a blurred input to add depth (defaults 0.4 / 4.dp).
- **specularIntensity**: Highlight strength `0..1` (default 0.4).
- **ambientResponse**: Fresnel/edge lift `0..1` (default 0.5).
- **edgeSoftness**: Soft fade at the edges (default 12.dp). Set to 0.dp for hard edges.
- **shape (RoundedCornerShape)**: Rounded-rect boundary for refraction and masking (default all radii 0).
- **lightPosition**: Optional light source; defaults to the layer center.
- **chromaticAberrationStrength**: Simple dispersion strength `0..1` (default 0). TODO: expand to channel/spread controls.
- **alpha**: Overall opacity multiplier.

## Fallbacks
- Runtime shader path: rounded SDF refraction, depth mix, tint/specular/Fresnel, chromatic aberration, and edge softness.
- Fallback path (when runtime shaders unavailable): tinted fill + radial highlight + edge falloff; respects rounded shapes and alpha.

## Usage
```kotlin
Box(
Modifier
.size(180.dp)
.clip(RoundedCornerShape(20.dp))
.hazeEffect(state = hazeState) {
liquidGlassEffect {
tint = Color.White.copy(alpha = 0.16f)
refractionStrength = 0.8f
refractionHeight = 0.32f
depth = 0.5f
specularIntensity = 0.7f
ambientResponse = 0.7f
edgeSoftness = 14.dp
shape = RoundedCornerShape(20.dp)
chromaticAberrationStrength = 0.2f // TODO: expand controls later
}
}
)
```

## Tips
- Lower `refractionHeight` for a pronounced edge “lens”; raise it for a fuller dome.
- Keep `chromaticAberrationStrength` modest; start at 0.1–0.25 to avoid rainbow artifacts.
- Combine `edgeSoftness` with rounded shapes for smooth clipping; set `edgeSoftness = 0.dp` to rely purely on the shape.
104 changes: 104 additions & 0 deletions haze-liquidglass/api/api.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
// Signature format: 4.0
package dev.chrisbanes.haze.liquidglass {

public final class HazeEffectScopeKt {
method public static inline void liquidGlassEffect(dev.chrisbanes.haze.HazeEffectScope, optional kotlin.jvm.functions.Function1<? super dev.chrisbanes.haze.liquidglass.LiquidGlassVisualEffect,kotlin.Unit> block);
}

public final class LiquidGlassDefaults {
method public androidx.compose.foundation.shape.RoundedCornerShape getShape();
property public static float ambientResponse;
property public androidx.compose.ui.unit.Dp blurRadius;
property public static float chromaticAberrationStrength;
property public static float depth;
property public androidx.compose.ui.unit.Dp edgeSoftness;
property public static float refractionHeight;
property public static float refractionStrength;
property public androidx.compose.foundation.shape.RoundedCornerShape shape;
property public static float specularIntensity;
property public androidx.compose.ui.graphics.Color tint;
field public static final dev.chrisbanes.haze.liquidglass.LiquidGlassDefaults INSTANCE;
field public static final float ambientResponse = 0.5f;
field public static final float chromaticAberrationStrength = 0.0f;
field public static final float depth = 0.4f;
field public static final float refractionHeight = 0.25f;
field public static final float refractionStrength = 0.7f;
field public static final float specularIntensity = 0.4f;
}

@androidx.compose.runtime.Immutable public final class LiquidGlassStyle {
ctor public LiquidGlassStyle();
ctor @KotlinOnly public LiquidGlassStyle(optional androidx.compose.ui.graphics.Color tint, optional float refractionStrength, optional float specularIntensity, optional float depth, optional float ambientResponse, optional androidx.compose.ui.unit.Dp edgeSoftness, optional androidx.compose.ui.geometry.Offset lightPosition, optional androidx.compose.ui.unit.Dp blurRadius, optional float refractionHeight, optional float chromaticAberrationStrength, optional androidx.compose.foundation.shape.RoundedCornerShape? shape);
method public float component10();
method public androidx.compose.foundation.shape.RoundedCornerShape? component11();
method public float component2();
method public float component3();
method public float component4();
method public float component5();
method public float component9();
method public dev.chrisbanes.haze.liquidglass.LiquidGlassStyle copy-Uuz26js(long tint, float refractionStrength, float specularIntensity, float depth, float ambientResponse, float edgeSoftness, long lightPosition, float blurRadius, float refractionHeight, float chromaticAberrationStrength, androidx.compose.foundation.shape.RoundedCornerShape? shape);
method public float getAmbientResponse();
method public float getChromaticAberrationStrength();
method public float getDepth();
method public float getRefractionHeight();
method public float getRefractionStrength();
method public androidx.compose.foundation.shape.RoundedCornerShape? getShape();
method public float getSpecularIntensity();
property public float ambientResponse;
property public androidx.compose.ui.unit.Dp blurRadius;
property public float chromaticAberrationStrength;
property public float depth;
property public androidx.compose.ui.unit.Dp edgeSoftness;
property public androidx.compose.ui.geometry.Offset lightPosition;
property public float refractionHeight;
property public float refractionStrength;
property public androidx.compose.foundation.shape.RoundedCornerShape? shape;
property public float specularIntensity;
property public androidx.compose.ui.graphics.Color tint;
field public static final dev.chrisbanes.haze.liquidglass.LiquidGlassStyle.Companion Companion;
}

public static final class LiquidGlassStyle.Companion {
method public dev.chrisbanes.haze.liquidglass.LiquidGlassStyle getUnspecified();
property public dev.chrisbanes.haze.liquidglass.LiquidGlassStyle Unspecified;
}

public final class LiquidGlassVisualEffect implements dev.chrisbanes.haze.VisualEffect {
ctor public LiquidGlassVisualEffect();
method public void draw(androidx.compose.ui.graphics.drawscope.DrawScope, dev.chrisbanes.haze.VisualEffectContext context);
method public float getAlpha();
method public float getAmbientResponse();
method public float getChromaticAberrationStrength();
method public float getDepth();
method public float getRefractionHeight();
method public float getRefractionStrength();
method public androidx.compose.foundation.shape.RoundedCornerShape getShape();
method public float getSpecularIntensity();
method public dev.chrisbanes.haze.liquidglass.LiquidGlassStyle getStyle();
method public void setAlpha(float);
method public void setAmbientResponse(float);
method public void setChromaticAberrationStrength(float);
method public void setDepth(float);
method public void setRefractionHeight(float);
method public void setRefractionStrength(float);
method public void setShape(androidx.compose.foundation.shape.RoundedCornerShape);
method public void setSpecularIntensity(float);
method public void setStyle(dev.chrisbanes.haze.liquidglass.LiquidGlassStyle);
property public float alpha;
property public float ambientResponse;
property public androidx.compose.ui.unit.Dp blurRadius;
property public float chromaticAberrationStrength;
property public float depth;
property public androidx.compose.ui.unit.Dp edgeSoftness;
property public androidx.compose.ui.geometry.Offset lightPosition;
property public float refractionHeight;
property public float refractionStrength;
property public androidx.compose.foundation.shape.RoundedCornerShape shape;
property public float specularIntensity;
property public dev.chrisbanes.haze.liquidglass.LiquidGlassStyle style;
property public androidx.compose.ui.graphics.Color tint;
field public static final String TAG = "LiquidGlassVisualEffect";
}

}

63 changes: 63 additions & 0 deletions haze-liquidglass/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
// Copyright 2025, Christopher Banes and the Haze project contributors
// SPDX-License-Identifier: Apache-2.0


import dev.chrisbanes.gradle.addDefaultHazeTargets

plugins {
id("dev.chrisbanes.android.library")
id("dev.chrisbanes.kotlin.multiplatform")
id("dev.chrisbanes.compose")
id("org.jetbrains.dokka")
id("com.vanniktech.maven.publish")
id("dev.chrisbanes.metalava")
}

android {
namespace = "dev.chrisbanes.haze.liquidglass"
}

kotlin {
addDefaultHazeTargets()
explicitApi()

sourceSets {
commonMain {
dependencies {
api(projects.haze)
implementation(projects.hazeUtils)
implementation(compose.ui)
implementation(compose.foundation)
}
}

val skikoMain by creating {
dependsOn(commonMain.get())
}

iosMain {
dependsOn(skikoMain)
}

macosMain {
dependsOn(skikoMain)
}

jvmMain {
dependsOn(skikoMain)
}

wasmJsMain {
dependsOn(skikoMain)
}

jsMain {
dependsOn(skikoMain)
}
}

compilerOptions {
optIn.add("dev.chrisbanes.haze.ExperimentalHazeApi")
optIn.add("dev.chrisbanes.haze.InternalHazeApi")
}
}
3 changes: 3 additions & 0 deletions haze-liquidglass/gradle.properties
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
POM_ARTIFACT_ID=haze-liquidglass
POM_NAME=Haze Liquid Glass

Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
// Copyright 2025, Christopher Banes and the Haze project contributors
// SPDX-License-Identifier: Apache-2.0

package dev.chrisbanes.haze.liquidglass

import androidx.compose.ui.graphics.drawscope.DrawScope
import dev.chrisbanes.haze.ExperimentalHazeApi
import dev.chrisbanes.haze.VisualEffectContext
import dev.chrisbanes.haze.isRuntimeShaderRenderEffectSupported

@OptIn(ExperimentalHazeApi::class)
internal actual fun LiquidGlassVisualEffect.updateDelegate(
context: VisualEffectContext,
drawScope: DrawScope,
) {
val wantsRuntime = isRuntimeShaderRenderEffectSupported()
delegate = when {
wantsRuntime && delegate !is RuntimeShaderLiquidGlassDelegate -> RuntimeShaderLiquidGlassDelegate(this)
!wantsRuntime && delegate !is FallbackLiquidGlassDelegate -> FallbackLiquidGlassDelegate(this)
else -> delegate
}
}
Loading
Loading