Animation for Layouts

- Popout for setting layout animation
- Supports hold, linear, cubic ease and elastic
- Layouts can have custom interpolation or inherit from up the layout tree
- This PR also implements the change to not store flexGrow or alignSelf when setting scaleType, rather we put the logic into syncStyle and directly pass the values for those into the layout engine
- Tested with Rive Renderer in editor and seems to work well!
- @alxgibsn I moved the animation popout to the Layout inspector. We discussed the Auto Layout inspector, but since animation can be applied to a layoutcomponent that has no children (whereas the Auto Layout inspector must have children to be expanded), it seemed more appropriate there.

Diffs=
31f5ee5c4 Animation for Layouts (#7426)

Co-authored-by: Philip Chung <philterdesign@gmail.com>
diff --git a/.rive_head b/.rive_head
index 2027035..bd73d8c 100644
--- a/.rive_head
+++ b/.rive_head
@@ -1 +1 @@
-a4439ee42b2a159663212b9bff3f0b32c46e06c3
+31f5ee5c480ab9b2c1a0d263305f56bfd943d780
diff --git a/dev/defs/layout/layout_component_style.json b/dev/defs/layout/layout_component_style.json
index d3da49c..194601b 100644
--- a/dev/defs/layout/layout_component_style.json
+++ b/dev/defs/layout/layout_component_style.json
@@ -37,6 +37,7 @@
       "type": "double",
       "initialValue": "0",
       "animates": true,
+      "group": "layoutgap",
       "key": {
         "int": 498,
         "string": "gaphorizontal"
@@ -47,6 +48,7 @@
       "type": "double",
       "initialValue": "0",
       "animates": true,
+      "group": "layoutgap",
       "key": {
         "int": 499,
         "string": "gapvertical"
@@ -57,6 +59,7 @@
       "type": "double",
       "initialValue": "0",
       "animates": true,
+      "group": "layoutmax",
       "key": {
         "int": 500,
         "string": "maxwidth"
@@ -67,6 +70,7 @@
       "type": "double",
       "initialValue": "0",
       "animates": true,
+      "group": "layoutmax",
       "key": {
         "int": 501,
         "string": "maxheight"
@@ -77,6 +81,7 @@
       "type": "double",
       "initialValue": "0",
       "animates": true,
+      "group": "layoutmin",
       "key": {
         "int": 502,
         "string": "minwidth"
@@ -87,6 +92,7 @@
       "type": "double",
       "initialValue": "0",
       "animates": true,
+      "group": "layoutmin",
       "key": {
         "int": 503,
         "string": "minheight"
@@ -137,6 +143,7 @@
       "type": "double",
       "initialValue": "0",
       "animates": true,
+      "group": "layoutmargin",
       "key": {
         "int": 508,
         "string": "marginleft"
@@ -147,6 +154,7 @@
       "type": "double",
       "initialValue": "0",
       "animates": true,
+      "group": "layoutmargin",
       "key": {
         "int": 509,
         "string": "marginright"
@@ -157,6 +165,7 @@
       "type": "double",
       "initialValue": "0",
       "animates": true,
+      "group": "layoutmargin",
       "key": {
         "int": 510,
         "string": "margintop"
@@ -167,6 +176,7 @@
       "type": "double",
       "initialValue": "0",
       "animates": true,
+      "group": "layoutmargin",
       "key": {
         "int": 511,
         "string": "marginbottom"
@@ -177,6 +187,7 @@
       "type": "double",
       "initialValue": "0",
       "animates": true,
+      "group": "layoutpadding",
       "key": {
         "int": 512,
         "string": "paddingleft"
@@ -187,6 +198,7 @@
       "type": "double",
       "initialValue": "0",
       "animates": true,
+      "group": "layoutpadding",
       "key": {
         "int": 513,
         "string": "paddingright"
@@ -197,6 +209,7 @@
       "type": "double",
       "initialValue": "0",
       "animates": true,
+      "group": "layoutpadding",
       "key": {
         "int": 514,
         "string": "paddingtop"
@@ -207,6 +220,7 @@
       "type": "double",
       "initialValue": "0",
       "animates": true,
+      "group": "layoutpadding",
       "key": {
         "int": 515,
         "string": "paddingbottom"
@@ -217,6 +231,7 @@
       "type": "double",
       "initialValue": "0",
       "animates": true,
+      "group": "layoutposition",
       "key": {
         "int": 516,
         "string": "positionleft"
@@ -227,6 +242,7 @@
       "type": "double",
       "initialValue": "0",
       "animates": true,
+      "group": "layoutposition",
       "key": {
         "int": 517,
         "string": "positionright"
@@ -237,6 +253,7 @@
       "type": "double",
       "initialValue": "0",
       "animates": true,
+      "group": "layoutposition",
       "key": {
         "int": 518,
         "string": "positiontop"
@@ -247,6 +264,7 @@
       "type": "double",
       "initialValue": "0",
       "animates": true,
+      "group": "layoutposition",
       "key": {
         "int": 519,
         "string": "positionbottom"
@@ -302,6 +320,71 @@
         "string": "aspectratio"
       },
       "description": "Aspect ratio value."
+    },
+    "showUnitToggle": {
+      "type": "bool",
+      "initialValue": "false",
+      "key": {
+        "int": 542,
+        "string": "showunittoggle"
+      },
+      "description": "Show layout unit toggle in inspector position widget. Editor only.",
+      "runtime": false
+    },
+    "edgeConstraints": {
+      "type": "uint",
+      "initialValue": "0",
+      "key": {
+        "int": 545,
+        "string": "edgeconstraints"
+      },
+      "runtime": false
+    },
+    "scaleType": {
+      "type": "uint",
+      "initialValue": "0",
+      "key": {
+        "int": 546,
+        "string": "scaletype"
+      }
+    },
+    "animationStyleType": {
+      "type": "uint",
+      "initialValue": "0",
+      "key": {
+        "int": 589,
+        "string": "animationstyletype"
+      },
+      "description": "The type of animation none|custom|inherit applied to this layout."
+    },
+    "interpolationType": {
+      "type": "uint",
+      "initialValue": "0",
+      "key": {
+        "int": 590,
+        "string": "interpolationtype"
+      },
+      "description": "The type of interpolation index in KeyframeInterpolation applied to this layout."
+    },
+    "interpolatorId": {
+      "type": "Id",
+      "typeRuntime": "uint",
+      "initialValue": "Core.missingId",
+      "initialValueRuntime": "-1",
+      "key": {
+        "int": 591,
+        "string": "interpolatorid"
+      },
+      "description": "The id of the custom interpolator used when interpolation is Cubic."
+    },
+    "interpolationTime": {
+      "type": "double",
+      "initialValue": "0",
+      "key": {
+        "int": 592,
+        "string": "interpolationtime"
+      },
+      "description": "The time over which the interpolator applies."
     }
   }
 }
\ No newline at end of file
diff --git a/dev/defs/layout_component_absolute.json b/dev/defs/layout_component_absolute.json
deleted file mode 100644
index a239d43..0000000
--- a/dev/defs/layout_component_absolute.json
+++ /dev/null
@@ -1,19 +0,0 @@
-{
-  "name": "AbsoluteLayoutComponent",
-  "key": {
-    "int": 423,
-    "string": "absolutelayoutcomponent"
-  },
-  "extends": "layout_component.json",
-  "properties": {
-    "constraints": {
-      "type": "uint",
-      "initialValue": "0",
-      "key": {
-        "int": 535,
-        "string": "constraints"
-      },
-      "runtime": false
-    }
-  }
-}
\ No newline at end of file
diff --git a/include/rive/component_dirt.hpp b/include/rive/component_dirt.hpp
index fedb0c7..ccd64c0 100644
--- a/include/rive/component_dirt.hpp
+++ b/include/rive/component_dirt.hpp
@@ -63,6 +63,8 @@
     // TODO: do we need this?
     // BlendMode = 1 << 9,
 
+    LayoutStyle = 1 << 11,
+
     /// All dirty. Every flag (apart from Collapsed) is set.
     Filthy = 0xFFFE
 };
diff --git a/include/rive/generated/core_registry.hpp b/include/rive/generated/core_registry.hpp
index c4018d2..b073d8a 100644
--- a/include/rive/generated/core_registry.hpp
+++ b/include/rive/generated/core_registry.hpp
@@ -108,7 +108,6 @@
 #include "rive/joystick.hpp"
 #include "rive/layout/layout_component_style.hpp"
 #include "rive/layout_component.hpp"
-#include "rive/layout_component_absolute.hpp"
 #include "rive/nested_animation.hpp"
 #include "rive/nested_artboard.hpp"
 #include "rive/node.hpp"
@@ -398,8 +397,6 @@
                 return new Joystick();
             case BackboardBase::typeKey:
                 return new Backboard();
-            case AbsoluteLayoutComponentBase::typeKey:
-                return new AbsoluteLayoutComponent();
             case OpenUrlEventBase::typeKey:
                 return new OpenUrlEvent();
             case DataBindBase::typeKey:
@@ -626,6 +623,18 @@
             case LayoutComponentStyleBase::layoutFlags2PropertyKey:
                 object->as<LayoutComponentStyleBase>()->layoutFlags2(value);
                 break;
+            case LayoutComponentStyleBase::scaleTypePropertyKey:
+                object->as<LayoutComponentStyleBase>()->scaleType(value);
+                break;
+            case LayoutComponentStyleBase::animationStyleTypePropertyKey:
+                object->as<LayoutComponentStyleBase>()->animationStyleType(value);
+                break;
+            case LayoutComponentStyleBase::interpolationTypePropertyKey:
+                object->as<LayoutComponentStyleBase>()->interpolationType(value);
+                break;
+            case LayoutComponentStyleBase::interpolatorIdPropertyKey:
+                object->as<LayoutComponentStyleBase>()->interpolatorId(value);
+                break;
             case ListenerFireEventBase::eventIdPropertyKey:
                 object->as<ListenerFireEventBase>()->eventId(value);
                 break;
@@ -1085,6 +1094,9 @@
             case LayoutComponentStyleBase::aspectRatioPropertyKey:
                 object->as<LayoutComponentStyleBase>()->aspectRatio(value);
                 break;
+            case LayoutComponentStyleBase::interpolationTimePropertyKey:
+                object->as<LayoutComponentStyleBase>()->interpolationTime(value);
+                break;
             case NestedLinearAnimationBase::mixPropertyKey:
                 object->as<NestedLinearAnimationBase>()->mix(value);
                 break;
@@ -1562,6 +1574,14 @@
                 return object->as<LayoutComponentStyleBase>()->layoutFlags1();
             case LayoutComponentStyleBase::layoutFlags2PropertyKey:
                 return object->as<LayoutComponentStyleBase>()->layoutFlags2();
+            case LayoutComponentStyleBase::scaleTypePropertyKey:
+                return object->as<LayoutComponentStyleBase>()->scaleType();
+            case LayoutComponentStyleBase::animationStyleTypePropertyKey:
+                return object->as<LayoutComponentStyleBase>()->animationStyleType();
+            case LayoutComponentStyleBase::interpolationTypePropertyKey:
+                return object->as<LayoutComponentStyleBase>()->interpolationType();
+            case LayoutComponentStyleBase::interpolatorIdPropertyKey:
+                return object->as<LayoutComponentStyleBase>()->interpolatorId();
             case ListenerFireEventBase::eventIdPropertyKey:
                 return object->as<ListenerFireEventBase>()->eventId();
             case LayerStateBase::flagsPropertyKey:
@@ -1877,6 +1897,8 @@
                 return object->as<LayoutComponentStyleBase>()->flexBasis();
             case LayoutComponentStyleBase::aspectRatioPropertyKey:
                 return object->as<LayoutComponentStyleBase>()->aspectRatio();
+            case LayoutComponentStyleBase::interpolationTimePropertyKey:
+                return object->as<LayoutComponentStyleBase>()->interpolationTime();
             case NestedLinearAnimationBase::mixPropertyKey:
                 return object->as<NestedLinearAnimationBase>()->mix();
             case NestedSimpleAnimationBase::speedPropertyKey:
@@ -2168,6 +2190,10 @@
             case LayoutComponentStyleBase::layoutFlags0PropertyKey:
             case LayoutComponentStyleBase::layoutFlags1PropertyKey:
             case LayoutComponentStyleBase::layoutFlags2PropertyKey:
+            case LayoutComponentStyleBase::scaleTypePropertyKey:
+            case LayoutComponentStyleBase::animationStyleTypePropertyKey:
+            case LayoutComponentStyleBase::interpolationTypePropertyKey:
+            case LayoutComponentStyleBase::interpolatorIdPropertyKey:
             case ListenerFireEventBase::eventIdPropertyKey:
             case LayerStateBase::flagsPropertyKey:
             case ListenerInputChangeBase::inputIdPropertyKey:
@@ -2318,6 +2344,7 @@
             case LayoutComponentStyleBase::flexShrinkPropertyKey:
             case LayoutComponentStyleBase::flexBasisPropertyKey:
             case LayoutComponentStyleBase::aspectRatioPropertyKey:
+            case LayoutComponentStyleBase::interpolationTimePropertyKey:
             case NestedLinearAnimationBase::mixPropertyKey:
             case NestedSimpleAnimationBase::speedPropertyKey:
             case AdvanceableStateBase::speedPropertyKey:
@@ -2568,6 +2595,14 @@
                 return object->is<LayoutComponentStyleBase>();
             case LayoutComponentStyleBase::layoutFlags2PropertyKey:
                 return object->is<LayoutComponentStyleBase>();
+            case LayoutComponentStyleBase::scaleTypePropertyKey:
+                return object->is<LayoutComponentStyleBase>();
+            case LayoutComponentStyleBase::animationStyleTypePropertyKey:
+                return object->is<LayoutComponentStyleBase>();
+            case LayoutComponentStyleBase::interpolationTypePropertyKey:
+                return object->is<LayoutComponentStyleBase>();
+            case LayoutComponentStyleBase::interpolatorIdPropertyKey:
+                return object->is<LayoutComponentStyleBase>();
             case ListenerFireEventBase::eventIdPropertyKey:
                 return object->is<ListenerFireEventBase>();
             case LayerStateBase::flagsPropertyKey:
@@ -2862,6 +2897,8 @@
                 return object->is<LayoutComponentStyleBase>();
             case LayoutComponentStyleBase::aspectRatioPropertyKey:
                 return object->is<LayoutComponentStyleBase>();
+            case LayoutComponentStyleBase::interpolationTimePropertyKey:
+                return object->is<LayoutComponentStyleBase>();
             case NestedLinearAnimationBase::mixPropertyKey:
                 return object->is<NestedLinearAnimationBase>();
             case NestedSimpleAnimationBase::speedPropertyKey:
diff --git a/include/rive/generated/layout/layout_component_style_base.hpp b/include/rive/generated/layout/layout_component_style_base.hpp
index 28dd01b..60163da 100644
--- a/include/rive/generated/layout/layout_component_style_base.hpp
+++ b/include/rive/generated/layout/layout_component_style_base.hpp
@@ -59,6 +59,11 @@
     static const uint16_t flexShrinkPropertyKey = 522;
     static const uint16_t flexBasisPropertyKey = 523;
     static const uint16_t aspectRatioPropertyKey = 524;
+    static const uint16_t scaleTypePropertyKey = 546;
+    static const uint16_t animationStyleTypePropertyKey = 589;
+    static const uint16_t interpolationTypePropertyKey = 590;
+    static const uint16_t interpolatorIdPropertyKey = 591;
+    static const uint16_t interpolationTimePropertyKey = 592;
 
 private:
     uint32_t m_LayoutFlags0 = 0x5000412;
@@ -91,6 +96,11 @@
     float m_FlexShrink = 1.0f;
     float m_FlexBasis = 1.0f;
     float m_AspectRatio = 0.0f;
+    uint32_t m_ScaleType = 0;
+    uint32_t m_AnimationStyleType = 0;
+    uint32_t m_InterpolationType = 0;
+    uint32_t m_InterpolatorId = -1;
+    float m_InterpolationTime = 0.0f;
 
 public:
     inline uint32_t layoutFlags0() const { return m_LayoutFlags0; }
@@ -423,6 +433,61 @@
         aspectRatioChanged();
     }
 
+    inline uint32_t scaleType() const { return m_ScaleType; }
+    void scaleType(uint32_t value)
+    {
+        if (m_ScaleType == value)
+        {
+            return;
+        }
+        m_ScaleType = value;
+        scaleTypeChanged();
+    }
+
+    inline uint32_t animationStyleType() const { return m_AnimationStyleType; }
+    void animationStyleType(uint32_t value)
+    {
+        if (m_AnimationStyleType == value)
+        {
+            return;
+        }
+        m_AnimationStyleType = value;
+        animationStyleTypeChanged();
+    }
+
+    inline uint32_t interpolationType() const { return m_InterpolationType; }
+    void interpolationType(uint32_t value)
+    {
+        if (m_InterpolationType == value)
+        {
+            return;
+        }
+        m_InterpolationType = value;
+        interpolationTypeChanged();
+    }
+
+    inline uint32_t interpolatorId() const { return m_InterpolatorId; }
+    void interpolatorId(uint32_t value)
+    {
+        if (m_InterpolatorId == value)
+        {
+            return;
+        }
+        m_InterpolatorId = value;
+        interpolatorIdChanged();
+    }
+
+    inline float interpolationTime() const { return m_InterpolationTime; }
+    void interpolationTime(float value)
+    {
+        if (m_InterpolationTime == value)
+        {
+            return;
+        }
+        m_InterpolationTime = value;
+        interpolationTimeChanged();
+    }
+
     Core* clone() const override;
     void copy(const LayoutComponentStyleBase& object)
     {
@@ -456,6 +521,11 @@
         m_FlexShrink = object.m_FlexShrink;
         m_FlexBasis = object.m_FlexBasis;
         m_AspectRatio = object.m_AspectRatio;
+        m_ScaleType = object.m_ScaleType;
+        m_AnimationStyleType = object.m_AnimationStyleType;
+        m_InterpolationType = object.m_InterpolationType;
+        m_InterpolatorId = object.m_InterpolatorId;
+        m_InterpolationTime = object.m_InterpolationTime;
         Component::copy(object);
     }
 
@@ -553,6 +623,21 @@
             case aspectRatioPropertyKey:
                 m_AspectRatio = CoreDoubleType::deserialize(reader);
                 return true;
+            case scaleTypePropertyKey:
+                m_ScaleType = CoreUintType::deserialize(reader);
+                return true;
+            case animationStyleTypePropertyKey:
+                m_AnimationStyleType = CoreUintType::deserialize(reader);
+                return true;
+            case interpolationTypePropertyKey:
+                m_InterpolationType = CoreUintType::deserialize(reader);
+                return true;
+            case interpolatorIdPropertyKey:
+                m_InterpolatorId = CoreUintType::deserialize(reader);
+                return true;
+            case interpolationTimePropertyKey:
+                m_InterpolationTime = CoreDoubleType::deserialize(reader);
+                return true;
         }
         return Component::deserialize(propertyKey, reader);
     }
@@ -588,6 +673,11 @@
     virtual void flexShrinkChanged() {}
     virtual void flexBasisChanged() {}
     virtual void aspectRatioChanged() {}
+    virtual void scaleTypeChanged() {}
+    virtual void animationStyleTypeChanged() {}
+    virtual void interpolationTypeChanged() {}
+    virtual void interpolatorIdChanged() {}
+    virtual void interpolationTimeChanged() {}
 };
 } // namespace rive
 
diff --git a/include/rive/generated/layout_component_absolute_base.hpp b/include/rive/generated/layout_component_absolute_base.hpp
deleted file mode 100644
index b61486f..0000000
--- a/include/rive/generated/layout_component_absolute_base.hpp
+++ /dev/null
@@ -1,39 +0,0 @@
-#ifndef _RIVE_ABSOLUTE_LAYOUT_COMPONENT_BASE_HPP_
-#define _RIVE_ABSOLUTE_LAYOUT_COMPONENT_BASE_HPP_
-#include "rive/layout_component.hpp"
-namespace rive
-{
-class AbsoluteLayoutComponentBase : public LayoutComponent
-{
-protected:
-    typedef LayoutComponent Super;
-
-public:
-    static const uint16_t typeKey = 423;
-
-    /// Helper to quickly determine if a core object extends another without RTTI
-    /// at runtime.
-    bool isTypeOf(uint16_t typeKey) const override
-    {
-        switch (typeKey)
-        {
-            case AbsoluteLayoutComponentBase::typeKey:
-            case LayoutComponentBase::typeKey:
-            case WorldTransformComponentBase::typeKey:
-            case ContainerComponentBase::typeKey:
-            case ComponentBase::typeKey:
-                return true;
-            default:
-                return false;
-        }
-    }
-
-    uint16_t coreType() const override { return typeKey; }
-
-    Core* clone() const override;
-
-protected:
-};
-} // namespace rive
-
-#endif
\ No newline at end of file
diff --git a/include/rive/layout/layout_component_style.hpp b/include/rive/layout/layout_component_style.hpp
index 6933593..4b8c0ff 100644
--- a/include/rive/layout/layout_component_style.hpp
+++ b/include/rive/layout/layout_component_style.hpp
@@ -49,12 +49,37 @@
 extern BitFieldLoc MaxWidthUnitsBits;
 extern BitFieldLoc MaxHeightUnitsBits;
 
+enum class LayoutAnimationStyle : uint8_t
+{
+    none,
+    custom,
+    inherit
+};
+
+enum class LayoutStyleInterpolation : uint8_t
+{
+    hold,
+    linear,
+    cubic,
+    elastic
+};
+
+class KeyFrameInterpolator;
 class LayoutComponentStyle : public LayoutComponentStyleBase
 {
+private:
+#ifdef WITH_RIVE_LAYOUT
+    KeyFrameInterpolator* m_interpolator;
+#endif
+
 public:
     LayoutComponentStyle() {}
 
 #ifdef WITH_RIVE_LAYOUT
+    StatusCode onAddedDirty(CoreContext* context) override;
+    KeyFrameInterpolator* interpolator();
+    LayoutStyleInterpolation interpolation();
+    LayoutAnimationStyle animationStyle();
     YGDisplay display();
     YGPositionType positionType();
 
@@ -97,6 +122,7 @@
 #endif
 
     void markLayoutNodeDirty();
+    void markLayoutStyleDirty();
 
     void layoutFlags0Changed() override;
     void layoutFlags1Changed() override;
diff --git a/include/rive/layout_component.hpp b/include/rive/layout_component.hpp
index 1049e3b..4a5be75 100644
--- a/include/rive/layout_component.hpp
+++ b/include/rive/layout_component.hpp
@@ -11,6 +11,10 @@
 #include <stdio.h>
 namespace rive
 {
+
+class AABB;
+class KeyFrameInterpolator;
+
 struct LayoutData
 {
 #ifdef WITH_RIVE_LAYOUT
@@ -19,6 +23,13 @@
 #endif
 };
 
+struct LayoutAnimationData
+{
+    float elapsedSeconds = 0;
+    AABB fromBounds = AABB();
+    AABB toBounds = AABB();
+};
+
 class LayoutComponent : public LayoutComponentBase
 {
 private:
@@ -30,6 +41,11 @@
     float m_layoutLocationX = 0;
     float m_layoutLocationY = 0;
 
+    LayoutAnimationData m_animationData;
+    KeyFrameInterpolator* m_inheritedInterpolator;
+    LayoutStyleInterpolation m_inheritedInterpolation = LayoutStyleInterpolation::hold;
+    float m_inheritedInterpolationTime = 0;
+
 #ifdef WITH_RIVE_LAYOUT
 private:
     YGNode& layoutNode() { return m_layoutData->node; }
@@ -37,6 +53,7 @@
     void syncLayoutChildren();
     void propagateSizeToChildren(ContainerComponent* component);
     AABB findMaxIntrinsicSize(ContainerComponent* component, AABB maxIntrinsicSize);
+    bool applyInterpolation(double elapsedSeconds);
 
 protected:
     void calculateLayout();
@@ -54,10 +71,33 @@
     void update(ComponentDirt value) override;
     StatusCode onAddedDirty(CoreContext* context) override;
 
+    bool advance(double elapsedSeconds);
+    bool animates();
+    LayoutAnimationStyle animationStyle();
+    KeyFrameInterpolator* interpolator();
+    LayoutStyleInterpolation interpolation();
+    float interpolationTime();
+
+    void cascadeAnimationStyle(LayoutStyleInterpolation inheritedInterpolation,
+                               KeyFrameInterpolator* inheritedInterpolator,
+                               float inheritedInterpolationTime);
+    void setInheritedInterpolation(LayoutStyleInterpolation inheritedInterpolation,
+                                   KeyFrameInterpolator* inheritedInterpolator,
+                                   float inheritedInterpolationTime);
+    void clearInheritedInterpolation();
+    AABB layoutBounds()
+    {
+        return AABB(m_layoutLocationX,
+                    m_layoutLocationY,
+                    m_layoutLocationX + m_layoutSizeWidth,
+                    m_layoutLocationY + m_layoutSizeHeight);
+    }
+
 #endif
     void buildDependencies() override;
 
     void markLayoutNodeDirty();
+    void markLayoutStyleDirty();
     void clipChanged() override;
     void widthChanged() override;
     void heightChanged() override;
diff --git a/include/rive/layout_component_absolute.hpp b/include/rive/layout_component_absolute.hpp
deleted file mode 100644
index 955f64c..0000000
--- a/include/rive/layout_component_absolute.hpp
+++ /dev/null
@@ -1,13 +0,0 @@
-#ifndef _RIVE_ABSOLUTE_LAYOUT_COMPONENT_HPP_
-#define _RIVE_ABSOLUTE_LAYOUT_COMPONENT_HPP_
-#include "rive/generated/layout_component_absolute_base.hpp"
-#include <stdio.h>
-namespace rive
-{
-class AbsoluteLayoutComponent : public AbsoluteLayoutComponentBase
-{
-public:
-};
-} // namespace rive
-
-#endif
\ No newline at end of file
diff --git a/src/artboard.cpp b/src/artboard.cpp
index 6c0fe6a..4ecf5da 100644
--- a/src/artboard.cpp
+++ b/src/artboard.cpp
@@ -532,6 +532,7 @@
 
 bool Artboard::advanceInternal(double elapsedSeconds, bool isRoot)
 {
+    bool didUpdate = false;
     m_HasChangedDrawOrderInLastUpdate = false;
 #ifdef WITH_RIVE_LAYOUT
     if (!m_dirtyLayout.empty())
@@ -543,12 +544,21 @@
         }
         m_dirtyLayout.clear();
         calculateLayout();
+        if (hasDirt(ComponentDirt::LayoutStyle))
+        {
+            cascadeAnimationStyle(interpolation(), interpolator(), interpolationTime());
+        }
         for (auto dep : m_DependencyOrder)
         {
             if (dep->is<LayoutComponent>())
             {
                 auto layout = dep->as<LayoutComponent>();
                 layout->updateLayoutBounds();
+                if ((dep == this && Super::advance(elapsedSeconds)) ||
+                    layout->advance(elapsedSeconds))
+                {
+                    didUpdate = true;
+                }
             }
         }
     }
@@ -564,7 +574,10 @@
     {
         updateDataBinds();
     }
-    bool didUpdate = updateComponents();
+    if (updateComponents())
+    {
+        didUpdate = true;
+    }
     if (!m_JoysticksApplyBeforeUpdate)
     {
         for (auto joystick : m_Joysticks)
diff --git a/src/generated/layout_component_absolute_base.cpp b/src/generated/layout_component_absolute_base.cpp
deleted file mode 100644
index c35c4a8..0000000
--- a/src/generated/layout_component_absolute_base.cpp
+++ /dev/null
@@ -1,11 +0,0 @@
-#include "rive/generated/layout_component_absolute_base.hpp"
-#include "rive/layout_component_absolute.hpp"
-
-using namespace rive;
-
-Core* AbsoluteLayoutComponentBase::clone() const
-{
-    auto cloned = new AbsoluteLayoutComponent();
-    cloned->copy(*this);
-    return cloned;
-}
diff --git a/src/layout/layout_component_style.cpp b/src/layout/layout_component_style.cpp
index c1693f6..325e92a 100644
--- a/src/layout/layout_component_style.cpp
+++ b/src/layout/layout_component_style.cpp
@@ -1,3 +1,5 @@
+#include "rive/animation/keyframe_interpolator.hpp"
+#include "rive/core_context.hpp"
 #include "rive/layout_component.hpp"
 #include "rive/layout/layout_component_style.hpp"
 #include <vector>
@@ -46,6 +48,19 @@
 BitFieldLoc rive::MaxHeightUnitsBits = BitFieldLoc(10, 11);
 
 #ifdef WITH_RIVE_LAYOUT
+
+KeyFrameInterpolator* LayoutComponentStyle::interpolator() { return m_interpolator; }
+
+LayoutStyleInterpolation LayoutComponentStyle::interpolation()
+{
+    return LayoutStyleInterpolation(interpolationType());
+}
+
+LayoutAnimationStyle LayoutComponentStyle::animationStyle()
+{
+    return LayoutAnimationStyle(animationStyleType());
+}
+
 YGDisplay LayoutComponentStyle::display() { return YGDisplay(DisplayBits.read(layoutFlags0())); }
 
 YGPositionType LayoutComponentStyle::positionType()
@@ -208,8 +223,33 @@
         parent()->as<LayoutComponent>()->markLayoutNodeDirty();
     }
 }
+
+void LayoutComponentStyle::markLayoutStyleDirty()
+{
+    if (parent()->is<LayoutComponent>())
+    {
+        parent()->as<LayoutComponent>()->markLayoutStyleDirty();
+    }
+}
+
+StatusCode LayoutComponentStyle::onAddedDirty(CoreContext* context)
+{
+    auto code = Super::onAddedDirty(context);
+    if (code != StatusCode::Ok)
+    {
+        return code;
+    }
+
+    auto coreObject = context->resolve(interpolatorId());
+    if (coreObject != nullptr && coreObject->is<KeyFrameInterpolator>())
+    {
+        m_interpolator = static_cast<KeyFrameInterpolator*>(coreObject);
+    }
+    return StatusCode::Ok;
+}
 #else
 void LayoutComponentStyle::markLayoutNodeDirty() {}
+void LayoutComponentStyle::markLayoutStyleDirty() {}
 #endif
 
 void LayoutComponentStyle::layoutFlags0Changed() { markLayoutNodeDirty(); }
diff --git a/src/layout_component.cpp b/src/layout_component.cpp
index c9b600f..402d0f4 100644
--- a/src/layout_component.cpp
+++ b/src/layout_component.cpp
@@ -1,3 +1,4 @@
+#include "rive/animation/keyframe_interpolator.hpp"
 #include "rive/artboard.hpp"
 #include "rive/layout_component.hpp"
 #include "rive/node.hpp"
@@ -21,7 +22,10 @@
 }
 
 #ifdef WITH_RIVE_LAYOUT
-LayoutComponent::LayoutComponent() : m_layoutData(std::unique_ptr<LayoutData>(new LayoutData())) {}
+LayoutComponent::LayoutComponent() : m_layoutData(std::unique_ptr<LayoutData>(new LayoutData()))
+{
+    layoutNode().getConfig()->setPointScaleFactor(0);
+}
 
 StatusCode LayoutComponent::onAddedDirty(CoreContext* context)
 {
@@ -39,6 +43,7 @@
     m_style = static_cast<LayoutComponentStyle*>(coreStyle);
     addChild(m_style);
     artboard()->markLayoutDirty(this);
+    markLayoutStyleDirty();
     if (parent() != nullptr && parent()->is<LayoutComponent>())
     {
         parent()->as<LayoutComponent>()->syncLayoutChildren();
@@ -237,8 +242,24 @@
     auto top = YGNodeLayoutGetTop(node);
     auto width = YGNodeLayoutGetWidth(node);
     auto height = YGNodeLayoutGetHeight(node);
-    if (left != m_layoutLocationX || top != m_layoutLocationY || width != m_layoutSizeWidth ||
-        height != m_layoutSizeHeight)
+    if (animates())
+    {
+        auto toBounds = m_animationData.toBounds;
+        if (left != toBounds.left() || top != toBounds.top() || width != toBounds.width() ||
+            height != toBounds.height())
+        {
+            m_animationData.elapsedSeconds = 0;
+            m_animationData.fromBounds = AABB(m_layoutLocationX,
+                                              m_layoutLocationY,
+                                              m_layoutLocationX + this->width(),
+                                              m_layoutLocationY + this->height());
+            m_animationData.toBounds = AABB(left, top, left + width, top + height);
+            propagateSize();
+            markWorldTransformDirty();
+        }
+    }
+    else if (left != m_layoutLocationX || top != m_layoutLocationY || width != m_layoutSizeWidth ||
+             height != m_layoutSizeHeight)
     {
         m_layoutLocationX = left;
         m_layoutLocationY = top;
@@ -249,13 +270,215 @@
     }
 }
 
+bool LayoutComponent::advance(double elapsedSeconds) { return applyInterpolation(elapsedSeconds); }
+
+bool LayoutComponent::animates()
+{
+    if (m_style == nullptr)
+    {
+        return false;
+    }
+    return m_style->positionType() == YGPositionType::YGPositionTypeRelative &&
+           m_style->animationStyle() != LayoutAnimationStyle::none &&
+           interpolation() != LayoutStyleInterpolation::hold && interpolationTime() > 0;
+}
+
+LayoutAnimationStyle LayoutComponent::animationStyle()
+{
+    if (m_style == nullptr)
+    {
+        return LayoutAnimationStyle::none;
+    }
+    return m_style->animationStyle();
+}
+
+KeyFrameInterpolator* LayoutComponent::interpolator()
+{
+    if (m_style == nullptr)
+    {
+        return nullptr;
+    }
+    switch (m_style->animationStyle())
+    {
+        case LayoutAnimationStyle::inherit:
+            return m_inheritedInterpolator != nullptr ? m_inheritedInterpolator
+                                                      : m_style->interpolator();
+        case LayoutAnimationStyle::custom:
+            return m_style->interpolator();
+        default:
+            return nullptr;
+    }
+}
+
+LayoutStyleInterpolation LayoutComponent::interpolation()
+{
+    auto defaultInterpolation = LayoutStyleInterpolation::hold;
+    if (m_style == nullptr)
+    {
+        return defaultInterpolation;
+    }
+    switch (m_style->animationStyle())
+    {
+        case LayoutAnimationStyle::inherit:
+            return m_inheritedInterpolation;
+        case LayoutAnimationStyle::custom:
+            return m_style->interpolation();
+        default:
+            return defaultInterpolation;
+    }
+}
+
+float LayoutComponent::interpolationTime()
+{
+    if (m_style == nullptr)
+    {
+        return 0;
+    }
+    switch (m_style->animationStyle())
+    {
+        case LayoutAnimationStyle::inherit:
+            return m_inheritedInterpolationTime;
+        case LayoutAnimationStyle::custom:
+            return m_style->interpolationTime();
+        default:
+            return 0;
+    }
+}
+
+void LayoutComponent::cascadeAnimationStyle(LayoutStyleInterpolation inheritedInterpolation,
+                                            KeyFrameInterpolator* inheritedInterpolator,
+                                            float inheritedInterpolationTime)
+{
+    if (m_style != nullptr && m_style->animationStyle() == LayoutAnimationStyle::inherit)
+    {
+        setInheritedInterpolation(inheritedInterpolation,
+                                  inheritedInterpolator,
+                                  inheritedInterpolationTime);
+    }
+    else
+    {
+        clearInheritedInterpolation();
+    }
+    for (auto child : children())
+    {
+        if (child->is<LayoutComponent>())
+        {
+            child->as<LayoutComponent>()->cascadeAnimationStyle(interpolation(),
+                                                                interpolator(),
+                                                                interpolationTime());
+        }
+    }
+}
+
+void LayoutComponent::setInheritedInterpolation(LayoutStyleInterpolation inheritedInterpolation,
+                                                KeyFrameInterpolator* inheritedInterpolator,
+                                                float inheritedInterpolationTime)
+{
+    m_inheritedInterpolation = inheritedInterpolation;
+    m_inheritedInterpolator = inheritedInterpolator;
+    m_inheritedInterpolationTime = inheritedInterpolationTime;
+}
+
+void LayoutComponent::clearInheritedInterpolation()
+{
+    m_inheritedInterpolation = LayoutStyleInterpolation::hold;
+    m_inheritedInterpolator = nullptr;
+    m_inheritedInterpolationTime = 0;
+}
+
+bool LayoutComponent::applyInterpolation(double elapsedSeconds)
+{
+    if (!animates() || m_style == nullptr || m_animationData.toBounds == layoutBounds())
+    {
+        return false;
+    }
+    if (m_animationData.elapsedSeconds >= interpolationTime())
+    {
+        m_layoutLocationX = m_animationData.toBounds.left();
+        m_layoutLocationY = m_animationData.toBounds.top();
+        m_layoutSizeWidth = m_animationData.toBounds.width();
+        m_layoutSizeHeight = m_animationData.toBounds.height();
+        m_animationData.elapsedSeconds = 0;
+        markWorldTransformDirty();
+        return false;
+    }
+    float f = 1;
+    if (interpolationTime() > 0)
+    {
+        f = m_animationData.elapsedSeconds / interpolationTime();
+    }
+    bool needsAdvance = false;
+    auto fromBounds = m_animationData.fromBounds;
+    auto toBounds = m_animationData.toBounds;
+    auto left = m_layoutLocationX;
+    auto top = m_layoutLocationY;
+    auto width = m_layoutSizeWidth;
+    auto height = m_layoutSizeHeight;
+    if (toBounds.left() != left || toBounds.top() != top)
+    {
+        if (interpolation() == LayoutStyleInterpolation::linear)
+        {
+            left = fromBounds.left() + f * (toBounds.left() - fromBounds.left());
+            top = fromBounds.top() + f * (toBounds.top() - fromBounds.top());
+        }
+        else
+        {
+            if (interpolator() != nullptr)
+            {
+                left = interpolator()->transformValue(fromBounds.left(), toBounds.left(), f);
+                top = interpolator()->transformValue(fromBounds.top(), toBounds.top(), f);
+            }
+        }
+        needsAdvance = true;
+        m_layoutLocationX = left;
+        m_layoutLocationY = top;
+    }
+    if (toBounds.width() != width || toBounds.height() != height)
+    {
+        if (interpolation() == LayoutStyleInterpolation::linear)
+        {
+            width = fromBounds.width() + f * (toBounds.width() - fromBounds.width());
+            height = fromBounds.height() + f * (toBounds.height() - fromBounds.height());
+        }
+        else
+        {
+            if (interpolator() != nullptr)
+            {
+                width = interpolator()->transformValue(fromBounds.width(), toBounds.width(), f);
+                height = interpolator()->transformValue(fromBounds.height(), toBounds.height(), f);
+            }
+        }
+        needsAdvance = true;
+        m_layoutSizeWidth = width;
+        m_layoutSizeHeight = height;
+    }
+    m_animationData.elapsedSeconds = m_animationData.elapsedSeconds + (float)elapsedSeconds;
+    if (needsAdvance)
+    {
+        propagateSize();
+        markWorldTransformDirty();
+        markLayoutNodeDirty();
+    }
+    return needsAdvance;
+}
+
 void LayoutComponent::markLayoutNodeDirty()
 {
     layoutNode().markDirtyAndPropagate();
     artboard()->markLayoutDirty(this);
 }
+
+void LayoutComponent::markLayoutStyleDirty()
+{
+    addDirt(ComponentDirt::LayoutStyle);
+    if (artboard() != this)
+    {
+        artboard()->markLayoutStyleDirty();
+    }
+}
 #else
 void LayoutComponent::markLayoutNodeDirty() {}
+void LayoutComponent::markLayoutStyleDirty() {}
 #endif
 
 void LayoutComponent::clipChanged() { markLayoutNodeDirty(); }
diff --git a/test/layout_test.cpp b/test/layout_test.cpp
index c5c07ec..e7176a1 100644
--- a/test/layout_test.cpp
+++ b/test/layout_test.cpp
@@ -138,6 +138,6 @@
     auto bounds = text->localBounds();
     REQUIRE(bounds.left() == 0);
     REQUIRE(bounds.top() == 0);
-    REQUIRE(bounds.width() == 63.0f);
-    REQUIRE(bounds.height() == 73.0f);
+    REQUIRE(bounds.width() == 62.48047f);
+    REQUIRE(bounds.height() == 72.62695f);
 }