blob: 52f1dbf87aa852a972de3793ed56ba41210cdf53 [file]
#ifndef _RIVE_LAYOUT_PARTICIPANT_HPP_
#define _RIVE_LAYOUT_PARTICIPANT_HPP_
#include "rive/generated/layout/layout_participant_base.hpp"
#include "rive/layout/layout_node_provider.hpp"
#include "rive/layout/layout_data.hpp"
#include "rive/layout_component.hpp"
#include "rive/advancing_component.hpp"
namespace rive
{
class TransformComponent;
class LayoutComponent;
class LayoutStyleApplier;
class KeyFrameInterpolator;
// Lazily-allocated animation state (defined in the .cpp); only exists while a
// participant is under an animated layout.
struct ParticipantAnimation;
/// A layout participant: a LayoutNodeStyle (sizing) that also owns the layout
/// node and provides it to the parent layout on behalf of its host node. Its
/// presence on a node signals "this node participates in layout."
///
/// Ported from LayoutNodeParticipant; unlike that mixin this lives as a child
/// object, so geometry/transform resolve to the host (its parent). The host
/// composes the resolved slot into its own world transform.
class LayoutParticipant : public LayoutParticipantBase,
public LayoutNodeProvider,
public AdvancingComponent
{
public:
~LayoutParticipant() override;
// The host node this participant belongs to (our parent). The
// LayoutNodeProvider machinery uses this as the "node" whose world
// transform the slot composes into, and as the IntrinsicallySizeable to
// measure/size.
TransformComponent* transformComponent() override;
StatusCode onAddedClean(CoreContext* context) override;
#ifdef WITH_RIVE_LAYOUT
void* layoutNode(int index) override;
// Re-establish participation: (de)allocate the node or, on a forwarding
// host (Solo), re-collect the forwarded child node. Called on load and when
// a Solo swaps its active child.
void resync();
#endif
size_t numLayoutNodes() override;
AABB layoutBounds() override;
AABB layoutBoundsForNode(int index) override;
bool syncStyleChanges() override;
void updateLayoutBounds(bool animate = true) override;
#ifdef WITH_RIVE_LAYOUT
void addLayoutStyleApplier(LayoutStyleApplier* applier) override;
void applyBaseStyle(YGStyle& style,
const LayoutSyncContext& context) override;
#endif
void markLayoutNodeDirty(
bool shouldForceUpdateLayoutBounds = false) override;
// Layout animation: advanced each frame (as an AdvancingComponent) to
// interpolate the resolved slot toward the newly solved layout, using the
// interpolation inherited from the parent layout via cascadeLayoutStyle.
bool advanceComponent(float elapsedSeconds,
AdvanceFlags flags = AdvanceFlags::Animate |
AdvanceFlags::NewFrame) override;
#ifdef WITH_RIVE_LAYOUT
bool cascadeLayoutStyle(LayoutStyleInterpolation inheritedInterpolation,
KeyFrameInterpolator* inheritedInterpolator,
float inheritedInterpolationTime,
LayoutDirection direction) override;
#endif
bool isParticipatingInLayout() const;
// The resolved slot (in the parent layout's space), read live from the
// YGNode. The host reads these to place its scaled geometry at the slot.
float resolvedLeft() const;
float resolvedTop() const;
float resolvedWidth() const;
float resolvedHeight() const;
protected:
// Any sizing change funnels here to re-sync the node.
void onSizingChanged();
void layoutWidthScaleTypeChanged() override { onSizingChanged(); }
void layoutHeightScaleTypeChanged() override { onSizingChanged(); }
void widthChanged() override { onSizingChanged(); }
void heightChanged() override { onSizingChanged(); }
void fractionalWidthChanged() override { onSizingChanged(); }
void fractionalHeightChanged() override { onSizingChanged(); }
void minWidthChanged() override { onSizingChanged(); }
void maxWidthChanged() override { onSizingChanged(); }
void minHeightChanged() override { onSizingChanged(); }
void maxHeightChanged() override { onSizingChanged(); }
void minWidthUnitsValueChanged() override { onSizingChanged(); }
void maxWidthUnitsValueChanged() override { onSizingChanged(); }
void minHeightUnitsValueChanged() override { onSizingChanged(); }
void maxHeightUnitsValueChanged() override { onSizingChanged(); }
void widthUnitsValueChanged() override { onSizingChanged(); }
void heightUnitsValueChanged() override { onSizingChanged(); }
void justifySelfValueChanged() override { onSizingChanged(); }
void displayValueChanged() override { onSizingChanged(); }
private:
// The nearest ancestor LayoutComponent, walking up through the host.
LayoutComponent* owningLayout();
public:
// Host fit state, kept here rather than on the host so a non-participating
// Shape carries none of it — only participants read or write these, and the
// participant is already allocated for exactly that case.
//
// Scale that fits the host's geometry to its slot, composed innermost in
// the host's world transform.
float hostScaleX() const { return m_hostScaleX; }
float hostScaleY() const { return m_hostScaleY; }
void hostScale(float x, float y)
{
m_hostScaleX = x;
m_hostScaleY = y;
}
// Memoized host intrinsic bounds. Recomputing walks and re-tessellates
// every path, and the host reads it on each world transform update.
bool hostBoundsValid() const { return m_hostBoundsValid; }
const AABB& hostBounds() const { return m_hostBounds; }
void hostBounds(const AABB& bounds, bool cache)
{
m_hostBounds = bounds;
m_hostBoundsValid = cache;
}
void invalidateHostBounds() { m_hostBoundsValid = false; }
private:
// Data members are grouped here, widest first, so the two flags share one
// tail hole instead of each opening its own.
//
// ── Layout animation. A participant has no animation style of its own; it
// inherits the parent layout's via cascadeLayoutStyle. All animation state
// (buffers, interpolation, the animated slot) is lazily heap-allocated only
// while under an animated layout — so a non-animating participant pays just
// the pointer.
ParticipantAnimation* m_animation = nullptr;
AABB m_hostBounds;
float m_hostScaleX = 1.0f;
float m_hostScaleY = 1.0f;
// Gates the first-solve snap (animate only once we've solved before).
bool m_hasSolvedLayout = false;
bool m_hostBoundsValid = false;
// The resolved slot straight from the yoga node (no animation).
// resolvedLeft etc. use this when not animating; the animated slot lives in
// m_animation.
Layout solvedLayout() const;
LayoutAnimationData* currentAnimationData();
bool animates() const;
LayoutStyleInterpolation interpolation() const;
float interpolationTime() const;
KeyFrameInterpolator* interpolator() const;
#ifdef WITH_RIVE_LAYOUT
bool applyInterpolation(float elapsedSeconds, bool animate);
LayoutData* m_layoutData = nullptr;
static float definedOrZero(float v)
{
return YGFloatIsUndefined(v) ? 0.0f : v;
}
void applyResolvedLayoutSize();
void releaseLayoutData();
// Whether our host forwards a child's node (Solo) rather than owning one.
// On a forwarding host we own no node and delegate the provider methods to
#endif
};
} // namespace rive
#endif