| #include "rive/animation/keyframe_interpolator.hpp" |
| #include "rive/artboard.hpp" |
| #include "rive/artboard_component_list.hpp" |
| #include "rive/constraints/layout_constraint.hpp" |
| #include "rive/drawable.hpp" |
| #include "rive/factory.hpp" |
| #include "rive/intrinsically_sizeable.hpp" |
| #include "rive/layout_component.hpp" |
| #include "rive/component_origin.hpp" |
| #include "rive/layout/grid_track.hpp" |
| #include "rive/layout/layout_style_applier.hpp" |
| #include "rive/nested_artboard_layout.hpp" |
| #include "rive/node.hpp" |
| #include "rive/math/aabb.hpp" |
| #include "rive/shapes/path.hpp" |
| #include "rive/shapes/paint/fill.hpp" |
| #include "rive/shapes/paint/shape_paint.hpp" |
| #include "rive/shapes/paint/stroke.hpp" |
| #include "rive/solo.hpp" |
| #include "rive/layout/layout_data.hpp" |
| #include "rive/layout/layout_component_style.hpp" |
| #ifdef WITH_RIVE_LAYOUT |
| #include "rive/transform_component.hpp" |
| #include "yoga/YGEnums.h" |
| #include "yoga/YGFloatOptional.h" |
| #endif |
| #include <vector> |
| |
| using namespace rive; |
| |
| #ifdef WITH_RIVE_LAYOUT |
| void LayoutComponent::addLayoutStyleApplier(LayoutStyleApplier* applier) |
| { |
| if (m_layoutData != nullptr) |
| { |
| m_layoutData->addApplier(applier); |
| } |
| } |
| #endif |
| |
| namespace |
| { |
| // Containers transparent to layout provide no layout node and no |
| // sizing of their own, so the layout above descends through them. A plain group |
| // exposes all its children; a Solo exposes only its active one. Anything else |
| // (n-slicers, nested artboards, shapes) is opaque — a participant inside one |
| // would never be collected. |
| bool isTransparentLayoutContainer(Component* component) |
| { |
| return component->coreType() == NodeBase::typeKey || component->is<Solo>(); |
| } |
| |
| // An ArtboardComponentList provides a layout node unconditionally — it never |
| // opted in the way a participant does. Wrapping one in a group is how a file |
| // says "render these items but don't lay them out", freeing them to be placed |
| // by x/y or a follow-path constraint, so a transparent container stays opaque |
| // to it unless the list opts in. Everything else is seen through. |
| bool joinsLayoutThroughContainer(Component* component) |
| { |
| if (!component->is<ArtboardComponentList>()) |
| { |
| return true; |
| } |
| auto flags = |
| static_cast<DrawableFlag>(component->as<Drawable>()->drawableFlags()); |
| return (flags & DrawableFlag::ParticipatesInLayout) == |
| DrawableFlag::ParticipatesInLayout; |
| } |
| |
| // Visit every provider that belongs to a layout — its direct children, plus any |
| // nested inside transparent containers; depth-first, so layout order follows |
| // hierarchy order. Nested LayoutComponents and providers are leaves here (they |
| // own their subtree). Every child-walking layout pass goes through this, so a |
| // nested participant is collected, sized, cascaded and re-synced like a direct |
| // child. |
| template <typename F> |
| void forEachLayoutProvider(Component* from, F&& visit, bool nested = false) |
| { |
| // A Solo only lets its active child through. |
| if (from->is<Solo>()) |
| { |
| if (auto* active = from->as<Solo>()->activeComponent()) |
| { |
| if (auto* provider = LayoutNodeProvider::from(active)) |
| { |
| if (!nested || joinsLayoutThroughContainer(active)) |
| { |
| visit(active, provider); |
| } |
| } |
| else if (isTransparentLayoutContainer(active)) |
| { |
| forEachLayoutProvider(active, visit, true); |
| } |
| } |
| return; |
| } |
| for (auto* child : from->as<ContainerComponent>()->children()) |
| { |
| if (auto* provider = LayoutNodeProvider::from(child)) |
| { |
| if (!nested || joinsLayoutThroughContainer(child)) |
| { |
| visit(child, provider); |
| } |
| } |
| else if (isTransparentLayoutContainer(child)) |
| { |
| forEachLayoutProvider(child, visit, true); |
| } |
| } |
| } |
| |
| // The optional origin child, or nullptr. Walked rather than cached, matching |
| // how NestedArtboard resolves the same child in this runtime. |
| ComponentOrigin* originChild(const ContainerComponent* owner) |
| { |
| for (auto* child : owner->children()) |
| { |
| if (child->is<ComponentOrigin>()) |
| { |
| return child->as<ComponentOrigin>(); |
| } |
| } |
| return nullptr; |
| } |
| } // namespace |
| |
| #if defined(WITH_RIVE_LAYOUT) && defined(WITH_RIVE_TOOLS) && defined(DEBUG) |
| uint32_t LayoutData::count = 0; |
| #endif |
| |
| float LayoutComponent::pivotOriginX() const |
| { |
| auto* origin = originChild(this); |
| return origin != nullptr ? origin->originX() : 0.0f; |
| } |
| |
| float LayoutComponent::pivotOriginY() const |
| { |
| auto* origin = originChild(this); |
| return origin != nullptr ? origin->originY() : 0.0f; |
| } |
| |
| void LayoutComponent::buildDependencies() |
| { |
| Super::buildDependencies(); |
| if (parent() != nullptr) |
| { |
| 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()); |
| } |
| } |
| |
| Core* LayoutComponent::hitTest(HitInfo*, const Mat2D&) { return nullptr; } |
| |
| bool LayoutComponent::hitTestPoint(const Vec2D& position, |
| bool skipOnUnclipped, |
| bool isPrimaryHit) |
| { |
| Mat2D inverseWorld; |
| if (worldTransform().invert(&inverseWorld)) |
| { |
| // If the layout is not clipped and skipOnUnclipped is true, we |
| // don't care about whether it contains the position |
| auto canSkip = skipOnUnclipped && !this->clip(); |
| if (!canSkip) |
| { |
| auto localWorld = inverseWorld * position; |
| if (this->is<Artboard>()) |
| { |
| auto artboard = this->as<Artboard>(); |
| if (artboard->originX() != 0 || artboard->originY() != 0) |
| { |
| localWorld += |
| Vec2D(artboard->originX() * artboard->layoutWidth(), |
| artboard->originY() * artboard->layoutHeight()); |
| } |
| } |
| if (!localBounds().contains(localWorld)) |
| { |
| return false; |
| } |
| } |
| return Drawable::hitTestPoint(position, true, isPrimaryHit); |
| } |
| return false; |
| } |
| |
| void LayoutComponent::update(ComponentDirt value) |
| { |
| Super::update(value); |
| #ifdef WITH_RIVE_LAYOUT |
| if (value == ComponentDirt::Filthy) |
| { |
| // Use this to prevent layout animation on startup |
| interruptAnimation(); |
| } |
| #endif |
| if (hasDirt(value, ComponentDirt::RenderOpacity)) |
| { |
| propagateOpacity(childOpacity()); |
| } |
| if (parent() != nullptr && hasDirt(value, ComponentDirt::WorldTransform)) |
| { |
| Mat2D parentWorld = |
| parent()->is<WorldTransformComponent>() |
| ? (parent()->as<WorldTransformComponent>())->worldTransform() |
| : Mat2D(); |
| auto location = Vec2D(m_layout.left(), m_layout.top()); |
| if (parent()->is<Artboard>()) |
| { |
| auto art = parent()->as<Artboard>(); |
| location -= Vec2D(art->layoutWidth() * art->originX(), |
| art->layoutHeight() * art->originY()); |
| } |
| auto slot = Mat2D::fromTranslation(location); |
| |
| // Apply the node's own rotation/scale on top of the layout slot, |
| // pivoting at the origin (default 0,0 = top-left). x/y offset: |
| // follow-up. |
| if (rotation() != 0.0f || scaleX() != 1.0f || scaleY() != 1.0f) |
| { |
| Mat2D local = |
| rotation() != 0.0f ? Mat2D::fromRotation(rotation()) : Mat2D(); |
| local.scaleByValues(scaleX(), scaleY()); |
| float ox = pivotOriginX(); |
| float oy = pivotOriginY(); |
| if (ox != 0.0f || oy != 0.0f) |
| { |
| float px = ox * layoutWidth(); |
| float py = oy * layoutHeight(); |
| local = Mat2D::multiply( |
| Mat2D::fromTranslate(px, py), |
| Mat2D::multiply(local, Mat2D::fromTranslate(-px, -py))); |
| } |
| slot = Mat2D::multiply(slot, local); |
| } |
| m_WorldTransform = Mat2D::multiply(parentWorld, slot); |
| updateConstraints(); |
| } |
| if (hasDirt(value, |
| ComponentDirt::Path | ComponentDirt::WorldTransform | |
| ComponentDirt::LayoutStyle)) |
| { |
| updateRenderPath(); |
| } |
| |
| m_positionLeftChanged = false; |
| m_positionTopChanged = false; |
| } |
| |
| void LayoutComponent::widthOverride(float width, int unitValue, bool isRow) |
| { |
| m_widthOverride = width; |
| m_widthUnitValueOverride = unitValue; |
| m_parentIsRow = isRow; |
| markLayoutNodeDirty(); |
| } |
| |
| void LayoutComponent::heightOverride(float height, int unitValue, bool isRow) |
| { |
| m_heightOverride = height; |
| m_heightUnitValueOverride = unitValue; |
| m_parentIsRow = isRow; |
| markLayoutNodeDirty(); |
| } |
| |
| void LayoutComponent::parentIsRow(bool isRow) |
| { |
| m_parentIsRow = isRow; |
| markLayoutNodeDirty(); |
| } |
| |
| void LayoutComponent::widthIntrinsicallySizeOverride(bool intrinsic) |
| { |
| m_widthIntrinsicallySizeOverride = intrinsic; |
| // If we have an intrinsically sized override, set units to auto |
| // otherwise set to points |
| m_widthUnitValueOverride = intrinsic ? 3 : 1; |
| markLayoutNodeDirty(); |
| } |
| |
| void LayoutComponent::heightIntrinsicallySizeOverride(bool intrinsic) |
| { |
| m_heightIntrinsicallySizeOverride = intrinsic; |
| // If we have an intrinsically sized override, set units to auto |
| // otherwise set to points |
| m_heightUnitValueOverride = intrinsic ? 3 : 1; |
| markLayoutNodeDirty(); |
| } |
| |
| void LayoutComponent::forcedWidth(float width) |
| { |
| if (m_forcedWidth == width) |
| { |
| return; |
| } |
| m_forcedWidth = width; |
| markLayoutStyleDirty(); |
| markLayoutNodeDirty(); |
| } |
| |
| void LayoutComponent::forcedHeight(float height) |
| { |
| if (m_forcedHeight == height) |
| { |
| return; |
| } |
| m_forcedHeight = height; |
| markLayoutStyleDirty(); |
| markLayoutNodeDirty(); |
| } |
| |
| void LayoutComponent::updateConstraints() |
| { |
| if (layoutConstraints().size() > 0) |
| { |
| for (auto parentConstraint : layoutConstraints()) |
| { |
| parentConstraint->constrainChild(this); |
| } |
| } |
| Super::updateConstraints(); |
| } |
| |
| bool LayoutComponent::overridesKeyedInterpolation(int propertyKey) |
| { |
| #ifdef WITH_RIVE_LAYOUT |
| if (animates()) |
| { |
| switch (propertyKey) |
| { |
| case LayoutComponentBase::widthPropertyKey: |
| case LayoutComponentBase::heightPropertyKey: |
| return true; |
| default: |
| return false; |
| } |
| } |
| #endif |
| return false; |
| } |
| |
| bool LayoutComponent::isHidden() const |
| { |
| return Super::isHidden() || isCollapsed(); |
| } |
| |
| bool LayoutComponent::isCollapsed() const |
| { |
| if (Super::isCollapsed()) |
| { |
| return true; |
| } |
| #ifdef WITH_RIVE_LAYOUT |
| return styleDisplayHidden(); |
| #endif |
| return false; |
| } |
| |
| void LayoutComponent::propagateCollapse(bool collapse) |
| { |
| auto collapsed = collapse || isCollapsed(); |
| for (Component* child : children()) |
| { |
| child->collapse(collapsed); |
| } |
| updateCollapsables(); |
| } |
| |
| bool LayoutComponent::collapse(bool value) |
| { |
| if (!Component::collapse(value)) |
| { |
| return false; |
| } |
| propagateCollapse(value); |
| return true; |
| } |
| |
| #ifdef WITH_RIVE_LAYOUT |
| |
| LayoutComponent::LayoutComponent() : |
| m_layoutData(new LayoutData()), m_proxy(this) |
| { |
| m_layoutData->node.getConfig()->setPointScaleFactor(0); |
| } |
| |
| float LayoutComponent::gapHorizontal() |
| { |
| if (m_style == nullptr) |
| { |
| return 0; |
| } |
| return m_style->gapHorizontalUnits() == YGUnitPercent |
| ? (m_style->gapHorizontal() / 100.0f) * layoutWidth() |
| : m_style->gapHorizontal(); |
| } |
| |
| float LayoutComponent::gapVertical() |
| { |
| if (m_style == nullptr) |
| { |
| return 0; |
| } |
| return m_style->gapVerticalUnits() == YGUnitPercent |
| ? (m_style->gapVertical() / 100.0f) * layoutHeight() |
| : m_style->gapVertical(); |
| } |
| |
| StatusCode LayoutComponent::onAddedDirty(CoreContext* context) |
| { |
| auto code = Super::onAddedDirty(context); |
| if (code != StatusCode::Ok) |
| { |
| return code; |
| } |
| |
| auto coreStyle = context->resolve(styleId()); |
| if (coreStyle == nullptr || !coreStyle->is<LayoutComponentStyle>()) |
| { |
| return StatusCode::MissingObject; |
| } |
| m_style = static_cast<LayoutComponentStyle*>(coreStyle); |
| addChild(m_style); |
| #ifdef WITH_RIVE_LAYOUT |
| // We contribute sizing the style cannot see; it contributes the rest. |
| addLayoutStyleApplier(this); |
| addLayoutStyleApplier(m_style); |
| #endif |
| |
| return StatusCode::Ok; |
| } |
| |
| StatusCode LayoutComponent::onAddedClean(CoreContext* context) |
| { |
| auto code = Super::onAddedClean(context); |
| if (code != StatusCode::Ok) |
| { |
| return code; |
| } |
| markLayoutStyleDirty(); |
| syncLayoutChildren(); |
| propagateCollapse(isCollapsed()); |
| return StatusCode::Ok; |
| } |
| |
| void LayoutComponent::drawProxy(Renderer* renderer) |
| { |
| if (clip()) |
| { |
| renderer->save(); |
| renderer->clipPath(m_worldPath.renderPath(this)); |
| } |
| for (auto shapePaint : m_ShapePaints) |
| { |
| if (!shapePaint->shouldDraw()) |
| { |
| continue; |
| } |
| auto shapePaintPath = shapePaint->pickPath(this); |
| if (shapePaintPath == nullptr) |
| { |
| continue; |
| } |
| shapePaint->draw(renderer, shapePaintPath, worldTransform()); |
| } |
| } |
| |
| void LayoutComponent::draw(Renderer* renderer) |
| { |
| // Restore clip before drawing stroke so we don't clip the stroke |
| if (clip()) |
| { |
| renderer->restore(); |
| } |
| } |
| |
| void LayoutComponent::updateRenderPath() |
| { |
| if (isHidden()) |
| { |
| return; |
| } |
| if (m_ShapePaints.empty() && !clip() && !m_hasForegroundDrawable) |
| { |
| return; |
| } |
| float tl = 0.0f, tr = 0.0f, br = 0.0f, bl = 0.0f; |
| if (style() != nullptr) |
| { |
| const bool isLTR = actualDirection() != LayoutDirection::rtl; |
| if (style()->linkCornerRadius()) |
| { |
| tl = tr = br = bl = style()->cornerRadiusTL(); |
| } |
| else |
| { |
| tl = isLTR ? style()->cornerRadiusTL() : style()->cornerRadiusTR(); |
| tr = isLTR ? style()->cornerRadiusTR() : style()->cornerRadiusTL(); |
| bl = isLTR ? style()->cornerRadiusBL() : style()->cornerRadiusBR(); |
| br = isLTR ? style()->cornerRadiusBR() : style()->cornerRadiusBL(); |
| } |
| } |
| |
| m_backgroundRawPath.rewind(); |
| Path::addRoundedRect(m_backgroundRawPath, |
| AABB{0.0f, 0.0f, m_layout.width(), m_layout.height()}, |
| tl, |
| tr, |
| br, |
| bl); |
| |
| m_localPath.rewind(); |
| m_localPath.addPath(m_backgroundRawPath); |
| |
| m_worldPath.rewind(false, FillRule::clockwise); |
| m_worldPath.addPath(m_backgroundRawPath, &m_WorldTransform); |
| |
| for (auto shapePaint : m_ShapePaints) |
| { |
| if (!shapePaint->shouldDraw()) |
| { |
| continue; |
| } |
| if (shapePaint->is<ShapePaint>()) |
| { |
| shapePaint->as<ShapePaint>()->invalidateEffects(); |
| } |
| } |
| } |
| |
| static YGSize measureFunc(YGNode* node, |
| float width, |
| YGMeasureMode widthMode, |
| float height, |
| YGMeasureMode heightMode) |
| { |
| Vec2D size = ((LayoutComponent*)node->getContext()) |
| ->measureLayout(width, |
| (LayoutMeasureMode)widthMode, |
| height, |
| (LayoutMeasureMode)heightMode); |
| |
| return YGSize{size.x, size.y}; |
| } |
| |
| Vec2D LayoutComponent::measureLayout(float width, |
| LayoutMeasureMode widthMode, |
| float height, |
| LayoutMeasureMode heightMode) |
| { |
| Vec2D size = Vec2D(); |
| for (auto child : children()) |
| { |
| if (child->is<LayoutComponent>()) |
| { |
| continue; |
| } |
| auto sizeableChild = IntrinsicallySizeable::from(child); |
| if (sizeableChild != nullptr) |
| { |
| Vec2D measured = sizeableChild->measureLayout(width, |
| widthMode, |
| height, |
| heightMode); |
| size = Vec2D(std::max(size.x, measured.x), |
| std::max(size.y, measured.y)); |
| } |
| } |
| return size; |
| } |
| |
| bool LayoutComponent::effectiveParentIsRow() |
| { |
| if (canHaveOverrides()) |
| { |
| return m_parentIsRow; |
| } |
| return layoutParent() != nullptr ? layoutParent()->mainAxisIsRow() : true; |
| } |
| |
| bool LayoutComponent::mainAxisIsRow() |
| { |
| if (style() == nullptr) |
| { |
| return true; |
| } |
| return style()->flexDirection() == YGFlexDirectionRow || |
| style()->flexDirection() == YGFlexDirectionRowReverse; |
| } |
| |
| bool LayoutComponent::mainAxisIsColumn() |
| { |
| if (style() == nullptr) |
| { |
| return false; |
| } |
| return style()->flexDirection() == YGFlexDirectionColumn || |
| style()->flexDirection() == YGFlexDirectionColumnReverse; |
| } |
| |
| bool LayoutComponent::isLeaf() |
| { |
| bool leaf = true; |
| forEachLayoutProvider(this, [&leaf](Component*, LayoutNodeProvider*) { |
| leaf = false; |
| }); |
| return leaf; |
| } |
| |
| void* LayoutComponent::layoutNode(int index) |
| { |
| if (m_layoutData != nullptr) |
| { |
| return static_cast<void*>(&m_layoutData->node); |
| } |
| return nullptr; |
| } |
| |
| #ifdef WITH_RIVE_LAYOUT |
| // Tracks come from GridTrack children, which the style cannot walk. A stack has |
| // no authored tracks — the style sets up its implicit cell instead. |
| void LayoutComponent::applyContainerStyle(YGStyle& ygStyle, |
| const LayoutSyncContext& context) |
| { |
| if (m_style == nullptr || m_style->isStack()) |
| { |
| return; |
| } |
| GridTrack::syncContainerStyle(ygStyle, this, m_style->justifyItemsValue()); |
| } |
| #endif |
| |
| #ifdef WITH_RIVE_LAYOUT |
| // This layout's own size and how it sits in its parent's flow. On the |
| // component, not the style, because it reads width()/height(), the |
| // NestedArtboardLayout overrides and the forced dimensions. |
| // |
| // Not shared with LayoutParticipant::applyBaseStyle: the participant has no |
| // flexBasis, no overrides and its own fractionalWidth, so merging would change |
| // behaviour. |
| void LayoutComponent::applyBaseStyle(YGStyle& ygStyle, |
| const LayoutSyncContext& context) |
| { |
| if (m_style == nullptr) |
| { |
| return; |
| } |
| const bool parentIsRow = context.parentIsRow; |
| const bool parentIsGridLike = context.parentIsGrid; |
| |
| // Derive the unit the layout engine needs from the persisted unit + scale |
| // type. |
| // - fill/hug always map to YGUnitAuto (regardless of position type) — |
| // these scale modes don't have a meaningful unit; the layout engine |
| // decides the size. |
| // - fixed uses the stored unit. Absolute layouts trust whatever's |
| // stored (e.g. the editor freezes a point value at the moment |
| // positionType becomes absolute, since absolute children sit outside |
| // flex flow). |
| bool isAbsolute = m_style->positionType() == YGPositionTypeAbsolute; |
| bool isLegacyHugEncoding = |
| m_style->widthScaleType() == LayoutScaleType::fixed && |
| m_style->heightScaleType() == LayoutScaleType::fixed && |
| m_style->intrinsicallySized() && isLeaf(); |
| auto effectiveUnits = [isAbsolute, |
| isLegacyHugEncoding](LayoutScaleType scaleType, |
| YGUnit storedUnits) { |
| if (isAbsolute && scaleType != LayoutScaleType::hug) |
| { |
| return storedUnits; |
| } |
| if (scaleType != LayoutScaleType::fixed) |
| { |
| return YGUnitAuto; |
| } |
| if (storedUnits == YGUnitPoint || storedUnits == YGUnitPercent) |
| { |
| return storedUnits; |
| } |
| return isLegacyHugEncoding ? YGUnitAuto : YGUnitPoint; |
| }; |
| auto realWidth = width(); |
| auto realWidthScaleType = m_style->widthScaleType(); |
| auto realWidthUnits = |
| effectiveUnits(realWidthScaleType, m_style->widthUnits()); |
| auto realHeight = height(); |
| auto realHeightScaleType = m_style->heightScaleType(); |
| auto realHeightUnits = |
| effectiveUnits(realHeightScaleType, m_style->heightUnits()); |
| |
| // If we have override width/height values, use those. |
| // Currently we only use these for Artboards that are part of a |
| // NestedArtboardLayout but perhaps there will be other use cases for |
| // overriding in the future? |
| if (canHaveOverrides()) |
| { |
| if (!std::isnan(m_widthOverride)) |
| { |
| realWidth = m_widthOverride; |
| } |
| if (!std::isnan(m_heightOverride)) |
| { |
| realHeight = m_heightOverride; |
| } |
| if (m_widthUnitValueOverride != -1) |
| { |
| realWidthUnits = YGUnit(m_widthUnitValueOverride); |
| switch (realWidthUnits) |
| { |
| case YGUnitPoint: |
| case YGUnitPercent: |
| realWidthScaleType = LayoutScaleType::fixed; |
| break; |
| case YGUnitAuto: |
| if (m_widthIntrinsicallySizeOverride) |
| { |
| realWidthScaleType = LayoutScaleType::hug; |
| } |
| else |
| { |
| realWidthScaleType = LayoutScaleType::fill; |
| } |
| break; |
| default: |
| break; |
| } |
| } |
| if (m_heightUnitValueOverride != -1) |
| { |
| realHeightUnits = YGUnit(m_heightUnitValueOverride); |
| switch (realHeightUnits) |
| { |
| case YGUnitPoint: |
| case YGUnitPercent: |
| realHeightScaleType = LayoutScaleType::fixed; |
| break; |
| case YGUnitAuto: |
| if (m_heightIntrinsicallySizeOverride) |
| { |
| realHeightScaleType = LayoutScaleType::hug; |
| } |
| else |
| { |
| realHeightScaleType = LayoutScaleType::fill; |
| } |
| break; |
| default: |
| break; |
| } |
| } |
| } |
| if (!std::isnan(m_forcedWidth)) |
| { |
| ygStyle.dimensions()[YGDimensionWidth] = |
| YGValue{std::max(0.0f, m_forcedWidth), YGUnitPoint}; |
| } |
| else |
| { |
| ygStyle.dimensions()[YGDimensionWidth] = |
| YGValue{std::max(0.0f, realWidth), realWidthUnits}; |
| } |
| if (!std::isnan(m_forcedHeight)) |
| { |
| ygStyle.dimensions()[YGDimensionHeight] = |
| YGValue{std::max(0.0f, m_forcedHeight), YGUnitPoint}; |
| } |
| else |
| { |
| ygStyle.dimensions()[YGDimensionHeight] = |
| YGValue{std::max(0.0f, realHeight), realHeightUnits}; |
| } |
| |
| if (parentIsGridLike) |
| { |
| // Grid/stack: size per-axis. fill stretches that axis (width -> |
| // justifySelf after grid placement below, height -> alignSelf); |
| // non-fill stays auto so the container alignment positions us. flexGrow |
| // ignored. |
| ygStyle.flexGrow() = YGFloatOptional(0); |
| ygStyle.flexShrink() = YGFloatOptional(0); |
| ygStyle.alignSelf() = realHeightScaleType == LayoutScaleType::fill |
| ? YGAlignStretch |
| : YGAlignAuto; |
| } |
| else |
| { |
| switch (realWidthScaleType) |
| { |
| case LayoutScaleType::fixed: |
| if (parentIsRow) |
| { |
| ygStyle.flexGrow() = YGFloatOptional(0); |
| ygStyle.flexShrink() = YGFloatOptional(0); |
| ygStyle.flexBasis() = |
| YGValue{m_style->flexBasis(), YGUnitAuto}; |
| } |
| else |
| { |
| ygStyle.alignSelf() = YGAlignAuto; |
| } |
| break; |
| case LayoutScaleType::fill: |
| if (parentIsRow) |
| { |
| ygStyle.flexGrow() = YGFloatOptional(fractionalWidth()); |
| ygStyle.flexShrink() = YGFloatOptional(fractionalWidth()); |
| ygStyle.flexBasis() = YGValue{m_style->flexBasis(), |
| m_style->flexBasisUnits()}; |
| } |
| else |
| { |
| ygStyle.alignSelf() = YGAlignStretch; |
| } |
| break; |
| case LayoutScaleType::hug: |
| if (parentIsRow) |
| { |
| ygStyle.flexGrow() = YGFloatOptional(0); |
| ygStyle.flexShrink() = YGFloatOptional(0); |
| ygStyle.flexBasis() = |
| YGValue{m_style->flexBasis(), YGUnitAuto}; |
| } |
| else |
| { |
| ygStyle.alignSelf() = YGAlignAuto; |
| } |
| break; |
| default: |
| break; |
| } |
| |
| switch (realHeightScaleType) |
| { |
| case LayoutScaleType::fixed: |
| if (!parentIsRow) |
| { |
| ygStyle.flexGrow() = YGFloatOptional(0); |
| ygStyle.flexShrink() = YGFloatOptional(0); |
| ygStyle.flexBasis() = |
| YGValue{m_style->flexBasis(), YGUnitAuto}; |
| } |
| else |
| { |
| ygStyle.alignSelf() = YGAlignAuto; |
| } |
| break; |
| case LayoutScaleType::fill: |
| if (!parentIsRow) |
| { |
| ygStyle.flexGrow() = YGFloatOptional(fractionalHeight()); |
| ygStyle.flexShrink() = YGFloatOptional(fractionalHeight()); |
| ygStyle.flexBasis() = YGValue{m_style->flexBasis(), |
| m_style->flexBasisUnits()}; |
| } |
| else |
| { |
| ygStyle.alignSelf() = YGAlignStretch; |
| } |
| break; |
| case LayoutScaleType::hug: |
| if (!parentIsRow) |
| { |
| ygStyle.flexGrow() = YGFloatOptional(0); |
| ygStyle.flexShrink() = YGFloatOptional(0); |
| ygStyle.flexBasis() = |
| YGValue{m_style->flexBasis(), YGUnitAuto}; |
| } |
| else |
| { |
| ygStyle.alignSelf() = YGAlignAuto; |
| } |
| break; |
| default: |
| break; |
| } |
| } |
| } |
| #endif |
| |
| void LayoutComponent::syncStyle() |
| { |
| if (m_style == nullptr || m_layoutData == nullptr) |
| { |
| return; |
| } |
| YGNode& ygNode = m_layoutData->node; |
| YGStyle& ygStyle = m_layoutData->style; |
| if (m_style->intrinsicallySized() && isLeaf()) |
| { |
| ygNode.setContext(this); |
| ygNode.setMeasureFunc(measureFunc); |
| } |
| else |
| { |
| ygNode.setMeasureFunc(nullptr); |
| } |
| |
| // Our owning layout may sit above a transparent container (group/Solo), so |
| // walk up for it rather than reading the immediate parent — a grouped item |
| // is still laid out by the container and needs its context. Cached because |
| // layoutParent() walks the chain on each call. |
| auto* parentLayout = layoutParent(); |
| auto* parentLayoutStyle = |
| parentLayout != nullptr ? parentLayout->style() : nullptr; |
| bool parentIsGridLike = |
| parentLayoutStyle != nullptr && parentLayoutStyle->isGrid(); |
| bool parentIsStack = |
| parentLayoutStyle != nullptr && parentLayoutStyle->isStack(); |
| uint32_t containerJustifyItems = |
| parentLayoutStyle != nullptr ? parentLayoutStyle->justifyItemsValue() |
| : (uint32_t)YGJustifyStretch; |
| // Every style write is an applier now; resolve the context and run them. |
| LayoutSyncContext syncContext; |
| syncContext.parentIsGrid = parentIsGridLike; |
| syncContext.parentIsStack = parentIsStack; |
| syncContext.containerJustifyItems = containerJustifyItems; |
| syncContext.inlineHugs = m_style->widthScaleType() == LayoutScaleType::hug; |
| syncContext.parentIsRow = effectiveParentIsRow(); |
| syncContext.isLTR = actualDirection() != LayoutDirection::rtl; |
| syncContext.hasLayoutParent = layoutParent() != nullptr; |
| m_layoutData->applyLayoutStyles(ygStyle, syncContext); |
| |
| ygNode.setStyle(ygStyle); |
| |
| // Sync the styles of participant children that provide their |
| // own layout node (including any nested inside transparent groups). |
| // LayoutComponent children sync their own styles through the dirty-layout |
| // set; these have no such entry. |
| forEachLayoutProvider(this, |
| [](Component* child, LayoutNodeProvider* provider) { |
| switch (child->coreType()) |
| { |
| case LayoutComponentBase::typeKey: |
| case NestedArtboardLayoutBase::typeKey: |
| case ArtboardComponentListBase::typeKey: |
| break; |
| default: |
| provider->syncStyleChanges(); |
| break; |
| } |
| }); |
| } |
| |
| void LayoutComponent::clearLayoutChildren() |
| { |
| YGNode& ourNode = m_layoutData->node; |
| YGNodeRemoveAllChildren(&ourNode); |
| } |
| |
| void LayoutComponent::syncLayoutChildren() |
| { |
| YGNode& ourNode = m_layoutData->node; |
| YGNodeRemoveAllChildren(&ourNode); |
| int index = 0; |
| forEachLayoutProvider( |
| this, |
| [&ourNode, &index](Component*, LayoutNodeProvider* provider) { |
| for (size_t i = 0; i < provider->numLayoutNodes(); i++) |
| { |
| auto* node = static_cast<YGNode*>(provider->layoutNode((int)i)); |
| if (node != nullptr) |
| { |
| ourNode.insertChild(node, index++); |
| node->setOwner(&ourNode); |
| ourNode.markDirtyAndPropagate(); |
| } |
| } |
| }); |
| markLayoutNodeDirty(); |
| } |
| |
| void LayoutComponent::propagateSize() { propagateSizeToChildren(this); } |
| |
| void LayoutComponent::propagateSizeToChildren(ContainerComponent* component) |
| { |
| if (isHidden()) |
| { |
| return; |
| } |
| for (auto child : component->children()) |
| { |
| // Don't content-size nested layouts, or containers transparent to |
| // layout (groups/Solos) — their contents are free content or |
| // participants, never content-sized from here. |
| if (child->is<LayoutComponent>() || isTransparentLayoutContainer(child)) |
| { |
| continue; |
| } |
| // A participating child owns its own layout node and is sized by the |
| // layout engine, not by controlSize — skip it. |
| if (LayoutNodeProvider::from(child) != nullptr) |
| { |
| continue; |
| } |
| auto sizeableChild = IntrinsicallySizeable::from(child); |
| if (sizeableChild != nullptr && m_style != nullptr) |
| { |
| LayoutScaleType widthScaleType = m_style->widthScaleType(); |
| LayoutScaleType heightScaleType = m_style->heightScaleType(); |
| sizeableChild->controlSize( |
| Vec2D(m_layout.width(), m_layout.height()), |
| widthScaleType, |
| heightScaleType, |
| actualDirection()); |
| |
| if (!sizeableChild->shouldPropagateSizeToChildren()) |
| { |
| // Do not propagate to its children |
| continue; |
| } |
| } |
| if (child->is<ContainerComponent>()) |
| { |
| propagateSizeToChildren(child->as<ContainerComponent>()); |
| } |
| } |
| } |
| |
| void LayoutComponent::calculateLayoutInternal(float availableWidth, |
| float availableHeight) |
| { |
| auto actualAvailWidth = |
| std::isnan(availableWidth) && m_style && m_style->intrinsicallySized() |
| ? availableWidth |
| : width(); |
| auto actualAvailHeight = |
| std::isnan(availableHeight) && m_style && m_style->intrinsicallySized() |
| ? availableHeight |
| : height(); |
| YGNodeCalculateLayout(&m_layoutData->node, |
| actualAvailWidth, |
| actualAvailHeight, |
| YGDirection::YGDirectionInherit); |
| } |
| |
| bool LayoutComponent::styleDisplayHidden() const |
| { |
| if (m_style == nullptr) |
| { |
| return false; |
| } |
| return m_style->display() == YGDisplayNone; |
| } |
| |
| LayoutDirection LayoutComponent::actualDirection() |
| { |
| if (m_style == nullptr) |
| { |
| return m_inheritedDirection; |
| } |
| switch (m_style->direction()) |
| { |
| case YGDirectionLTR: |
| return LayoutDirection::ltr; |
| case YGDirectionRTL: |
| return LayoutDirection::rtl; |
| default: |
| return m_inheritedDirection; |
| } |
| } |
| |
| void LayoutComponent::onDirty(ComponentDirt value) |
| { |
| Super::onDirty(value); |
| if ((value & ComponentDirt::WorldTransform) == |
| ComponentDirt::WorldTransform && |
| clip()) |
| { |
| addDirt(ComponentDirt::Path); |
| } |
| } |
| |
| static Layout layoutFromYoga(const YGLayout& layout) |
| { |
| return Layout(layout.position[YGEdgeLeft], |
| layout.position[YGEdgeTop], |
| layout.dimensions[YGDimensionWidth], |
| layout.dimensions[YGDimensionHeight]); |
| } |
| |
| static LayoutPadding layoutPaddingFromYoga(const YGLayout& layout) |
| { |
| return LayoutPadding(layout.padding[YGEdgeLeft], |
| layout.padding[YGEdgeTop], |
| layout.padding[YGEdgeRight], |
| layout.padding[YGEdgeBottom]); |
| } |
| |
| void LayoutComponent::updateLayoutBounds(bool animate) |
| { |
| YGNode& node = m_layoutData->node; |
| bool updated = node.getHasNewLayout(); |
| if (!updated) |
| { |
| return; |
| } |
| node.setHasNewLayout(false); |
| |
| forEachLayoutProvider(this, |
| [animate](Component*, LayoutNodeProvider* provider) { |
| provider->updateLayoutBounds(animate); |
| }); |
| |
| const auto& yogaLayout = node.getLayout(); |
| Layout newLayout = layoutFromYoga(yogaLayout); |
| m_layoutPadding = layoutPaddingFromYoga(yogaLayout); |
| |
| if (m_justAddedToHost) |
| { |
| m_justAddedToHost = false; |
| // In cases were we have a host (ie, Component List, etc), we |
| // don't want to animate the x/y/width/height because the initial |
| // x/y/width/height will have been 0,0,0,0 within the parent host. |
| // |
| // Instead, we want to the position/size to start at whatever this |
| // layout's computed position is within its host. |
| // |
| // We'll keep this as an entirely seperate code path because |
| // this may be useful in adding support for animate in/out of |
| // items in an ArtboardHost. |
| m_layout = newLayout; |
| auto animationData = currentAnimationData(); |
| animationData->from = newLayout; |
| animationData->to = newLayout; |
| animationData->elapsedSeconds = 0.0f; |
| propagateSize(); |
| markWorldTransformDirty(); |
| m_forceUpdateLayoutBounds = false; |
| return; |
| } |
| |
| if (animate && animates()) |
| { |
| auto animationData = currentAnimationData(); |
| if (newLayout != animationData->to || m_forceUpdateLayoutBounds) |
| { |
| if (animationData->elapsedSeconds != 0.0f) |
| { |
| if (m_isSmoothingAnimation) |
| { |
| // we were already smoothening. |
| m_animationDataA.copy(m_animationDataB); |
| } |
| m_isSmoothingAnimation = true; |
| } |
| else |
| { |
| m_isSmoothingAnimation = false; |
| } |
| animationData = currentAnimationData(); |
| animationData->from = m_layout; |
| animationData->to = newLayout; |
| animationData->elapsedSeconds = 0.0f; |
| propagateSize(); |
| markWorldTransformDirty(); |
| } |
| } |
| else if (newLayout != m_layout || m_forceUpdateLayoutBounds) |
| { |
| if (m_layout.width() != newLayout.width() || |
| m_layout.height() != newLayout.height()) |
| { |
| // Width changed, we need to rebuild the path. |
| addDirt(ComponentDirt::Path); |
| } |
| m_animationDataA.to = m_layout = newLayout; |
| |
| propagateSize(); |
| markWorldTransformDirty(); |
| } |
| m_forceUpdateLayoutBounds = false; |
| } |
| |
| bool LayoutComponent::advanceComponent(float elapsedSeconds, AdvanceFlags flags) |
| { |
| if ((flags & AdvanceFlags::NewFrame) != AdvanceFlags::NewFrame || |
| isCollapsed()) |
| { |
| return false; |
| } |
| |
| return applyInterpolation(elapsedSeconds, |
| (flags & AdvanceFlags::Animate) == |
| AdvanceFlags::Animate || |
| (flags & AdvanceFlags::AdvanceNested) == |
| AdvanceFlags::AdvanceNested); |
| } |
| |
| bool LayoutComponent::animates() |
| { |
| if (m_style == nullptr) |
| { |
| return false; |
| } |
| return 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; |
| } |
| } |
| |
| bool LayoutComponent::cascadeLayoutStyle( |
| LayoutStyleInterpolation inheritedInterpolation, |
| KeyFrameInterpolator* inheritedInterpolator, |
| float inheritedInterpolationTime, |
| LayoutDirection direction) |
| { |
| bool updated = false; |
| if (m_style != nullptr && |
| m_style->animationStyle() == LayoutAnimationStyle::inherit) |
| { |
| updated = setInheritedInterpolation(inheritedInterpolation, |
| inheritedInterpolator, |
| inheritedInterpolationTime); |
| } |
| else |
| { |
| clearInheritedInterpolation(); |
| } |
| auto oldDirection = m_inheritedDirection; |
| if (direction == LayoutDirection::inherit || |
| (m_style != nullptr && m_style->direction() != YGDirectionInherit)) |
| { |
| m_inheritedDirection = LayoutDirection::inherit; |
| } |
| else |
| { |
| m_inheritedDirection = direction; |
| } |
| if (m_inheritedDirection != oldDirection) |
| { |
| markLayoutNodeDirty(true); |
| addDirt(ComponentDirt::Path); |
| updated = true; |
| } |
| forEachLayoutProvider(this, |
| [this](Component*, LayoutNodeProvider* provider) { |
| provider->cascadeLayoutStyle(interpolation(), |
| interpolator(), |
| interpolationTime(), |
| actualDirection()); |
| }); |
| return updated; |
| } |
| |
| bool LayoutComponent::setInheritedInterpolation( |
| LayoutStyleInterpolation inheritedInterpolation, |
| KeyFrameInterpolator* inheritedInterpolator, |
| float inheritedInterpolationTime) |
| { |
| if (inheritedInterpolation != m_inheritedInterpolation || |
| inheritedInterpolator != m_inheritedInterpolator || |
| inheritedInterpolationTime != m_inheritedInterpolationTime) |
| { |
| m_inheritedInterpolation = inheritedInterpolation; |
| m_inheritedInterpolator = inheritedInterpolator; |
| m_inheritedInterpolationTime = inheritedInterpolationTime; |
| return true; |
| } |
| return false; |
| } |
| |
| void LayoutComponent::clearInheritedInterpolation() |
| { |
| m_inheritedInterpolation = LayoutStyleInterpolation::hold; |
| m_inheritedInterpolator = nullptr; |
| m_inheritedInterpolationTime = 0; |
| } |
| |
| LayoutAnimationData* LayoutComponent::currentAnimationData() |
| { |
| return m_isSmoothingAnimation ? &m_animationDataB : &m_animationDataA; |
| } |
| |
| void LayoutAnimationData::copy(const LayoutAnimationData& source) |
| { |
| from = source.from; |
| to = source.to; |
| elapsedSeconds = source.elapsedSeconds; |
| } |
| |
| bool LayoutComponent::applyInterpolation(float elapsedSeconds, bool animate) |
| { |
| auto animationData = currentAnimationData(); |
| if (!animate || !animates() || m_style == nullptr || |
| animationData->to == m_layout) |
| { |
| return false; |
| } |
| if (m_isSmoothingAnimation) |
| { |
| float f = std::fmin(1.0f, |
| interpolationTime() > 0.0f |
| ? m_animationDataA.elapsedSeconds / |
| interpolationTime() |
| : 1.0f); |
| |
| if (interpolation() != LayoutStyleInterpolation::linear && |
| interpolator() != nullptr) |
| { |
| f = interpolator()->transform(f); |
| } |
| m_animationDataB.from = m_animationDataA.interpolate(f); |
| if (f == 1.0f) |
| { |
| m_animationDataA.copy(m_animationDataB); |
| m_isSmoothingAnimation = false; |
| } |
| else |
| { |
| m_animationDataA.elapsedSeconds += elapsedSeconds; |
| } |
| } |
| |
| if ((animationData = currentAnimationData())->elapsedSeconds >= |
| interpolationTime()) |
| { |
| Layout newLayout = animationData->to; |
| if (m_layout.width() != newLayout.width() || |
| m_layout.height() != newLayout.height()) |
| { |
| addDirt(ComponentDirt::Path); |
| } |
| m_layout = animationData->to; |
| |
| if (m_isSmoothingAnimation) |
| { |
| m_isSmoothingAnimation = false; |
| m_animationDataA.copy(m_animationDataB); |
| m_animationDataA.elapsedSeconds = m_animationDataB.elapsedSeconds = |
| 0.0f; |
| } |
| else |
| { |
| m_animationDataA.elapsedSeconds = 0.0f; |
| } |
| propagateSize(); |
| markWorldTransformDirty(); |
| |
| return false; |
| } |
| float f = |
| std::fmin(1.0f, |
| interpolationTime() > 0 |
| ? animationData->elapsedSeconds / interpolationTime() |
| : 1.0f); |
| if (interpolation() != LayoutStyleInterpolation::linear && |
| interpolator() != nullptr) |
| { |
| f = interpolator()->transform(f); |
| } |
| |
| auto current = animationData->interpolate(f); |
| if (m_layout != current) |
| { |
| bool sizeChanged = m_layout.width() != current.width() || |
| m_layout.height() != current.height(); |
| m_layout = current; |
| if (sizeChanged) |
| { |
| propagateSize(); |
| } |
| markWorldTransformDirty(); |
| } |
| |
| animationData->elapsedSeconds += elapsedSeconds; |
| if (f != 1) |
| { |
| // Do we really need to mark the layout node dirty!!?? |
| markLayoutNodeDirty(); |
| return true; |
| } |
| return false; |
| } |
| |
| void LayoutComponent::interruptAnimation() |
| { |
| if (animates()) |
| { |
| m_layout = currentAnimationData()->to; |
| propagateSize(); |
| } |
| } |
| |
| void LayoutComponent::markLayoutNodeDirty(bool shouldForceUpdateLayoutBounds) |
| { |
| if (shouldForceUpdateLayoutBounds == true) |
| { |
| m_forceUpdateLayoutBounds = shouldForceUpdateLayoutBounds; |
| } |
| m_layoutData->node.markDirtyAndPropagate(); |
| artboard()->markLayoutDirty(this); |
| } |
| |
| void LayoutComponent::markLayoutStyleDirty() |
| { |
| clearInheritedInterpolation(); |
| addDirt(ComponentDirt::LayoutStyle); |
| if (artboard() != this) |
| { |
| artboard()->markLayoutStyleDirty(); |
| } |
| } |
| |
| void LayoutComponent::positionTypeChanged() |
| { |
| if (m_style == nullptr) |
| { |
| return; |
| } |
| if (m_style->positionType() == YGPositionTypeAbsolute) |
| { |
| // Preserve computed position only if left/top were not explicitly keyed |
| // this frame. If keyed, honor those values and only set units |
| // appropriately. |
| if (!m_positionLeftChanged) |
| { |
| m_style->positionLeft(layoutBounds().left()); |
| } |
| if (!m_positionTopChanged) |
| { |
| m_style->positionTop(layoutBounds().top()); |
| } |
| m_style->positionRight(0); |
| m_style->positionBottom(0); |
| m_style->positionLeftUnitsValue(YGUnitPoint); |
| m_style->positionTopUnitsValue(YGUnitPoint); |
| m_style->positionRightUnitsValue(YGUnitUndefined); |
| m_style->positionBottomUnitsValue(YGUnitUndefined); |
| } |
| else |
| { |
| m_style->positionLeft(0); |
| m_style->positionTop(0); |
| m_style->positionRight(0); |
| m_style->positionBottom(0); |
| m_style->positionLeftUnitsValue(YGUnitUndefined); |
| m_style->positionTopUnitsValue(YGUnitUndefined); |
| m_style->positionRightUnitsValue(YGUnitUndefined); |
| m_style->positionBottomUnitsValue(YGUnitUndefined); |
| } |
| markLayoutNodeDirty(); |
| } |
| |
| void LayoutComponent::scaleTypeChanged() |
| { |
| if (m_style == nullptr) |
| { |
| return; |
| } |
| m_style->intrinsicallySizedValue( |
| m_style->widthScaleType() == LayoutScaleType::hug || |
| m_style->heightScaleType() == LayoutScaleType::hug); |
| markLayoutNodeDirty(); |
| } |
| |
| void LayoutComponent::displayChanged() |
| { |
| if (m_style == nullptr) |
| { |
| return; |
| } |
| propagateCollapse(isCollapsed()); |
| markLayoutNodeDirty(); |
| } |
| |
| void LayoutComponent::syncChildProviderStyles() |
| { |
| forEachLayoutProvider(this, [](Component*, LayoutNodeProvider* provider) { |
| provider->syncStyleChanges(); |
| provider->markLayoutNodeDirty(); |
| }); |
| } |
| |
| void LayoutComponent::flexDirectionChanged() |
| { |
| markLayoutNodeDirty(); |
| syncChildProviderStyles(); |
| } |
| |
| // Switching grid/flex/stack changes how each child's style maps to yoga. |
| void LayoutComponent::layoutTypeChanged() |
| { |
| markLayoutNodeDirty(); |
| syncChildProviderStyles(); |
| } |
| |
| void LayoutComponent::directionChanged() |
| { |
| markLayoutStyleDirty(); |
| markLayoutNodeDirty(true); |
| } |
| #else |
| LayoutComponent::LayoutComponent() : |
| m_layoutData(new LayoutData()), m_proxy(this) |
| {} |
| |
| float LayoutComponent::gapHorizontal() { return 0; } |
| float LayoutComponent::gapVertical() { return 0; } |
| |
| void LayoutComponent::drawProxy(Renderer* renderer) {} |
| |
| void LayoutComponent::draw(Renderer* renderer) {} |
| |
| void LayoutComponent::updateRenderPath() {} |
| |
| Vec2D LayoutComponent::measureLayout(float width, |
| LayoutMeasureMode widthMode, |
| float height, |
| LayoutMeasureMode heightMode) |
| { |
| return Vec2D(); |
| } |
| |
| bool LayoutComponent::advanceComponent(float elapsedSeconds, AdvanceFlags flags) |
| { |
| return false; |
| } |
| |
| void LayoutComponent::markLayoutNodeDirty(bool shouldForceUpdateLayoutBounds) {} |
| void LayoutComponent::markLayoutStyleDirty() {} |
| void LayoutComponent::onDirty(ComponentDirt value) {} |
| bool LayoutComponent::mainAxisIsRow() { return true; } |
| |
| bool LayoutComponent::mainAxisIsColumn() { return false; } |
| void LayoutComponent::calculateLayoutInternal(float availableWidth, |
| float availableHeight) |
| {} |
| #endif |
| |
| LayoutComponent::~LayoutComponent() |
| { |
| if (artboard() != nullptr) |
| { |
| artboard()->cleanLayout(this); |
| } |
| #ifdef WITH_RIVE_TOOLS |
| m_layoutData->unref(); |
| #else |
| delete m_layoutData; |
| #endif |
| } |
| |
| void LayoutComponent::clipChanged() |
| { |
| markLayoutNodeDirty(); |
| addDirt(ComponentDirt::Path); |
| } |
| void LayoutComponent::widthChanged() { markLayoutNodeDirty(); } |
| void LayoutComponent::heightChanged() { markLayoutNodeDirty(); } |
| void LayoutComponent::styleIdChanged() { markLayoutNodeDirty(); } |
| void LayoutComponent::fractionalWidthChanged() { markLayoutNodeDirty(); } |
| void LayoutComponent::fractionalHeightChanged() { markLayoutNodeDirty(); } |
| |
| ShapePaintPath* LayoutComponent::worldPath() { return &m_worldPath; } |
| ShapePaintPath* LayoutComponent::localPath() { return &m_localPath; } |
| ShapePaintPath* LayoutComponent::localClockwisePath() { return &m_localPath; } |
| |
| Component* LayoutComponent::pathBuilder() { return this; } |