Skip to content

Commit

Permalink
added slots support
Browse files Browse the repository at this point in the history
  • Loading branch information
Zhirkevich Alexander Y authored and Zhirkevich Alexander Y committed Oct 21, 2024
1 parent 9dc11ba commit 9bf8a57
Show file tree
Hide file tree
Showing 15 changed files with 280 additions and 44 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ public class LottieFontSpec internal constructor(
public val weight : FontWeight,
public val path : String?,
public val origin: FontOrigin,
public val accent : Float
public val ascent : Float
) {
public enum class FontOrigin {
/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,14 @@ package io.github.alexzhirkevich.compottie.internal

import io.github.alexzhirkevich.compottie.internal.assets.CharacterData
import io.github.alexzhirkevich.compottie.internal.assets.FontList
import io.github.alexzhirkevich.compottie.internal.helpers.Marker
import io.github.alexzhirkevich.compottie.internal.assets.LottieAsset
import io.github.alexzhirkevich.compottie.internal.helpers.Marker
import io.github.alexzhirkevich.compottie.internal.layers.Layer
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
import kotlinx.serialization.Transient
import kotlinx.serialization.json.JsonElement
import kotlinx.serialization.json.jsonObject


@Serializable
Expand Down Expand Up @@ -40,9 +43,19 @@ internal class Animation(

val chars : List<CharacterData> = emptyList(),

val markers : List<Marker> = emptyList()
val markers : List<Marker> = emptyList(),

@SerialName("slots")
private val slotsMap : Map<String, JsonElement> ?= null,
) {

@Transient
val slots = Slots(slotsMap?.mapValues {
checkNotNull(it.value.jsonObject["p"]) {
"Invalid slottable property: ${it.value}"
}
}.orEmpty())

fun deepCopy() : Animation {
return Animation(
frameRate = frameRate,
Expand All @@ -56,7 +69,8 @@ internal class Animation(
assets = assets.map(LottieAsset::copy),
fonts = fonts?.deepCopy(),
chars = chars.map(CharacterData::deepCopy),
markers = markers
markers = markers,
slotsMap = slotsMap
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@ package io.github.alexzhirkevich.compottie.internal

import io.github.alexzhirkevich.compottie.internal.animation.AnimatedColor
import io.github.alexzhirkevich.compottie.internal.animation.AnimatedGradient
import io.github.alexzhirkevich.compottie.internal.animation.AnimatedShape
import io.github.alexzhirkevich.compottie.internal.animation.AnimatedNumber
import io.github.alexzhirkevich.compottie.internal.animation.AnimatedShape
import io.github.alexzhirkevich.compottie.internal.animation.AnimatedVector2
import io.github.alexzhirkevich.compottie.internal.animation.AnimatedVectorN
import io.github.alexzhirkevich.compottie.internal.assets.ImageAsset
Expand All @@ -15,11 +15,11 @@ import io.github.alexzhirkevich.compottie.internal.effects.EffectValue
import io.github.alexzhirkevich.compottie.internal.effects.FillEffect
import io.github.alexzhirkevich.compottie.internal.effects.LayerEffect
import io.github.alexzhirkevich.compottie.internal.effects.TintEffect
import io.github.alexzhirkevich.compottie.internal.layers.ImageLayer
import io.github.alexzhirkevich.compottie.internal.layers.Layer
import io.github.alexzhirkevich.compottie.internal.layers.NullLayer
import io.github.alexzhirkevich.compottie.internal.layers.ShapeLayer
import io.github.alexzhirkevich.compottie.internal.layers.ImageLayer
import io.github.alexzhirkevich.compottie.internal.layers.PrecompositionLayer
import io.github.alexzhirkevich.compottie.internal.layers.ShapeLayer
import io.github.alexzhirkevich.compottie.internal.layers.SolidColorLayer
import io.github.alexzhirkevich.compottie.internal.layers.TextLayer
import io.github.alexzhirkevich.compottie.internal.shapes.EllipseShape
Expand All @@ -39,9 +39,13 @@ import io.github.alexzhirkevich.compottie.internal.shapes.TransformShape
import io.github.alexzhirkevich.compottie.internal.shapes.TrimPathShape
import kotlinx.serialization.ExperimentalSerializationApi
import kotlinx.serialization.json.Json
import kotlinx.serialization.json.JsonElement
import kotlinx.serialization.json.JsonNull
import kotlinx.serialization.modules.SerializersModule
import kotlinx.serialization.modules.polymorphic
import kotlinx.serialization.modules.subclass
import kotlin.contracts.ExperimentalContracts
import kotlin.contracts.contract

@OptIn(ExperimentalSerializationApi::class)
internal val LottieJson by lazy{
Expand Down Expand Up @@ -119,27 +123,32 @@ internal val LottieJson by lazy{
polymorphic(AnimatedColor::class) {
subclass(AnimatedColor.Default::class)
subclass(AnimatedColor.Animated::class)
subclass(AnimatedColor.Slottable::class)
}

polymorphic(AnimatedGradient::class) {
subclass(AnimatedGradient.Default::class)
subclass(AnimatedGradient.Animated::class)
subclass(AnimatedGradient.Slottable::class)
}

polymorphic(AnimatedShape::class) {
subclass(AnimatedShape.Default::class)
subclass(AnimatedShape.Animated::class)
subclass(AnimatedShape.Slottable::class)
}

polymorphic(AnimatedNumber::class) {
subclass(AnimatedNumber.Default::class)
subclass(AnimatedNumber.Animated::class)
subclass(AnimatedNumber.Slottable::class)
}

polymorphic(AnimatedVector2::class) {
subclass(AnimatedVector2.Default::class)
subclass(AnimatedVector2.Animated::class)
subclass(AnimatedVector2.Split::class)
subclass(AnimatedVector2.Slottable::class)
}

polymorphic(AnimatedVectorN::class) {
Expand All @@ -148,4 +157,14 @@ internal val LottieJson by lazy{
}
}
}
}

internal fun JsonElement?.isNull() : Boolean = this == null || this is JsonNull

@OptIn(ExperimentalContracts::class)
internal fun JsonElement?.isNotNull() : Boolean {
contract {
returns(true) implies (this@isNotNull != null)
}
return !isNull()
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package io.github.alexzhirkevich.compottie.internal

import io.github.alexzhirkevich.compottie.internal.animation.AnimatedColor
import io.github.alexzhirkevich.compottie.internal.animation.AnimatedGradient
import io.github.alexzhirkevich.compottie.internal.animation.AnimatedNumber
import io.github.alexzhirkevich.compottie.internal.animation.AnimatedShape
import io.github.alexzhirkevich.compottie.internal.animation.AnimatedVector2
import io.github.alexzhirkevich.compottie.internal.animation.RawProperty
import kotlinx.serialization.DeserializationStrategy
import kotlinx.serialization.json.JsonElement

internal class Slots(
private val slots : Map<String, JsonElement>
) {
private val cache = mutableMapOf<String, RawProperty<*>>()

fun number(sid: String): AnimatedNumber? = property(sid, AnimatedNumber.serializer())
fun vector(sid: String): AnimatedVector2? = property(sid, AnimatedVector2.serializer())
fun color(sid: String): AnimatedColor? = property(sid, AnimatedColor.serializer())
fun gradient(sid: String): AnimatedGradient? = property(sid, AnimatedGradient.serializer())
fun shape(sid: String) : AnimatedShape? = property(sid, AnimatedShape.serializer())

private fun <T : RawProperty<*>> property(
sid : String,
deserializer : DeserializationStrategy<T>,
) : T? {
println("Slot requested: $sid")
val json = slots[sid] ?: return null
val cached = cache[sid]
if (cached != null){
return cached as T
}
val new = LottieJson.decodeFromJsonElement(deserializer, json)
cache[sid] = new
return new
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ package io.github.alexzhirkevich.compottie.internal.animation
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.lerp
import io.github.alexzhirkevich.compottie.internal.AnimationState
import io.github.alexzhirkevich.compottie.internal.isNotNull
import kotlinx.serialization.DeserializationStrategy
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
Expand All @@ -19,10 +20,10 @@ import kotlinx.serialization.json.jsonPrimitive
@Serializable(with = AnimatedColorSerializer::class)
internal sealed class AnimatedColor : ExpressionProperty<Color>() {

abstract fun copy() : AnimatedColor
abstract fun copy(): AnimatedColor

override fun mapEvaluated(e: Any): Color {
return when (e){
return when (e) {
is Color -> e
is List<*> -> (e as List<Number>).toColor2()

Expand Down Expand Up @@ -62,7 +63,6 @@ internal sealed class AnimatedColor : ExpressionProperty<Color>() {

@Serializable
class Animated(

@SerialName("k")
val value: List<VectorKeyframe>,

Expand Down Expand Up @@ -92,6 +92,30 @@ internal sealed class AnimatedColor : ExpressionProperty<Color>() {
)
}
}

@Serializable
class Slottable(
val sid: String,

@SerialName("x")
override val expression: String? = null,

@SerialName("ix")
override val index: Int? = null
) : AnimatedColor() {
override fun copy(): AnimatedColor {
return Slottable(
sid = sid,
expression = expression,
index = index
)
}

override fun raw(state: AnimationState): Color {
return state.composition.animation.slots.color(sid)?.interpolated(state)
?: Color.Transparent
}
}
}

internal fun List<Float>.toColor() = Color(
Expand Down Expand Up @@ -124,6 +148,15 @@ internal object AnimatedColorSerializer : JsonContentPolymorphicSerializer<Anima
baseClass = AnimatedColor::class
) {
override fun selectDeserializer(element: JsonElement): DeserializationStrategy<AnimatedColor> {

check(element is JsonObject){
"Invalid color: $element"
}

if (element["sid"].isNotNull()){
return AnimatedColor.Slottable.serializer()
}

val k = requireNotNull(element.jsonObject["k"]) {
"Animated shape must have 'k' parameter"
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,18 +3,18 @@ package io.github.alexzhirkevich.compottie.internal.animation
import androidx.compose.ui.util.fastMap
import io.github.alexzhirkevich.compottie.internal.AnimationState
import io.github.alexzhirkevich.compottie.internal.helpers.ColorsWithStops
import kotlinx.serialization.ExperimentalSerializationApi
import io.github.alexzhirkevich.compottie.internal.isNotNull
import kotlinx.serialization.DeserializationStrategy
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
import kotlinx.serialization.Transient
import kotlinx.serialization.json.JsonClassDiscriminator
import kotlin.jvm.JvmInline
import kotlinx.serialization.json.JsonContentPolymorphicSerializer
import kotlinx.serialization.json.JsonElement
import kotlinx.serialization.json.JsonObject
import kotlinx.serialization.json.int
import kotlinx.serialization.json.jsonPrimitive



@OptIn(ExperimentalSerializationApi::class)
@Serializable
@JsonClassDiscriminator("a")
@Serializable(with = AnimatedGradientSerializer::class)
internal abstract class AnimatedGradient : ExpressionProperty<ColorsWithStops>() {

@Transient
Expand Down Expand Up @@ -42,7 +42,6 @@ internal abstract class AnimatedGradient : ExpressionProperty<ColorsWithStops>()
}
}

@SerialName("0")
@Serializable
class Default(
@SerialName("k")
Expand Down Expand Up @@ -74,7 +73,6 @@ internal abstract class AnimatedGradient : ExpressionProperty<ColorsWithStops>()
}
}

@SerialName("1")
@Serializable
class Animated(
@SerialName("k")
Expand Down Expand Up @@ -125,4 +123,45 @@ internal abstract class AnimatedGradient : ExpressionProperty<ColorsWithStops>()
return delegate.raw(state)
}
}

@Serializable
class Slottable(
val sid : String,

@SerialName("ix")
override val index: Int? = null,
@SerialName("x")
override val expression: String? = null
) : AnimatedGradient() {

@Transient
private val emptyColorStops = ColorsWithStops(0)

override fun copy(): AnimatedGradient {
return Slottable(
sid = sid,
index = index,
expression = expression
)
}

override fun raw(state: AnimationState): ColorsWithStops {
return state.composition.animation.slots.gradient(sid)?.interpolated(state) ?: emptyColorStops
}
}
}

internal object AnimatedGradientSerializer : JsonContentPolymorphicSerializer<AnimatedGradient>(AnimatedGradient::class){
override fun selectDeserializer(element: JsonElement): DeserializationStrategy<AnimatedGradient> {
check(element is JsonObject){
"Invalid gradient: $element"
}

return when {
element["sid"].isNotNull() -> AnimatedGradient.Slottable.serializer()
element["a"]?.jsonPrimitive?.int == 1 -> AnimatedGradient.Animated.serializer()
else -> AnimatedGradient.Default.serializer()
}
}

}
Loading

0 comments on commit 9bf8a57

Please sign in to comment.