Coverage Summary for Class: PathMorphingKt (com.vsevolodganin.clicktrack.utils.compose)

Class Method, % Branch, % Line, % Instruction, %
PathMorphingKt 0% (0/15) 2% (1/50) 0% (0/137) 0% (0/1135)
PathMorphingKt$animatePathAsState$2$1 0% (0/1) 0% (0/2) 0% (0/5) 0% (0/50)
Total 0% (0/16) 1.9% (1/52) 0% (0/142) 0% (0/1185)


 package com.vsevolodganin.clicktrack.utils.compose
 
 import androidx.compose.animation.core.Animatable
 import androidx.compose.foundation.layout.Box
 import androidx.compose.foundation.layout.fillMaxSize
 import androidx.compose.material.Icon
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.LaunchedEffect
 import androidx.compose.runtime.State
 import androidx.compose.runtime.derivedStateOf
 import androidx.compose.runtime.getValue
 import androidx.compose.runtime.mutableStateOf
 import androidx.compose.runtime.remember
 import androidx.compose.runtime.setValue
 import androidx.compose.ui.Alignment
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.graphics.Color
 import androidx.compose.ui.graphics.SolidColor
 import androidx.compose.ui.graphics.vector.ImageVector
 import androidx.compose.ui.graphics.vector.PathNode
 import androidx.compose.ui.graphics.vector.addPathNodes
 import androidx.compose.ui.unit.dp
 import androidx.compose.ui.util.lerp
 import com.vsevolodganin.clicktrack.ui.piece.FloatingActionButton
 
 @Composable
 fun animatePathAsState(path: String): State<List<PathNode>> {
     return animatePathAsState(remember(path) { addPathNodes(path) })
 }
 
 @Composable
 fun animatePathAsState(path: List<PathNode>): State<List<PathNode>> {
     var from by remember { mutableStateOf(path) }
     var to by remember { mutableStateOf(path) }
     val fraction = remember { Animatable(0f) }
 
     LaunchedEffect(path) {
         if (to != path) {
             from = to
             to = path
             fraction.snapTo(0f)
             fraction.animateTo(1f)
         }
     }
 
     return remember {
         derivedStateOf {
             if (canMorph(from, to)) {
                 lerp(from, to, fraction.value)
             } else {
                 to
             }
         }
     }
 }
 
 // Paths can morph if same size and same node types at same positions.
 fun canMorph(from: List<PathNode>, to: List<PathNode>): Boolean {
     if (from.size != to.size) {
         return false
     }
 
     for (i in from.indices) {
         if (from[i]::class != to[i]::class) {
             return false
         }
     }
 
     return true
 }
 
 // Assume paths can morph (see [canMorph]). If not, will throw.
 private fun lerp(fromPath: List<PathNode>, toPath: List<PathNode>, fraction: Float): List<PathNode> {
     return fromPath.mapIndexed { i, from ->
         val to = toPath[i]
         lerp(from, to, fraction)
     }
 }
 
 private fun lerp(from: PathNode, to: PathNode, fraction: Float): PathNode {
     return when (from) {
         PathNode.Close -> {
             to as PathNode.Close
             from
         }
 
         is PathNode.RelativeMoveTo -> {
             to as PathNode.RelativeMoveTo
             PathNode.RelativeMoveTo(
                 lerp(from.dx, to.dx, fraction),
                 lerp(from.dy, to.dy, fraction),
             )
         }
 
         is PathNode.MoveTo -> {
             to as PathNode.MoveTo
             PathNode.MoveTo(
                 lerp(from.x, to.x, fraction),
                 lerp(from.y, to.y, fraction),
             )
         }
 
         is PathNode.RelativeLineTo -> {
             to as PathNode.RelativeLineTo
             PathNode.RelativeLineTo(
                 lerp(from.dx, to.dx, fraction),
                 lerp(from.dy, to.dy, fraction),
             )
         }
 
         is PathNode.LineTo -> {
             to as PathNode.LineTo
             PathNode.LineTo(
                 lerp(from.x, to.x, fraction),
                 lerp(from.y, to.y, fraction),
             )
         }
 
         is PathNode.RelativeHorizontalTo -> {
             to as PathNode.RelativeHorizontalTo
             PathNode.RelativeHorizontalTo(
                 lerp(from.dx, to.dx, fraction),
             )
         }
 
         is PathNode.HorizontalTo -> {
             to as PathNode.HorizontalTo
             PathNode.HorizontalTo(
                 lerp(from.x, to.x, fraction),
             )
         }
 
         is PathNode.RelativeVerticalTo -> {
             to as PathNode.RelativeVerticalTo
             PathNode.RelativeVerticalTo(
                 lerp(from.dy, to.dy, fraction),
             )
         }
 
         is PathNode.VerticalTo -> {
             to as PathNode.VerticalTo
             PathNode.VerticalTo(
                 lerp(from.y, to.y, fraction),
             )
         }
 
         is PathNode.RelativeCurveTo -> {
             to as PathNode.RelativeCurveTo
             PathNode.RelativeCurveTo(
                 lerp(from.dx1, to.dx1, fraction),
                 lerp(from.dy1, to.dy1, fraction),
                 lerp(from.dx2, to.dx2, fraction),
                 lerp(from.dy2, to.dy2, fraction),
                 lerp(from.dx3, to.dx3, fraction),
                 lerp(from.dy3, to.dy3, fraction),
             )
         }
 
         is PathNode.CurveTo -> {
             to as PathNode.CurveTo
             PathNode.CurveTo(
                 lerp(from.x1, to.x1, fraction),
                 lerp(from.y1, to.y1, fraction),
                 lerp(from.x2, to.x2, fraction),
                 lerp(from.y2, to.y2, fraction),
                 lerp(from.x3, to.x3, fraction),
                 lerp(from.y3, to.y3, fraction),
             )
         }
 
         is PathNode.RelativeReflectiveCurveTo -> {
             to as PathNode.RelativeReflectiveCurveTo
             PathNode.RelativeReflectiveCurveTo(
                 lerp(from.dx1, to.dx1, fraction),
                 lerp(from.dy1, to.dy1, fraction),
                 lerp(from.dx2, to.dx2, fraction),
                 lerp(from.dy2, to.dy2, fraction),
             )
         }
 
         is PathNode.ReflectiveCurveTo -> {
             to as PathNode.ReflectiveCurveTo
             PathNode.ReflectiveCurveTo(
                 lerp(from.x1, to.x1, fraction),
                 lerp(from.y1, to.y1, fraction),
                 lerp(from.x2, to.x2, fraction),
                 lerp(from.y2, to.y2, fraction),
             )
         }
 
         is PathNode.RelativeQuadTo -> {
             to as PathNode.RelativeQuadTo
             PathNode.RelativeQuadTo(
                 lerp(from.dx1, to.dx1, fraction),
                 lerp(from.dy1, to.dy1, fraction),
                 lerp(from.dx2, to.dx2, fraction),
                 lerp(from.dy2, to.dy2, fraction),
             )
         }
 
         is PathNode.QuadTo -> {
             to as PathNode.QuadTo
             PathNode.QuadTo(
                 lerp(from.x1, to.x1, fraction),
                 lerp(from.y1, to.y1, fraction),
                 lerp(from.x2, to.x2, fraction),
                 lerp(from.y2, to.y2, fraction),
             )
         }
 
         is PathNode.RelativeReflectiveQuadTo -> {
             to as PathNode.RelativeReflectiveQuadTo
             PathNode.RelativeReflectiveQuadTo(
                 lerp(from.dx, to.dx, fraction),
                 lerp(from.dy, to.dy, fraction),
             )
         }
 
         is PathNode.ReflectiveQuadTo -> {
             to as PathNode.ReflectiveQuadTo
             PathNode.ReflectiveQuadTo(
                 lerp(from.x, to.x, fraction),
                 lerp(from.y, to.y, fraction),
             )
         }
 
         is PathNode.RelativeArcTo -> TODO("Support for RelativeArcTo not implemented yet")
         is PathNode.ArcTo -> TODO("Support for ArcTo not implemented yet")
     }
 }
 
 @Preview
 @Composable
 private fun PreviewPathMorphing() {
     Box(
         modifier = Modifier.fillMaxSize(),
         contentAlignment = Alignment.Center,
     ) {
         var isPlaying by remember { mutableStateOf(false) }
         FloatingActionButton(onClick = { isPlaying = !isPlaying }) {
             val pathData by animatePathAsState(
                 if (isPlaying) {
                     "M 10 38 L 10 10 L 21.75 10 L 21.75 38 L 10 38 M 26.25 38 L 26.25 10 L 38 10 L 38 38 L 26.25 38"
                 } else {
                     "M 16 9.85 L 38 23.85 L 38 23.85 L 16 23.957 L 16 9.85 M 16 23.957 L 38 23.85 L 38 23.85 L 16 37.85 L 16 23.957"
                 },
             )
             Icon(
                 imageVector = ImageVector.Builder(defaultWidth = 24.dp, defaultHeight = 24.dp, viewportWidth = 48f, viewportHeight = 48f)
                     .addPath(pathData = pathData, fill = SolidColor(Color.White))
                     .build(),
                 contentDescription = null,
             )
         }
     }
 }