Add new backdrop module

This commit is contained in:
Kyant0 committed 2025-09-13 10:23:13 +08:00
1 parent 090374a06d
commit 3eaf794064
17 files changed
+336 -243

No files matched your search

+1
View File
@@ -11,6 +11,7 @@
<set>
<option value="$PROJECT_DIR$" />
<option value="$PROJECT_DIR$/app" />
<option value="$PROJECT_DIR$/backdrop" />
<option value="$PROJECT_DIR$/glassmusic" />
<option value="$PROJECT_DIR$/liquid-glass" />
</set>
+1
View File
@@ -0,0 +1 @@
/build
+47
View File
@@ -0,0 +1,47 @@
import org.jetbrains.kotlin.gradle.dsl.JvmTarget
plugins {
alias(libs.plugins.android.library)
alias(libs.plugins.kotlin.android)
alias(libs.plugins.kotlin.compose)
id("dev.shreyaspatil.compose-compiler-report-generator")
}
android {
namespace = "com.kyant.backdrop"
compileSdk = 36
buildToolsVersion = "36.0.0"
defaultConfig {
minSdk = 21
consumerProguardFiles("consumer-rules.pro")
}
buildTypes {
release {
isMinifyEnabled = false
proguardFiles(getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro")
}
}
compileOptions {
sourceCompatibility = JavaVersion.VERSION_21
targetCompatibility = JavaVersion.VERSION_21
}
kotlin {
compilerOptions {
jvmTarget = JvmTarget.JVM_21
freeCompilerArgs.addAll(
"-jvm-default=no-compatibility",
)
}
}
buildFeatures {
compose = true
}
}
dependencies {
implementation(libs.androidx.compose.foundation)
implementation(libs.androidx.compose.ui)
implementation(libs.androidx.compose.ui.graphics)
}
View File
Whitespace-only changes.
+21
View File
@@ -0,0 +1,21 @@
# Add project specific ProGuard rules here.
# You can control the set of applied configuration files using the
# proguardFiles setting in build.gradle.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html
# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#}
# Uncomment this to preserve the line number information for
# debugging stack traces.
#-keepattributes SourceFile,LineNumberTable
# If you keep the line number information, uncomment this to
# hide the original source file name.
#-renamesourcefileattribute SourceFile
+4
View File
@@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
</manifest>
@@ -0,0 +1,21 @@
package com.kyant.backdrop
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.drawscope.DrawScope
import androidx.compose.ui.layout.LayoutCoordinates
fun Modifier.backdrop(backdrop: Backdrop): Modifier {
val element = backdrop.element
return if (element != null) {
this then element
} else {
this
}
}
interface Backdrop {
val element: Modifier.Element?
fun DrawScope.drawChild(coordinates: LayoutCoordinates)
}
@@ -1,4 +1,4 @@
package com.kyant.liquidglass
package com.kyant.backdrop
import androidx.compose.ui.graphics.drawscope.DrawScope
@@ -7,7 +7,7 @@ sealed interface BackdropDrawScope : DrawScope {
fun drawBackdrop()
}
internal class BackdropDrawScopeImpl internal constructor(
class SimpleBackdropDrawScope(
val drawScope: DrawScope,
val onDrawBackdrop: DrawScope.() -> Unit
) : BackdropDrawScope, DrawScope by drawScope {
@@ -1,4 +1,4 @@
package com.kyant.liquidglass
package com.kyant.backdrop
import androidx.compose.runtime.Composable
import androidx.compose.runtime.Stable
@@ -9,6 +9,7 @@ import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.drawscope.ContentDrawScope
import androidx.compose.ui.graphics.drawscope.DrawScope
import androidx.compose.ui.graphics.drawscope.translate
import androidx.compose.ui.graphics.isSpecified
@@ -19,6 +20,10 @@ import androidx.compose.ui.graphics.rememberGraphicsLayer
import androidx.compose.ui.graphics.toArgb
import androidx.compose.ui.layout.LayoutCoordinates
import androidx.compose.ui.layout.positionOnScreen
import androidx.compose.ui.node.DrawModifierNode
import androidx.compose.ui.node.GlobalPositionAwareModifierNode
import androidx.compose.ui.node.ModifierNodeElement
import androidx.compose.ui.platform.InspectorInfo
@Composable
fun rememberLayerBackdrop(backgroundColor: Color?): LayerBackdrop {
@@ -31,13 +36,6 @@ fun rememberLayerBackdrop(backgroundColor: Color?): LayerBackdrop {
}
}
interface Backdrop {
val element: Modifier.Element?
fun DrawScope.drawChild(coordinates: LayoutCoordinates)
}
@Stable
class LayerBackdrop(
val graphicsLayer: GraphicsLayer,
@@ -59,3 +57,60 @@ class LayerBackdrop(
}
}
}
private class LayerBackdropElement(
val backdrop: LayerBackdrop
) : ModifierNodeElement<LayerBackdropNode>() {
override fun create(): LayerBackdropNode {
return LayerBackdropNode(backdrop = backdrop)
}
override fun update(node: LayerBackdropNode) {
node.update(backdrop = backdrop)
}
override fun InspectorInfo.inspectableProperties() {
name = "layerBackdrop"
properties["backdrop"] = backdrop
}
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (other !is LayerBackdropElement) return false
if (backdrop != other.backdrop) return false
return true
}
override fun hashCode(): Int {
return backdrop.hashCode()
}
}
private class LayerBackdropNode(
var backdrop: LayerBackdrop
) : DrawModifierNode, GlobalPositionAwareModifierNode, Modifier.Node() {
override val shouldAutoInvalidate: Boolean = false
override fun ContentDrawScope.draw() {
drawContent()
backdrop.graphicsLayer.record {
backdrop.backgroundColor?.let { drawRect(it) }
this@draw.drawContent()
}
}
override fun onGloballyPositioned(coordinates: LayoutCoordinates) {
if (coordinates.isAttached) {
backdrop.backdropPosition = coordinates.positionOnScreen()
}
}
fun update(backdrop: LayerBackdrop) {
this.backdrop = backdrop
}
}
@@ -31,7 +31,6 @@ import androidx.compose.ui.draw.drawBehind
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.SolidColor
import androidx.compose.ui.graphics.drawscope.scale
import androidx.compose.ui.graphics.graphicsLayer
import androidx.compose.ui.input.pointer.pointerInput
import androidx.compose.ui.layout.layout
@@ -125,9 +124,7 @@ fun <T> BottomTabs(
) {
val maxScale = 1f + 4.dp / height
val scale = lerp(1f, maxScale, dragFraction)
scale(1f / scale, 1f / scale, Offset.Zero) {
this@liquidGlass.drawBackdrop()
}
scale(1f / scale, 1f / scale, Offset.Zero)
}
.fillMaxSize()
.padding(padding),
@@ -233,13 +230,11 @@ fun <T> BottomTabs(
scale(
lerp(1f, 0.8f, scaleXFraction),
lerp(1f, 0.8f, scaleYFraction)
) {
val scaleX = lerp(1f, 0.9f, scaleXFraction)
val scaleY = lerp(1f, 0.9f, scaleYFraction)
scale(1f / scaleX, 1f / scaleY, Offset.Zero) {
this@liquidGlass.drawBackdrop()
}
}
)
val scaleX = lerp(1f, 0.9f, scaleXFraction)
val scaleY = lerp(1f, 0.9f, scaleYFraction)
scale(1f / scaleX, 1f / scaleY, Offset.Zero)
}
.draggable(
rememberDraggableState { delta ->
+1
View File
@@ -41,6 +41,7 @@ android {
}
dependencies {
api(project(":backdrop"))
implementation(libs.androidx.compose.foundation)
implementation(libs.androidx.compose.ui)
implementation(libs.androidx.compose.ui.graphics)
@@ -1,76 +0,0 @@
package com.kyant.liquidglass
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.drawscope.ContentDrawScope
import androidx.compose.ui.layout.LayoutCoordinates
import androidx.compose.ui.layout.positionOnScreen
import androidx.compose.ui.node.DrawModifierNode
import androidx.compose.ui.node.GlobalPositionAwareModifierNode
import androidx.compose.ui.node.ModifierNodeElement
import androidx.compose.ui.platform.InspectorInfo
fun Modifier.backdrop(backdrop: Backdrop): Modifier {
val element = backdrop.element
return if (element != null) {
this then element
} else {
this
}
}
internal class LayerBackdropElement(
val backdrop: LayerBackdrop
) : ModifierNodeElement<LayerBackdropNode>() {
override fun create(): LayerBackdropNode {
return LayerBackdropNode(backdrop = backdrop)
}
override fun update(node: LayerBackdropNode) {
node.update(backdrop = backdrop)
}
override fun InspectorInfo.inspectableProperties() {
name = "layerBackdrop"
properties["backdrop"] = backdrop
}
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (other !is LayerBackdropElement) return false
if (backdrop != other.backdrop) return false
return true
}
override fun hashCode(): Int {
return backdrop.hashCode()
}
}
internal class LayerBackdropNode(
var backdrop: LayerBackdrop
) : DrawModifierNode, GlobalPositionAwareModifierNode, Modifier.Node() {
override val shouldAutoInvalidate: Boolean = false
override fun ContentDrawScope.draw() {
drawContent()
backdrop.graphicsLayer.record {
backdrop.backgroundColor?.let { drawRect(it) }
this@draw.drawContent()
}
}
override fun onGloballyPositioned(coordinates: LayoutCoordinates) {
if (coordinates.isAttached) {
backdrop.backdropPosition = coordinates.positionOnScreen()
}
}
fun update(backdrop: LayerBackdrop) {
this.backdrop = backdrop
}
}
@@ -0,0 +1,163 @@
package com.kyant.liquidglass
import android.os.Build
import androidx.annotation.RequiresApi
import androidx.compose.runtime.Composable
import androidx.compose.runtime.Stable
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.CompositingStrategy
import androidx.compose.ui.graphics.drawscope.ContentDrawScope
import androidx.compose.ui.graphics.drawscope.DrawTransform
import androidx.compose.ui.graphics.drawscope.withTransform
import androidx.compose.ui.graphics.layer.GraphicsLayer
import androidx.compose.ui.graphics.rememberGraphicsLayer
import androidx.compose.ui.layout.LayoutCoordinates
import androidx.compose.ui.layout.positionOnScreen
import androidx.compose.ui.node.DrawModifierNode
import androidx.compose.ui.node.GlobalPositionAwareModifierNode
import androidx.compose.ui.node.ModifierNodeElement
import androidx.compose.ui.platform.InspectorInfo
import com.kyant.backdrop.LayerBackdrop
import com.kyant.backdrop.backdrop
@Deprecated(message = "[Backdrop API] Use backdrop instead of state, use onDrawBackdrop instead of transformBlock")
fun Modifier.liquidGlass(
state: LiquidGlassProviderState,
style: GlassStyle,
compositingStrategy: CompositingStrategy = CompositingStrategy.Offscreen,
transformBlock: (DrawTransform.() -> Unit)? = null
): Modifier =
this.liquidGlass(
backdrop = state.backdrop,
style = style,
compositingStrategy = compositingStrategy,
onDrawBackdrop = draw@{
if (transformBlock != null) {
withTransform(transformBlock) {
this@draw.drawBackdrop()
}
} else {
drawBackdrop()
}
}
)
@Deprecated(message = "[Backdrop API] Use backdrop instead of state, use onDrawBackdrop instead of transformBlock")
fun Modifier.liquidGlass(
state: LiquidGlassProviderState,
compositingStrategy: CompositingStrategy = CompositingStrategy.Offscreen,
transformBlock: (DrawTransform.() -> Unit)? = null,
style: () -> GlassStyle
): Modifier =
this.liquidGlass(
backdrop = state.backdrop,
compositingStrategy = compositingStrategy,
onDrawBackdrop = draw@{
if (transformBlock != null) {
withTransform(transformBlock) {
this@draw.drawBackdrop()
}
} else {
drawBackdrop()
}
},
style = style
)
@Deprecated(message = "[Backdrop API] Use backdrop modifier instead")
fun Modifier.liquidGlassProvider(state: LiquidGlassProviderState): Modifier =
this.backdrop(state.backdrop)
@RequiresApi(Build.VERSION_CODES.TIRAMISU)
private class LiquidGlassProviderElement(
val state: LiquidGlassProviderState
) : ModifierNodeElement<LiquidGlassProviderNode>() {
override fun create(): LiquidGlassProviderNode {
return LiquidGlassProviderNode(state = state)
}
override fun update(node: LiquidGlassProviderNode) {
node.update(state = state)
}
override fun InspectorInfo.inspectableProperties() {
name = "liquidGlassProvider"
properties["state"] = state
}
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (other !is LiquidGlassProviderElement) return false
if (state != other.state) return false
return true
}
override fun hashCode(): Int {
return state.hashCode()
}
}
@RequiresApi(Build.VERSION_CODES.TIRAMISU)
private class LiquidGlassProviderNode(
var state: LiquidGlassProviderState
) : DrawModifierNode, GlobalPositionAwareModifierNode, Modifier.Node() {
override val shouldAutoInvalidate: Boolean = false
override fun ContentDrawScope.draw() {
drawContent()
state.graphicsLayer.record {
state.backgroundColor?.let { drawRect(it) }
this@draw.drawContent()
}
}
override fun onGloballyPositioned(coordinates: LayoutCoordinates) {
if (coordinates.isAttached) {
state.position = coordinates.positionOnScreen()
}
}
fun update(
state: LiquidGlassProviderState
) {
this.state = state
}
}
@Deprecated("[Backdrop API] Use `rememberLayerBackdrop` instead")
@Composable
fun rememberLiquidGlassProviderState(
backgroundColor: Color?
): LiquidGlassProviderState {
val graphicsLayer = rememberGraphicsLayer()
return remember(backgroundColor, graphicsLayer) {
LiquidGlassProviderState(
backgroundColor = backgroundColor,
graphicsLayer = graphicsLayer
)
}
}
@Deprecated("[Backdrop API] Use `LayerBackdrop` instead")
@Stable
class LiquidGlassProviderState internal constructor(
val backgroundColor: Color?,
internal val graphicsLayer: GraphicsLayer
) {
internal val backdrop = LayerBackdrop(graphicsLayer, backgroundColor)
internal var position: Offset
get() = backdrop.backdropPosition
set(value) {
backdrop.backdropPosition = value
}
}
@@ -22,6 +22,9 @@ import androidx.compose.ui.node.invalidateDraw
import androidx.compose.ui.node.observeReads
import androidx.compose.ui.node.requireGraphicsContext
import androidx.compose.ui.platform.InspectorInfo
import com.kyant.backdrop.Backdrop
import com.kyant.backdrop.BackdropDrawScope
import com.kyant.backdrop.SimpleBackdropDrawScope
import com.kyant.liquidglass.dispersion.Dispersion
import com.kyant.liquidglass.highlight.GlassHighlightElement
import com.kyant.liquidglass.highlight.SimpleGlassHighlightElement
@@ -79,34 +82,6 @@ fun Modifier.liquidGlass(
.then(GlassBrushElement(style))
.then(GlassHighlightElement(style))
@Deprecated(message = "[Backdrop API] Use backdrop instead of state")
fun Modifier.liquidGlass(
state: LiquidGlassProviderState,
style: GlassStyle,
compositingStrategy: CompositingStrategy = CompositingStrategy.Offscreen,
onDrawBackdrop: BackdropDrawScope.() -> Unit = DefaultOnDrawBackdrop
): Modifier =
this.liquidGlass(
backdrop = state.backdrop,
style = style,
compositingStrategy = compositingStrategy,
onDrawBackdrop = onDrawBackdrop
)
@Deprecated(message = "[Backdrop API] Use backdrop instead of state")
fun Modifier.liquidGlass(
state: LiquidGlassProviderState,
compositingStrategy: CompositingStrategy = CompositingStrategy.Offscreen,
onDrawBackdrop: BackdropDrawScope.() -> Unit = DefaultOnDrawBackdrop,
style: () -> GlassStyle
): Modifier =
this.liquidGlass(
backdrop = state.backdrop,
compositingStrategy = compositingStrategy,
onDrawBackdrop = onDrawBackdrop,
style = style
)
@RequiresApi(Build.VERSION_CODES.TIRAMISU)
private class SimpleLiquidGlassElement(
val backdrop: Backdrop,
@@ -335,7 +310,7 @@ private class SimpleLiquidGlassNode(
onDrawWithContent {
if (graphicsLayer != null) {
graphicsLayer.record {
val backdropDrawScope = BackdropDrawScopeImpl(this, drawBackdropBlock)
val backdropDrawScope = SimpleBackdropDrawScope(this, drawBackdropBlock)
onDrawBackdrop(backdropDrawScope)
}
@@ -1,77 +0,0 @@
package com.kyant.liquidglass
import android.os.Build
import androidx.annotation.RequiresApi
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.drawscope.ContentDrawScope
import androidx.compose.ui.layout.LayoutCoordinates
import androidx.compose.ui.layout.positionOnScreen
import androidx.compose.ui.node.DrawModifierNode
import androidx.compose.ui.node.GlobalPositionAwareModifierNode
import androidx.compose.ui.node.ModifierNodeElement
import androidx.compose.ui.platform.InspectorInfo
@Deprecated(message = "[Backdrop API] Use backdrop modifier instead")
fun Modifier.liquidGlassProvider(state: LiquidGlassProviderState): Modifier =
this.backdrop(state.backdrop)
@RequiresApi(Build.VERSION_CODES.TIRAMISU)
private class LiquidGlassProviderElement(
val state: LiquidGlassProviderState
) : ModifierNodeElement<LiquidGlassProviderNode>() {
override fun create(): LiquidGlassProviderNode {
return LiquidGlassProviderNode(state = state)
}
override fun update(node: LiquidGlassProviderNode) {
node.update(state = state)
}
override fun InspectorInfo.inspectableProperties() {
name = "liquidGlassProvider"
properties["state"] = state
}
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (other !is LiquidGlassProviderElement) return false
if (state != other.state) return false
return true
}
override fun hashCode(): Int {
return state.hashCode()
}
}
@RequiresApi(Build.VERSION_CODES.TIRAMISU)
private class LiquidGlassProviderNode(
var state: LiquidGlassProviderState
) : DrawModifierNode, GlobalPositionAwareModifierNode, Modifier.Node() {
override val shouldAutoInvalidate: Boolean = false
override fun ContentDrawScope.draw() {
drawContent()
state.graphicsLayer.record {
state.backgroundColor?.let { drawRect(it) }
this@draw.drawContent()
}
}
override fun onGloballyPositioned(coordinates: LayoutCoordinates) {
if (coordinates.isAttached) {
state.position = coordinates.positionOnScreen()
}
}
fun update(
state: LiquidGlassProviderState
) {
this.state = state
}
}
@@ -1,39 +0,0 @@
package com.kyant.liquidglass
import androidx.compose.runtime.Composable
import androidx.compose.runtime.Stable
import androidx.compose.runtime.remember
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.layer.GraphicsLayer
import androidx.compose.ui.graphics.rememberGraphicsLayer
@Deprecated("[Backdrop API] Use `rememberLayerBackdrop` instead")
@Composable
fun rememberLiquidGlassProviderState(
backgroundColor: Color?
): LiquidGlassProviderState {
val graphicsLayer = rememberGraphicsLayer()
return remember(backgroundColor, graphicsLayer) {
LiquidGlassProviderState(
backgroundColor = backgroundColor,
graphicsLayer = graphicsLayer
)
}
}
@Deprecated("[Backdrop API] Use `LayerBackdrop` instead")
@Stable
class LiquidGlassProviderState internal constructor(
val backgroundColor: Color?,
internal val graphicsLayer: GraphicsLayer
) {
internal val backdrop = LayerBackdrop(graphicsLayer, backgroundColor)
internal var position: Offset
get() = backdrop.backdropPosition
set(value) {
backdrop.backdropPosition = value
}
}
+2 -1
View File
@@ -20,6 +20,7 @@ dependencyResolutionManagement {
}
rootProject.name = "Glass"
include(":app")
include(":backdrop")
include(":liquid-glass")
include(":app")
include(":glassmusic")