Layout drawable

- [x] Changes LayoutComponent to extend Drawable (implements ShapePaintContainer)
- [x] Fixes some API naming conflicts
- [x] Adds DrawableProxy to allow inserting custom draw commands into draw order (allows LayoutComponent fills to be drawn below children and strokes to be drawn above
- [x] Works with Fill/Stroke inspectors
- [x] Adds corner radius core props
- [x] Clipping
- [x] CPP Updates
- [x] Clipping in CPP
- [x] Deal with conflicting x/y properties in Node & Artboard (CPP)

https://github.com/rive-app/rive/assets/186340/5aec1cd5-6b00-4627-bfce-9cdeec8e3e96

Showing clipping and blend modes / opacity

https://github.com/user-attachments/assets/843b6c74-cec0-4333-8ef1-6fee9b910a59

Diffs=
114da4e39 Layout drawable (#7544)

Co-authored-by: Philip Chung <philterdesign@gmail.com>
diff --git a/.rive_head b/.rive_head
index 518b856..8cbe00c 100644
--- a/.rive_head
+++ b/.rive_head
@@ -1 +1 @@
-b5f342002be71c61608ad6c37835349eb4e7c719
+114da4e39ba61dd337e8f6f47c1c7b2fb2223915
diff --git a/dev/core_generator/lib/src/definition.dart b/dev/core_generator/lib/src/definition.dart
index 5a04630..bf94a2b 100644
--- a/dev/core_generator/lib/src/definition.dart
+++ b/dev/core_generator/lib/src/definition.dart
@@ -183,6 +183,11 @@
       for (final property in properties) {
         code.writeln('static const uint16_t ${property.name}PropertyKey = '
             '${property.key!.intValue};');
+        for (final altKey in property.key!.alternates) {
+          code.writeln(
+              'static const uint16_t ${altKey.stringValue}PropertyKey = '
+              '${altKey.intValue};');
+        }
       }
       if (storedProperties.any((prop) => !prop.isEncoded)) {
         code.writeln('private:');
@@ -486,6 +491,12 @@
         for (final property in properties) {
           ctxCode.writeln('case ${property.definition.name}Base'
               '::${property.name}PropertyKey:');
+          if (property.key != null) {
+            for (final altKey in property.key!.alternates) {
+              ctxCode.writeln('case ${property.definition.name}Base'
+                  '::${altKey.stringValue}PropertyKey:');
+            }
+          }
           ctxCode.writeln('object->as<${property.definition.name}Base>()->'
               '${property.name}(value);');
           ctxCode.writeln('break;');
@@ -506,6 +517,10 @@
         for (final property in properties) {
           ctxCode.writeln('case ${property.definition.name}Base'
               '::${property.name}PropertyKey:');
+          for (final altKey in property.key!.alternates) {
+            ctxCode.writeln('case ${property.definition.name}Base'
+                '::${altKey.stringValue}PropertyKey:');
+          }
           ctxCode
               .writeln('return object->as<${property.definition.name}Base>()->'
                   '${property.name}();');
@@ -528,6 +543,10 @@
         for (final property in properties) {
           ctxCode.writeln('case ${property.definition.name}Base'
               '::${property.name}PropertyKey:');
+          for (final altKey in property.key!.alternates) {
+            ctxCode.writeln('case ${property.definition.name}Base'
+                '::${altKey.stringValue}PropertyKey:');
+          }
         }
       }
       ctxCode.writeln('return Core${fieldType.capitalizedName}Type::id;');
@@ -567,6 +586,10 @@
         for (final property in properties) {
           ctxCode.writeln('case ${property.definition.name}Base'
               '::${property.name}PropertyKey:');
+          for (final altKey in property.key!.alternates) {
+            ctxCode.writeln('case ${property.definition.name}Base'
+                '::${altKey.stringValue}PropertyKey:');
+          }
           ctxCode
               .writeln('return object->is<${property.definition.name}Base>();');
         }
diff --git a/dev/core_generator/lib/src/key.dart b/dev/core_generator/lib/src/key.dart
index 8e1a8d0..ca83684 100644
--- a/dev/core_generator/lib/src/key.dart
+++ b/dev/core_generator/lib/src/key.dart
@@ -4,6 +4,7 @@
 class Key {
   final String? stringValue;
   final int? intValue;
+  final List<Key> alternates = [];
 
   bool get isMissing => intValue == null;
 
@@ -23,12 +24,36 @@
     }
     dynamic iv = data['int'];
     dynamic sv = data['string'];
+    dynamic av = data['alternates'];
     if (iv is int && sv is String) {
-      return Key(sv, iv);
+      final key = Key(sv, iv);
+      if (av is List) {
+        for (final a in av) {
+          if (a is Map<String, dynamic>) {
+            dynamic altiv = a['int'];
+            dynamic altsv = a['string'];
+            key.alternates.add(Key(altsv, altiv));
+          }
+        }
+      }
+      return key;
     }
     return null;
   }
 
-  Map<String, dynamic> serialize() =>
-      <String, dynamic>{'int': intValue, 'string': stringValue};
+  Map<String, dynamic> serialize() {
+    final json = <String, dynamic>{'int': intValue, 'string': stringValue};
+    final altsJson = [];
+    for (final alt in alternates) {
+      final altJson = <String, dynamic>{
+        'int': alt.intValue,
+        'string': alt.stringValue
+      };
+      altsJson.add(altJson);
+    }
+    if (altsJson.isNotEmpty) {
+      json['alternates'] = altsJson;
+    }
+    return json;
+  }
 }
diff --git a/dev/defs/artboard.json b/dev/defs/artboard.json
index 4079ccb..bb52736 100644
--- a/dev/defs/artboard.json
+++ b/dev/defs/artboard.json
@@ -6,24 +6,6 @@
   },
   "extends": "layout_component.json",
   "properties": {
-    "x": {
-      "type": "double",
-      "initialValue": "0",
-      "key": {
-        "int": 9,
-        "string": "x"
-      },
-      "description": "X coordinate in editor world space."
-    },
-    "y": {
-      "type": "double",
-      "initialValue": "0",
-      "key": {
-        "int": 10,
-        "string": "y"
-      },
-      "description": "Y coordinate in editor world space."
-    },
     "originX": {
       "type": "double",
       "initialValue": "0",
diff --git a/dev/defs/layout/layout_component_style.json b/dev/defs/layout/layout_component_style.json
index 9bddf11..cb46d46 100644
--- a/dev/defs/layout/layout_component_style.json
+++ b/dev/defs/layout/layout_component_style.json
@@ -717,6 +717,55 @@
         "string": "maxheightunitsvalue"
       },
       "description": ""
+    },
+    "linkCornerRadius": {
+      "type": "bool",
+      "initialValue": "true",
+      "key": {
+        "int": 639,
+        "string": "linkcornerradius"
+      },
+      "description": "Whether the TL corner radius defines all the radiuses"
+    },
+    "cornerRadiusTL": {
+      "type": "double",
+      "initialValue": "0",
+      "animates": true,
+      "key": {
+        "int": 640,
+        "string": "cornerradiustl"
+      },
+      "description": "Top left radius of the corners of this layout"
+    },
+    "cornerRadiusTR": {
+      "type": "double",
+      "initialValue": "0",
+      "animates": true,
+      "key": {
+        "int": 641,
+        "string": "cornerradiustr"
+      },
+      "description": "Top right radius of the corners of this layout"
+    },
+    "cornerRadiusBL": {
+      "type": "double",
+      "initialValue": "0",
+      "animates": true,
+      "key": {
+        "int": 642,
+        "string": "cornerradiusbl"
+      },
+      "description": "Bottom left radius of the corners of this layout"
+    },
+    "cornerRadiusBR": {
+      "type": "double",
+      "initialValue": "0",
+      "animates": true,
+      "key": {
+        "int": 643,
+        "string": "cornerradiusbr"
+      },
+      "description": "Bottom right radius of the corners of this layout"
     }
   }
 }
\ No newline at end of file
diff --git a/dev/defs/layout_component.json b/dev/defs/layout_component.json
index ac24d51..8aca893 100644
--- a/dev/defs/layout_component.json
+++ b/dev/defs/layout_component.json
@@ -4,7 +4,7 @@
     "int": 409,
     "string": "layoutcomponent"
   },
-  "extends": "world_transform_component.json",
+  "extends": "drawable.json",
   "properties": {
     "clip": {
       "type": "bool",
diff --git a/dev/defs/node.json b/dev/defs/node.json
index f604980..90ff182 100644
--- a/dev/defs/node.json
+++ b/dev/defs/node.json
@@ -14,7 +14,13 @@
       "group": "position",
       "key": {
         "int": 13,
-        "string": "x"
+        "string": "x",
+        "alternates": [
+          {
+            "int": 9,
+            "string": "xArtboard"
+          }
+        ]
       }
     },
     "y": {
@@ -25,7 +31,13 @@
       "group": "position",
       "key": {
         "int": 14,
-        "string": "y"
+        "string": "y",
+        "alternates": [
+          {
+            "int": 10,
+            "string": "yArtboard"
+          }
+        ]
       }
     },
     "styleValue": {
diff --git a/include/rive/artboard.hpp b/include/rive/artboard.hpp
index 9411ceb..4b7cc3d 100644
--- a/include/rive/artboard.hpp
+++ b/include/rive/artboard.hpp
@@ -13,7 +13,6 @@
 #include "rive/hit_info.hpp"
 #include "rive/math/aabb.hpp"
 #include "rive/renderer.hpp"
-#include "rive/shapes/shape_paint_container.hpp"
 #include "rive/text/text_value_run.hpp"
 #include "rive/event.hpp"
 #include "rive/audio/audio_engine.hpp"
@@ -48,7 +47,7 @@
 typedef void (*ArtboardCallback)(Artboard*);
 #endif
 
-class Artboard : public ArtboardBase, public CoreContext, public ShapePaintContainer
+class Artboard : public ArtboardBase, public CoreContext
 {
     friend class File;
     friend class ArtboardImporter;
@@ -71,8 +70,6 @@
 
     unsigned int m_DirtDepth = 0;
     RawPath m_backgroundRawPath;
-    rcp<RenderPath> m_BackgroundPath;
-    rcp<RenderPath> m_ClipPath;
     Factory* m_Factory = nullptr;
     Drawable* m_FirstDrawable = nullptr;
     bool m_IsInstance = false;
@@ -88,8 +85,7 @@
     void sortDependencies();
     void sortDrawOrder();
     void updateDataBinds();
-
-    Artboard* getArtboard() override { return this; }
+    void performUpdate(ComponentDirt value) override;
 
 #ifdef TESTING
 public:
@@ -114,15 +110,20 @@
 
     // EXPERIMENTAL -- for internal testing only for now.
     // DO NOT RELY ON THIS as it may change/disappear in the future.
-    Core* hitTest(HitInfo*, const Mat2D* = nullptr);
+    Core* hitTest(HitInfo*, const Mat2D&) override;
 
     void onComponentDirty(Component* component);
 
     /// Update components that depend on each other in DAG order.
     bool updateComponents();
-    void update(ComponentDirt value) override;
     void onDirty(ComponentDirt dirt) override;
 
+    // Artboards don't update their world transforms in the same way
+    // as other TransformComponents so we override this.
+    // This is because LayoutComponent extends Drawable, but
+    // Artboard is a special type of LayoutComponent
+    void updateWorldTransform() override {}
+
     void markLayoutDirty(LayoutComponent* layoutComponent)
     {
         m_dirtyLayout.insert(layoutComponent);
@@ -150,12 +151,13 @@
         kHideBG,
         kHideFG,
     };
-    void draw(Renderer* renderer, DrawOption = DrawOption::kNormal);
+    void draw(Renderer* renderer, DrawOption option);
+    void draw(Renderer* renderer) override;
     void addToRenderPath(RenderPath* path, const Mat2D& transform);
 
 #ifdef TESTING
-    RenderPath* clipPath() const { return m_ClipPath.get(); }
-    RenderPath* backgroundPath() const { return m_BackgroundPath.get(); }
+    RenderPath* clipPath() const { return m_clipPath.get(); }
+    RenderPath* backgroundPath() const { return m_backgroundPath.get(); }
 #endif
 
     const std::vector<Core*>& objects() const { return m_Objects; }
diff --git a/include/rive/drawable.hpp b/include/rive/drawable.hpp
index ca5e996..b4e79b8 100644
--- a/include/rive/drawable.hpp
+++ b/include/rive/drawable.hpp
@@ -28,7 +28,7 @@
 
 public:
     BlendMode blendMode() const { return (BlendMode)blendModeValue(); }
-    ClipResult clip(Renderer* renderer) const;
+    ClipResult applyClip(Renderer* renderer) const;
     virtual void draw(Renderer* renderer) = 0;
     virtual Core* hitTest(HitInfo*, const Mat2D&) = 0;
     void addClippingShape(ClippingShape* shape);
@@ -49,6 +49,25 @@
 
     StatusCode onAddedDirty(CoreContext* context) override;
 };
+
+class ProxyDrawing
+{
+public:
+    virtual void drawProxy(Renderer* renderer) = 0;
+};
+
+class DrawableProxy : public Drawable
+{
+private:
+    ProxyDrawing* m_proxyDrawing;
+
+public:
+    DrawableProxy(ProxyDrawing* proxy) : m_proxyDrawing(proxy) {}
+
+    void draw(Renderer* renderer) override { m_proxyDrawing->drawProxy(renderer); }
+
+    Core* hitTest(HitInfo*, const Mat2D&) override { return nullptr; }
+};
 } // namespace rive
 
 #endif
diff --git a/include/rive/generated/artboard_base.hpp b/include/rive/generated/artboard_base.hpp
index ff19a02..b7ae1b9 100644
--- a/include/rive/generated/artboard_base.hpp
+++ b/include/rive/generated/artboard_base.hpp
@@ -21,6 +21,9 @@
         {
             case ArtboardBase::typeKey:
             case LayoutComponentBase::typeKey:
+            case DrawableBase::typeKey:
+            case NodeBase::typeKey:
+            case TransformComponentBase::typeKey:
             case WorldTransformComponentBase::typeKey:
             case ContainerComponentBase::typeKey:
             case ComponentBase::typeKey:
@@ -32,44 +35,18 @@
 
     uint16_t coreType() const override { return typeKey; }
 
-    static const uint16_t xPropertyKey = 9;
-    static const uint16_t yPropertyKey = 10;
     static const uint16_t originXPropertyKey = 11;
     static const uint16_t originYPropertyKey = 12;
     static const uint16_t defaultStateMachineIdPropertyKey = 236;
     static const uint16_t viewModelIdPropertyKey = 583;
 
 private:
-    float m_X = 0.0f;
-    float m_Y = 0.0f;
     float m_OriginX = 0.0f;
     float m_OriginY = 0.0f;
     uint32_t m_DefaultStateMachineId = -1;
     uint32_t m_ViewModelId = -1;
 
 public:
-    inline float x() const { return m_X; }
-    void x(float value)
-    {
-        if (m_X == value)
-        {
-            return;
-        }
-        m_X = value;
-        xChanged();
-    }
-
-    inline float y() const { return m_Y; }
-    void y(float value)
-    {
-        if (m_Y == value)
-        {
-            return;
-        }
-        m_Y = value;
-        yChanged();
-    }
-
     inline float originX() const { return m_OriginX; }
     void originX(float value)
     {
@@ -117,8 +94,6 @@
     Core* clone() const override;
     void copy(const ArtboardBase& object)
     {
-        m_X = object.m_X;
-        m_Y = object.m_Y;
         m_OriginX = object.m_OriginX;
         m_OriginY = object.m_OriginY;
         m_DefaultStateMachineId = object.m_DefaultStateMachineId;
@@ -130,12 +105,6 @@
     {
         switch (propertyKey)
         {
-            case xPropertyKey:
-                m_X = CoreDoubleType::deserialize(reader);
-                return true;
-            case yPropertyKey:
-                m_Y = CoreDoubleType::deserialize(reader);
-                return true;
             case originXPropertyKey:
                 m_OriginX = CoreDoubleType::deserialize(reader);
                 return true;
@@ -153,8 +122,6 @@
     }
 
 protected:
-    virtual void xChanged() {}
-    virtual void yChanged() {}
     virtual void originXChanged() {}
     virtual void originYChanged() {}
     virtual void defaultStateMachineIdChanged() {}
diff --git a/include/rive/generated/core_registry.hpp b/include/rive/generated/core_registry.hpp
index 3096b79..abeae9a 100644
--- a/include/rive/generated/core_registry.hpp
+++ b/include/rive/generated/core_registry.hpp
@@ -516,6 +516,9 @@
             case LayoutComponentStyleBase::intrinsicallySizedValuePropertyKey:
                 object->as<LayoutComponentStyleBase>()->intrinsicallySizedValue(value);
                 break;
+            case LayoutComponentStyleBase::linkCornerRadiusPropertyKey:
+                object->as<LayoutComponentStyleBase>()->linkCornerRadius(value);
+                break;
             case NestedSimpleAnimationBase::isPlayingPropertyKey:
                 object->as<NestedSimpleAnimationBase>()->isPlaying(value);
                 break;
@@ -1147,9 +1150,11 @@
                 object->as<TransformComponentBase>()->scaleY(value);
                 break;
             case NodeBase::xPropertyKey:
+            case NodeBase::xArtboardPropertyKey:
                 object->as<NodeBase>()->x(value);
                 break;
             case NodeBase::yPropertyKey:
+            case NodeBase::yArtboardPropertyKey:
                 object->as<NodeBase>()->y(value);
                 break;
             case LayoutComponentStyleBase::gapHorizontalPropertyKey:
@@ -1236,6 +1241,18 @@
             case LayoutComponentStyleBase::interpolationTimePropertyKey:
                 object->as<LayoutComponentStyleBase>()->interpolationTime(value);
                 break;
+            case LayoutComponentStyleBase::cornerRadiusTLPropertyKey:
+                object->as<LayoutComponentStyleBase>()->cornerRadiusTL(value);
+                break;
+            case LayoutComponentStyleBase::cornerRadiusTRPropertyKey:
+                object->as<LayoutComponentStyleBase>()->cornerRadiusTR(value);
+                break;
+            case LayoutComponentStyleBase::cornerRadiusBLPropertyKey:
+                object->as<LayoutComponentStyleBase>()->cornerRadiusBL(value);
+                break;
+            case LayoutComponentStyleBase::cornerRadiusBRPropertyKey:
+                object->as<LayoutComponentStyleBase>()->cornerRadiusBR(value);
+                break;
             case NestedLinearAnimationBase::mixPropertyKey:
                 object->as<NestedLinearAnimationBase>()->mix(value);
                 break;
@@ -1416,12 +1433,6 @@
             case LayoutComponentBase::heightPropertyKey:
                 object->as<LayoutComponentBase>()->height(value);
                 break;
-            case ArtboardBase::xPropertyKey:
-                object->as<ArtboardBase>()->x(value);
-                break;
-            case ArtboardBase::yPropertyKey:
-                object->as<ArtboardBase>()->y(value);
-                break;
             case ArtboardBase::originXPropertyKey:
                 object->as<ArtboardBase>()->originX(value);
                 break;
@@ -1625,6 +1636,8 @@
                 return object->as<FollowPathConstraintBase>()->offset();
             case LayoutComponentStyleBase::intrinsicallySizedValuePropertyKey:
                 return object->as<LayoutComponentStyleBase>()->intrinsicallySizedValue();
+            case LayoutComponentStyleBase::linkCornerRadiusPropertyKey:
+                return object->as<LayoutComponentStyleBase>()->linkCornerRadius();
             case NestedSimpleAnimationBase::isPlayingPropertyKey:
                 return object->as<NestedSimpleAnimationBase>()->isPlaying();
             case KeyFrameBoolBase::valuePropertyKey:
@@ -2058,8 +2071,10 @@
             case TransformComponentBase::scaleYPropertyKey:
                 return object->as<TransformComponentBase>()->scaleY();
             case NodeBase::xPropertyKey:
+            case NodeBase::xArtboardPropertyKey:
                 return object->as<NodeBase>()->x();
             case NodeBase::yPropertyKey:
+            case NodeBase::yArtboardPropertyKey:
                 return object->as<NodeBase>()->y();
             case LayoutComponentStyleBase::gapHorizontalPropertyKey:
                 return object->as<LayoutComponentStyleBase>()->gapHorizontal();
@@ -2117,6 +2132,14 @@
                 return object->as<LayoutComponentStyleBase>()->aspectRatio();
             case LayoutComponentStyleBase::interpolationTimePropertyKey:
                 return object->as<LayoutComponentStyleBase>()->interpolationTime();
+            case LayoutComponentStyleBase::cornerRadiusTLPropertyKey:
+                return object->as<LayoutComponentStyleBase>()->cornerRadiusTL();
+            case LayoutComponentStyleBase::cornerRadiusTRPropertyKey:
+                return object->as<LayoutComponentStyleBase>()->cornerRadiusTR();
+            case LayoutComponentStyleBase::cornerRadiusBLPropertyKey:
+                return object->as<LayoutComponentStyleBase>()->cornerRadiusBL();
+            case LayoutComponentStyleBase::cornerRadiusBRPropertyKey:
+                return object->as<LayoutComponentStyleBase>()->cornerRadiusBR();
             case NestedLinearAnimationBase::mixPropertyKey:
                 return object->as<NestedLinearAnimationBase>()->mix();
             case NestedSimpleAnimationBase::speedPropertyKey:
@@ -2237,10 +2260,6 @@
                 return object->as<LayoutComponentBase>()->width();
             case LayoutComponentBase::heightPropertyKey:
                 return object->as<LayoutComponentBase>()->height();
-            case ArtboardBase::xPropertyKey:
-                return object->as<ArtboardBase>()->x();
-            case ArtboardBase::yPropertyKey:
-                return object->as<ArtboardBase>()->y();
             case ArtboardBase::originXPropertyKey:
                 return object->as<ArtboardBase>()->originX();
             case ArtboardBase::originYPropertyKey:
@@ -2367,6 +2386,7 @@
             case FollowPathConstraintBase::orientPropertyKey:
             case FollowPathConstraintBase::offsetPropertyKey:
             case LayoutComponentStyleBase::intrinsicallySizedValuePropertyKey:
+            case LayoutComponentStyleBase::linkCornerRadiusPropertyKey:
             case NestedSimpleAnimationBase::isPlayingPropertyKey:
             case KeyFrameBoolBase::valuePropertyKey:
             case ListenerAlignTargetBase::preserveOffsetPropertyKey:
@@ -2574,7 +2594,9 @@
             case TransformComponentBase::scaleXPropertyKey:
             case TransformComponentBase::scaleYPropertyKey:
             case NodeBase::xPropertyKey:
+            case NodeBase::xArtboardPropertyKey:
             case NodeBase::yPropertyKey:
+            case NodeBase::yArtboardPropertyKey:
             case LayoutComponentStyleBase::gapHorizontalPropertyKey:
             case LayoutComponentStyleBase::gapVerticalPropertyKey:
             case LayoutComponentStyleBase::maxWidthPropertyKey:
@@ -2603,6 +2625,10 @@
             case LayoutComponentStyleBase::flexBasisPropertyKey:
             case LayoutComponentStyleBase::aspectRatioPropertyKey:
             case LayoutComponentStyleBase::interpolationTimePropertyKey:
+            case LayoutComponentStyleBase::cornerRadiusTLPropertyKey:
+            case LayoutComponentStyleBase::cornerRadiusTRPropertyKey:
+            case LayoutComponentStyleBase::cornerRadiusBLPropertyKey:
+            case LayoutComponentStyleBase::cornerRadiusBRPropertyKey:
             case NestedLinearAnimationBase::mixPropertyKey:
             case NestedSimpleAnimationBase::speedPropertyKey:
             case AdvanceableStateBase::speedPropertyKey:
@@ -2663,8 +2689,6 @@
             case CubicDetachedVertexBase::outDistancePropertyKey:
             case LayoutComponentBase::widthPropertyKey:
             case LayoutComponentBase::heightPropertyKey:
-            case ArtboardBase::xPropertyKey:
-            case ArtboardBase::yPropertyKey:
             case ArtboardBase::originXPropertyKey:
             case ArtboardBase::originYPropertyKey:
             case JoystickBase::xPropertyKey:
@@ -2770,6 +2794,8 @@
                 return object->is<FollowPathConstraintBase>();
             case LayoutComponentStyleBase::intrinsicallySizedValuePropertyKey:
                 return object->is<LayoutComponentStyleBase>();
+            case LayoutComponentStyleBase::linkCornerRadiusPropertyKey:
+                return object->is<LayoutComponentStyleBase>();
             case NestedSimpleAnimationBase::isPlayingPropertyKey:
                 return object->is<NestedSimpleAnimationBase>();
             case KeyFrameBoolBase::valuePropertyKey:
@@ -3175,8 +3201,10 @@
             case TransformComponentBase::scaleYPropertyKey:
                 return object->is<TransformComponentBase>();
             case NodeBase::xPropertyKey:
+            case NodeBase::xArtboardPropertyKey:
                 return object->is<NodeBase>();
             case NodeBase::yPropertyKey:
+            case NodeBase::yArtboardPropertyKey:
                 return object->is<NodeBase>();
             case LayoutComponentStyleBase::gapHorizontalPropertyKey:
                 return object->is<LayoutComponentStyleBase>();
@@ -3234,6 +3262,14 @@
                 return object->is<LayoutComponentStyleBase>();
             case LayoutComponentStyleBase::interpolationTimePropertyKey:
                 return object->is<LayoutComponentStyleBase>();
+            case LayoutComponentStyleBase::cornerRadiusTLPropertyKey:
+                return object->is<LayoutComponentStyleBase>();
+            case LayoutComponentStyleBase::cornerRadiusTRPropertyKey:
+                return object->is<LayoutComponentStyleBase>();
+            case LayoutComponentStyleBase::cornerRadiusBLPropertyKey:
+                return object->is<LayoutComponentStyleBase>();
+            case LayoutComponentStyleBase::cornerRadiusBRPropertyKey:
+                return object->is<LayoutComponentStyleBase>();
             case NestedLinearAnimationBase::mixPropertyKey:
                 return object->is<NestedLinearAnimationBase>();
             case NestedSimpleAnimationBase::speedPropertyKey:
@@ -3354,10 +3390,6 @@
                 return object->is<LayoutComponentBase>();
             case LayoutComponentBase::heightPropertyKey:
                 return object->is<LayoutComponentBase>();
-            case ArtboardBase::xPropertyKey:
-                return object->is<ArtboardBase>();
-            case ArtboardBase::yPropertyKey:
-                return object->is<ArtboardBase>();
             case ArtboardBase::originXPropertyKey:
                 return object->is<ArtboardBase>();
             case ArtboardBase::originYPropertyKey:
diff --git a/include/rive/generated/layout/layout_component_style_base.hpp b/include/rive/generated/layout/layout_component_style_base.hpp
index b62fa97..a6f0cb5 100644
--- a/include/rive/generated/layout/layout_component_style_base.hpp
+++ b/include/rive/generated/layout/layout_component_style_base.hpp
@@ -98,6 +98,11 @@
     static const uint16_t minHeightUnitsValuePropertyKey = 628;
     static const uint16_t maxWidthUnitsValuePropertyKey = 629;
     static const uint16_t maxHeightUnitsValuePropertyKey = 630;
+    static const uint16_t linkCornerRadiusPropertyKey = 639;
+    static const uint16_t cornerRadiusTLPropertyKey = 640;
+    static const uint16_t cornerRadiusTRPropertyKey = 641;
+    static const uint16_t cornerRadiusBLPropertyKey = 642;
+    static const uint16_t cornerRadiusBRPropertyKey = 643;
 
 private:
     float m_GapHorizontal = 0.0f;
@@ -168,6 +173,11 @@
     uint32_t m_MinHeightUnitsValue = 0;
     uint32_t m_MaxWidthUnitsValue = 0;
     uint32_t m_MaxHeightUnitsValue = 0;
+    bool m_LinkCornerRadius = true;
+    float m_CornerRadiusTL = 0.0f;
+    float m_CornerRadiusTR = 0.0f;
+    float m_CornerRadiusBL = 0.0f;
+    float m_CornerRadiusBR = 0.0f;
 
 public:
     inline float gapHorizontal() const { return m_GapHorizontal; }
@@ -918,6 +928,61 @@
         maxHeightUnitsValueChanged();
     }
 
+    inline bool linkCornerRadius() const { return m_LinkCornerRadius; }
+    void linkCornerRadius(bool value)
+    {
+        if (m_LinkCornerRadius == value)
+        {
+            return;
+        }
+        m_LinkCornerRadius = value;
+        linkCornerRadiusChanged();
+    }
+
+    inline float cornerRadiusTL() const { return m_CornerRadiusTL; }
+    void cornerRadiusTL(float value)
+    {
+        if (m_CornerRadiusTL == value)
+        {
+            return;
+        }
+        m_CornerRadiusTL = value;
+        cornerRadiusTLChanged();
+    }
+
+    inline float cornerRadiusTR() const { return m_CornerRadiusTR; }
+    void cornerRadiusTR(float value)
+    {
+        if (m_CornerRadiusTR == value)
+        {
+            return;
+        }
+        m_CornerRadiusTR = value;
+        cornerRadiusTRChanged();
+    }
+
+    inline float cornerRadiusBL() const { return m_CornerRadiusBL; }
+    void cornerRadiusBL(float value)
+    {
+        if (m_CornerRadiusBL == value)
+        {
+            return;
+        }
+        m_CornerRadiusBL = value;
+        cornerRadiusBLChanged();
+    }
+
+    inline float cornerRadiusBR() const { return m_CornerRadiusBR; }
+    void cornerRadiusBR(float value)
+    {
+        if (m_CornerRadiusBR == value)
+        {
+            return;
+        }
+        m_CornerRadiusBR = value;
+        cornerRadiusBRChanged();
+    }
+
     Core* clone() const override;
     void copy(const LayoutComponentStyleBase& object)
     {
@@ -989,6 +1054,11 @@
         m_MinHeightUnitsValue = object.m_MinHeightUnitsValue;
         m_MaxWidthUnitsValue = object.m_MaxWidthUnitsValue;
         m_MaxHeightUnitsValue = object.m_MaxHeightUnitsValue;
+        m_LinkCornerRadius = object.m_LinkCornerRadius;
+        m_CornerRadiusTL = object.m_CornerRadiusTL;
+        m_CornerRadiusTR = object.m_CornerRadiusTR;
+        m_CornerRadiusBL = object.m_CornerRadiusBL;
+        m_CornerRadiusBR = object.m_CornerRadiusBR;
         Component::copy(object);
     }
 
@@ -1200,6 +1270,21 @@
             case maxHeightUnitsValuePropertyKey:
                 m_MaxHeightUnitsValue = CoreUintType::deserialize(reader);
                 return true;
+            case linkCornerRadiusPropertyKey:
+                m_LinkCornerRadius = CoreBoolType::deserialize(reader);
+                return true;
+            case cornerRadiusTLPropertyKey:
+                m_CornerRadiusTL = CoreDoubleType::deserialize(reader);
+                return true;
+            case cornerRadiusTRPropertyKey:
+                m_CornerRadiusTR = CoreDoubleType::deserialize(reader);
+                return true;
+            case cornerRadiusBLPropertyKey:
+                m_CornerRadiusBL = CoreDoubleType::deserialize(reader);
+                return true;
+            case cornerRadiusBRPropertyKey:
+                m_CornerRadiusBR = CoreDoubleType::deserialize(reader);
+                return true;
         }
         return Component::deserialize(propertyKey, reader);
     }
@@ -1273,6 +1358,11 @@
     virtual void minHeightUnitsValueChanged() {}
     virtual void maxWidthUnitsValueChanged() {}
     virtual void maxHeightUnitsValueChanged() {}
+    virtual void linkCornerRadiusChanged() {}
+    virtual void cornerRadiusTLChanged() {}
+    virtual void cornerRadiusTRChanged() {}
+    virtual void cornerRadiusBLChanged() {}
+    virtual void cornerRadiusBRChanged() {}
 };
 } // namespace rive
 
diff --git a/include/rive/generated/layout_component_base.hpp b/include/rive/generated/layout_component_base.hpp
index e0c508a..680f031 100644
--- a/include/rive/generated/layout_component_base.hpp
+++ b/include/rive/generated/layout_component_base.hpp
@@ -3,13 +3,13 @@
 #include "rive/core/field_types/core_bool_type.hpp"
 #include "rive/core/field_types/core_double_type.hpp"
 #include "rive/core/field_types/core_uint_type.hpp"
-#include "rive/world_transform_component.hpp"
+#include "rive/drawable.hpp"
 namespace rive
 {
-class LayoutComponentBase : public WorldTransformComponent
+class LayoutComponentBase : public Drawable
 {
 protected:
-    typedef WorldTransformComponent Super;
+    typedef Drawable Super;
 
 public:
     static const uint16_t typeKey = 409;
@@ -21,6 +21,9 @@
         switch (typeKey)
         {
             case LayoutComponentBase::typeKey:
+            case DrawableBase::typeKey:
+            case NodeBase::typeKey:
+            case TransformComponentBase::typeKey:
             case WorldTransformComponentBase::typeKey:
             case ContainerComponentBase::typeKey:
             case ComponentBase::typeKey:
@@ -95,7 +98,7 @@
         m_Width = object.m_Width;
         m_Height = object.m_Height;
         m_StyleId = object.m_StyleId;
-        WorldTransformComponent::copy(object);
+        Drawable::copy(object);
     }
 
     bool deserialize(uint16_t propertyKey, BinaryReader& reader) override
@@ -115,7 +118,7 @@
                 m_StyleId = CoreUintType::deserialize(reader);
                 return true;
         }
-        return WorldTransformComponent::deserialize(propertyKey, reader);
+        return Drawable::deserialize(propertyKey, reader);
     }
 
 protected:
diff --git a/include/rive/generated/node_base.hpp b/include/rive/generated/node_base.hpp
index f27da20..38c4f60 100644
--- a/include/rive/generated/node_base.hpp
+++ b/include/rive/generated/node_base.hpp
@@ -32,7 +32,9 @@
     uint16_t coreType() const override { return typeKey; }
 
     static const uint16_t xPropertyKey = 13;
+    static const uint16_t xArtboardPropertyKey = 9;
     static const uint16_t yPropertyKey = 14;
+    static const uint16_t yArtboardPropertyKey = 10;
 
 private:
     float m_X = 0.0f;
diff --git a/include/rive/layout/layout_component_style.hpp b/include/rive/layout/layout_component_style.hpp
index 861545d..6327414 100644
--- a/include/rive/layout/layout_component_style.hpp
+++ b/include/rive/layout/layout_component_style.hpp
@@ -10,8 +10,8 @@
 enum class LayoutAnimationStyle : uint8_t
 {
     none,
-    custom,
-    inherit
+    inherit,
+    custom
 };
 
 enum class LayoutStyleInterpolation : uint8_t
@@ -163,6 +163,11 @@
     void positionRightUnitsValueChanged() override;
     void positionTopUnitsValueChanged() override;
     void positionBottomUnitsValueChanged() override;
+
+    void cornerRadiusTLChanged() override;
+    void cornerRadiusTRChanged() override;
+    void cornerRadiusBLChanged() override;
+    void cornerRadiusBRChanged() override;
 };
 } // namespace rive
 
diff --git a/include/rive/layout_component.hpp b/include/rive/layout_component.hpp
index c070587..976544c 100644
--- a/include/rive/layout_component.hpp
+++ b/include/rive/layout_component.hpp
@@ -1,8 +1,12 @@
 #ifndef _RIVE_LAYOUT_COMPONENT_HPP_
 #define _RIVE_LAYOUT_COMPONENT_HPP_
+#include "rive/drawable.hpp"
 #include "rive/generated/layout_component_base.hpp"
 #include "rive/layout/layout_component_style.hpp"
 #include "rive/layout/layout_measure_mode.hpp"
+#include "rive/math/raw_path.hpp"
+#include "rive/shapes/rectangle.hpp"
+#include "rive/shapes/shape_paint_container.hpp"
 #ifdef WITH_RIVE_LAYOUT
 #include "yoga/YGNode.h"
 #include "yoga/YGStyle.h"
@@ -30,7 +34,7 @@
     AABB toBounds = AABB();
 };
 
-class LayoutComponent : public LayoutComponentBase
+class LayoutComponent : public LayoutComponentBase, public ProxyDrawing, public ShapePaintContainer
 {
 protected:
     LayoutComponentStyle* m_style = nullptr;
@@ -45,6 +49,15 @@
     KeyFrameInterpolator* m_inheritedInterpolator;
     LayoutStyleInterpolation m_inheritedInterpolation = LayoutStyleInterpolation::hold;
     float m_inheritedInterpolationTime = 0;
+    Rectangle* m_backgroundRect = new Rectangle();
+    rcp<RenderPath> m_backgroundPath;
+    rcp<RenderPath> m_clipPath;
+    DrawableProxy m_proxy;
+
+    Artboard* getArtboard() override { return artboard(); }
+
+private:
+    virtual void performUpdate(ComponentDirt value);
 
 #ifdef WITH_RIVE_LAYOUT
 private:
@@ -62,13 +75,21 @@
 public:
     LayoutComponentStyle* style() { return m_style; }
     void style(LayoutComponentStyle* style) { m_style = style; }
+    void draw(Renderer* renderer) override;
+    void drawProxy(Renderer* renderer) override;
+    Core* hitTest(HitInfo*, const Mat2D&) override;
+    DrawableProxy* proxy() { return &m_proxy; };
+    void update(ComponentDirt value) override;
 
 #ifdef WITH_RIVE_LAYOUT
-    LayoutComponent();
+    LayoutComponent() : m_layoutData(std::unique_ptr<LayoutData>(new LayoutData())), m_proxy(this)
+    {
+        layoutNode().getConfig()->setPointScaleFactor(0);
+    }
+    ~LayoutComponent() { delete m_backgroundRect; }
     void syncStyle();
     virtual void propagateSize();
     void updateLayoutBounds();
-    void update(ComponentDirt value) override;
     StatusCode onAddedDirty(CoreContext* context) override;
 
     bool advance(double elapsedSeconds);
@@ -97,6 +118,9 @@
         return m_layoutLocationX != 0 || m_layoutLocationY != 0 || m_layoutSizeWidth != 0 ||
                m_layoutSizeHeight != 0;
     };
+#else
+    LayoutComponent() : m_layoutData(std::unique_ptr<LayoutData>(new LayoutData())), m_proxy(this)
+    {}
 
 #endif
     void buildDependencies() override;
@@ -111,7 +135,7 @@
     Vec2D measureLayout(float width,
                         LayoutMeasureMode widthMode,
                         float height,
-                        LayoutMeasureMode heightMode);
+                        LayoutMeasureMode heightMode) override;
 };
 } // namespace rive
 
diff --git a/include/rive/transform_component.hpp b/include/rive/transform_component.hpp
index 534165c..151c31b 100644
--- a/include/rive/transform_component.hpp
+++ b/include/rive/transform_component.hpp
@@ -18,6 +18,9 @@
     WorldTransformComponent* m_ParentTransformComponent = nullptr;
     std::vector<Constraint*> m_Constraints;
 
+protected:
+    void updateConstraints();
+
 public:
     bool collapse(bool value) override;
     const std::vector<Constraint*>& constraints() const { return m_Constraints; }
@@ -25,7 +28,7 @@
     void buildDependencies() override;
     void update(ComponentDirt value) override;
     void updateTransform();
-    void updateWorldTransform();
+    virtual void updateWorldTransform();
     void markTransformDirty();
 
     /// Opacity inherited by any child of this transform component. This'll
diff --git a/src/artboard.cpp b/src/artboard.cpp
index 3a0b299..7cd9b02 100644
--- a/src/artboard.cpp
+++ b/src/artboard.cpp
@@ -15,6 +15,7 @@
 #include "rive/shapes/paint/shape_paint.hpp"
 #include "rive/importers/import_stack.hpp"
 #include "rive/importers/backboard_importer.hpp"
+#include "rive/layout_component.hpp"
 #include "rive/nested_artboard.hpp"
 #include "rive/joystick.hpp"
 #include "rive/data_bind_flags.hpp"
@@ -84,8 +85,8 @@
     StatusCode code;
 
     // these will be re-built in update() -- are they needed here?
-    m_BackgroundPath = factory()->makeEmptyRenderPath();
-    m_ClipPath = factory()->makeEmptyRenderPath();
+    m_backgroundPath = factory()->makeEmptyRenderPath();
+    m_clipPath = factory()->makeEmptyRenderPath();
     m_layoutSizeWidth = width();
     m_layoutSizeHeight = height();
 
@@ -218,7 +219,7 @@
         {
             object->as<Component>()->buildDependencies();
         }
-        if (object->is<Drawable>())
+        if (object->is<Drawable>() && object != this)
         {
             Drawable* drawable = object->as<Drawable>();
             m_Drawables.push_back(drawable);
@@ -235,6 +236,48 @@
             }
         }
     }
+    // Iterate over the drawables in order to inject proxies for layouts
+    std::vector<LayoutComponent*> layouts;
+    for (int i = 0; i < m_Drawables.size(); i++)
+    {
+        auto drawable = m_Drawables[i];
+        LayoutComponent* currentLayout;
+        bool isInCurrentLayout = true;
+        if (!layouts.empty())
+        {
+            currentLayout = layouts.back();
+            isInCurrentLayout = false;
+        }
+        for (ContainerComponent* parent = drawable; parent != nullptr; parent = parent->parent())
+        {
+            if (parent == currentLayout)
+            {
+                isInCurrentLayout = true;
+                break;
+            }
+        }
+        // We inject a DrawableProxy after all of the children of a LayoutComponent
+        // so that we can draw a stroke above and background below the children
+        // This also allows us to clip the children
+        if (currentLayout != nullptr && !isInCurrentLayout)
+        {
+            // This is the first item in the list of drawables that isn't a child
+            // of the layout, so we insert a proxy before it
+            m_Drawables.insert(m_Drawables.begin() + i, currentLayout->proxy());
+            layouts.pop_back();
+            i += 1;
+        }
+        if (drawable->is<LayoutComponent>())
+        {
+            layouts.push_back(drawable->as<LayoutComponent>());
+        }
+    }
+    while (!layouts.empty())
+    {
+        auto layout = layouts.back();
+        m_Drawables.push_back(layout->proxy());
+        layouts.pop_back();
+    }
 
     sortDependencies();
 
@@ -474,7 +517,7 @@
 #endif
 }
 
-void Artboard::update(ComponentDirt value)
+void Artboard::performUpdate(ComponentDirt value)
 {
     if (hasDirt(value, ComponentDirt::DrawOrder))
     {
@@ -495,11 +538,11 @@
         {
             clip = bg;
         }
-        m_ClipPath = factory()->makeRenderPath(clip);
+        m_clipPath = factory()->makeRenderPath(clip);
 
         m_backgroundRawPath.addRect(bg);
-        m_BackgroundPath->rewind();
-        m_backgroundRawPath.addTo(m_BackgroundPath.get());
+        m_backgroundPath->rewind();
+        m_backgroundRawPath.addTo(m_backgroundPath.get());
     }
     if (hasDirt(value, ComponentDirt::RenderOpacity))
     {
@@ -655,14 +698,14 @@
     return advanceInternal(elapsedSeconds, true, nested);
 }
 
-Core* Artboard::hitTest(HitInfo* hinfo, const Mat2D* xform)
+Core* Artboard::hitTest(HitInfo* hinfo, const Mat2D& xform)
 {
     if (clip())
     {
         // TODO: can we get the rawpath for the clip?
     }
 
-    auto mx = xform ? *xform : Mat2D();
+    auto mx = xform;
     if (m_FrameOrigin)
     {
         mx *= Mat2D::fromTranslate(layoutWidth() * originX(), layoutHeight() * originY());
@@ -694,12 +737,14 @@
     return nullptr;
 }
 
+void Artboard::draw(Renderer* renderer) { draw(renderer, DrawOption::kNormal); }
+
 void Artboard::draw(Renderer* renderer, DrawOption option)
 {
     renderer->save();
     if (clip())
     {
-        renderer->clipPath(m_ClipPath.get());
+        renderer->clipPath(m_clipPath.get());
     }
 
     if (m_FrameOrigin)
@@ -714,7 +759,7 @@
     {
         for (auto shapePaint : m_ShapePaints)
         {
-            shapePaint->draw(renderer, m_BackgroundPath.get(), &m_backgroundRawPath);
+            shapePaint->draw(renderer, m_backgroundPath.get(), &m_backgroundRawPath);
         }
     }
 
diff --git a/src/drawable.cpp b/src/drawable.cpp
index 2f01c79..522bd5f 100644
--- a/src/drawable.cpp
+++ b/src/drawable.cpp
@@ -40,7 +40,7 @@
 
 void Drawable::addClippingShape(ClippingShape* shape) { m_ClippingShapes.push_back(shape); }
 
-ClipResult Drawable::clip(Renderer* renderer) const
+ClipResult Drawable::applyClip(Renderer* renderer) const
 {
     if (m_ClippingShapes.size() == 0)
     {
diff --git a/src/layout/layout_component_style.cpp b/src/layout/layout_component_style.cpp
index 8fe4a32..a9310be 100644
--- a/src/layout/layout_component_style.cpp
+++ b/src/layout/layout_component_style.cpp
@@ -196,4 +196,8 @@
 void LayoutComponentStyle::positionLeftUnitsValueChanged() { markLayoutNodeDirty(); }
 void LayoutComponentStyle::positionRightUnitsValueChanged() { markLayoutNodeDirty(); }
 void LayoutComponentStyle::positionTopUnitsValueChanged() { markLayoutNodeDirty(); }
-void LayoutComponentStyle::positionBottomUnitsValueChanged() { markLayoutNodeDirty(); }
\ No newline at end of file
+void LayoutComponentStyle::positionBottomUnitsValueChanged() { markLayoutNodeDirty(); }
+void LayoutComponentStyle::cornerRadiusTLChanged() { markLayoutNodeDirty(); }
+void LayoutComponentStyle::cornerRadiusTRChanged() { markLayoutNodeDirty(); }
+void LayoutComponentStyle::cornerRadiusBLChanged() { markLayoutNodeDirty(); }
+void LayoutComponentStyle::cornerRadiusBRChanged() { markLayoutNodeDirty(); }
\ No newline at end of file
diff --git a/src/layout_component.cpp b/src/layout_component.cpp
index 350b7cc..52040d4 100644
--- a/src/layout_component.cpp
+++ b/src/layout_component.cpp
@@ -1,8 +1,14 @@
 #include "rive/animation/keyframe_interpolator.hpp"
 #include "rive/artboard.hpp"
+#include "rive/drawable.hpp"
+#include "rive/factory.hpp"
 #include "rive/layout_component.hpp"
 #include "rive/node.hpp"
 #include "rive/math/aabb.hpp"
+#include "rive/shapes/paint/fill.hpp"
+#include "rive/shapes/paint/shape_paint.hpp"
+#include "rive/shapes/paint/stroke.hpp"
+#include "rive/shapes/rectangle.hpp"
 #ifdef WITH_RIVE_LAYOUT
 #include "rive/transform_component.hpp"
 #include "yoga/YGEnums.h"
@@ -19,14 +25,111 @@
     {
         parent()->addDependent(this);
     }
+    // Set the blend mode on all the shape paints. If we ever animate this
+    // property, we'll need to update it in the update cycle/mark dirty when the
+    // blend mode changes.
+    for (auto paint : m_ShapePaints)
+    {
+        paint->blendMode(blendMode());
+    }
+}
+
+void LayoutComponent::drawProxy(Renderer* renderer)
+{
+    if (clip())
+    {
+        renderer->save();
+        renderer->clipPath(m_clipPath.get());
+    }
+    renderer->save();
+    renderer->transform(worldTransform());
+    for (auto shapePaint : m_ShapePaints)
+    {
+        if (!shapePaint->isVisible())
+        {
+            continue;
+        }
+        if (shapePaint->is<Fill>())
+        {
+            shapePaint->draw(renderer, m_backgroundPath.get(), &m_backgroundRect->rawPath());
+        }
+    }
+    renderer->restore();
+}
+
+void LayoutComponent::draw(Renderer* renderer)
+{
+    // Restore clip before drawing stroke so we don't clip the stroke
+    if (clip())
+    {
+        renderer->restore();
+    }
+    renderer->save();
+    renderer->transform(worldTransform());
+    for (auto shapePaint : m_ShapePaints)
+    {
+        if (!shapePaint->isVisible())
+        {
+            continue;
+        }
+        if (shapePaint->is<Stroke>())
+        {
+            shapePaint->draw(renderer, m_backgroundPath.get(), &m_backgroundRect->rawPath());
+        }
+    }
+    renderer->restore();
+}
+
+Core* LayoutComponent::hitTest(HitInfo*, const Mat2D&) { return nullptr; }
+
+void LayoutComponent::update(ComponentDirt value)
+{
+    Super::update(value);
+    performUpdate(value);
+}
+
+void LayoutComponent::performUpdate(ComponentDirt value)
+{
+    if (hasDirt(value, ComponentDirt::RenderOpacity))
+    {
+        propagateOpacity(renderOpacity());
+    }
+    if (hasDirt(value, ComponentDirt::Path))
+    {
+        m_backgroundRect->width(m_layoutSizeWidth);
+        m_backgroundRect->height(m_layoutSizeHeight);
+        m_backgroundRect->linkCornerRadius(style()->linkCornerRadius());
+        m_backgroundRect->cornerRadiusTL(style()->cornerRadiusTL());
+        m_backgroundRect->cornerRadiusTR(style()->cornerRadiusTR());
+        m_backgroundRect->cornerRadiusBL(style()->cornerRadiusBL());
+        m_backgroundRect->cornerRadiusBR(style()->cornerRadiusBR());
+        m_backgroundRect->update(value);
+
+        m_backgroundPath->rewind();
+        m_backgroundRect->rawPath().addTo(m_backgroundPath.get());
+        AABB clipBounds = AABB::fromLTWH(worldTranslation().x,
+                                         worldTranslation().y,
+                                         worldTranslation().x + m_layoutSizeWidth,
+                                         worldTranslation().y + m_layoutSizeHeight);
+        m_clipPath = artboard()->factory()->makeRenderPath(clipBounds);
+    }
+    if (hasDirt(value, ComponentDirt::WorldTransform))
+    {
+        Mat2D parentWorld = parent()->is<WorldTransformComponent>()
+                                ? (parent()->as<WorldTransformComponent>())->worldTransform()
+                                : Mat2D();
+        auto transform = Mat2D();
+        transform[4] = m_layoutLocationX;
+        transform[5] = m_layoutLocationY;
+
+        auto multipliedTransform = Mat2D::multiply(parentWorld, transform);
+        m_WorldTransform = multipliedTransform;
+
+        updateConstraints();
+    }
 }
 
 #ifdef WITH_RIVE_LAYOUT
-LayoutComponent::LayoutComponent() : m_layoutData(std::unique_ptr<LayoutData>(new LayoutData()))
-{
-    layoutNode().getConfig()->setPointScaleFactor(0);
-}
-
 StatusCode LayoutComponent::onAddedDirty(CoreContext* context)
 {
     auto code = Super::onAddedDirty(context);
@@ -48,25 +151,13 @@
     {
         parent()->as<LayoutComponent>()->syncLayoutChildren();
     }
+    m_backgroundPath = artboard()->factory()->makeEmptyRenderPath();
+    m_clipPath = artboard()->factory()->makeEmptyRenderPath();
+    m_backgroundRect->originX(0);
+    m_backgroundRect->originY(0);
     return StatusCode::Ok;
 }
 
-void LayoutComponent::update(ComponentDirt value)
-{
-    if (hasDirt(value, ComponentDirt::WorldTransform))
-    {
-        Mat2D parentWorld = parent()->is<WorldTransformComponent>()
-                                ? (parent()->as<WorldTransformComponent>())->worldTransform()
-                                : Mat2D();
-        auto transform = Mat2D();
-        transform[4] = m_layoutLocationX;
-        transform[5] = m_layoutLocationY;
-
-        auto multipliedTransform = Mat2D::multiply(parentWorld, transform);
-        m_WorldTransform = multipliedTransform;
-    }
-}
-
 static YGSize measureFunc(YGNode* node,
                           float width,
                           YGMeasureMode widthMode,
@@ -567,6 +658,14 @@
     }
 }
 #else
+Vec2D LayoutComponent::measureLayout(float width,
+                                     LayoutMeasureMode widthMode,
+                                     float height,
+                                     LayoutMeasureMode heightMode)
+{
+    return Vec2D();
+}
+
 void LayoutComponent::markLayoutNodeDirty() {}
 void LayoutComponent::markLayoutStyleDirty() {}
 #endif
diff --git a/src/nested_artboard.cpp b/src/nested_artboard.cpp
index 19437dc..95138b0 100644
--- a/src/nested_artboard.cpp
+++ b/src/nested_artboard.cpp
@@ -60,7 +60,7 @@
     {
         return;
     }
-    ClipResult clipResult = clip(renderer);
+    ClipResult clipResult = applyClip(renderer);
     if (clipResult == ClipResult::noClip)
     {
         // We didn't clip, so make sure to save as we'll be doing some
@@ -83,7 +83,7 @@
     }
     hinfo->mounts.push_back(this);
     auto mx = xform * worldTransform() * makeTranslate(m_Artboard);
-    if (auto c = m_Artboard->hitTest(hinfo, &mx))
+    if (auto c = m_Artboard->hitTest(hinfo, mx))
     {
         return c;
     }
diff --git a/src/shapes/image.cpp b/src/shapes/image.cpp
index e761230..d86687e 100644
--- a/src/shapes/image.cpp
+++ b/src/shapes/image.cpp
@@ -24,7 +24,7 @@
         return;
     }
 
-    ClipResult clipResult = clip(renderer);
+    ClipResult clipResult = applyClip(renderer);
 
     if (clipResult == ClipResult::noClip)
     {
diff --git a/src/shapes/path.cpp b/src/shapes/path.cpp
index fd5af98..f5898fe 100644
--- a/src/shapes/path.cpp
+++ b/src/shapes/path.cpp
@@ -56,6 +56,10 @@
 
 bool Path::canDeferPathUpdate()
 {
+    if (m_Shape == nullptr)
+    {
+        return false;
+    }
     // A path cannot defer its update if the shapes requires an update. Note the
     // nuance here where we track that the shape may be marked for follow path
     // (meaning all child paths need to follow path). This doesn't mean the
diff --git a/src/shapes/shape.cpp b/src/shapes/shape.cpp
index 6a22160..d69648b 100644
--- a/src/shapes/shape.cpp
+++ b/src/shapes/shape.cpp
@@ -91,7 +91,7 @@
     {
         return;
     }
-    ClipResult clipResult = clip(renderer);
+    ClipResult clipResult = applyClip(renderer);
 
     if (clipResult != ClipResult::emptyClip)
     {
diff --git a/src/shapes/shape_paint_container.cpp b/src/shapes/shape_paint_container.cpp
index 37e5853..6a702eb 100644
--- a/src/shapes/shape_paint_container.cpp
+++ b/src/shapes/shape_paint_container.cpp
@@ -2,6 +2,7 @@
 #include "rive/artboard.hpp"
 #include "rive/factory.hpp"
 #include "rive/component.hpp"
+#include "rive/layout_component.hpp"
 #include "rive/shapes/paint/stroke.hpp"
 #include "rive/shapes/shape.hpp"
 #include "rive/text/text_style.hpp"
@@ -14,6 +15,8 @@
     {
         case Artboard::typeKey:
             return component->as<Artboard>();
+        case LayoutComponent::typeKey:
+            return component->as<LayoutComponent>();
         case Shape::typeKey:
             return component->as<Shape>();
         case TextStyle::typeKey:
diff --git a/src/text/text.cpp b/src/text/text.cpp
index 48624a5..59403c7 100644
--- a/src/text/text.cpp
+++ b/src/text/text.cpp
@@ -509,7 +509,7 @@
 void Text::draw(Renderer* renderer)
 {
 
-    ClipResult clipResult = clip(renderer);
+    ClipResult clipResult = applyClip(renderer);
     if (clipResult == ClipResult::noClip)
     {
         // We didn't clip, so make sure to save as we'll be doing some
diff --git a/src/transform_component.cpp b/src/transform_component.cpp
index 0e9c1ab..73bc031 100644
--- a/src/transform_component.cpp
+++ b/src/transform_component.cpp
@@ -79,7 +79,11 @@
     {
         m_WorldTransform = m_Transform;
     }
+    updateConstraints();
+}
 
+void TransformComponent::updateConstraints()
+{
     for (auto constraint : m_Constraints)
     {
         constraint->constrain(this);
diff --git a/test/clip_test.cpp b/test/clip_test.cpp
index d4dbdeb..19ff144 100644
--- a/test/clip_test.cpp
+++ b/test/clip_test.cpp
@@ -106,7 +106,7 @@
     REQUIRE(clippedNode->is<rive::Shape>());
     rive::Shape* clippedShape = static_cast<rive::Shape*>(clippedNode);
     rive::NoOpRenderer renderer;
-    auto clipResult = clippedShape->clip(&renderer);
+    auto clipResult = clippedShape->applyClip(&renderer);
     REQUIRE(clipResult == rive::ClipResult::emptyClip);
 }
 
@@ -128,7 +128,7 @@
     REQUIRE(clippedNode->is<rive::Shape>());
     rive::Shape* clippedShape = static_cast<rive::Shape*>(clippedNode);
     rive::NoOpRenderer renderer;
-    auto clipResult = clippedShape->clip(&renderer);
+    auto clipResult = clippedShape->applyClip(&renderer);
     REQUIRE(clipResult == rive::ClipResult::clip);
 }
 
@@ -146,6 +146,6 @@
     rive::Shape* shape = static_cast<rive::Shape*>(node);
 
     rive::NoOpRenderer renderer;
-    auto clipResult = shape->clip(&renderer);
+    auto clipResult = shape->applyClip(&renderer);
     REQUIRE(clipResult == rive::ClipResult::emptyClip);
 }