net.peanuuutz.fork.ui.foundation.animation.EnterExitEffect.kt Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of fork-ui Show documentation
Show all versions of fork-ui Show documentation
Comprehensive API designed for Minecraft modders
The newest version!
/*
* Copyright 2020 The Android Open Source Project
* Modifications Copyright 2022 Peanuuutz
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.peanuuutz.fork.ui.foundation.animation
import androidx.compose.runtime.Immutable
import androidx.compose.runtime.Stable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember
import net.peanuuutz.fork.ui.animation.Transition
import net.peanuuutz.fork.ui.animation.animateFloat
import net.peanuuutz.fork.ui.animation.createDeferredAnimation
import net.peanuuutz.fork.ui.animation.spec.target.composite.FiniteAnimationSpec
import net.peanuuutz.fork.ui.animation.spec.target.composite.SnapSpec
import net.peanuuutz.fork.ui.animation.spec.target.composite.SpringPhysics
import net.peanuuutz.fork.ui.animation.spec.target.composite.SpringSpec
import net.peanuuutz.fork.ui.animation.spec.target.primitive.FloatAnimationSpec
import net.peanuuutz.fork.ui.animation.spec.target.primitive.NoopFloatAnimationSpec
import net.peanuuutz.fork.ui.animation.spec.target.vectorized.VectorizedAnimationSpec
import net.peanuuutz.fork.ui.animation.spec.target.vectorized.VectorizedDimensionIndependentAnimationSpec
import net.peanuuutz.fork.ui.animation.spec.target.vectorized.VectorizedFiniteAnimationSpec
import net.peanuuutz.fork.ui.animation.vector.AnimationVector
import net.peanuuutz.fork.ui.animation.vector.AnimationVector1D
import net.peanuuutz.fork.ui.animation.vector.AnimationVector2D
import net.peanuuutz.fork.ui.animation.vector.VectorConvertor
import net.peanuuutz.fork.ui.foundation.animation.EnterExitEffect.Builder
import net.peanuuutz.fork.ui.foundation.animation.EnterExitEffect.Element
import net.peanuuutz.fork.ui.foundation.animation.EnterExitEffect.Type
import net.peanuuutz.fork.ui.foundation.animation.EnterExitState.PostExit
import net.peanuuutz.fork.ui.foundation.animation.EnterExitState.PreEnter
import net.peanuuutz.fork.ui.foundation.animation.EnterExitState.Visible
import net.peanuuutz.fork.ui.inspection.InspectInfo
import net.peanuuutz.fork.ui.ui.layout.Alignment
import net.peanuuutz.fork.ui.ui.layout.Constraints
import net.peanuuutz.fork.ui.ui.layout.LayoutOrientation
import net.peanuuutz.fork.ui.ui.layout.LayoutOrientation.Horizontal
import net.peanuuutz.fork.ui.ui.layout.LayoutOrientation.Vertical
import net.peanuuutz.fork.ui.ui.layout.Measurable
import net.peanuuutz.fork.ui.ui.layout.MeasureResult
import net.peanuuutz.fork.ui.ui.modifier.Modifier
import net.peanuuutz.fork.ui.ui.modifier.ModifierNodeElement
import net.peanuuutz.fork.ui.ui.modifier.composed
import net.peanuuutz.fork.ui.ui.modifier.draw.graphicsLayer
import net.peanuuutz.fork.ui.ui.node.LayoutModifierNode
import net.peanuuutz.fork.ui.ui.node.ModifierNode
import net.peanuuutz.fork.ui.ui.unit.IntOffset
import net.peanuuutz.fork.ui.ui.unit.IntSize
@Stable
sealed interface EnterExitEffect {
fun fold(
initial: R,
operation: (acc: R, element: Element<*>) -> R
): R
operator fun > get(type: Type): E?
operator fun plus(other: EnterExitEffect): EnterExitEffect {
if (other === EnterExitEffect) {
return this
}
return other.fold(this) { acc, element ->
val type = element.type
val existing = acc[type]
if (existing == null) {
CombinedEnterExitEffect(acc, element)
} else {
val merged = mergeUnsafe(existing, element)
val remaining = acc - type
if (remaining === EnterExitEffect) {
merged
} else {
CombinedEnterExitEffect(remaining, merged)
}
}
}
}
operator fun minus(type: Type<*>): EnterExitEffect
@Stable
interface Type>
@Stable
interface Element> : EnterExitEffect {
val type: Type
fun Modifier.transition(transition: Transition): Modifier
infix fun merge(new: E): E {
return new
}
override fun fold(initial: R, operation: (R, Element<*>) -> R): R {
return operation(initial, this)
}
override fun > get(type: Type): E? {
return if (this.type == type) {
@Suppress("UNCHECKED_CAST")
this as E
} else {
null
}
}
override fun minus(type: Type<*>): EnterExitEffect {
return if (this.type == type) EnterExitEffect else this
}
}
class Builder {
fun append(effect: EnterExitEffect) {
current += effect
}
fun remove(type: Type<*>) {
current -= type
}
fun build(): EnterExitEffect {
return current
}
// ======== Internal ========
private var current: EnterExitEffect = EnterExitEffect
}
@Immutable
companion object : EnterExitEffect {
override fun fold(initial: R, operation: (R, Element<*>) -> R): R {
return initial
}
override fun > get(type: Type): E? {
return null
}
override fun plus(other: EnterExitEffect): EnterExitEffect {
return other
}
override fun minus(type: Type<*>): EnterExitEffect {
return EnterExitEffect
}
override fun toString(): String {
return "EnterExitEffect"
}
}
}
inline fun EnterExitEffect(
block: Builder.() -> Unit
): EnterExitEffect {
return Builder().apply(block).build()
}
context(Builder)
operator fun EnterExitEffect.unaryPlus() {
append(this)
}
context(Builder)
operator fun Type<*>.unaryMinus() {
remove(this)
}
// -------- Presets --------
@Stable
val Fade: Type<*> = FadeEffect
@Stable
val Slide: Type<*> = SlideEffect
@Stable
val ExpandShrink: Type<*> = ExpandShrinkEffect
@Stable
fun Fade(
animationSpec: FiniteAnimationSpec = DefaultEffectFloatAnimationSpec,
alphaOnAbsent: Float = 0.0f
): EnterExitEffect {
val data = FadeEffectData(
animationSpec = animationSpec,
alphaOnAbsent = alphaOnAbsent
)
return FadeEffect(
enter = data,
exit = data
)
}
@Stable
fun FadeIn(
animationSpec: FiniteAnimationSpec = DefaultEffectFloatAnimationSpec,
startAlpha: Float = 0.0f
): EnterExitEffect {
val data = FadeEffectData(
animationSpec = animationSpec,
alphaOnAbsent = startAlpha
)
return FadeEffect(
enter = data,
exit = null
)
}
@Stable
fun FadeOut(
animationSpec: FiniteAnimationSpec = DefaultEffectFloatAnimationSpec,
endAlpha: Float = 0.0f
): EnterExitEffect {
val data = FadeEffectData(
animationSpec = animationSpec,
alphaOnAbsent = endAlpha
)
return FadeEffect(
enter = null,
exit = data
)
}
@Stable
fun Slide(
animationSpec: FiniteAnimationSpec = DefaultEffectIntOffsetAnimationSpec,
offsetOnAbsent: (size: IntSize) -> IntOffset
): EnterExitEffect {
val data = SlideEffectData(
animationSpec = animationSpec,
offsetOnAbsent = offsetOnAbsent
)
return SlideEffect(
enter = data,
exit = data
)
}
@Stable
fun SlideIn(
animationSpec: FiniteAnimationSpec = DefaultEffectIntOffsetAnimationSpec,
startOffset: (size: IntSize) -> IntOffset
): EnterExitEffect {
val data = SlideEffectData(
animationSpec = animationSpec,
offsetOnAbsent = startOffset
)
return SlideEffect(
enter = data,
exit = null
)
}
@Stable
fun SlideOut(
animationSpec: FiniteAnimationSpec = DefaultEffectIntOffsetAnimationSpec,
endOffset: (size: IntSize) -> IntOffset
): EnterExitEffect {
val data = SlideEffectData(
animationSpec = animationSpec,
offsetOnAbsent = endOffset
)
return SlideEffect(
enter = null,
exit = data
)
}
@Stable
fun Slide(
orientation: LayoutOrientation,
animationSpec: FiniteAnimationSpec = DefaultEffectIntAnimationSpec,
offsetOnAbsent: (size: Int) -> Int
): EnterExitEffect {
val data = SlideEffectData(
orientation = orientation,
animationSpec = animationSpec,
axisOffsetOnAbsent = offsetOnAbsent
)
return SlideEffect(
enter = data,
exit = data
)
}
@Stable
fun SlideIn(
orientation: LayoutOrientation,
animationSpec: FiniteAnimationSpec = DefaultEffectIntAnimationSpec,
startOffset: (size: Int) -> Int
): EnterExitEffect {
val data = SlideEffectData(
orientation = orientation,
animationSpec = animationSpec,
axisOffsetOnAbsent = startOffset
)
return SlideEffect(
enter = data,
exit = null
)
}
@Stable
fun SlideOut(
orientation: LayoutOrientation,
animationSpec: FiniteAnimationSpec = DefaultEffectIntAnimationSpec,
endOffset: (size: Int) -> Int
): EnterExitEffect {
val data = SlideEffectData(
orientation = orientation,
animationSpec = animationSpec,
axisOffsetOnAbsent = endOffset
)
return SlideEffect(
enter = null,
exit = data
)
}
@Stable
fun ExpandShrink(
alignmentOnAbsent: Alignment,
animationSpec: FiniteAnimationSpec = DefaultEffectIntSizeAnimationSpec,
sizeOnAbsent: (fullSize: IntSize) -> IntSize = { IntSize.Zero }
): EnterExitEffect {
val data = ExpandShrinkEffectData(
alignmentOnAbsent = alignmentOnAbsent,
animationSpec = animationSpec,
sizeOnAbsent = sizeOnAbsent
)
return ExpandShrinkEffect(
enter = data,
exit = data
)
}
@Stable
fun ExpandIn(
expandFrom: Alignment,
animationSpec: FiniteAnimationSpec = DefaultEffectIntSizeAnimationSpec,
startSize: (fullSize: IntSize) -> IntSize = { IntSize.Zero }
): EnterExitEffect {
val data = ExpandShrinkEffectData(
alignmentOnAbsent = expandFrom,
animationSpec = animationSpec,
sizeOnAbsent = startSize
)
return ExpandShrinkEffect(
enter = data,
exit = null
)
}
@Stable
fun ShrinkOut(
shrinkTo: Alignment,
animationSpec: FiniteAnimationSpec = DefaultEffectIntSizeAnimationSpec,
endSize: (fullSize: IntSize) -> IntSize = { IntSize.Zero }
): EnterExitEffect {
val data = ExpandShrinkEffectData(
alignmentOnAbsent = shrinkTo,
animationSpec = animationSpec,
sizeOnAbsent = endSize
)
return ExpandShrinkEffect(
enter = null,
exit = data
)
}
@Stable
fun ExpandShrink(
alignmentOnAbsent: Alignment.Horizontal,
animationSpec: FiniteAnimationSpec = DefaultEffectIntAnimationSpec,
sizeOnAbsent: (fullSize: Int) -> Int = { 0 }
): EnterExitEffect {
val data = ExpandShrinkEffectData(
axisAlignmentOnAbsent = alignmentOnAbsent,
animationSpec = animationSpec,
axisSizeOnAbsent = sizeOnAbsent
)
return ExpandShrinkEffect(
enter = data,
exit = data
)
}
@Stable
fun ExpandIn(
expandFrom: Alignment.Horizontal,
animationSpec: FiniteAnimationSpec = DefaultEffectIntAnimationSpec,
startSize: (fullSize: Int) -> Int = { 0 }
): EnterExitEffect {
val data = ExpandShrinkEffectData(
axisAlignmentOnAbsent = expandFrom,
animationSpec = animationSpec,
axisSizeOnAbsent = startSize
)
return ExpandShrinkEffect(
enter = data,
exit = null
)
}
@Stable
fun ShrinkOut(
shrinkTo: Alignment.Horizontal,
animationSpec: FiniteAnimationSpec = DefaultEffectIntAnimationSpec,
endSize: (fullSize: Int) -> Int = { 0 }
): EnterExitEffect {
val data = ExpandShrinkEffectData(
axisAlignmentOnAbsent = shrinkTo,
animationSpec = animationSpec,
axisSizeOnAbsent = endSize
)
return ExpandShrinkEffect(
enter = null,
exit = data
)
}
@Stable
fun ExpandShrink(
alignmentOnAbsent: Alignment.Vertical,
animationSpec: FiniteAnimationSpec = DefaultEffectIntAnimationSpec,
sizeOnAbsent: (fullSize: Int) -> Int = { 0 }
): EnterExitEffect {
val data = ExpandShrinkEffectData(
axisAlignmentOnAbsent = alignmentOnAbsent,
animationSpec = animationSpec,
axisSizeOnAbsent = sizeOnAbsent
)
return ExpandShrinkEffect(
enter = data,
exit = data
)
}
@Stable
fun ExpandIn(
expandFrom: Alignment.Vertical,
animationSpec: FiniteAnimationSpec = DefaultEffectIntAnimationSpec,
startSize: (fullSize: Int) -> Int = { 0 }
): EnterExitEffect {
val data = ExpandShrinkEffectData(
axisAlignmentOnAbsent = expandFrom,
animationSpec = animationSpec,
axisSizeOnAbsent = startSize
)
return ExpandShrinkEffect(
enter = data,
exit = null
)
}
@Stable
fun ShrinkOut(
shrinkTo: Alignment.Vertical,
animationSpec: FiniteAnimationSpec = DefaultEffectIntAnimationSpec,
endSize: (fullSize: Int) -> Int = { 0 }
): EnterExitEffect {
val data = ExpandShrinkEffectData(
axisAlignmentOnAbsent = shrinkTo,
animationSpec = animationSpec,
axisSizeOnAbsent = endSize
)
return ExpandShrinkEffect(
enter = null,
exit = data
)
}
// ======== Internal ========
// -------- Constants --------
private val DefaultEffectFloatAnimationSpec: FiniteAnimationSpec = SpringSpec(
stiffness = SpringPhysics.Stiffness.MediumLow
)
private val DefaultEffectIntAnimationSpec: FiniteAnimationSpec = SpringSpec(
stiffness = SpringPhysics.Stiffness.MediumLow
)
private val DefaultEffectIntOffsetAnimationSpec: FiniteAnimationSpec = SpringSpec(
stiffness = SpringPhysics.Stiffness.MediumLow
)
private val DefaultEffectIntSizeAnimationSpec: FiniteAnimationSpec = SpringSpec(
stiffness = SpringPhysics.Stiffness.MediumLow
)
private val SnapFloatAnimationSpec: FiniteAnimationSpec = SnapSpec()
private val SnapIntOffsetAnimationSpec: FiniteAnimationSpec = SnapSpec()
private val SnapIntSizeAnimationSpec: FiniteAnimationSpec = SnapSpec()
// -------- Combined Enter Exit Effect --------
private data class CombinedEnterExitEffect(
val outer: EnterExitEffect,
val inner: Element<*>
) : EnterExitEffect {
override fun fold(initial: R, operation: (R, Element<*>) -> R): R {
return inner.fold(outer.fold(initial, operation), operation)
}
override fun > get(type: Type): E? {
return outer[type] ?: inner[type]
}
override fun minus(type: Type<*>): EnterExitEffect {
if (inner[type] != null) {
return outer
}
return when (val remainingOuter = outer - type) {
outer -> this
EnterExitEffect -> inner
else -> CombinedEnterExitEffect(remainingOuter, inner)
}
}
override fun toString(): String {
val content = fold("") { acc, element ->
if (acc.isNotEmpty()) "$acc, $element" else element.toString()
}
return "[$content]"
}
}
// -------- Merge Element --------
private fun > mergeUnsafe(
old: Element,
new: Element<*>
): Element {
require(old::class.java == new::class.java) {
"Cannot cast ${new::class.simpleName} to ${old::class.simpleName}. " +
"Did you use a single Type for two different Element implementations?"
}
@Suppress("UNCHECKED_CAST")
new as E
return old merge new
}
// -------- Fade --------
private data class FadeEffect(
val enter: FadeEffectData?,
val exit: FadeEffectData?
) : Element {
override val type: Type
get() = FadeEffect
override fun Modifier.transition(transition: Transition): Modifier {
return composed {
val alpha by transition.animateFloat(
transitionSpec = {
when {
PreEnter to Visible || PostExit to Visible -> enter?.animationSpec ?: SnapFloatAnimationSpec
Visible to PostExit -> exit?.animationSpec ?: SnapFloatAnimationSpec
else -> SnapFloatAnimationSpec
}
}
) { state ->
when (state) {
PreEnter -> enter?.alphaOnAbsent ?: 1.0f
Visible -> 1.0f
PostExit -> exit?.alphaOnAbsent ?: 1.0f
}
}
graphicsLayer {
this.alpha = alpha
}
}
}
override fun merge(new: FadeEffect): FadeEffect {
val enter = new.enter ?: this.enter
val exit = new.exit ?: this.exit
return FadeEffect(
enter = enter,
exit = exit
)
}
companion object : Type
}
private data class FadeEffectData(
val animationSpec: FiniteAnimationSpec,
val alphaOnAbsent: Float
)
// -------- Slide --------
private data class SlideEffect(
val enter: SlideEffectData?,
val exit: SlideEffectData?
) : Element {
override val type: Type
get() = SlideEffect
override fun Modifier.transition(transition: Transition): Modifier {
return composed {
val offsetAnimation = transition.createDeferredAnimation(IntOffset.VectorConvertor)
val element = remember(offsetAnimation) {
SlideEffectModifier(
offsetAnimation = offsetAnimation,
enter = enter,
exit = exit
)
}
this then element
}
}
override fun merge(new: SlideEffect): SlideEffect {
val enter = new.enter ?: this.enter
val exit = new.exit ?: this.exit
return SlideEffect(
enter = enter,
exit = exit
)
}
companion object : Type
}
private data class SlideEffectData(
val animationSpec: FiniteAnimationSpec,
val offsetOnAbsent: (IntSize) -> IntOffset
) {
constructor(
orientation: LayoutOrientation,
animationSpec: FiniteAnimationSpec,
axisOffsetOnAbsent: (Int) -> Int
) : this(
animationSpec = AxisAnimationSpec(
orientation = orientation,
original = animationSpec
),
offsetOnAbsent = { size ->
when (orientation) {
Horizontal -> {
val x = axisOffsetOnAbsent(size.width)
IntOffset(x, 0)
}
Vertical -> {
val y = axisOffsetOnAbsent(size.height)
IntOffset(0, y)
}
}
}
)
}
private data class SlideEffectModifier(
val offsetAnimation: Transition.DeferredAnimation,
val enter: SlideEffectData?,
val exit: SlideEffectData?
) : ModifierNodeElement() {
override fun create(): SlideEffectModifierNode {
return SlideEffectModifierNode(
offsetAnimation = offsetAnimation,
enter = enter,
exit = exit
)
}
override fun update(node: SlideEffectModifierNode) {
node.offsetAnimation = offsetAnimation
node.enter = enter
node.exit = exit
}
override fun InspectInfo.inspect() {
set("enter", enter)
set("exit", exit)
}
}
private class SlideEffectModifierNode(
var offsetAnimation: Transition.DeferredAnimation,
var enter: SlideEffectData?,
var exit: SlideEffectData?
) : ModifierNode(), LayoutModifierNode {
private val transitionSpec: Transition.Segment.() -> FiniteAnimationSpec = {
when {
PreEnter to Visible || PostExit to Visible -> enter?.animationSpec ?: SnapIntOffsetAnimationSpec
Visible to PostExit -> exit?.animationSpec ?: SnapIntOffsetAnimationSpec
else -> SnapIntOffsetAnimationSpec
}
}
override fun measure(measurable: Measurable, constraints: Constraints): MeasureResult {
val placeable = measurable.measure(constraints)
val size = IntSize(placeable.width, placeable.height)
return MeasureResult(placeable.width, placeable.height) {
val offset by offsetAnimation.animate(
transitionSpec = transitionSpec
) { state ->
when (state) {
PreEnter -> enter?.offsetOnAbsent?.invoke(size) ?: IntOffset.Zero
Visible -> IntOffset.Zero
PostExit -> exit?.offsetOnAbsent?.invoke(size) ?: IntOffset.Zero
}
}
placeable.place(offset)
}
}
}
// -------- Expand Shrink --------
private data class ExpandShrinkEffect(
val enter: ExpandShrinkEffectData?,
val exit: ExpandShrinkEffectData?
) : Element {
override val type: Type
get() = Companion
override fun Modifier.transition(transition: Transition): Modifier {
return composed {
val sizeAnimation = transition.createDeferredAnimation(IntSize.VectorConvertor)
val currentAlignment = if (transition.state == transition.targetState) {
Alignment.TopLeft
} else {
with(transition.segment) {
when {
PreEnter to Visible || PostExit to Visible -> enter?.alignmentOnAbsent ?: Alignment.TopLeft
Visible to PostExit -> exit?.alignmentOnAbsent ?: Alignment.TopLeft
else -> Alignment.TopLeft
}
}
}
val element = remember(sizeAnimation, currentAlignment) {
ExpandShrinkEffectModifier(
sizeAnimation = sizeAnimation,
enter = enter,
exit = exit,
currentAlignment = currentAlignment
)
}
this then element
}
}
override fun merge(new: ExpandShrinkEffect): ExpandShrinkEffect {
val enter = new.enter ?: this.enter
val exit = new.exit ?: this.exit
return ExpandShrinkEffect(
enter = enter,
exit = exit
)
}
companion object : Type
}
private data class ExpandShrinkEffectData(
val alignmentOnAbsent: Alignment,
val animationSpec: FiniteAnimationSpec,
val sizeOnAbsent: (IntSize) -> IntSize
) {
constructor(
axisAlignmentOnAbsent: Alignment.Horizontal,
animationSpec: FiniteAnimationSpec,
axisSizeOnAbsent: (Int) -> Int
) : this(
alignmentOnAbsent = HorizontalAlignmentAdapter(axisAlignmentOnAbsent),
animationSpec = AxisAnimationSpec(
orientation = Horizontal,
original = animationSpec
),
sizeOnAbsent = { size ->
IntSize(
width = axisSizeOnAbsent(size.width),
height = size.height
)
}
)
constructor(
axisAlignmentOnAbsent: Alignment.Vertical,
animationSpec: FiniteAnimationSpec,
axisSizeOnAbsent: (Int) -> Int
) : this(
alignmentOnAbsent = VerticalAlignmentAdapter(axisAlignmentOnAbsent),
animationSpec = AxisAnimationSpec(
orientation = Vertical,
original = animationSpec
),
sizeOnAbsent = { size ->
IntSize(
width = size.width,
height = axisSizeOnAbsent(size.height)
)
}
)
}
private data class ExpandShrinkEffectModifier(
val sizeAnimation: Transition.DeferredAnimation,
val enter: ExpandShrinkEffectData?,
val exit: ExpandShrinkEffectData?,
val currentAlignment: Alignment
) : ModifierNodeElement() {
override fun create(): ExpandShrinkEffectModifierNode {
return ExpandShrinkEffectModifierNode(
sizeAnimation = sizeAnimation,
enter = enter,
exit = exit,
currentAlignment = currentAlignment
)
}
override fun update(node: ExpandShrinkEffectModifierNode) {
node.sizeAnimation = sizeAnimation
node.enter = enter
node.exit = exit
node.currentAlignment = currentAlignment
}
override fun InspectInfo.inspect() {
set("enter", enter)
set("exit", exit)
}
}
private class ExpandShrinkEffectModifierNode(
var sizeAnimation: Transition.DeferredAnimation,
var enter: ExpandShrinkEffectData?,
var exit: ExpandShrinkEffectData?,
var currentAlignment: Alignment
) : ModifierNode(), LayoutModifierNode {
private val transitionSpec: Transition.Segment.() -> FiniteAnimationSpec = {
when {
PreEnter to Visible || PostExit to Visible -> enter?.animationSpec ?: SnapIntSizeAnimationSpec
Visible to PostExit -> exit?.animationSpec ?: SnapIntSizeAnimationSpec
else -> SnapIntSizeAnimationSpec
}
}
override fun measure(measurable: Measurable, constraints: Constraints): MeasureResult {
val placeable = measurable.measure(constraints)
val fullSize = IntSize(placeable.width, placeable.height)
val size by sizeAnimation.animate(
transitionSpec = transitionSpec
) { state ->
when (state) {
PreEnter -> enter?.sizeOnAbsent?.invoke(fullSize) ?: fullSize
Visible -> fullSize
PostExit -> exit?.sizeOnAbsent?.invoke(fullSize) ?: fullSize
}
}
val position = currentAlignment.align(
contentSize = fullSize,
availableSpace = size
)
return MeasureResult(size.width, size.height) {
placeable.place(position)
}
}
}
// -------- Axis Animation Spec --------
private data class AxisAnimationSpec(
val orientation: LayoutOrientation,
val original: FiniteAnimationSpec
) : FiniteAnimationSpec {
override fun > vectorize(
convertor: VectorConvertor
): VectorizedFiniteAnimationSpec {
// We simply ignore convertor because it must be (IntOffset/IntSize).VectorConvertor
val dynamicAxisAnimationSpec = original.vectorize(Int.VectorConvertor)
val dynamicAxisFloatAnimationSpec = DelegatedFloatAnimationSpec(dynamicAxisAnimationSpec)
val axisFloatAnimationSpecs = when (orientation) {
Horizontal -> arrayOf(dynamicAxisFloatAnimationSpec, NoopFloatAnimationSpec)
Vertical -> arrayOf(NoopFloatAnimationSpec, dynamicAxisFloatAnimationSpec)
}
return VectorizedAxisAnimationSpec(axisFloatAnimationSpecs)
}
}
// V must be AnimationVector2D (IntOffset/IntSize)
private class VectorizedAxisAnimationSpec>(
val axisFloatAnimationSpecs: Array
) : VectorizedDimensionIndependentAnimationSpec(),
VectorizedFiniteAnimationSpec
{
override fun get(index: Int): FloatAnimationSpec {
return axisFloatAnimationSpecs[index]
}
}
// -------- Delegated Float Animation Spec --------
// This class may break @Stable contract, so we won't open it
private data class DelegatedFloatAnimationSpec(
val delegate: VectorizedAnimationSpec
) : FloatAnimationSpec {
// Cache some vectors to reduce overhead
private val initialValueVector: AnimationVector1D = AnimationVector1D()
private val targetValueVector: AnimationVector1D = AnimationVector1D()
private val initialVelocityVector: AnimationVector1D = AnimationVector1D()
override fun getValueFromMillis(
playTimeMillis: Int,
initialValue: Float,
targetValue: Float,
initialVelocity: Float
): Float {
val initialValueVector = initialValueVector.apply { v1 = initialValue }
val targetValueVector = targetValueVector.apply { v1 = targetValue }
val initialVelocityVector = initialVelocityVector.apply { v1 = initialVelocity }
val valueVector = delegate.getValueFromMillis(
playTimeMillis = playTimeMillis,
initialValue = initialValueVector,
targetValue = targetValueVector,
initialVelocity = initialVelocityVector
)
return valueVector.v1
}
override fun getVelocityFromMillis(
playTimeMillis: Int,
initialValue: Float,
targetValue: Float,
initialVelocity: Float
): Float {
val initialValueVector = initialValueVector.apply { v1 = initialValue }
val targetValueVector = targetValueVector.apply { v1 = targetValue }
val initialVelocityVector = initialVelocityVector.apply { v1 = initialVelocity }
val velocityVector = delegate.getVelocityFromMillis(
playTimeMillis = playTimeMillis,
initialValue = initialValueVector,
targetValue = targetValueVector,
initialVelocity = initialVelocityVector
)
return velocityVector.v1
}
override fun getDurationMillis(
initialValue: Float,
targetValue: Float,
initialVelocity: Float
): Int {
val initialValueVector = initialValueVector.apply { v1 = initialValue }
val targetValueVector = targetValueVector.apply { v1 = targetValue }
val initialVelocityVector = initialVelocityVector.apply { v1 = initialVelocity }
return delegate.getDurationMillis(
initialValue = initialValueVector,
targetValue = targetValueVector,
initialVelocity = initialVelocityVector
)
}
}
// -------- Alignment Adapter --------
private data class HorizontalAlignmentAdapter(
val original: Alignment.Horizontal
) : Alignment {
override fun align(contentSize: IntSize, availableSpace: IntSize): IntOffset {
val x = original.alignHorizontally(contentSize.width, availableSpace.width)
val y = Alignment.CenterVertically.alignVertically(contentSize.height, availableSpace.height)
return IntOffset(x, y)
}
}
private data class VerticalAlignmentAdapter(
val original: Alignment.Vertical
) : Alignment {
override fun align(contentSize: IntSize, availableSpace: IntSize): IntOffset {
val x = Alignment.CenterHorizontally.alignHorizontally(contentSize.width, availableSpace.width)
val y = original.alignVertically(contentSize.height, availableSpace.height)
return IntOffset(x, y)
}
}