Add border API, update border style

This commit is contained in:
Kyant0 committed 2025-07-05 20:24:19 +08:00
1 parent 4e2c1a10cb
commit abee093c01
8 files changed
+114 -26

No files matched your search

Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -49,6 +49,7 @@ import androidx.compose.ui.util.fastCoerceAtMost
import com.kyant.expressa.prelude.*
import com.kyant.expressa.shape.RoundedRectangle
import com.kyant.glass.R
import com.kyant.liquidglass.GlassLightBorderBrush
import com.kyant.liquidglass.LocalLiquidGlassProviderState
import com.kyant.liquidglass.liquidGlassProvider
import com.kyant.liquidglass.rememberLiquidGlassProviderState
@@ -164,13 +165,21 @@ fun Preview() {
.drawWithCache { // border
val shape = RoundedRectangle(state.cornerRadius.value)
val outline = shape.createOutline(size, layoutDirection, this)
val cornerRadiusPx = state.cornerRadius.value.toPx()
val borderWidthPx = 3.dp.toPx()
val brush =
GlassLightBorderBrush(
color = Color.White,
cornerRadius = cornerRadiusPx,
borderWidth = borderWidthPx
)
onDrawWithContent {
drawContent()
drawOutline(
outline = outline,
color = Color.White.copy(alpha = 0.2f),
style = Stroke(3.dp.toPx()),
brush = brush,
style = Stroke(borderWidthPx),
blendMode = BlendMode.Plus
)
}
@@ -0,0 +1,44 @@
package com.kyant.liquidglass
import android.graphics.RuntimeShader
import android.os.Build
import androidx.annotation.RequiresApi
import androidx.compose.ui.geometry.Size
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.Shader
import androidx.compose.ui.graphics.ShaderBrush
import kotlin.math.PI
@RequiresApi(Build.VERSION_CODES.TIRAMISU)
class GlassLightBorderBrush(
private val color: Color,
private val cornerRadius: Float,
private val borderWidth: Float
) : ShaderBrush() {
override fun createShader(size: Size): Shader {
return RuntimeShader(
"""// This file belongs to Kyant. You must not use it without permission.
${LiquidGlassShaders.sdRectangleShaderUtils}
half4 main(float2 coord) {
float cornerRadius = ${cornerRadius};
float2 size = float2(${size.width}, ${size.height});
float2 halfSize = size * 0.5;
float2 centeredCoord = coord - halfSize;
float2 grad = gradSdRoundedRectangle(centeredCoord, halfSize, cornerRadius);
float2 topLightNormal = float2(-cos(${75.0 / 180.0 * PI}), -sin(${75.0 / 180.0 * PI}));
float topLightFraction = dot(topLightNormal, grad);
float bottomLightFraction = dot(-topLightNormal, grad);
float fraction = pow(max(topLightFraction, bottomLightFraction), 5.0);
float sd = sdRoundedRectangle(centeredCoord, halfSize, cornerRadius);
sd = min(sd, 0.0);
fraction = mix(fraction, 0.0, pow(-sd / $borderWidth, 0.5));
return half4(${color.red}, ${color.green}, ${color.blue}, ${color.alpha}) * fraction;
}"""
)
}
}
@@ -12,7 +12,6 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.drawWithCache
import androidx.compose.ui.geometry.Rect
import androidx.compose.ui.graphics.BlendMode
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.asComposeRenderEffect
import androidx.compose.ui.graphics.drawOutline
import androidx.compose.ui.graphics.drawscope.Stroke
@@ -21,7 +20,6 @@ import androidx.compose.ui.graphics.graphicsLayer
import androidx.compose.ui.graphics.layer.drawLayer
import androidx.compose.ui.layout.boundsInRoot
import androidx.compose.ui.layout.onGloballyPositioned
import androidx.compose.ui.unit.dp
@Composable
fun Modifier.liquidGlass(
@@ -135,6 +133,17 @@ fun Modifier.liquidGlass(
graphicsLayer.renderEffect = renderEffect
val outline = style.shape.createOutline(size, layoutDirection, this)
val borderWidthPx = style.borderWidth.toPx()
val borderBrush =
if (borderWidthPx > 0f) {
GlassLightBorderBrush(
color = style.borderColor,
cornerRadius = cornerRadiusPx,
borderWidth = borderWidthPx
)
} else {
null
}
onDrawBehind {
val rect = rect ?: return@onDrawBehind
@@ -143,15 +152,18 @@ fun Modifier.liquidGlass(
drawLayer(providerState.graphicsLayer)
}
}
drawLayer(graphicsLayer)
borderBrush?.let {
drawOutline(
outline = outline,
color = Color.White.copy(alpha = 0.2f),
style = Stroke(3.dp.toPx()),
brush = it,
style = Stroke(borderWidthPx),
blendMode = BlendMode.Plus
)
}
}
}
.onGloballyPositioned { layoutCoordinates ->
rect = providerState.rect?.let {
layoutCoordinates.boundsInRoot().translate(-it.topLeft)
@@ -10,15 +10,10 @@ internal object LiquidGlassShaders {
float luma(half4 color) {
return dot(toLinearSrgb(color.rgb), rgbToY);
}
""".trimIndent()
}"""
@Language("AGSL")
private val refractionShaderUtils = """// This file belongs to Kyant. You must not use it without permission.
float circleMap(float x) {
return 1.0 - sqrt(1.0 - x * x);
}
internal val sdRectangleShaderUtils = """// This file belongs to Kyant. You must not use it without permission.
float sdRectangle(float2 coord, float2 halfSize) {
float2 d = abs(coord) - halfSize;
float outside = length(max(d, 0.0));
@@ -40,6 +35,14 @@ internal object LiquidGlassShaders {
} else {
return sign(coord) * ((-cornerCoord.x < -cornerCoord.y) ? float2(1.0, 0.0) : float2(0.0, 1.0));
}
}"""
@Language("AGSL")
private val refractionShaderUtils = """// This file belongs to Kyant. You must not use it without permission.
$sdRectangleShaderUtils
float circleMap(float x) {
return 1.0 - sqrt(1.0 - x * x);
}
half4 refractionColor(float2 coord, float2 size, float cornerRadius, float eccentricFactor, float height, float amount) {
@@ -65,8 +68,7 @@ internal object LiquidGlassShaders {
} else {
return image.eval(coord);
}
}
""".trimIndent()
}"""
@Language("AGSL")
val refractionShaderString = """// This file belongs to Kyant. You must not use it without permission.
@@ -6,6 +6,7 @@ import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableFloatStateOf
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
@@ -13,6 +14,8 @@ import androidx.compose.ui.unit.dp
fun LiquidGlassStyle(
shape: CornerBasedShape,
blurRadius: Dp = 2.dp,
borderWidth: Dp = 3.dp,
borderColor: Color = Color.White,
refractionHeight: Dp = 8.dp,
refractionAmount: Dp = (-16).dp,
@@ -29,6 +32,8 @@ fun LiquidGlassStyle(
ImmutableLiquidGlassStyle(
shape = shape,
blurRadius = blurRadius,
borderWidth = borderWidth,
borderColor = borderColor,
refractionHeight = refractionHeight,
refractionAmount = refractionAmount,
eccentricFactor = eccentricFactor,
@@ -43,6 +48,8 @@ fun LiquidGlassStyle(
fun MutableLiquidGlassState(
shape: CornerBasedShape,
blurRadius: Dp = 2.dp,
borderWidth: Dp = 3.dp,
borderColor: Color = Color.White,
refractionHeight: Dp = 8.dp,
refractionAmount: Dp = (-16).dp,
@@ -58,17 +65,19 @@ fun MutableLiquidGlassState(
): MutableLiquidGlassState =
MutableLiquidGlassState(
initialStyle = ImmutableLiquidGlassStyle(
shape,
blurRadius,
refractionHeight,
refractionAmount,
eccentricFactor,
bleedAmount,
bleedBlurRadius,
bleedOpacity,
contrast,
whitePoint,
chromaMultiplier
shape = shape,
blurRadius = blurRadius,
borderWidth = borderWidth,
borderColor = borderColor,
refractionHeight = refractionHeight,
refractionAmount = refractionAmount,
eccentricFactor = eccentricFactor,
bleedAmount = bleedAmount,
bleedBlurRadius = bleedBlurRadius,
bleedOpacity = bleedOpacity,
contrast = contrast,
whitePoint = whitePoint,
chromaMultiplier = chromaMultiplier
)
)
@@ -78,6 +87,10 @@ sealed interface LiquidGlassStyle {
val blurRadius: Dp
val borderWidth: Dp
val borderColor: Color
val refractionHeight: Dp
val refractionAmount: Dp
@@ -106,6 +119,8 @@ class ImmutableLiquidGlassStyle
internal constructor(
override val shape: CornerBasedShape,
override val blurRadius: Dp = 2.dp,
override val borderWidth: Dp = 3.dp,
override val borderColor: Color = Color.White,
override val refractionHeight: Dp = 8.dp,
override val refractionAmount: Dp = (-16).dp,
@@ -123,6 +138,8 @@ internal constructor(
fun copy(
shape: CornerBasedShape = this.shape,
blurRadius: Dp = this.blurRadius,
borderWidth: Dp = this.borderWidth,
borderColor: Color = this.borderColor,
refractionHeight: Dp = this.refractionHeight,
refractionAmount: Dp = this.refractionAmount,
@@ -139,6 +156,8 @@ internal constructor(
ImmutableLiquidGlassStyle(
shape = shape,
blurRadius = blurRadius,
borderWidth = borderWidth,
borderColor = borderColor,
refractionHeight = refractionHeight,
refractionAmount = refractionAmount,
eccentricFactor = eccentricFactor,
@@ -158,6 +177,8 @@ internal constructor(
override var shape: CornerBasedShape by mutableStateOf(initialStyle.shape)
override var blurRadius: Dp by mutableStateOf(initialStyle.blurRadius)
override var borderWidth: Dp by mutableStateOf(initialStyle.borderWidth)
override var borderColor: Color by mutableStateOf(initialStyle.borderColor)
override var refractionHeight: Dp by mutableStateOf(initialStyle.refractionHeight)
override var refractionAmount: Dp by mutableStateOf(initialStyle.refractionAmount)