Skip to content

Commit

Permalink
Use onPlaced for screenshot tests (#433)
Browse files Browse the repository at this point in the history
This fixes misplaced/drawing issues in screenshot tests (Roborazzi, etc)
due to our usage of `onGloballyPositioned`. `onGloballyPositioned` will
be called after an entire composition, therefore doesn't work for
screenshot tests which only 'run' the content until the first draw.

In this instance, we use now `onPlaced` to prime the various internal
state.
  • Loading branch information
chrisbanes authored Dec 4, 2024
1 parent 1132f7f commit 9c639ff
Show file tree
Hide file tree
Showing 3 changed files with 44 additions and 6 deletions.
4 changes: 2 additions & 2 deletions haze/api/api.txt
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ package dev.chrisbanes.haze {
method @androidx.compose.runtime.Stable public static androidx.compose.ui.Modifier hazeChild(androidx.compose.ui.Modifier, dev.chrisbanes.haze.HazeState state, optional dev.chrisbanes.haze.HazeStyle style, optional kotlin.jvm.functions.Function1<? super dev.chrisbanes.haze.HazeChildScope,kotlin.Unit>? block);
}

@dev.chrisbanes.haze.ExperimentalHazeApi public final class HazeChildNode extends androidx.compose.ui.Modifier.Node implements androidx.compose.ui.node.CompositionLocalConsumerModifierNode androidx.compose.ui.node.DrawModifierNode androidx.compose.ui.node.GlobalPositionAwareModifierNode dev.chrisbanes.haze.HazeChildScope androidx.compose.ui.node.ObserverModifierNode {
@dev.chrisbanes.haze.ExperimentalHazeApi public final class HazeChildNode extends androidx.compose.ui.Modifier.Node implements androidx.compose.ui.node.CompositionLocalConsumerModifierNode androidx.compose.ui.node.DrawModifierNode androidx.compose.ui.node.GlobalPositionAwareModifierNode dev.chrisbanes.haze.HazeChildScope androidx.compose.ui.node.LayoutAwareModifierNode androidx.compose.ui.node.ObserverModifierNode {
ctor public HazeChildNode(dev.chrisbanes.haze.HazeState state, optional dev.chrisbanes.haze.HazeStyle style, optional kotlin.jvm.functions.Function1<? super dev.chrisbanes.haze.HazeChildScope,kotlin.Unit>? block);
method public void draw(androidx.compose.ui.graphics.drawscope.ContentDrawScope);
method public float getAlpha();
Expand Down Expand Up @@ -132,7 +132,7 @@ package dev.chrisbanes.haze {
method @androidx.compose.runtime.Stable public static androidx.compose.ui.Modifier haze(androidx.compose.ui.Modifier, dev.chrisbanes.haze.HazeState state);
}

@dev.chrisbanes.haze.ExperimentalHazeApi public final class HazeNode extends androidx.compose.ui.Modifier.Node implements androidx.compose.ui.node.CompositionLocalConsumerModifierNode androidx.compose.ui.node.DrawModifierNode androidx.compose.ui.node.GlobalPositionAwareModifierNode {
@dev.chrisbanes.haze.ExperimentalHazeApi public final class HazeNode extends androidx.compose.ui.Modifier.Node implements androidx.compose.ui.node.CompositionLocalConsumerModifierNode androidx.compose.ui.node.DrawModifierNode androidx.compose.ui.node.GlobalPositionAwareModifierNode androidx.compose.ui.node.LayoutAwareModifierNode {
ctor public HazeNode(dev.chrisbanes.haze.HazeState state);
method public void draw(androidx.compose.ui.graphics.drawscope.ContentDrawScope);
method public dev.chrisbanes.haze.HazeState getState();
Expand Down
22 changes: 18 additions & 4 deletions haze/src/commonMain/kotlin/dev/chrisbanes/haze/HazeChildNode.kt
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.geometry.Size
import androidx.compose.ui.geometry.isSpecified
import androidx.compose.ui.geometry.isUnspecified
import androidx.compose.ui.geometry.takeOrElse
import androidx.compose.ui.geometry.toRect
import androidx.compose.ui.graphics.Brush
Expand All @@ -30,6 +31,7 @@ import androidx.compose.ui.layout.positionInWindow
import androidx.compose.ui.node.CompositionLocalConsumerModifierNode
import androidx.compose.ui.node.DrawModifierNode
import androidx.compose.ui.node.GlobalPositionAwareModifierNode
import androidx.compose.ui.node.LayoutAwareModifierNode
import androidx.compose.ui.node.ObserverModifierNode
import androidx.compose.ui.node.currentValueOf
import androidx.compose.ui.node.invalidateDraw
Expand Down Expand Up @@ -58,6 +60,7 @@ class HazeChildNode(
) : Modifier.Node(),
CompositionLocalConsumerModifierNode,
GlobalPositionAwareModifierNode,
LayoutAwareModifierNode,
ObserverModifierNode,
DrawModifierNode,
HazeChildScope {
Expand Down Expand Up @@ -240,12 +243,23 @@ class HazeChildNode(
}
}

override fun onGloballyPositioned(coordinates: LayoutCoordinates) {
log(TAG) {
"onGloballyPositioned: " +
"positionInWindow=${coordinates.positionInWindow()}"
override fun onPlaced(coordinates: LayoutCoordinates) {
// If the positionOnScreen has not been placed yet, we use the value from onPlaced,
// otherwise we ignore it. This primarily fixes screenshot tests which only run tests
// up to the first draw. We usually need onGloballyPositioned which tends to happen after
// the first pass
if (positionInContent.isUnspecified) {
log(TAG) { "onPlaced: positionInWindow=${coordinates.positionInWindow()}" }
onPositioned(coordinates)
}
}

override fun onGloballyPositioned(coordinates: LayoutCoordinates) {
log(TAG) { "onGloballyPositioned: positionInWindow=${coordinates.positionInWindow()}" }
onPositioned(coordinates)
}

private fun onPositioned(coordinates: LayoutCoordinates) {
positionInContent = coordinates.positionInWindow() +
calculateWindowOffset() - state.positionOnScreen.takeOrElse { Offset.Zero }

Expand Down
24 changes: 24 additions & 0 deletions haze/src/commonMain/kotlin/dev/chrisbanes/haze/HazeNode.kt
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@

package dev.chrisbanes.haze

import androidx.compose.runtime.snapshots.Snapshot
import androidx.compose.ui.Modifier
import androidx.compose.ui.geometry.isUnspecified
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.drawscope.ContentDrawScope
import androidx.compose.ui.graphics.drawscope.DrawScope
Expand All @@ -13,6 +15,7 @@ import androidx.compose.ui.layout.positionInWindow
import androidx.compose.ui.node.CompositionLocalConsumerModifierNode
import androidx.compose.ui.node.DrawModifierNode
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.Dp
Expand All @@ -33,15 +36,36 @@ class HazeNode(
) : Modifier.Node(),
CompositionLocalConsumerModifierNode,
GlobalPositionAwareModifierNode,
LayoutAwareModifierNode,
DrawModifierNode {

override fun onPlaced(coordinates: LayoutCoordinates) {
// If the positionOnScreen has not been placed yet, we use the value on onPlaced,
// otherwise we ignore it. This primarily fixes screenshot tests which only run tests
// up to the first draw. We need onGloballyPositioned which tends to happen after
// the first pass
Snapshot.withoutReadObservation {
if (state.positionOnScreen.isUnspecified) {
log(TAG) {
"onPlaced: " +
"positionInWindow=${coordinates.positionInWindow()}, " +
"content positionOnScreens=${state.positionOnScreen}"
}
onPositioned(coordinates)
}
}
}

override fun onGloballyPositioned(coordinates: LayoutCoordinates) {
log(TAG) {
"onGloballyPositioned: " +
"positionInWindow=${coordinates.positionInWindow()}, " +
"content positionOnScreens=${state.positionOnScreen}"
}
onPositioned(coordinates)
}

private fun onPositioned(coordinates: LayoutCoordinates) {
state.positionOnScreen = coordinates.positionInWindow() + calculateWindowOffset()
}

Expand Down

0 comments on commit 9c639ff

Please sign in to comment.