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

Fix NPE from missing content layer #263

Merged
merged 1 commit into from
Jul 17, 2024
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
4 changes: 2 additions & 2 deletions haze/api/api.txt
Original file line number Diff line number Diff line change
Expand Up @@ -35,9 +35,9 @@ package dev.chrisbanes.haze {

@androidx.compose.runtime.Stable public final class HazeState {
ctor public HazeState();
method public dev.chrisbanes.haze.HazeArea getContent();
method public dev.chrisbanes.haze.HazeArea getContentArea();
method public androidx.compose.ui.graphics.layer.GraphicsLayer? getContentLayer();
property public final dev.chrisbanes.haze.HazeArea content;
property public final dev.chrisbanes.haze.HazeArea contentArea;
property public final androidx.compose.ui.graphics.layer.GraphicsLayer? contentLayer;
}

Expand Down
8 changes: 7 additions & 1 deletion haze/src/commonMain/kotlin/dev/chrisbanes/haze/Haze.kt
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,14 @@ import dev.drewhamilton.poko.Poko
@Stable
class HazeState {

val content: HazeArea by lazy { HazeArea() }
val contentArea: HazeArea by lazy { HazeArea() }

/**
* The content [GraphicsLayer]. This is used by [hazeChild] draw nodes when drawing their
* blurred areas.
*
* This is explicitly NOT snapshot or state backed, as doing so would cause draw loops.
*/
var contentLayer: GraphicsLayer? = null
internal set
}
Expand Down
16 changes: 15 additions & 1 deletion haze/src/commonMain/kotlin/dev/chrisbanes/haze/HazeChild.kt
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import androidx.compose.ui.graphics.Shape
import androidx.compose.ui.graphics.drawscope.ContentDrawScope
import androidx.compose.ui.layout.LayoutCoordinates
import androidx.compose.ui.node.ModifierNodeElement
import androidx.compose.ui.node.invalidateDraw
import androidx.compose.ui.platform.InspectorInfo
import androidx.compose.ui.unit.toSize

Expand Down Expand Up @@ -61,6 +62,8 @@ private class HazeChildNode(
HazeArea(shape = shape, style = style)
}

private var drawWithContentLayerCount = 0

override fun update() {
// Propagate any shape changes to the HazeArea
area.shape = shape
Expand Down Expand Up @@ -94,7 +97,18 @@ private class HazeChildNode(
}

if (USE_GRAPHICS_LAYERS) {
drawEffectsWithGraphicsLayer(requireNotNull(state.contentLayer))
val contentLayer = state.contentLayer
if (contentLayer != null) {
drawWithContentLayerCount = 0
drawEffectsWithGraphicsLayer(contentLayer)
} else {
// The content layer has not have been drawn yet (draw order matters here). If it hasn't
// there's not much we do other than invalidate and wait for the next frame.
// We only want to force a few frames, otherwise we're causing a draw loop.
if (++drawWithContentLayerCount <= 2) {
invalidateDraw()
}
}
} else {
drawEffectsWithScrim()
}
Expand Down
4 changes: 2 additions & 2 deletions haze/src/commonMain/kotlin/dev/chrisbanes/haze/HazeEffect.kt
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@ internal abstract class HazeEffectNode :
currentEffects.remove(area) ?: HazeEffect(area = area)
}
.onEach { effect ->
val resolvedStyle = resolveStyle(state.content.style, effect.area.style)
val resolvedStyle = resolveStyle(state.contentArea.style, effect.area.style)

effect.size = effect.area.size
effect.positionOnScreen = effect.area.positionOnScreen
Expand Down Expand Up @@ -127,7 +127,7 @@ internal abstract class HazeEffectNode :
for (effect in effects) {
// Now we need to draw `contentNode` into each of an 'effect' graphic layers.
// The RenderEffect applied will provide the blurring effect.
val contentArea = state.content
val contentArea = state.contentArea
val boundsInContent = effect.calculateBounds(-contentArea.positionOnScreen)

graphicsContext.useGraphicsLayer { layer ->
Expand Down
33 changes: 15 additions & 18 deletions haze/src/commonMain/kotlin/dev/chrisbanes/haze/HazeNode.kt
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

package dev.chrisbanes.haze

import androidx.compose.runtime.snapshots.Snapshot
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.drawscope.ContentDrawScope
import androidx.compose.ui.graphics.layer.drawLayer
Expand All @@ -14,7 +15,6 @@ import androidx.compose.ui.node.GlobalPositionAwareModifierNode
import androidx.compose.ui.node.LayoutAwareModifierNode
import androidx.compose.ui.node.currentValueOf
import androidx.compose.ui.platform.LocalGraphicsContext
import androidx.compose.ui.unit.roundToIntSize
import androidx.compose.ui.unit.toSize

internal class HazeNode(
Expand All @@ -27,7 +27,7 @@ internal class HazeNode(
DrawModifierNode {

fun update() {
state.content.style = defaultStyle
state.contentArea.style = defaultStyle
}

override fun onAttach() {
Expand All @@ -37,45 +37,42 @@ internal class HazeNode(
override fun onGloballyPositioned(coordinates: LayoutCoordinates) = onPlaced(coordinates)

override fun onPlaced(coordinates: LayoutCoordinates) {
state.content.apply {
size = coordinates.size.toSize()
positionOnScreen = coordinates.positionInWindow() + calculateWindowOffset()
Snapshot.withMutableSnapshot {
state.contentArea.apply {
size = coordinates.size.toSize()
positionOnScreen = coordinates.positionInWindow() + calculateWindowOffset()
}
}
}

override fun ContentDrawScope.draw() {
val graphicsContext = currentValueOf(LocalGraphicsContext)

state.contentLayer?.let { graphicsContext.releaseGraphicsLayer(it) }
state.contentLayer = null

if (!USE_GRAPHICS_LAYERS) {
// If we're not using graphics layers, just call drawContent and return early
drawContent()
return
}

val contentLayer = graphicsContext.createGraphicsLayer()
val graphicsContext = currentValueOf(LocalGraphicsContext)

val contentLayer = state.contentLayer ?: graphicsContext.createGraphicsLayer()
state.contentLayer = contentLayer

// First we draw the composable content into a graphics layer
contentLayer.record(size = size.roundToIntSize()) {
contentLayer.record {
this@draw.drawContent()
}

// Now we draw `content` into the window canvas
drawLayer(contentLayer)

// Otherwise we need to stuff the content graphics layer into the HazeState
state.contentLayer = contentLayer
}

override fun onDetach() {
super.onDetach()

state.contentLayer?.let { old ->
currentValueOf(LocalGraphicsContext).releaseGraphicsLayer(old)
state.contentLayer = null
state.contentLayer?.let { layer ->
currentValueOf(LocalGraphicsContext).releaseGraphicsLayer(layer)
}
state.contentLayer = null
}
}

Expand Down
Loading