Skip to content

Commit 39f866a

Browse files
authored
Fix Skiko backed Haze not displaying multiple Rects (#17)
Caused the runtime shaders drawing the entire content on top of each, meaning that only the last rectangle is actually visible. Fixed by updating the runtime effect to only draw the blurred content, and transparent for anything else. ImageFilter.makeMerge then draws each filter on top of each other. Also updated the samples to include a bottom bar, validating that this works.
1 parent 6d0c07d commit 39f866a

File tree

3 files changed

+125
-30
lines changed
  • haze/src/skikoMain/kotlin/dev/chrisbanes/haze
  • sample
    • android/src/main/kotlin/dev/chrisbanes/haze/sample/android
    • shared/src/commonMain/kotlin/dev/chrisbanes/haze/sample

3 files changed

+125
-30
lines changed

haze/src/skikoMain/kotlin/dev/chrisbanes/haze/HazeNode.kt

Lines changed: 39 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -43,14 +43,18 @@ private const val SHADER_SKSL = """
4343
return length(max(q,0.0)) + min(max(q.x,q.y),0.0);
4444
}
4545
46-
vec4 main(vec2 coord) {
47-
vec2 shiftRect = (rectangle.zw - rectangle.xy) / 2.0;
48-
vec2 shiftCoord = coord - rectangle.xy;
49-
float distanceToClosestEdge = boxSDF(shiftCoord - shiftRect, shiftRect);
46+
bool rectContains(vec4 rectangle, vec2 coord) {
47+
vec2 shiftRect = (rectangle.zw - rectangle.xy) / 2.0;
48+
vec2 shiftCoord = coord - rectangle.xy;
49+
return boxSDF(shiftCoord - shiftRect, shiftRect) <= 0.0;
50+
}
5051
52+
vec4 main(vec2 coord) {
5153
vec4 c = content.eval(coord);
52-
if (distanceToClosestEdge > 0.0) {
53-
return c;
54+
55+
if (!rectContains(rectangle, coord)) {
56+
// If we're not drawing in the rectangle, return transparent
57+
return vec4(0.0, 0.0, 0.0, 0.0);
5458
}
5559
5660
vec4 b = blur.eval(coord);
@@ -128,30 +132,35 @@ internal actual class HazeNode actual constructor(
128132
}
129133

130134
private fun createBlurRenderEffect(): RenderEffect? {
131-
return areas.asSequence()
132-
.filterNot { it.isEmpty }
133-
.map { area ->
134-
val compositeShaderBuilder = RuntimeShaderBuilder(RUNTIME_SHADER).apply {
135-
uniform("rectangle", area.left, area.top, area.right, area.bottom)
136-
uniform("color", tint.red, tint.green, tint.blue, 1f)
137-
uniform("colorShift", tint.alpha)
138-
139-
child("noise", NOISE_SHADER)
140-
}
141-
142-
ImageFilter.makeRuntimeShader(
143-
runtimeShaderBuilder = compositeShaderBuilder,
144-
shaderNames = arrayOf("content", "blur"),
145-
inputs = arrayOf(null, blurFilter),
146-
)
135+
val rects = areas.filterNot { it.isEmpty }
136+
if (rects.isEmpty()) {
137+
return null
138+
}
139+
140+
val filters = rects.asSequence().map { area ->
141+
val compositeShaderBuilder = RuntimeShaderBuilder(RUNTIME_SHADER).apply {
142+
uniform("rectangle", area.left, area.top, area.right, area.bottom)
143+
uniform("color", tint.red, tint.green, tint.blue, 1f)
144+
uniform("colorShift", tint.alpha)
145+
146+
child("noise", NOISE_SHADER)
147147
}
148-
.toList()
149-
.flatten()?.asComposeRenderEffect()
150-
}
151-
}
152148

153-
private fun Collection<ImageFilter>.flatten(): ImageFilter? = when {
154-
isEmpty() -> null
155-
size == 1 -> first()
156-
else -> ImageFilter.makeMerge(toTypedArray(), null)
149+
ImageFilter.makeRuntimeShader(
150+
runtimeShaderBuilder = compositeShaderBuilder,
151+
shaderNames = arrayOf("content", "blur"),
152+
inputs = arrayOf(null, blurFilter),
153+
)
154+
}
155+
156+
return ImageFilter.makeMerge(
157+
buildList {
158+
// We need null as the first item, which tells Skia to draw the content without any filter.
159+
// The filters then draw on top, clipped to their respective areas.
160+
add(null)
161+
addAll(filters)
162+
}.toTypedArray(),
163+
null,
164+
).asComposeRenderEffect()
165+
}
157166
}

sample/android/src/main/kotlin/dev/chrisbanes/haze/sample/android/HazeSample.kt

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,13 +10,24 @@ import androidx.compose.foundation.layout.fillMaxSize
1010
import androidx.compose.foundation.layout.fillMaxWidth
1111
import androidx.compose.foundation.lazy.grid.GridCells
1212
import androidx.compose.foundation.lazy.grid.LazyVerticalGrid
13+
import androidx.compose.material.icons.Icons
14+
import androidx.compose.material.icons.filled.Call
15+
import androidx.compose.material.icons.filled.Lock
16+
import androidx.compose.material.icons.filled.Search
1317
import androidx.compose.material3.ExperimentalMaterial3Api
18+
import androidx.compose.material3.Icon
1419
import androidx.compose.material3.LargeTopAppBar
1520
import androidx.compose.material3.MaterialTheme
21+
import androidx.compose.material3.NavigationBar
22+
import androidx.compose.material3.NavigationBarItem
1623
import androidx.compose.material3.Scaffold
1724
import androidx.compose.material3.Text
1825
import androidx.compose.material3.TopAppBarDefaults
1926
import androidx.compose.runtime.Composable
27+
import androidx.compose.runtime.getValue
28+
import androidx.compose.runtime.mutableIntStateOf
29+
import androidx.compose.runtime.remember
30+
import androidx.compose.runtime.setValue
2031
import androidx.compose.ui.Modifier
2132
import androidx.compose.ui.geometry.Offset
2233
import androidx.compose.ui.geometry.Rect
@@ -37,6 +48,31 @@ fun HazeSample(appTitle: String) {
3748
modifier = Modifier.fillMaxWidth(),
3849
)
3950
},
51+
bottomBar = {
52+
var selectedIndex by remember { mutableIntStateOf(0) }
53+
54+
NavigationBar(
55+
containerColor = Color.Transparent,
56+
modifier = Modifier.fillMaxWidth(),
57+
) {
58+
for (i in (0 until 3)) {
59+
NavigationBarItem(
60+
selected = selectedIndex == i,
61+
onClick = { selectedIndex = i },
62+
icon = {
63+
Icon(
64+
imageVector = when (i) {
65+
0 -> Icons.Default.Call
66+
1 -> Icons.Default.Lock
67+
else -> Icons.Default.Search
68+
},
69+
contentDescription = null,
70+
)
71+
},
72+
)
73+
}
74+
}
75+
},
4076
modifier = Modifier.fillMaxSize(),
4177
) { contentPadding ->
4278
BoxWithConstraints {
@@ -46,6 +82,12 @@ fun HazeSample(appTitle: String) {
4682
Offset(maxWidth.toPx(), contentPadding.calculateTopPadding().toPx()),
4783
)
4884
}
85+
val bottomBarsBounds = with(LocalDensity.current) {
86+
Rect(
87+
Offset(0f, maxHeight.toPx() - contentPadding.calculateBottomPadding().toPx()),
88+
Offset(maxWidth.toPx(), maxHeight.toPx()),
89+
)
90+
}
4991

5092
LazyVerticalGrid(
5193
columns = GridCells.Adaptive(128.dp),
@@ -56,6 +98,7 @@ fun HazeSample(appTitle: String) {
5698
.fillMaxSize()
5799
.haze(
58100
topBarBounds,
101+
bottomBarsBounds,
59102
backgroundColor = MaterialTheme.colorScheme.surface,
60103
),
61104
) {

sample/shared/src/commonMain/kotlin/dev/chrisbanes/haze/sample/HazeSample.kt

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,13 +10,24 @@ import androidx.compose.foundation.layout.fillMaxSize
1010
import androidx.compose.foundation.layout.fillMaxWidth
1111
import androidx.compose.foundation.lazy.grid.GridCells
1212
import androidx.compose.foundation.lazy.grid.LazyVerticalGrid
13+
import androidx.compose.material.icons.Icons
14+
import androidx.compose.material.icons.filled.Call
15+
import androidx.compose.material.icons.filled.Lock
16+
import androidx.compose.material.icons.filled.Search
1317
import androidx.compose.material3.ExperimentalMaterial3Api
18+
import androidx.compose.material3.Icon
1419
import androidx.compose.material3.LargeTopAppBar
1520
import androidx.compose.material3.MaterialTheme
21+
import androidx.compose.material3.NavigationBar
22+
import androidx.compose.material3.NavigationBarItem
1623
import androidx.compose.material3.Scaffold
1724
import androidx.compose.material3.Text
1825
import androidx.compose.material3.TopAppBarDefaults
1926
import androidx.compose.runtime.Composable
27+
import androidx.compose.runtime.getValue
28+
import androidx.compose.runtime.mutableIntStateOf
29+
import androidx.compose.runtime.remember
30+
import androidx.compose.runtime.setValue
2031
import androidx.compose.ui.Modifier
2132
import androidx.compose.ui.geometry.Offset
2233
import androidx.compose.ui.geometry.Rect
@@ -37,6 +48,31 @@ fun HazeSample(appTitle: String) {
3748
modifier = Modifier.fillMaxWidth(),
3849
)
3950
},
51+
bottomBar = {
52+
var selectedIndex by remember { mutableIntStateOf(0) }
53+
54+
NavigationBar(
55+
containerColor = Color.Transparent,
56+
modifier = Modifier.fillMaxWidth(),
57+
) {
58+
for (i in (0 until 3)) {
59+
NavigationBarItem(
60+
selected = selectedIndex == i,
61+
onClick = { selectedIndex = i },
62+
icon = {
63+
Icon(
64+
imageVector = when (i) {
65+
0 -> Icons.Default.Call
66+
1 -> Icons.Default.Lock
67+
else -> Icons.Default.Search
68+
},
69+
contentDescription = null,
70+
)
71+
},
72+
)
73+
}
74+
}
75+
},
4076
modifier = Modifier.fillMaxSize(),
4177
) { contentPadding ->
4278
BoxWithConstraints {
@@ -46,6 +82,12 @@ fun HazeSample(appTitle: String) {
4682
Offset(maxWidth.toPx(), contentPadding.calculateTopPadding().toPx()),
4783
)
4884
}
85+
val bottomBarsBounds = with(LocalDensity.current) {
86+
Rect(
87+
Offset(0f, maxHeight.toPx() - contentPadding.calculateBottomPadding().toPx()),
88+
Offset(maxWidth.toPx(), maxHeight.toPx()),
89+
)
90+
}
4991

5092
LazyVerticalGrid(
5193
columns = GridCells.Adaptive(128.dp),
@@ -56,6 +98,7 @@ fun HazeSample(appTitle: String) {
5698
.fillMaxSize()
5799
.haze(
58100
topBarBounds,
101+
bottomBarsBounds,
59102
backgroundColor = MaterialTheme.colorScheme.surface,
60103
),
61104
) {

0 commit comments

Comments
 (0)