blob: eb6bb62f9f268222beeb0be5f5abe7c9ce4040a1 [file]
#include "rive/solo.hpp"
#include "rive/constraints/constraint.hpp"
#include "rive/shapes/clipping_shape.hpp"
#include "rive/focus_data.hpp"
#include "rive/semantic/semantic_data.hpp"
#include "rive/artboard.hpp"
#include "rive/node.hpp"
#include "rive/container_component.hpp"
#include "rive/transform_component.hpp"
#include "rive/layout_component.hpp"
#include "rive/layout/layout_node_provider.hpp"
using namespace rive;
// Some child components shouldn't be considered as part of the solo set as they
// are more akin to properties/metadata of the solo itself (constraints,
// clipping shapes, focus and semantic data) rather than selectable solo
// options. These are excluded both from collapse propagation and from
// index/name based selection so that data binding targets only the real solo
// options.
static bool isSoloSetMember(Component* child)
{
return !(child->is<Constraint>() || child->is<ClippingShape>() ||
child->is<FocusData>() || child->is<SemanticData>());
}
Component* Solo::activeComponent()
{
auto* ab = artboard();
Core* active = ab != nullptr ? ab->resolve(activeComponentId()) : nullptr;
// The active id refers to one of our children; match by pointer so we get
// it typed as a Component.
for (Component* child : children())
{
if (child == active)
{
return child;
}
}
return nullptr;
}
#ifdef WITH_RIVE_LAYOUT
void Solo::recollectOwningLayout()
{
// We are not a LayoutComponent, so walk up for the layout that owns the
// slot our active child occupies.
for (Component* p = parent(); p != nullptr; p = p->parent())
{
if (p->is<LayoutComponent>())
{
p->as<LayoutComponent>()->syncLayoutChildren();
return;
}
}
}
#endif
void Solo::propagateCollapse(bool collapse)
{
Core* active =
collapse ? nullptr : artboard()->resolve(activeComponentId());
for (Component* child : children())
{
// For components that aren't part of the solo set, simply pass on the
// collapse value of the solo itself.
if (!isSoloSetMember(child))
{
child->collapse(collapse);
continue;
}
// This child is part of the solo set so only make it active if it's the
// currently marked solo object.
child->collapse(child != active);
}
}
bool Solo::collapse(bool value)
{
// Intentionally using Component instead of Super as we don't want to call
// collapse on the Container logic which just propagates blindly to
// children.
if (!Component::collapse(value))
{
return false;
}
propagateCollapse(value);
return true;
}
void Solo::activeComponentIdChanged()
{
propagateCollapse(isCollapsed());
#ifdef WITH_RIVE_LAYOUT
// Only the active child is exposed to layout, so a swap changes the owning
// layout's child set.
recollectOwningLayout();
#endif
}
StatusCode Solo::onAddedClean(CoreContext* context)
{
StatusCode code = Super::onAddedClean(context);
if (code != StatusCode::Ok)
{
return code;
}
propagateCollapse(isCollapsed());
#ifdef WITH_RIVE_LAYOUT
// Parent chain + active child are resolved now; make sure the owning layout
// has collected the active child.
recollectOwningLayout();
#endif
return StatusCode::Ok;
}
void Solo::updateByIndex(size_t index)
{
// The number of solo options is always <= children().size(), so any index
// that big can never match. Bail early to avoid an O(n) walk every frame
// for out-of-range indices (the data-binding path casts rounded floats to
// size_t without clamping, so negative values arrive as huge indices).
if (!artboard() || index >= children().size())
{
return;
}
// The index refers to the Nth solo option, skipping property-like children
// (constraints, clipping shapes, focus/semantic data) so it matches the
// ordering exposed by getActiveChildIndex.
size_t soloIndex = 0;
for (auto& child : children())
{
if (!isSoloSetMember(child))
{
continue;
}
if (soloIndex == index)
{
activeComponentId(artboard()->idOf(child));
return;
}
soloIndex++;
}
}
void Solo::updateByName(const std::string& name)
{
if (!artboard())
{
return;
}
for (auto& child : children())
{
if (!isSoloSetMember(child))
{
continue;
}
if (child->name() == name)
{
int globalIndex = artboard()->idOf(child);
activeComponentId(globalIndex);
break;
}
}
}
int Solo::getActiveChildIndex()
{
if (!artboard())
{
return -1;
}
Core* active = artboard()->resolve(activeComponentId());
if (active)
{
int index = 0;
for (auto& child : children())
{
if (!isSoloSetMember(child))
{
continue;
}
if (child == active)
{
return index;
}
index++;
}
}
return -1;
}
std::string Solo::getActiveChildName()
{
if (!artboard())
{
return "";
}
Core* active = artboard()->resolve(activeComponentId());
if (active && active->is<Component>())
{
return active->as<Component>()->name();
}
return "";
}