Compare commits

...

1 Commits

Author SHA1 Message Date
Patrick Goldinger
1252b3b2c3 Switch to AndroidExternalSurface for bg rendering 2025-08-06 02:31:58 +02:00

View File

@@ -18,9 +18,13 @@ package org.florisboard.lib.snygg.ui
import android.graphics.Canvas
import android.graphics.PixelFormat
import android.graphics.Rect
import android.graphics.drawable.Animatable
import android.util.Log
import android.view.Surface
import android.view.SurfaceView
import androidx.compose.foundation.AndroidExternalSurface
import androidx.compose.foundation.AndroidExternalSurfaceZOrder
import androidx.compose.foundation.layout.Box
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
@@ -28,6 +32,7 @@ import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.runtime.withFrameNanos
import androidx.compose.ui.Modifier
import androidx.compose.ui.geometry.Size
import androidx.compose.ui.geometry.center
@@ -114,73 +119,58 @@ fun SnyggSurfaceView(
}
}
var showSurfaceView by remember { mutableStateOf(false) }
LifecycleResumeEffect(Unit) {
showSurfaceView = true
onPauseOrDispose {
showSurfaceView = false
var surfaceRef by remember { mutableStateOf<Surface?>(null) }
AndroidExternalSurface(
modifier = modifier,
isOpaque = true,
zOrder = AndroidExternalSurfaceZOrder.Behind,
) {
onSurface { surface, _, _ ->
surfaceRef = surface
surface.onDestroyed {
surfaceRef = null
}
}
}
if (showSurfaceView) {
var surfaceView by remember { mutableStateOf<SurfaceView?>(null) }
AndroidView(
modifier = modifier,
factory = { context ->
Log.d("SnyggSurfaceView", "creating new instance")
SurfaceView(context).apply {
setZOrderOnTop(false)
holder.setFormat(PixelFormat.TRANSPARENT)
}
},
update = { surfaceView = it },
)
surfaceView?.let { surfaceView ->
LaunchedEffect(surfaceView, backgroundColor, loadedImage, contentScale) {
val image = loadedImage
if (image is DrawableImage && image.drawable is Animatable) {
// Slow path, need animation
val fps = 30L // TODO: read frame delays from drawable
val animatedDrawable = image.drawable as Animatable
try {
animatedDrawable.start()
while (isActive) {
surfaceView.drawToSurface(backgroundColor, loadedImage, contentScale)
delay(1000L / fps)
}
} finally {
animatedDrawable.stop()
}
} else {
// Fast path, render once and be done with it
surfaceView.drawToSurface(backgroundColor, loadedImage, contentScale)
LaunchedEffect(surfaceRef, backgroundColor, loadedImage, contentScale) {
val surface = surfaceRef ?: return@LaunchedEffect
val image = loadedImage
if (image is DrawableImage && image.drawable is Animatable) {
// Slow path, need animation
val fps = 30L // TODO: read frame delays from drawable
val animatedDrawable = image.drawable as Animatable
try {
animatedDrawable.start()
while (isActive) {
surface.drawColorAndImage(backgroundColor, loadedImage, contentScale)
delay(1000L / fps)
}
} finally {
animatedDrawable.stop()
}
} else {
// Fast path, render once and be done with it
surface.drawColorAndImage(backgroundColor, loadedImage, contentScale)
}
}
}
}
private fun SurfaceView.drawToSurface(
private fun Surface.drawColorAndImage(
color: Color,
image: Image?,
contentScale: ContentScale,
) {
Log.d("SnyggSurfaceView", "drawToSurface(color=$color, image=$image)")
val surface = holder.surface
if (!surface.isValid) {
Log.w("SnyggSurfaceView", "drawToSurface: surface.isValid=false, may indicate state issue")
return
}
val canvas = surface.lockCanvas(null)
val canvas = lockCanvas(null)
try {
canvas.drawColor(color.toArgb())
when (image) {
is BitmapImage -> image.bitmap.drawToSurface(canvas, contentScale)
is DrawableImage -> image.drawToSurface(canvas, contentScale)
is DrawableImage -> image.toBitmap().drawToSurface(canvas, contentScale)
}
} finally {
surface.unlockCanvasAndPost(canvas)
unlockCanvasAndPost(canvas)
}
}
@@ -199,8 +189,3 @@ private fun Bitmap.drawToSurface(canvas: Canvas, contentScale: ContentScale) {
}.toAndroidRectF().toRect()
canvas.drawBitmap(bitmap, srcRect, dstRect, null)
}
private fun DrawableImage.drawToSurface(canvas: Canvas, contentScale: ContentScale) {
this.toBitmap().drawToSurface(canvas, contentScale)
}