Huge API refactor of modifiers
This commit is contained in:
1 parent
3d397744b9
commit
19051076cd
12 files changed
+273
-278
No files matched your search
@@ -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
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -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()
|
||||
)
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
)
|
||||
}
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -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 <T : Comparable<T>> SliderChip(
|
||||
value: PreviewState.LiquidGlassParamValue<T>,
|
||||
label: String?,
|
||||
icon: @Composable () -> Unit,
|
||||
providerState: LiquidGlassProviderState,
|
||||
modifier: Modifier = Modifier,
|
||||
dragDelta: (dragAmount: Offset) -> Float = { it.x }
|
||||
) {
|
||||
@@ -47,12 +48,11 @@ fun <T : Comparable<T>> 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))
|
||||
|
||||
@@ -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<LiquidGlassModifierNode>() {
|
||||
|
||||
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()
|
||||
}
|
||||
|
||||
@@ -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<LiquidGlassProviderModifierNode>() {
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
@@ -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<LiquidGlassProviderState> =
|
||||
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
|
||||
) {
|
||||
|
||||
|
||||
Reference in new issue
Block a user