Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

More API tweaks #54

Merged
merged 9 commits into from
Dec 11, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 4 additions & 5 deletions docs/usage.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ Box {
modifier = Modifier
// We use hazeChild on anything where we want the background
// blurred.
.hazeChild(key = "app_bar", state = hazeState)
.hazeChild(state = hazeState)
.fillMaxWidth(),
)
}
Expand All @@ -40,7 +40,7 @@ Haze has some support for blurring of `Shape`s. Each platform has varying suppor

To use a shape, you can just pass it in to `hazeChild`:

``` kotlin hl_lines="11"
``` kotlin hl_lines="10"
val hazeState = remember { HazeState() }

Box {
Expand All @@ -49,7 +49,6 @@ Box {
LargeTopAppBar(
modifier = Modifier
.hazeChild(
key = "app_bar",
state = hazeState,
shape = RoundedCornerShape(16.dp),
),
Expand All @@ -74,7 +73,7 @@ Scaffold(
// Need to make app bar transparent to see the content behind
colors = TopAppBarDefaults.largeTopAppBarColors(Color.Transparent),
modifier = Modifier
.hazeChild(key = "app_bar", state = hazeState)
.hazeChild(state = hazeState)
.fillMaxWidth(),
) {
/* todo */
Expand All @@ -84,7 +83,7 @@ Scaffold(
NavigationBar(
containerColor = Color.Transparent,
modifier = Modifier
.hazeChild(key = "app_bar", state = hazeState)
.hazeChild(state = hazeState)
.fillMaxWidth(),
) {
/* todo */
Expand Down
16 changes: 7 additions & 9 deletions haze-jetpack-compose/api/api.txt
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,15 @@ package dev.chrisbanes.haze {
method public long getPositionInRoot();
method public androidx.compose.ui.graphics.Shape getShape();
method public long getSize();
method public boolean isEmpty();
property public final boolean isEmpty;
method public boolean isValid();
property public final boolean isValid;
property public final long positionInRoot;
property public final androidx.compose.ui.graphics.Shape shape;
property public final long size;
}

public final class HazeChildKt {
method public static androidx.compose.ui.Modifier hazeChild(androidx.compose.ui.Modifier, Object key, dev.chrisbanes.haze.HazeState state, optional androidx.compose.ui.graphics.Shape shape);
method public static androidx.compose.ui.Modifier hazeChild(androidx.compose.ui.Modifier, dev.chrisbanes.haze.HazeState state, optional androidx.compose.ui.graphics.Shape shape);
}

public final class HazeDefaults {
Expand All @@ -33,12 +33,10 @@ package dev.chrisbanes.haze {

@androidx.compose.runtime.Stable public final class HazeState {
ctor public HazeState();
method public void clearArea(Object key);
method public java.util.Set<dev.chrisbanes.haze.HazeArea> getAreas();
method public void updateAreaPosition(Object key, long positionInRoot);
method public void updateAreaShape(Object key, androidx.compose.ui.graphics.Shape shape);
method public void updateAreaSize(Object key, long size);
property public final java.util.Set<dev.chrisbanes.haze.HazeArea> areas;
method public java.util.List<dev.chrisbanes.haze.HazeArea> getAreas();
method public void registerArea(dev.chrisbanes.haze.HazeArea area);
method public void unregisterArea(dev.chrisbanes.haze.HazeArea area);
property public final java.util.List<dev.chrisbanes.haze.HazeArea> areas;
}

}
Expand Down
42 changes: 16 additions & 26 deletions haze-jetpack-compose/src/main/kotlin/dev/chrisbanes/haze/Haze.kt
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,14 @@ package dev.chrisbanes.haze

import androidx.compose.runtime.Stable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateMapOf
import androidx.compose.runtime.mutableStateListOf
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.geometry.Rect
import androidx.compose.ui.geometry.Size
import androidx.compose.ui.geometry.isSpecified
import androidx.compose.ui.geometry.isUnspecified
import androidx.compose.ui.geometry.toRect
import androidx.compose.ui.geometry.translate
Expand All @@ -32,44 +33,33 @@ class HazeState {
/**
* The areas which are blurred by any [Modifier.haze] instances which use this state.
*/
private val _areas = mutableStateMapOf<Any, HazeArea>()
private val _areas = mutableStateListOf<HazeArea>()

val areas: Set<HazeArea> get() = _areas.values.toSet()
val areas: List<HazeArea> get() = _areas.toList()

fun updateAreaPosition(key: Any, positionInRoot: Offset) {
_areas.getOrPut(key, ::HazeArea).apply {
this.positionInRoot = positionInRoot
}
}

fun updateAreaSize(key: Any, size: Size) {
_areas.getOrPut(key, ::HazeArea).apply {
this.size = size
}
fun registerArea(area: HazeArea) {
_areas.add(area)
}

fun updateAreaShape(key: Any, shape: Shape) {
_areas.getOrPut(key, ::HazeArea).apply {
this.shape = shape
}
}

fun clearArea(key: Any) {
_areas.remove(key)
fun unregisterArea(area: HazeArea) {
_areas.remove(area)
}
}

internal fun HazeState.addAreasToPath(
path: Path,
positionInRoot: Offset,
layoutDirection: LayoutDirection,
density: Density,
) {
if (positionInRoot.isUnspecified) return

areas.asSequence()
.filterNot { it.isEmpty }
.filter { it.isValid }
.forEach { area ->
path.addOutline(
outline = area.shape.createOutline(area.size, layoutDirection, density),
offset = area.positionInRoot,
offset = area.positionInRoot - positionInRoot,
)
}
}
Expand All @@ -91,13 +81,13 @@ class HazeArea {
var shape: Shape by mutableStateOf(RectangleShape)
internal set

val isEmpty: Boolean get() = size.isEmpty()
val isValid: Boolean
get() = size.isSpecified && positionInRoot.isSpecified && !size.isEmpty()
}

internal fun HazeArea.boundsInLocal(hazePositionInRoot: Offset): Rect? {
if (size.isUnspecified) return null
if (!isValid) return null
if (hazePositionInRoot.isUnspecified) return null
if (positionInRoot.isUnspecified) return null

return size.toRect().translate(positionInRoot - hazePositionInRoot)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ import androidx.compose.ui.layout.positionInRoot
import androidx.compose.ui.node.LayoutAwareModifierNode
import androidx.compose.ui.node.ModifierNodeElement
import androidx.compose.ui.platform.InspectorInfo
import androidx.compose.ui.unit.IntSize
import androidx.compose.ui.unit.toSize

/**
Expand All @@ -21,60 +20,70 @@ import androidx.compose.ui.unit.toSize
* [Modifier.haze] to blur any content behind the host composable.
*/
fun Modifier.hazeChild(
key: Any,
state: HazeState,
shape: Shape = RectangleShape,
): Modifier = this then HazeChildNodeElement(key, state, shape)
): Modifier = this then HazeChildNodeElement(state, shape)

private data class HazeChildNodeElement(
val key: Any,
val state: HazeState,
val shape: Shape,
) : ModifierNodeElement<HazeChildNode>() {
override fun create(): HazeChildNode = HazeChildNode(key, state, shape)
override fun create(): HazeChildNode = HazeChildNode(state, shape)

override fun update(node: HazeChildNode) {
node.key = key
node.state = state
node.shape = shape
node.onUpdate()
}

override fun InspectorInfo.inspectableProperties() {
name = "HazeChild"
properties["key"] = key
properties["shape"] = shape
}
}

private data class HazeChildNode(
var key: Any,
var state: HazeState,
var shape: Shape,
) : Modifier.Node(), LayoutAwareModifierNode {
override fun onPlaced(coordinates: LayoutCoordinates) {
// After we've been placed, update the state with our new bounds (in root coordinates)
state.updateAreaPosition(key, coordinates.positionInRoot())
}

private val area: HazeArea by lazy { HazeArea() }

private var attachedState: HazeState? = null

override fun onAttach() {
state.updateAreaShape(key, shape)
attachToHazeState()
}

fun onUpdate() {
state.updateAreaShape(key, shape)
}
// Propagate any shape changes to the HazeArea
area.shape = shape

override fun onRemeasured(size: IntSize) {
// After we've been remeasured, update the state with our new size
state.updateAreaSize(key, size.toSize())
if (state != attachedState) {
// The provided HazeState has changed, so we need to detach from the old one,
// and attach to the new one
detachFromHazeState()
attachToHazeState()
}
}

override fun onReset() {
state.clearArea(key)
override fun onPlaced(coordinates: LayoutCoordinates) {
// After we've been placed, update the state with our new bounds (in root coordinates)
area.positionInRoot = coordinates.positionInRoot()
area.size = coordinates.size.toSize()
}

override fun onDetach() {
state.clearArea(key)
detachFromHazeState()
}

private fun attachToHazeState() {
state.registerArea(area)
attachedState = state
}

private fun detachFromHazeState() {
attachedState?.unregisterArea(area)
attachedState = null
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import android.graphics.Shader.TileMode.REPEAT
import androidx.annotation.RequiresApi
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.geometry.Rect
import androidx.compose.ui.geometry.Size
import androidx.compose.ui.graphics.Canvas
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.Path
Expand All @@ -39,9 +40,12 @@ import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.unit.Density
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.IntSize
import androidx.compose.ui.unit.LayoutDirection
import androidx.compose.ui.unit.toSize
import dev.chrisbanes.haze.jetpackcompose.R
import kotlin.math.roundToInt
import kotlin.properties.Delegates.observable

@RequiresApi(31)
internal class HazeNode31(
Expand All @@ -64,29 +68,41 @@ internal class HazeNode31(

private var effectsDirty = true
private var effects: List<EffectHolder> = emptyList()
private var positionInRoot = Offset.Zero

private var positionInRoot by observable(Offset.Unspecified) { _, oldValue, newValue ->
if (oldValue != newValue) {
invalidateEffects()
}
}
private var size by observable(Size.Unspecified) { _, oldValue, newValue ->
if (oldValue != newValue) {
invalidateEffects()
}
}

private var noiseTexture: Bitmap? = null
private var noiseTextureFactor: Float = Float.MIN_VALUE

override fun onUpdate() {
effectsDirty = true
invalidateDraw()
invalidateEffects()
}

override fun onObservedReadsChanged() {
invalidateEffects()
}

private fun invalidateEffects() {
effectsDirty = true
invalidateDraw()
}

override fun onPlaced(coordinates: LayoutCoordinates) {
val newPositionInRoot = coordinates.positionInRoot()
if (positionInRoot != newPositionInRoot) {
positionInRoot = newPositionInRoot
positionInRoot = coordinates.positionInRoot()
size = coordinates.size.toSize()
}

effectsDirty = true
invalidateDraw()
}
override fun onRemeasured(size: IntSize) {
this.size = size.toSize()
}

override fun ContentDrawScope.draw() {
Expand Down
Loading