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
@@ -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,61 +0,0 @@
package com.kyant.liquidglass
import androidx.compose.runtime.Composable
import androidx.compose.runtime.Stable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
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.DrawScope
import androidx.compose.ui.graphics.drawscope.translate
import androidx.compose.ui.graphics.isSpecified
import androidx.compose.ui.graphics.layer.GraphicsLayer
import androidx.compose.ui.graphics.layer.drawLayer
import androidx.compose.ui.graphics.nativeCanvas
import androidx.compose.ui.graphics.rememberGraphicsLayer
import androidx.compose.ui.graphics.toArgb
import androidx.compose.ui.layout.LayoutCoordinates
import androidx.compose.ui.layout.positionOnScreen
@Composable
fun rememberLayerBackdrop(backgroundColor: Color?): LayerBackdrop {
val graphicsLayer = rememberGraphicsLayer()
return remember(graphicsLayer, backgroundColor) {
LayerBackdrop(
graphicsLayer = graphicsLayer,
backgroundColor = backgroundColor
)
}
}
interface Backdrop {
val element: Modifier.Element?
fun DrawScope.drawChild(coordinates: LayoutCoordinates)
}
@Stable
class LayerBackdrop(
val graphicsLayer: GraphicsLayer,
val backgroundColor: Color?
) : Backdrop {
override val element: Modifier.Element?
get() = LayerBackdropElement(this)
var backdropPosition: Offset by mutableStateOf(Offset.Zero)
override fun DrawScope.drawChild(coordinates: LayoutCoordinates) {
val position = coordinates.positionOnScreen() - backdropPosition
translate(-position.x, -position.y) {
if (backgroundColor != null && backgroundColor.isSpecified) {
drawContext.canvas.nativeCanvas.drawColor(backgroundColor.toArgb())
}
drawLayer(graphicsLayer)
}
}
}
@@ -1,18 +0,0 @@
package com.kyant.liquidglass
import androidx.compose.ui.graphics.drawscope.DrawScope
sealed interface BackdropDrawScope : DrawScope {
fun drawBackdrop()
}
internal class BackdropDrawScopeImpl internal constructor(
val drawScope: DrawScope,
val onDrawBackdrop: DrawScope.() -> Unit
) : BackdropDrawScope, DrawScope by drawScope {
override fun drawBackdrop() {
onDrawBackdrop()
}
}
@@ -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
}
}