diff --git a/README.md b/README.md index 520ae6d..a0f7bcd 100644 --- a/README.md +++ b/README.md @@ -19,34 +19,31 @@ It doesn't support nested glass effects for now. ### Examples ```kotlin -val providerState = rememberLiquidGlassProviderState() +val providerState = rememberLiquidGlassProviderState( + backgroundColor = Color.White +) -CompositionLocalProvider( - LocalLiquidGlassProviderState provides providerState -) { - Box( - Modifier - .liquidGlassProvider(providerState) - ) +// content behind the glass +Box(Modifier.liquidGlassProvider(providerState)) - Box( - Modifier - .liquidGlass( - LiquidGlassStyle( - CornerShape.large, - innerRefraction = InnerRefraction( - height = RefractionValue(8.dp), - amount = RefractionValue.Full - ), - material = GlassMaterial( - blurRadius = 8.dp, - whitePoint = 0.1f, - chromaMultiplier = 1.5f - ) - ) +// glass +Box( + Modifier.liquidGlass( + providerState, + LiquidGlassStyle( + CornerShape.large, + innerRefraction = InnerRefraction( + height = RefractionValue(8.dp), + amount = RefractionValue.Full + ), + material = GlassMaterial( + blurRadius = 8.dp, + whitePoint = 0.1f, + chromaMultiplier = 1.5f ) + ) ) -} +) ``` ## Glass parameters diff --git a/app/release/app-release.apk b/app/release/app-release.apk index d59dd09..9e94bac 100644 Binary files a/app/release/app-release.apk and b/app/release/app-release.apk differ diff --git a/app/release/baselineProfiles/0/app-release.dm b/app/release/baselineProfiles/0/app-release.dm index 2fa1bee..fbae95a 100644 Binary files a/app/release/baselineProfiles/0/app-release.dm and b/app/release/baselineProfiles/0/app-release.dm differ diff --git a/app/release/baselineProfiles/1/app-release.dm b/app/release/baselineProfiles/1/app-release.dm index 11bd8f8..7019bca 100644 Binary files a/app/release/baselineProfiles/1/app-release.dm and b/app/release/baselineProfiles/1/app-release.dm differ diff --git a/app/src/main/java/com/kyant/glass/playground/ConfigurationBottomSheet.kt b/app/src/main/java/com/kyant/glass/playground/ConfigurationBottomSheet.kt index e7dbc2c..c29f87a 100644 --- a/app/src/main/java/com/kyant/glass/playground/ConfigurationBottomSheet.kt +++ b/app/src/main/java/com/kyant/glass/playground/ConfigurationBottomSheet.kt @@ -30,6 +30,7 @@ import com.kyant.expressa.ui.Text import com.kyant.glass.R import com.kyant.liquidglass.GlassMaterial import com.kyant.liquidglass.InnerRefraction +import com.kyant.liquidglass.LiquidGlassProviderState import com.kyant.liquidglass.LiquidGlassStyle import com.kyant.liquidglass.RefractionValue import com.kyant.liquidglass.liquidGlass @@ -37,6 +38,7 @@ import com.kyant.liquidglass.liquidGlass @Composable fun ConfigurationBottomSheet( state: PreviewState, + providerState: LiquidGlassProviderState, modifier: Modifier = Modifier ) { // val providerState = rememberLiquidGlassProviderState() @@ -48,6 +50,7 @@ fun ConfigurationBottomSheet( modifier // .liquidGlassProvider(providerState) .liquidGlass( + providerState, LiquidGlassStyle( shape = CornerShape.extraLarge, innerRefraction = InnerRefraction( @@ -88,7 +91,7 @@ fun ConfigurationBottomSheet( IconButton( { state.configurationMode = null }, - Modifier.liquidGlass(LiquidGlassStyle(CornerShape.full)), + Modifier.liquidGlass(providerState, LiquidGlassStyle(CornerShape.full)), colors = IconButtonColors.tonal( containerColor = primaryContainer.copy(alpha = 0.85f) ) @@ -112,24 +115,28 @@ fun ConfigurationBottomSheet( state.blurRadius, stringResource(R.string.blur_radius), { Icon(painterResource(R.drawable.lens_blur_24px)) }, + providerState, Modifier.fillMaxWidth() ) SliderChip( state.contrast, stringResource(R.string.contrast), { Icon(painterResource(R.drawable.contrast_24px)) }, + providerState, Modifier.fillMaxWidth() ) SliderChip( state.whitePoint, stringResource(R.string.white_point), { Icon(painterResource(R.drawable.wb_sunny_24px)) }, + providerState, Modifier.fillMaxWidth() ) SliderChip( state.chromaMultiplier, stringResource(R.string.chroma_multiplier), { Icon(painterResource(R.drawable.palette_24px)) }, + providerState, Modifier.fillMaxWidth() ) } @@ -137,7 +144,7 @@ fun ConfigurationBottomSheet( ConfigurationMode.Advanced -> { Button( { state.unsafeMode = !state.unsafeMode }, - Modifier.liquidGlass(LiquidGlassStyle(CornerShape.full)), + Modifier.liquidGlass(providerState, LiquidGlassStyle(CornerShape.full)), colors = ButtonColors.filled( containerColor = primary.copy(alpha = 0.85f) ) @@ -152,18 +159,21 @@ fun ConfigurationBottomSheet( state.bleedBlurRadius, stringResource(R.string.bleed_blur_radius), { Icon(painterResource(R.drawable.lens_blur_24px)) }, + providerState, Modifier.fillMaxWidth() ) SliderChip( state.eccentricFactor, stringResource(R.string.eccentric_factor), { Icon(painterResource(R.drawable.all_out_24px)) }, + providerState, Modifier.fillMaxWidth() ) SliderChip( state.dispersionHeight, stringResource(R.string.dispersion_height), { Icon(painterResource(R.drawable.star_shine_24px)) }, + providerState, Modifier.fillMaxWidth() ) } diff --git a/app/src/main/java/com/kyant/glass/playground/Preview.kt b/app/src/main/java/com/kyant/glass/playground/Preview.kt index e497b19..1d564c9 100644 --- a/app/src/main/java/com/kyant/glass/playground/Preview.kt +++ b/app/src/main/java/com/kyant/glass/playground/Preview.kt @@ -11,7 +11,6 @@ import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.safeDrawingPadding import androidx.compose.runtime.Composable -import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember @@ -44,7 +43,6 @@ 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.LocalLiquidGlassProviderState import com.kyant.liquidglass.liquidGlassProvider import com.kyant.liquidglass.rememberLiquidGlassProviderState import org.intellij.lang.annotations.Language @@ -61,30 +59,28 @@ fun Preview() { state.imageBitmap?.let { BitmapPainter(it) } ?: painterResource(R.drawable.homescreen) - val providerState = rememberLiquidGlassProviderState() - CompositionLocalProvider( - LocalLiquidGlassProviderState provides providerState - ) { - Box(Modifier.fillMaxSize()) { - // background - Image( - painter, null, - Modifier - .fillMaxSize() - .drawWithContent { - drawContent() + val providerState = rememberLiquidGlassProviderState(backgroundColor) - graphicsLayer.record { - drawRect(backgroundColor) - this@drawWithContent.drawContent() - } + Box(Modifier.fillMaxSize()) { + // background + Image( + painter, null, + Modifier + .fillMaxSize() + .drawWithContent { + drawContent() + + graphicsLayer.record { + drawRect(backgroundColor) + this@drawWithContent.drawContent() } - .liquidGlassProvider(providerState), - contentScale = ContentScale.Crop - ) + } + .liquidGlassProvider(providerState), + contentScale = ContentScale.Crop + ) - @Language("AGSL") - val colorShaderUtils = """// This file belongs to Kyant. You must not use it without permission. + @Language("AGSL") + val colorShaderUtils = """// This file belongs to Kyant. You must not use it without permission. const half3 rgbToY = half3(0.2126, 0.7152, 0.0722); float luma(half4 color) { @@ -92,8 +88,8 @@ fun Preview() { } """.trimIndent() - @Language("AGSL") - val refractionShaderUtils = """// This file belongs to Kyant. You must not use it without permission. + @Language("AGSL") + 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); } @@ -147,24 +143,24 @@ fun Preview() { } """.trimIndent() - // glass - Box( - Modifier - .graphicsLayer { // transform and clip - translationX = state.offset.x - translationY = state.offset.y - clip = true - shape = RoundedRectangle(state.cornerRadius.value) - } - .graphicsLayer { // white point & chroma boost - val contrast = state.contrast.value - val whitePoint = state.whitePoint.value - val chromaMultiplier = state.chromaMultiplier.value + // glass + Box( + Modifier + .graphicsLayer { // transform and clip + translationX = state.offset.x + translationY = state.offset.y + clip = true + shape = RoundedRectangle(state.cornerRadius.value) + } + .graphicsLayer { // white point & chroma boost + val contrast = state.contrast.value + val whitePoint = state.whitePoint.value + val chromaMultiplier = state.chromaMultiplier.value - if (contrast != 0f || whitePoint != 0f || chromaMultiplier != 1f) { - renderEffect = RenderEffect.createRuntimeShaderEffect( - RuntimeShader( - """// This file belongs to Kyant. You must not use it without permission. + if (contrast != 0f || whitePoint != 0f || chromaMultiplier != 1f) { + renderEffect = RenderEffect.createRuntimeShaderEffect( + RuntimeShader( + """// This file belongs to Kyant. You must not use it without permission. uniform shader image; uniform float contrast; @@ -194,19 +190,19 @@ fun Preview() { return color; }""" - ).apply { - setFloatUniform("contrast", contrast) - setFloatUniform("whitePoint", whitePoint) - setFloatUniform("chromaMultiplier", chromaMultiplier) - }, - "image" - ).asComposeRenderEffect() - } + ).apply { + setFloatUniform("contrast", contrast) + setFloatUniform("whitePoint", whitePoint) + setFloatUniform("chromaMultiplier", chromaMultiplier) + }, + "image" + ).asComposeRenderEffect() } - .graphicsLayer { // dispersion effect - renderEffect = RenderEffect.createRuntimeShaderEffect( - RuntimeShader( - """// This file belongs to Kyant. You must not use it without permission. + } + .graphicsLayer { // dispersion effect + renderEffect = RenderEffect.createRuntimeShaderEffect( + RuntimeShader( + """// This file belongs to Kyant. You must not use it without permission. uniform shader image; uniform float2 size; @@ -295,22 +291,22 @@ fun Preview() { return color; } }""" - ).apply { - val cornerRadius = - state.cornerRadius.value.toPx() - .fastCoerceAtMost(size.minDimension / 2f) + ).apply { + val cornerRadius = + state.cornerRadius.value.toPx() + .fastCoerceAtMost(size.minDimension / 2f) - setFloatUniform("size", size.width, size.height) - setFloatUniform("cornerRadius", cornerRadius) - setFloatUniform("dispersionHeight", state.dispersionHeight.value.toPx()) - }, - "image" - ).asComposeRenderEffect() - } - .graphicsLayer { // refraction & bleed - val refractionRenderEffect = RenderEffect.createRuntimeShaderEffect( - RuntimeShader( - """// This file belongs to Kyant. You must not use it without permission. + setFloatUniform("size", size.width, size.height) + setFloatUniform("cornerRadius", cornerRadius) + setFloatUniform("dispersionHeight", state.dispersionHeight.value.toPx()) + }, + "image" + ).asComposeRenderEffect() + } + .graphicsLayer { // refraction & bleed + val refractionRenderEffect = RenderEffect.createRuntimeShaderEffect( + RuntimeShader( + """// This file belongs to Kyant. You must not use it without permission. uniform shader image; uniform float2 size; @@ -335,22 +331,22 @@ fun Preview() { return color; } }""" - ).apply { - setFloatUniform("size", size.width, size.height) - setFloatUniform("cornerRadius", state.cornerRadius.value.toPx()) + ).apply { + setFloatUniform("size", size.width, size.height) + setFloatUniform("cornerRadius", state.cornerRadius.value.toPx()) - setFloatUniform("refractionHeight", state.refractionHeight.value.toPx()) - setFloatUniform("refractionAmount", state.refractionAmount.value.toPx()) - setFloatUniform("eccentricFactor", state.eccentricFactor.value) + setFloatUniform("refractionHeight", state.refractionHeight.value.toPx()) + setFloatUniform("refractionAmount", state.refractionAmount.value.toPx()) + setFloatUniform("eccentricFactor", state.eccentricFactor.value) - setFloatUniform("bleedOpacity", state.bleedOpacity.value) - }, - "image" - ) + setFloatUniform("bleedOpacity", state.bleedOpacity.value) + }, + "image" + ) - val bleedRenderEffect = RenderEffect.createRuntimeShaderEffect( - RuntimeShader( - """// This file belongs to Kyant. You must not use it without permission. + val bleedRenderEffect = RenderEffect.createRuntimeShaderEffect( + RuntimeShader( + """// This file belongs to Kyant. You must not use it without permission. uniform shader image; uniform float2 size; @@ -368,110 +364,110 @@ fun Preview() { color.rgb = mix(color.rgb, half3(1.0), 0.5 * circleMap(1.0 - luma)); return color; }""" - ).apply { - setFloatUniform("size", size.width, size.height) - setFloatUniform("cornerRadius", state.cornerRadius.value.toPx()) + ).apply { + setFloatUniform("size", size.width, size.height) + setFloatUniform("cornerRadius", state.cornerRadius.value.toPx()) - setFloatUniform("eccentricFactor", state.eccentricFactor.value) - setFloatUniform("bleedAmount", state.bleedAmount.value.toPx()) - }, - "image" - ) + setFloatUniform("eccentricFactor", state.eccentricFactor.value) + setFloatUniform("bleedAmount", state.bleedAmount.value.toPx()) + }, + "image" + ) - renderEffect = - if (state.bleedOpacity.value > 0f) { - val bleedBlurRadiusPx = state.bleedBlurRadius.value.toPx() - val blurredBleedRenderEffect = - if (bleedBlurRadiusPx > 0f) { - RenderEffect.createChainEffect( - bleedRenderEffect, - RenderEffect.createBlurEffect( - bleedBlurRadiusPx, - bleedBlurRadiusPx, - Shader.TileMode.CLAMP - ) + renderEffect = + if (state.bleedOpacity.value > 0f) { + val bleedBlurRadiusPx = state.bleedBlurRadius.value.toPx() + val blurredBleedRenderEffect = + if (bleedBlurRadiusPx > 0f) { + RenderEffect.createChainEffect( + bleedRenderEffect, + RenderEffect.createBlurEffect( + bleedBlurRadiusPx, + bleedBlurRadiusPx, + Shader.TileMode.CLAMP ) - } else { - bleedRenderEffect - } + ) + } else { + bleedRenderEffect + } - RenderEffect.createBlendModeEffect( - blurredBleedRenderEffect, - refractionRenderEffect, - android.graphics.BlendMode.SRC_OVER - ).asComposeRenderEffect() - } else { - refractionRenderEffect.asComposeRenderEffect() - } - } - .graphicsLayer { // blur - val blurRadiusPx = state.blurRadius.value.toPx() - if (blurRadiusPx > 0f) { - renderEffect = - RenderEffect.createBlurEffect( - blurRadiusPx, - blurRadiusPx, - Shader.TileMode.DECAL - ).asComposeRenderEffect() + RenderEffect.createBlendModeEffect( + blurredBleedRenderEffect, + refractionRenderEffect, + android.graphics.BlendMode.SRC_OVER + ).asComposeRenderEffect() + } else { + refractionRenderEffect.asComposeRenderEffect() } - } - .drawBehind { - clipRect(0f, 0f, rect.width, rect.height) { - translate(-rect.left, -rect.top) { - drawLayer(graphicsLayer) - } - } - } - .onGloballyPositioned { layoutCoordinates -> - rect = layoutCoordinates.boundsInParent() - } - .pointerInput(Unit) { - detectTransformGestures { _, pan, zoom, _ -> - state.offset += pan - state.size = DpSize( - (state.size.width * zoom).coerceIn( - state.minSize.width, - containerSize.width.toDp() - ), - (state.size.height * zoom).coerceIn( - state.minSize.height, - containerSize.height.toDp() - ) - ) - } - } - .layout { measurable, constraints -> - val size = state.size.toSize().roundToIntSize() - val width = size.width.fastCoerceAtMost(constraints.maxWidth) - val height = size.height.fastCoerceAtMost(constraints.maxHeight) - val placeable = measurable.measure(Constraints.fixed(width, height)) - - layout(width, height) { - placeable.place(0, 0) - } - } - .align(Alignment.Center) - ) - - if (state.displayControls) { - PreviewControls( - state, - { rect } - ) - } - - AnimatedContent( - state.isInConfigurationMode, - Modifier - .padding(16.dp) - .safeDrawingPadding() - .align(Alignment.BottomCenter) - ) { isInConfigurationMode -> - if (!isInConfigurationMode) { - PreviewToolbar(state) - } else { - ConfigurationBottomSheet(state) } + .graphicsLayer { // blur + val blurRadiusPx = state.blurRadius.value.toPx() + if (blurRadiusPx > 0f) { + renderEffect = + RenderEffect.createBlurEffect( + blurRadiusPx, + blurRadiusPx, + Shader.TileMode.DECAL + ).asComposeRenderEffect() + } + } + .drawBehind { + clipRect(0f, 0f, rect.width, rect.height) { + translate(-rect.left, -rect.top) { + drawLayer(graphicsLayer) + } + } + } + .onGloballyPositioned { layoutCoordinates -> + rect = layoutCoordinates.boundsInParent() + } + .pointerInput(Unit) { + detectTransformGestures { _, pan, zoom, _ -> + state.offset += pan + state.size = DpSize( + (state.size.width * zoom).coerceIn( + state.minSize.width, + containerSize.width.toDp() + ), + (state.size.height * zoom).coerceIn( + state.minSize.height, + containerSize.height.toDp() + ) + ) + } + } + .layout { measurable, constraints -> + val size = state.size.toSize().roundToIntSize() + val width = size.width.fastCoerceAtMost(constraints.maxWidth) + val height = size.height.fastCoerceAtMost(constraints.maxHeight) + val placeable = measurable.measure(Constraints.fixed(width, height)) + + layout(width, height) { + placeable.place(0, 0) + } + } + .align(Alignment.Center) + ) + + if (state.displayControls) { + PreviewControls( + state, + providerState, + rect = { rect } + ) + } + + AnimatedContent( + state.isInConfigurationMode, + Modifier + .padding(16.dp) + .safeDrawingPadding() + .align(Alignment.BottomCenter) + ) { isInConfigurationMode -> + if (!isInConfigurationMode) { + PreviewToolbar(state, providerState) + } else { + ConfigurationBottomSheet(state, providerState) } } } diff --git a/app/src/main/java/com/kyant/glass/playground/PreviewControls.kt b/app/src/main/java/com/kyant/glass/playground/PreviewControls.kt index b7f4edf..e2bb158 100644 --- a/app/src/main/java/com/kyant/glass/playground/PreviewControls.kt +++ b/app/src/main/java/com/kyant/glass/playground/PreviewControls.kt @@ -31,11 +31,13 @@ import com.kyant.expressa.m3.shape.CornerShape import com.kyant.expressa.prelude.* import com.kyant.expressa.ui.Icon import com.kyant.glass.R +import com.kyant.liquidglass.LiquidGlassProviderState import kotlin.math.sqrt @Composable fun PreviewControls( state: PreviewState, + providerState: LiquidGlassProviderState, rect: () -> Rect, modifier: Modifier = Modifier ) { @@ -130,6 +132,7 @@ fun PreviewControls( } ) }, + providerState, Modifier .graphicsLayer { val rect = rect() @@ -169,24 +172,28 @@ fun PreviewControls( state.bleedAmount, stringResource(R.string.bleed_amount), { Icon(painterResource(R.drawable.center_focus_weak_24px)) }, + providerState, Modifier.fillMaxWidth() ) SliderChip( state.bleedOpacity, stringResource(R.string.bleed_opacity), { Icon(painterResource(R.drawable.opacity_24px)) }, + providerState, Modifier.fillMaxWidth() ) SliderChip( state.refractionAmount, stringResource(R.string.refraction_amount), { Icon(painterResource(R.drawable.center_focus_weak_24px)) }, + providerState, Modifier.fillMaxWidth() ) SliderChip( state.refractionHeight, stringResource(R.string.refraction_height), { Icon(painterResource(R.drawable.water_lux_24px)) }, + providerState, Modifier.fillMaxWidth() ) } diff --git a/app/src/main/java/com/kyant/glass/playground/PreviewToolbar.kt b/app/src/main/java/com/kyant/glass/playground/PreviewToolbar.kt index c9e900c..d242f46 100644 --- a/app/src/main/java/com/kyant/glass/playground/PreviewToolbar.kt +++ b/app/src/main/java/com/kyant/glass/playground/PreviewToolbar.kt @@ -28,6 +28,7 @@ import com.kyant.expressa.prelude.* import com.kyant.expressa.ui.Icon import com.kyant.glass.R import com.kyant.liquidglass.GlassMaterial +import com.kyant.liquidglass.LiquidGlassProviderState import com.kyant.liquidglass.LiquidGlassStyle import com.kyant.liquidglass.liquidGlass import kotlinx.coroutines.launch @@ -35,6 +36,7 @@ import kotlinx.coroutines.launch @Composable fun PreviewToolbar( state: PreviewState, + providerState: LiquidGlassProviderState, modifier: Modifier = Modifier ) { val scope = rememberCoroutineScope() @@ -53,7 +55,7 @@ fun PreviewToolbar( ) { IconButton( { scope.launch { state.reset() } }, - Modifier.liquidGlass(iconButtonLiquidGlassStyle), + Modifier.liquidGlass(providerState, iconButtonLiquidGlassStyle), sizes = IconButtonSizes.medium, colors = IconButtonColors.tonal( containerColor = primary.copy(alpha = 0.6f), @@ -78,6 +80,7 @@ fun PreviewToolbar( IconButton( { state.displayControls = !state.displayControls }, Modifier.liquidGlass( + providerState, iconButtonLiquidGlassStyle.copy( material = GlassMaterial( whitePoint = if (state.displayControls) 0.5f else -0.5f @@ -103,7 +106,7 @@ fun PreviewToolbar( } IconButton( { state.configurationMode = ConfigurationMode.Colors }, - Modifier.liquidGlass(iconButtonLiquidGlassStyle), + Modifier.liquidGlass(providerState, iconButtonLiquidGlassStyle), sizes = IconButtonSizes.medium, colors = IconButtonColors.tonal( containerColor = Color.Transparent, @@ -117,7 +120,7 @@ fun PreviewToolbar( } IconButton( { state.configurationMode = ConfigurationMode.Advanced }, - Modifier.liquidGlass(iconButtonLiquidGlassStyle), + Modifier.liquidGlass(providerState, iconButtonLiquidGlassStyle), sizes = IconButtonSizes.medium, colors = IconButtonColors.tonal( containerColor = Color.Transparent, @@ -148,7 +151,7 @@ fun PreviewToolbar( ) ) }, - Modifier.liquidGlass(iconButtonLiquidGlassStyle), + Modifier.liquidGlass(providerState, iconButtonLiquidGlassStyle), sizes = IconButtonSizes.medium, colors = IconButtonColors.tonal( containerColor = primary.copy(alpha = 0.6f), diff --git a/app/src/main/java/com/kyant/glass/playground/SliderChip.kt b/app/src/main/java/com/kyant/glass/playground/SliderChip.kt index 4f39182..6a8f436 100644 --- a/app/src/main/java/com/kyant/glass/playground/SliderChip.kt +++ b/app/src/main/java/com/kyant/glass/playground/SliderChip.kt @@ -9,7 +9,6 @@ import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.padding import androidx.compose.runtime.Composable import androidx.compose.runtime.CompositionLocalProvider -import androidx.compose.runtime.remember import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip @@ -27,6 +26,7 @@ import com.kyant.expressa.ui.LocalTextStyle import com.kyant.expressa.ui.ProvideTextStyle import com.kyant.expressa.ui.Text import com.kyant.liquidglass.GlassMaterial +import com.kyant.liquidglass.LiquidGlassProviderState import com.kyant.liquidglass.LiquidGlassStyle import com.kyant.liquidglass.liquidGlass @@ -35,6 +35,7 @@ fun > SliderChip( value: PreviewState.LiquidGlassParamValue, label: String?, icon: @Composable () -> Unit, + providerState: LiquidGlassProviderState, modifier: Modifier = Modifier, dragDelta: (dragAmount: Offset) -> Float = { it.x } ) { @@ -47,12 +48,11 @@ fun > SliderChip( Row( modifier .liquidGlass( - remember { - LiquidGlassStyle( - CornerShape.extraLarge, - material = GlassMaterial(chromaMultiplier = 1.5f) - ) - } + providerState, + LiquidGlassStyle( + CornerShape.extraLarge, + material = GlassMaterial(chromaMultiplier = 1.5f) + ) ) .clip(CornerShape.full) .background(surfaceBright.copy(alpha = 0.85f)) diff --git a/liquid-glass/src/main/java/com/kyant/liquidglass/LiquidGlassModifier.kt b/liquid-glass/src/main/java/com/kyant/liquidglass/LiquidGlassModifier.kt index ec1e798..95c8631 100644 --- a/liquid-glass/src/main/java/com/kyant/liquidglass/LiquidGlassModifier.kt +++ b/liquid-glass/src/main/java/com/kyant/liquidglass/LiquidGlassModifier.kt @@ -4,7 +4,6 @@ import android.graphics.RenderEffect import android.graphics.Shader import android.os.Build import androidx.annotation.RequiresApi -import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.setValue @@ -37,15 +36,14 @@ import androidx.compose.ui.unit.Constraints import kotlin.math.ceil import kotlin.math.min -@Composable fun Modifier.liquidGlass( - style: LiquidGlassStyle, - providerState: LiquidGlassProviderState = LocalLiquidGlassProviderState.current + state: LiquidGlassProviderState, + style: LiquidGlassStyle ): Modifier = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { this then LiquidGlassElement( - style = style, - providerState = providerState + state = state, + style = style ) } else { this @@ -53,51 +51,51 @@ fun Modifier.liquidGlass( @RequiresApi(Build.VERSION_CODES.TIRAMISU) private class LiquidGlassElement( - val style: LiquidGlassStyle, - val providerState: LiquidGlassProviderState + val state: LiquidGlassProviderState, + val style: LiquidGlassStyle ) : ModifierNodeElement() { override fun create(): LiquidGlassModifierNode { return LiquidGlassModifierNode( - style = style, - providerState = providerState + state = state, + style = style ) } override fun update(node: LiquidGlassModifierNode) { node.update( - style = style, - providerState = providerState + state = state, + style = style ) } override fun InspectorInfo.inspectableProperties() { name = "liquidGlass" + properties["state"] = state properties["style"] = style - properties["providerState"] = providerState } override fun equals(other: Any?): Boolean { if (this === other) return true if (other !is LiquidGlassElement) return false + if (state != other.state) return false if (style != other.style) return false - if (providerState != other.providerState) return false return true } override fun hashCode(): Int { - var result = style.hashCode() - result = 31 * result + providerState.hashCode() + var result = state.hashCode() + result = 31 * result + style.hashCode() return result } } @RequiresApi(Build.VERSION_CODES.TIRAMISU) internal class LiquidGlassModifierNode( - var style: LiquidGlassStyle, - var providerState: LiquidGlassProviderState + var state: LiquidGlassProviderState, + var style: LiquidGlassStyle ) : LayoutModifierNode, GlobalPositionAwareModifierNode, DelegatingNode() { override val shouldAutoInvalidate: Boolean = false @@ -260,7 +258,7 @@ internal class LiquidGlassModifierNode( val rect = rect ?: return@onDrawBehind graphicsLayer.record { translate(-rect.left, -rect.top) { - drawLayer(providerState.graphicsLayer) + drawLayer(state.graphicsLayer) } } drawLayer(graphicsLayer) @@ -299,20 +297,20 @@ internal class LiquidGlassModifierNode( } override fun onGloballyPositioned(coordinates: LayoutCoordinates) { - rect = providerState.rect?.let { + rect = state.rect?.let { coordinates.boundsInRoot().translate(-it.topLeft) } } fun update( - style: LiquidGlassStyle, - providerState: LiquidGlassProviderState + state: LiquidGlassProviderState, + style: LiquidGlassStyle ) { - if (this.style != style || - this.providerState != providerState + if (this.state != state || + this.style != style ) { + this.state = state this.style = style - this.providerState = providerState drawWithCacheModifierNode.invalidateDrawCache() invalidateSubtree() } diff --git a/liquid-glass/src/main/java/com/kyant/liquidglass/LiquidGlassProviderModifier.kt b/liquid-glass/src/main/java/com/kyant/liquidglass/LiquidGlassProviderModifier.kt index 01edacf..af4ed2f 100644 --- a/liquid-glass/src/main/java/com/kyant/liquidglass/LiquidGlassProviderModifier.kt +++ b/liquid-glass/src/main/java/com/kyant/liquidglass/LiquidGlassProviderModifier.kt @@ -3,7 +3,6 @@ package com.kyant.liquidglass import android.os.Build import androidx.annotation.RequiresApi import androidx.compose.ui.Modifier -import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.drawscope.ContentDrawScope import androidx.compose.ui.layout.LayoutCoordinates import androidx.compose.ui.layout.boundsInRoot @@ -13,42 +12,30 @@ import androidx.compose.ui.node.ModifierNodeElement import androidx.compose.ui.platform.InspectorInfo fun Modifier.liquidGlassProvider( - state: LiquidGlassProviderState, - backgroundColor: Color? = null + state: LiquidGlassProviderState ): Modifier = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { - this then LiquidGlassProviderElement( - state = state, - backgroundColor = backgroundColor - ) + this then LiquidGlassProviderElement(state = state) } else { this } @RequiresApi(Build.VERSION_CODES.TIRAMISU) private class LiquidGlassProviderElement( - val state: LiquidGlassProviderState, - val backgroundColor: Color? + val state: LiquidGlassProviderState ) : ModifierNodeElement() { override fun create(): LiquidGlassProviderModifierNode { - return LiquidGlassProviderModifierNode( - state = state, - backgroundColor = backgroundColor - ) + return LiquidGlassProviderModifierNode(state = state) } override fun update(node: LiquidGlassProviderModifierNode) { - node.update( - state = state, - backgroundColor = backgroundColor - ) + node.update(state = state) } override fun InspectorInfo.inspectableProperties() { name = "liquidGlassProvider" properties["state"] = state - properties["backgroundColor"] = backgroundColor } override fun equals(other: Any?): Boolean { @@ -56,30 +43,27 @@ private class LiquidGlassProviderElement( if (other !is LiquidGlassProviderElement) return false if (state != other.state) return false - if (backgroundColor != other.backgroundColor) return false return true } override fun hashCode(): Int { - var result = state.hashCode() - result = 31 * result + backgroundColor.hashCode() - return result + return state.hashCode() } } @RequiresApi(Build.VERSION_CODES.TIRAMISU) internal class LiquidGlassProviderModifierNode( - var state: LiquidGlassProviderState, - var backgroundColor: Color? + var state: LiquidGlassProviderState ) : DrawModifierNode, GlobalPositionAwareModifierNode, Modifier.Node() { override val shouldAutoInvalidate: Boolean = false override fun ContentDrawScope.draw() { drawContent() + state.graphicsLayer.record { - backgroundColor?.let { drawRect(it) } + state.backgroundColor?.let { drawRect(it) } this@draw.drawContent() } } @@ -89,10 +73,8 @@ internal class LiquidGlassProviderModifierNode( } fun update( - state: LiquidGlassProviderState, - backgroundColor: Color? + state: LiquidGlassProviderState ) { this.state = state - this.backgroundColor = backgroundColor } } diff --git a/liquid-glass/src/main/java/com/kyant/liquidglass/LiquidGlassProviderState.kt b/liquid-glass/src/main/java/com/kyant/liquidglass/LiquidGlassProviderState.kt index deb9a8e..7929006 100644 --- a/liquid-glass/src/main/java/com/kyant/liquidglass/LiquidGlassProviderState.kt +++ b/liquid-glass/src/main/java/com/kyant/liquidglass/LiquidGlassProviderState.kt @@ -1,28 +1,30 @@ package com.kyant.liquidglass import androidx.compose.runtime.Composable -import androidx.compose.runtime.ProvidableCompositionLocal import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue -import androidx.compose.runtime.staticCompositionLocalOf import androidx.compose.ui.geometry.Rect +import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.layer.GraphicsLayer import androidx.compose.ui.graphics.rememberGraphicsLayer -val LocalLiquidGlassProviderState: ProvidableCompositionLocal = - staticCompositionLocalOf { error("CompositionLocal LocalLiquidGlassProviderState not present") } - @Composable -fun rememberLiquidGlassProviderState(): LiquidGlassProviderState { +fun rememberLiquidGlassProviderState( + backgroundColor: Color? +): LiquidGlassProviderState { val graphicsLayer = rememberGraphicsLayer() - return remember(graphicsLayer) { - LiquidGlassProviderState(graphicsLayer) + return remember(backgroundColor, graphicsLayer) { + LiquidGlassProviderState( + backgroundColor = backgroundColor, + graphicsLayer = graphicsLayer + ) } } class LiquidGlassProviderState internal constructor( + val backgroundColor: Color?, internal val graphicsLayer: GraphicsLayer ) {