Mikołaj Kąkol
12/06/2022, 9:36 AMLayoutNode
? My usecase is that drawing params (shader) has change but compose don't know about that. So I would like to force a redraw on next frame a composable function.Zach Klippenstein (he/him) [MOD]
12/06/2022, 11:55 AMMikołaj Kąkol
12/06/2022, 11:56 AMMikołaj Kąkol
12/06/2022, 11:56 AMval shader = remember {
RuntimeShader(SHADER_COLOR)
.apply { setFloatUniform("iDuration", DURATION) }
}
val brush = remember { ShaderBrush(shader) }
val infiniteAnimation = remember {
infiniteRepeatable<Float>(
tween(DURATION.toInt(), easing = LinearEasing),
RepeatMode.Restart
)
}
val timePassed by rememberInfiniteTransition().animateFloat(
initialValue = 0f,
targetValue = DURATION,
animationSpec = infiniteAnimation
)
shader.setFloatUniform("iTime", timePassed)
and here last line wont trigger proper invalidationMikołaj Kąkol
12/06/2022, 11:57 AMText(
modifier = Modifier.alpha(1 - (timePassed + 1) / 1000 / DURATION),
text = text,
fontFamily = shaderFont,
onTextLayout = {
shader.setFloatUniform(
"iResolution",
it.size.width.toFloat(),
it.size.height.toFloat()
)
}
)
to change alpha slightlyshikasd
12/06/2022, 2:09 PMtimePassed
state in a draw modifier lambda, which should invalidate draw directly
It doesn't have to do anything, just make sure you mention timePassed
in itMikołaj Kąkol
12/06/2022, 2:11 PMtext
in Text
buildAnnotatedString {
withStyle(SpanStyle(brush = brush)) {
append(msg)
}
}
Mikołaj Kąkol
12/06/2022, 2:12 PMcanvas.drawText
in drawModifier as you suggest to make it work, I just thought it can be somehow done usual wayshikasd
12/06/2022, 2:17 PM.drawBehind {
timePassed // assuming time passed is a mutable state, just referencing it here is enough to invalidate draw
}
shikasd
12/06/2022, 2:18 PMMikołaj Kąkol
12/06/2022, 2:19 PMMikołaj Kąkol
12/06/2022, 2:20 PM.drawBehind {
drawLine(brush, Offset(0f,0f), Offset(timePassed, timePassed))
},
it draws lines but shader is not updating properlyMikołaj Kąkol
12/06/2022, 2:20 PMMikołaj Kąkol
12/06/2022, 2:20 PMshikasd
12/06/2022, 2:21 PMMikołaj Kąkol
12/06/2022, 2:21 PMMikołaj Kąkol
12/06/2022, 2:40 PM.drawWithContent {
drawLine(brush, Offset(0f,0f), Offset(timePassed, timePassed))
drawContent()
},
and without this drawContent text is not drawn, so I'm guessing this is the same layershikasd
12/06/2022, 2:45 PMZach Klippenstein (he/him) [MOD]
12/06/2022, 10:41 PMHalil Ozercan
12/07/2022, 12:41 PMobject : ShaderBrush
. Then override the equals function to always return false or make a comparison to an older state.Mikołaj Kąkol
12/07/2022, 12:49 PMonTextLayout
is called in multiple time, while this alfa trick doesn’t invalidates layout resultHalil Ozercan
12/07/2022, 12:54 PMonTextLayout
being called multiple times is not a guaranteed sign of multiple measures. Even if you change brush completely, like creating a new one in each composition, Text internally should reuse the layout and apply the new brush in draw phase.
Except if you are applying brush as a SpanStyle in annotatedstring.Mikołaj Kąkol
12/07/2022, 12:55 PMMikołaj Kąkol
12/07/2022, 12:56 PMHalil Ozercan
12/07/2022, 12:58 PMText
composable. I'm not sure it will ever be even if it graduates to a stable API. Best way to use Brush for entirety of Text composable is to send it through like the following
style = LocalTextStyle.current.copy(brush = ...)
Mikołaj Kąkol
12/07/2022, 1:00 PMText(
text = msg,
fontFamily = shaderFont,
style = remember { TextStyle(brush = brush) },
onTextLayout = {
shader.setFloatUniform(
"iResolution",
it.size.width.toFloat(),
it.size.height.toFloat()
)
}
)
this doesn’t animate
Text(
text = msg,
fontFamily = shaderFont,
style = TextStyle(brush = brush),
onTextLayout = {
shader.setFloatUniform(
"iResolution",
it.size.width.toFloat(),
it.size.height.toFloat()
)
}
)
this does, but calls onTextLayout every timeMikołaj Kąkol
12/07/2022, 1:03 PMHalil Ozercan
12/07/2022, 3:14 PMsize
parameter from ShaderBrush.createShader
. Text stack will call that function with the proper size after layout happens. This means that you won't even lose a frame because the result of onTextLayout always lags behind a single frame.
I came up with this solution that should work hack-free* https://gist.github.com/halilozercan/a6fb2b9977b386f9ad6ca6ce7cf3c72fHalil Ozercan
12/07/2022, 3:21 PMShaderBrush()
function creates an anonymous instance which does not override equals
, meaning that each time this function is called, it's going to create a new unequal Brush instance. This is going to cause a re-draw and the internal shader will be reused albeit with the new time parameter applied on it. If you happen to remember
the Brush, those unequal new instances won't happen, redraw won't happen, latest state of the shader won't be applied.
Something similar is also true when Brush is applied on Text. As long as Brush stays the same, redraw will be skipped. So, we need a way to tell the framework that internals of brush has changed. This will trigger a re-draw where brush is used (createShader is called). Hence, the weird setTime
function in my implementation. It recreates the Brush with new time parameter but reuses the existing shader, passing it along to the next Brush.Halil Ozercan
12/07/2022, 3:27 PMMikołaj Kąkol
12/08/2022, 9:06 AM