Summary
LayoutComponent::propagateSizeToChildren() contains a hardcoded exclusion that skips all children of type NodeBase (Groups). This prevents the layout engine from traversing into Group containers to find nested Image components, effectively cutting off the entire subtree from receiving layout dimensions. Images inside Groups inside Layouts never receive controlSize() calls and render with editor-baked scale values.
Severity
Medium-High — Affects any Layout hierarchy where Images are nested inside Groups (which DataBinding can produce via ViewModel structure).
Environment
- Runtime: rive-cpp (C++ runtime)
- Platform: All platforms using the C++ runtime
Steps to Reproduce
- Create a Rive file with:
- A
LayoutComponent (fixed dimensions)
- A
Group (Node) inside the Layout
- An
Image inside the Group
- Load the
.riv file and inject a dynamic image
- Observe that the Image never receives
controlSize() and renders at placeholder dimensions
Root Cause
In propagateSizeToChildren(), the original code contained:
void LayoutComponent::propagateSizeToChildren(ContainerComponent* component)
{
for (auto child : component->children())
{
if (child->is<LayoutComponent>()) { continue; }
// ⚠️ BUG: This skips ALL Group nodes, preventing recursion
// into their children where Images may live
if (child->coreType() == NodeBase::typeKey) {
continue; // ← Entire subtree cut off!
}
auto sizeableChild = IntrinsicallySizeable::from(child);
if (sizeableChild != nullptr) {
sizeableChild->controlSize(...);
}
// Recursion into ContainerComponent children
if (child->is<ContainerComponent>()) {
propagateSizeToChildren(child->as<ContainerComponent>());
}
}
}
The NodeBase::typeKey check prevents the function from ever reaching the recursive propagateSizeToChildren() call at the bottom, so any Image nested inside a Group is never visited.
Proposed Fix
Remove the NodeBase exclusion. The function should recurse into all ContainerComponent children, including Groups:
void LayoutComponent::propagateSizeToChildren(ContainerComponent* component)
{
for (auto child : component->children())
{
if (child->is<LayoutComponent>()) { continue; }
// Removed: if (child->coreType() == NodeBase::typeKey) { continue; }
auto sizeableChild = IntrinsicallySizeable::from(child);
if (sizeableChild != nullptr) {
sizeableChild->controlSize(...);
if (!sizeableChild->shouldPropagateSizeToChildren()) {
continue;
}
}
if (child->is<ContainerComponent>()) {
propagateSizeToChildren(child->as<ContainerComponent>());
}
}
}
Impact Assessment
The NodeBase exclusion may have been added to avoid traversing into Groups that shouldn't receive layout sizing (e.g., purely organizational folders). However, Groups can legitimately contain sizeable children like Images in DataBinding hierarchies. The IntrinsicallySizeable::from() check and shouldPropagateSizeToChildren() guard already handle the case where a child shouldn't receive sizing information.
Diagnostic Evidence
Before fix — Image never receives controlSize because Group child is skipped entirely:
(no propagateSizeToChildren or controlSize logs for the Image — the Group's subtree is never entered)
After fix — Group (isSizeable=0) is traversed, Image (isSizeable=1) is found and receives correct dimensions:
[RIVE DIAG] propagateSize: m_layout w=32.000000 h=32.000000, effective w=547.000000 h=461.000000, authored w=547.000000 h=461.000000
[RIVE DIAG] propagateSizeToChildren: found child (isSizeable=0)
[RIVE DIAG] propagateSizeToChildren: found child (isSizeable=1)
[RIVE DIAG] Image::controlSize called: w=547.000000, h=461.000000 (prev w=nan, h=nan)
[RIVE DIAG] updateImageScale: renderImage=000001A290F997C0, layoutW=547.000000, layoutH=461.000000, isNanW=0, isNanH=0
[RIVE DIAG] updateImageScale: COMPUTING fit=1, imgW=2160.000000, imgH=3840.000000, layoutW=547.000000, layoutH=461.000000
[RIVE DIAG] updateImageScale: CONTAIN s=0.120052, newScaleX=0.120052, newScaleY=0.120052
Summary
LayoutComponent::propagateSizeToChildren()contains a hardcoded exclusion that skips all children of typeNodeBase(Groups). This prevents the layout engine from traversing into Group containers to find nestedImagecomponents, effectively cutting off the entire subtree from receiving layout dimensions. Images inside Groups inside Layouts never receivecontrolSize()calls and render with editor-baked scale values.Severity
Medium-High — Affects any Layout hierarchy where Images are nested inside Groups (which DataBinding can produce via ViewModel structure).
Environment
Steps to Reproduce
LayoutComponent(fixed dimensions)Group(Node) inside the LayoutImageinside the Group.rivfile and inject a dynamic imagecontrolSize()and renders at placeholder dimensionsRoot Cause
In
propagateSizeToChildren(), the original code contained:The
NodeBase::typeKeycheck prevents the function from ever reaching the recursivepropagateSizeToChildren()call at the bottom, so anyImagenested inside a Group is never visited.Proposed Fix
Remove the
NodeBaseexclusion. The function should recurse into allContainerComponentchildren, including Groups:Impact Assessment
The
NodeBaseexclusion may have been added to avoid traversing into Groups that shouldn't receive layout sizing (e.g., purely organizational folders). However, Groups can legitimately contain sizeable children like Images in DataBinding hierarchies. TheIntrinsicallySizeable::from()check andshouldPropagateSizeToChildren()guard already handle the case where a child shouldn't receive sizing information.Diagnostic Evidence
Before fix — Image never receives
controlSizebecause Group child is skipped entirely:After fix — Group (isSizeable=0) is traversed, Image (isSizeable=1) is found and receives correct dimensions: