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

Calculate areas in screen coordinates #88

Merged
merged 1 commit into from
Jan 9, 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
6 changes: 3 additions & 3 deletions haze-jetpack-compose/api/api.txt
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,14 @@
package dev.chrisbanes.haze {

@androidx.compose.runtime.Stable public final class HazeArea {
ctor public HazeArea(optional long size, optional long positionInRoot, optional androidx.compose.ui.graphics.Shape shape, optional long tint);
method public long getPositionInRoot();
ctor public HazeArea(optional long size, optional long positionOnScreen, optional androidx.compose.ui.graphics.Shape shape, optional long tint);
method public long getPositionOnScreen();
method public androidx.compose.ui.graphics.Shape getShape();
method public long getSize();
method public long getTint();
method public boolean isValid();
property public final boolean isValid;
property public final long positionInRoot;
property public final long positionOnScreen;
property public final androidx.compose.ui.graphics.Shape shape;
property public final long size;
property public final long tint;
Expand Down
30 changes: 6 additions & 24 deletions haze-jetpack-compose/src/main/kotlin/dev/chrisbanes/haze/Haze.kt
Original file line number Diff line number Diff line change
Expand Up @@ -46,24 +46,6 @@ class HazeState {
}
}

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

areas.asSequence()
.filter { it.isValid }
.forEach { area ->
path.addOutline(
outline = area.shape.createOutline(area.size, layoutDirection, density),
offset = area.positionInRoot - positionInRoot,
)
}
}

internal fun Path.addOutline(outline: Outline, offset: Offset) = when (outline) {
is Outline.Rectangle -> addRect(outline.rect.translate(offset))
is Outline.Rounded -> addRoundRect(outline.roundRect.translate(offset))
Expand All @@ -73,14 +55,14 @@ internal fun Path.addOutline(outline: Outline, offset: Offset) = when (outline)
@Stable
class HazeArea(
size: Size = Size.Unspecified,
positionInRoot: Offset = Offset.Unspecified,
positionOnScreen: Offset = Offset.Unspecified,
shape: Shape = RectangleShape,
tint: Color = Color.Unspecified,
) {
var size: Size by mutableStateOf(size)
internal set

var positionInRoot: Offset by mutableStateOf(positionInRoot)
var positionOnScreen: Offset by mutableStateOf(positionOnScreen)
internal set

var shape: Shape by mutableStateOf(shape)
Expand All @@ -90,14 +72,14 @@ class HazeArea(
internal set

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

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

return size.toRect().translate(positionInRoot - hazePositionInRoot)
return size.toRect().translate(positionOnScreen - position)
}

internal fun HazeArea.updatePath(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@ import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.RectangleShape
import androidx.compose.ui.graphics.Shape
import androidx.compose.ui.layout.LayoutCoordinates
import androidx.compose.ui.layout.positionInRoot
import androidx.compose.ui.layout.positionInWindow
import androidx.compose.ui.node.CompositionLocalConsumerModifierNode
import androidx.compose.ui.node.LayoutAwareModifierNode
import androidx.compose.ui.node.ModifierNodeElement
import androidx.compose.ui.platform.InspectorInfo
Expand Down Expand Up @@ -57,7 +58,9 @@ private data class HazeChildNode(
var state: HazeState,
var shape: Shape,
var tint: Color,
) : Modifier.Node(), LayoutAwareModifierNode {
) : Modifier.Node(),
LayoutAwareModifierNode,
CompositionLocalConsumerModifierNode {

private val area: HazeArea by lazy {
HazeArea(shape = shape, tint = tint)
Expand All @@ -83,8 +86,8 @@ private data class HazeChildNode(
}

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

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import androidx.compose.ui.graphics.Path
import androidx.compose.ui.graphics.drawscope.ContentDrawScope
import androidx.compose.ui.graphics.isSpecified
import androidx.compose.ui.layout.LayoutCoordinates
import androidx.compose.ui.layout.positionInRoot
import androidx.compose.ui.layout.positionInWindow
import androidx.compose.ui.node.CompositionLocalConsumerModifierNode
import androidx.compose.ui.node.DrawModifierNode
import androidx.compose.ui.node.LayoutAwareModifierNode
Expand Down Expand Up @@ -44,7 +44,7 @@ internal class HazeNodeBase(
private var pathsDirty = true
private var paths: List<PathHolder> = emptyList()

private var positionInRoot by observable(Offset.Unspecified) { _, oldValue, newValue ->
private var position by observable(Offset.Unspecified) { _, oldValue, newValue ->
if (oldValue != newValue) {
invalidatePaths()
}
Expand All @@ -69,7 +69,7 @@ internal class HazeNodeBase(
}

override fun onPlaced(coordinates: LayoutCoordinates) {
positionInRoot = coordinates.positionInRoot()
position = coordinates.positionInWindow() + calculateWindowOffset()
size = coordinates.size.toSize()
}

Expand Down Expand Up @@ -101,7 +101,7 @@ internal class HazeNodeBase(
): List<PathHolder> = state.areas.asSequence()
.filter { it.isValid }
.mapNotNull { area ->
val bounds = area.boundsInLocal(positionInRoot) ?: return@mapNotNull null
val bounds = area.boundsInLocal(position) ?: return@mapNotNull null

// TODO: Should try and re-use this
val path = Path()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ import androidx.compose.ui.graphics.isSpecified
import androidx.compose.ui.graphics.nativeCanvas
import androidx.compose.ui.graphics.toArgb
import androidx.compose.ui.layout.LayoutCoordinates
import androidx.compose.ui.layout.positionInRoot
import androidx.compose.ui.layout.positionInWindow
import androidx.compose.ui.node.CompositionLocalConsumerModifierNode
import androidx.compose.ui.node.DrawModifierNode
import androidx.compose.ui.node.LayoutAwareModifierNode
Expand Down Expand Up @@ -70,7 +70,7 @@ internal class HazeNodeRenderEffect(
private var effectsDirty = true
private var effects: List<EffectHolder> = emptyList()

private var positionInRoot by observable(Offset.Unspecified) { _, oldValue, newValue ->
private var position by observable(Offset.Unspecified) { _, oldValue, newValue ->
if (oldValue != newValue) {
invalidateEffects()
}
Expand Down Expand Up @@ -98,7 +98,7 @@ internal class HazeNodeRenderEffect(
}

override fun onPlaced(coordinates: LayoutCoordinates) {
positionInRoot = coordinates.positionInRoot()
position = coordinates.positionInWindow() + calculateWindowOffset()
size = coordinates.size.toSize()
}

Expand Down Expand Up @@ -197,7 +197,7 @@ internal class HazeNodeRenderEffect(

// We create a RenderNode for each of the areas we need to apply our effect to
return state.areas.asSequence().mapNotNull { area ->
val bounds = area.boundsInLocal(positionInRoot) ?: return@mapNotNull null
val bounds = area.boundsInLocal(position) ?: return@mapNotNull null

// We expand the area where our effect is applied to. This is necessary so that the blur
// effect is applied evenly to all edges. If we don't do this, the blur effect is much less
Expand Down Expand Up @@ -275,5 +275,6 @@ private fun RenderEffect.applyTint(tint: Color): RenderEffect = when {
this,
)
}

else -> this
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,13 @@
package dev.chrisbanes.haze

import android.os.Build
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.node.CompositionLocalConsumerModifierNode
import androidx.compose.ui.node.currentValueOf
import androidx.compose.ui.platform.LocalView
import androidx.compose.ui.unit.Dp
import kotlin.concurrent.getOrSet

internal fun createHazeNode(
state: HazeState,
Expand Down Expand Up @@ -36,3 +41,12 @@ internal fun createHazeNode(
)
}
}

internal fun CompositionLocalConsumerModifierNode.calculateWindowOffset(): Offset {
val view = currentValueOf(LocalView)
val loc = tmpArray.getOrSet { IntArray(2) }
view.getLocationOnScreen(loc)
return Offset(loc[0].toFloat(), loc[1].toFloat())
}

private val tmpArray = ThreadLocal<IntArray>()
6 changes: 3 additions & 3 deletions haze/api/api.txt
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,14 @@
package dev.chrisbanes.haze {

@androidx.compose.runtime.Stable public final class HazeArea {
ctor public HazeArea(optional long size, optional long positionInRoot, optional androidx.compose.ui.graphics.Shape shape, optional long tint);
method public long getPositionInRoot();
ctor public HazeArea(optional long size, optional long positionOnScreen, optional androidx.compose.ui.graphics.Shape shape, optional long tint);
method public long getPositionOnScreen();
method public androidx.compose.ui.graphics.Shape getShape();
method public long getSize();
method public long getTint();
method public boolean isValid();
property public final boolean isValid;
property public final long positionInRoot;
property public final long positionOnScreen;
property public final androidx.compose.ui.graphics.Shape shape;
property public final long size;
property public final long tint;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import androidx.compose.ui.graphics.Path
import androidx.compose.ui.graphics.drawscope.ContentDrawScope
import androidx.compose.ui.graphics.isSpecified
import androidx.compose.ui.layout.LayoutCoordinates
import androidx.compose.ui.layout.positionInRoot
import androidx.compose.ui.layout.positionInWindow
import androidx.compose.ui.node.CompositionLocalConsumerModifierNode
import androidx.compose.ui.node.DrawModifierNode
import androidx.compose.ui.node.LayoutAwareModifierNode
Expand Down Expand Up @@ -44,7 +44,7 @@ internal class HazeNodeBase(
private var pathsDirty = true
private var paths: List<PathHolder> = emptyList()

private var positionInRoot by observable(Offset.Unspecified) { _, oldValue, newValue ->
private var position by observable(Offset.Unspecified) { _, oldValue, newValue ->
if (oldValue != newValue) {
invalidatePaths()
}
Expand All @@ -69,7 +69,7 @@ internal class HazeNodeBase(
}

override fun onPlaced(coordinates: LayoutCoordinates) {
positionInRoot = coordinates.positionInRoot()
position = coordinates.positionInWindow() + calculateWindowOffset()
size = coordinates.size.toSize()
}

Expand Down Expand Up @@ -101,7 +101,7 @@ internal class HazeNodeBase(
): List<PathHolder> = state.areas.asSequence()
.filter { it.isValid }
.mapNotNull { area ->
val bounds = area.boundsInLocal(positionInRoot) ?: return@mapNotNull null
val bounds = area.boundsInLocal(position) ?: return@mapNotNull null

// TODO: Should try and re-use this
val path = Path()
Expand Down
14 changes: 14 additions & 0 deletions haze/src/androidMain/kotlin/dev/chrisbanes/haze/Platform.kt
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,13 @@

package dev.chrisbanes.haze

import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.node.CompositionLocalConsumerModifierNode
import androidx.compose.ui.node.currentValueOf
import androidx.compose.ui.platform.LocalView
import androidx.compose.ui.unit.Dp
import kotlin.concurrent.getOrSet

internal actual fun createHazeNode(
state: HazeState,
Expand All @@ -13,3 +18,12 @@ internal actual fun createHazeNode(
blurRadius: Dp,
noiseFactor: Float,
): HazeNode = HazeNodeBase(state, backgroundColor, tint, blurRadius, noiseFactor)

internal actual fun CompositionLocalConsumerModifierNode.calculateWindowOffset(): Offset {
val view = currentValueOf(LocalView)
val loc = tmpArray.getOrSet { IntArray(2) }
view.getLocationOnScreen(loc)
return Offset(loc[0].toFloat(), loc[1].toFloat())
}

private val tmpArray = ThreadLocal<IntArray>()
30 changes: 6 additions & 24 deletions haze/src/commonMain/kotlin/dev/chrisbanes/haze/Haze.kt
Original file line number Diff line number Diff line change
Expand Up @@ -46,24 +46,6 @@ class HazeState {
}
}

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

areas.asSequence()
.filter { it.isValid }
.forEach { area ->
path.addOutline(
outline = area.shape.createOutline(area.size, layoutDirection, density),
offset = area.positionInRoot - positionInRoot,
)
}
}

internal fun Path.addOutline(outline: Outline, offset: Offset) = when (outline) {
is Outline.Rectangle -> addRect(outline.rect.translate(offset))
is Outline.Rounded -> addRoundRect(outline.roundRect.translate(offset))
Expand All @@ -73,14 +55,14 @@ internal fun Path.addOutline(outline: Outline, offset: Offset) = when (outline)
@Stable
class HazeArea(
size: Size = Size.Unspecified,
positionInRoot: Offset = Offset.Unspecified,
positionOnScreen: Offset = Offset.Unspecified,
shape: Shape = RectangleShape,
tint: Color = Color.Unspecified,
) {
var size: Size by mutableStateOf(size)
internal set

var positionInRoot: Offset by mutableStateOf(positionInRoot)
var positionOnScreen: Offset by mutableStateOf(positionOnScreen)
internal set

var shape: Shape by mutableStateOf(shape)
Expand All @@ -90,14 +72,14 @@ class HazeArea(
internal set

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

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

return size.toRect().translate(positionInRoot - hazePositionInRoot)
return size.toRect().translate(positionOnScreen - position)
}

internal fun HazeArea.updatePath(
Expand Down
11 changes: 7 additions & 4 deletions haze/src/commonMain/kotlin/dev/chrisbanes/haze/HazeChild.kt
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@ import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.RectangleShape
import androidx.compose.ui.graphics.Shape
import androidx.compose.ui.layout.LayoutCoordinates
import androidx.compose.ui.layout.positionInRoot
import androidx.compose.ui.layout.positionInWindow
import androidx.compose.ui.node.CompositionLocalConsumerModifierNode
import androidx.compose.ui.node.LayoutAwareModifierNode
import androidx.compose.ui.node.ModifierNodeElement
import androidx.compose.ui.platform.InspectorInfo
Expand Down Expand Up @@ -57,7 +58,9 @@ private data class HazeChildNode(
var state: HazeState,
var shape: Shape,
var tint: Color,
) : Modifier.Node(), LayoutAwareModifierNode {
) : Modifier.Node(),
LayoutAwareModifierNode,
CompositionLocalConsumerModifierNode {

private val area: HazeArea by lazy {
HazeArea(shape = shape, tint = tint)
Expand All @@ -83,8 +86,8 @@ private data class HazeChildNode(
}

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

Expand Down
Loading