| #include "rive/animation/keyframe_interpolator.hpp" | |
| #include "rive/artboard.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/nested_artboard_layout.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" | |
| #include "rive/nested_artboard_layout.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; | |
| 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; } | |
| 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 transform = Mat2D::fromTranslation(location); | |
| m_WorldTransform = Mat2D::multiply(parentWorld, transform); | |
| updateConstraints(); | |
| } | |
| if (hasDirt(value, | |
| ComponentDirt::Path | ComponentDirt::WorldTransform | | |
| ComponentDirt::LayoutStyle)) | |
| { | |
| updateRenderPath(); | |
| } | |
| } | |
| 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::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) | |
| { | |
| m_forcedWidth = width; | |
| markLayoutStyleDirty(); | |
| markLayoutNodeDirty(); | |
| } | |
| void LayoutComponent::forcedHeight(float height) | |
| { | |
| m_forcedHeight = height; | |
| markLayoutStyleDirty(); | |
| markLayoutNodeDirty(); | |
| } | |
| void LayoutComponent::updateConstraints() | |
| { | |
| if (m_layoutConstraints.size() > 0) | |
| { | |
| for (auto parentConstraint : m_layoutConstraints) | |
| { | |
| parentConstraint->constrainChild(this); | |
| } | |
| } | |
| Super::updateConstraints(); | |
| } | |
| void LayoutComponent::addLayoutConstraint(LayoutConstraint* constraint) | |
| { | |
| assert(std::find(m_layoutConstraints.begin(), | |
| m_layoutConstraints.end(), | |
| constraint) == m_layoutConstraints.end()); | |
| m_layoutConstraints.push_back(constraint); | |
| } | |
| 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() || isDisplayHidden(); | |
| } | |
| bool LayoutComponent::isDisplayHidden() const | |
| { | |
| #ifdef WITH_RIVE_LAYOUT | |
| if (m_style != nullptr && m_style->display() == YGDisplayNone) | |
| { | |
| return true; | |
| } | |
| auto p = parent(); | |
| while (p != nullptr) | |
| { | |
| if (p->is<LayoutComponent>()) | |
| { | |
| auto layout = p->as<LayoutComponent>(); | |
| if (layout->style() != nullptr && | |
| layout->style()->display() == YGDisplayNone) | |
| { | |
| return true; | |
| } | |
| } | |
| p = p->parent(); | |
| } | |
| return false; | |
| #endif | |
| return false; | |
| } | |
| void LayoutComponent::propagateCollapse(bool collapse) | |
| { | |
| for (Component* child : children()) | |
| { | |
| child->collapse(collapse || isDisplayHidden()); | |
| } | |
| } | |
| bool LayoutComponent::collapse(bool value) | |
| { | |
| if (!Component::collapse(value)) | |
| { | |
| return false; | |
| } | |
| for (Component* child : children()) | |
| { | |
| child->collapse(value || isDisplayHidden()); | |
| } | |
| return true; | |
| } | |
| #ifdef WITH_RIVE_LAYOUT | |
| LayoutComponent::LayoutComponent() : | |
| m_layoutData(new LayoutData()), m_proxy(this) | |
| { | |
| m_layoutData->node.getConfig()->setPointScaleFactor(0); | |
| } | |
| 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); | |
| return StatusCode::Ok; | |
| } | |
| StatusCode LayoutComponent::onAddedClean(CoreContext* context) | |
| { | |
| auto code = Super::onAddedClean(context); | |
| if (code != StatusCode::Ok) | |
| { | |
| return code; | |
| } | |
| artboard()->markLayoutDirty(this); | |
| markLayoutStyleDirty(); | |
| m_backgroundPath = artboard()->factory()->makeEmptyRenderPath(); | |
| m_clipPath = artboard()->factory()->makeEmptyRenderPath(); | |
| m_backgroundRect.originX(0); | |
| m_backgroundRect.originY(0); | |
| syncLayoutChildren(); | |
| propagateCollapse(isCollapsed()); | |
| return StatusCode::Ok; | |
| } | |
| 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<Stroke>()) | |
| { | |
| shapePaint->draw(renderer, | |
| m_backgroundPath.get(), | |
| &m_backgroundRect.rawPath()); | |
| } | |
| 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(); | |
| } | |
| } | |
| void LayoutComponent::updateRenderPath() | |
| { | |
| m_backgroundRect.width(m_layout.width()); | |
| m_backgroundRect.height(m_layout.height()); | |
| if (style() != nullptr) | |
| { | |
| 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(ComponentDirt::Path); | |
| m_backgroundPath->rewind(); | |
| m_backgroundRect.rawPath().addTo(m_backgroundPath.get()); | |
| RawPath clipPath; | |
| clipPath.addPath(m_backgroundRect.rawPath(), &m_WorldTransform); | |
| m_clipPath = | |
| artboard()->factory()->makeRenderPath(clipPath, FillRule::nonZero); | |
| for (auto shapePaint : m_ShapePaints) | |
| { | |
| if (!shapePaint->isVisible()) | |
| { | |
| continue; | |
| } | |
| if (shapePaint->is<Stroke>()) | |
| { | |
| shapePaint->as<Stroke>()->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::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() | |
| { | |
| for (auto child : children()) | |
| { | |
| if (child->is<LayoutComponent>() || child->is<NestedArtboardLayout>()) | |
| { | |
| return false; | |
| } | |
| } | |
| return true; | |
| } | |
| void LayoutComponent::syncStyle() | |
| { | |
| if (m_style == 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); | |
| } | |
| auto realWidth = width(); | |
| auto realWidthUnits = m_style->widthUnits(); | |
| auto realWidthScaleType = m_style->widthScaleType(); | |
| auto realHeight = height(); | |
| auto realHeightUnits = m_style->heightUnits(); | |
| auto realHeightScaleType = m_style->heightScaleType(); | |
| auto parentIsRow = | |
| layoutParent() != nullptr ? layoutParent()->mainAxisIsRow() : true; | |
| // 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; | |
| } | |
| parentIsRow = m_parentIsRow; | |
| 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}; | |
| } | |
| 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; | |
| } | |
| bool isRowForAlignment = mainAxisIsRow(); | |
| switch (m_style->alignmentType()) | |
| { | |
| case LayoutAlignmentType::topLeft: | |
| case LayoutAlignmentType::topCenter: | |
| case LayoutAlignmentType::topRight: | |
| case LayoutAlignmentType::spaceBetweenStart: | |
| if (isRowForAlignment) | |
| { | |
| ygStyle.alignItems() = YGAlignFlexStart; | |
| ygStyle.alignContent() = YGAlignFlexStart; | |
| } | |
| else | |
| { | |
| ygStyle.justifyContent() = YGJustifyFlexStart; | |
| } | |
| break; | |
| case LayoutAlignmentType::centerLeft: | |
| case LayoutAlignmentType::center: | |
| case LayoutAlignmentType::centerRight: | |
| case LayoutAlignmentType::spaceBetweenCenter: | |
| if (isRowForAlignment) | |
| { | |
| ygStyle.alignItems() = YGAlignCenter; | |
| ygStyle.alignContent() = YGAlignCenter; | |
| } | |
| else | |
| { | |
| ygStyle.justifyContent() = YGJustifyCenter; | |
| } | |
| break; | |
| case LayoutAlignmentType::bottomLeft: | |
| case LayoutAlignmentType::bottomCenter: | |
| case LayoutAlignmentType::bottomRight: | |
| case LayoutAlignmentType::spaceBetweenEnd: | |
| if (isRowForAlignment) | |
| { | |
| ygStyle.alignItems() = YGAlignFlexEnd; | |
| ygStyle.alignContent() = YGAlignFlexEnd; | |
| } | |
| else | |
| { | |
| ygStyle.justifyContent() = YGJustifyFlexEnd; | |
| } | |
| break; | |
| } | |
| switch (m_style->alignmentType()) | |
| { | |
| case LayoutAlignmentType::topLeft: | |
| case LayoutAlignmentType::centerLeft: | |
| case LayoutAlignmentType::bottomLeft: | |
| if (isRowForAlignment) | |
| { | |
| ygStyle.justifyContent() = YGJustifyFlexStart; | |
| } | |
| else | |
| { | |
| ygStyle.alignItems() = YGAlignFlexStart; | |
| ygStyle.alignContent() = YGAlignFlexStart; | |
| } | |
| break; | |
| case LayoutAlignmentType::topCenter: | |
| case LayoutAlignmentType::center: | |
| case LayoutAlignmentType::bottomCenter: | |
| if (isRowForAlignment) | |
| { | |
| ygStyle.justifyContent() = YGJustifyCenter; | |
| } | |
| else | |
| { | |
| ygStyle.alignItems() = YGAlignCenter; | |
| ygStyle.alignContent() = YGAlignCenter; | |
| } | |
| break; | |
| case LayoutAlignmentType::topRight: | |
| case LayoutAlignmentType::centerRight: | |
| case LayoutAlignmentType::bottomRight: | |
| if (isRowForAlignment) | |
| { | |
| ygStyle.justifyContent() = YGJustifyFlexEnd; | |
| } | |
| else | |
| { | |
| ygStyle.alignItems() = YGAlignFlexEnd; | |
| ygStyle.alignContent() = YGAlignFlexEnd; | |
| } | |
| break; | |
| case LayoutAlignmentType::spaceBetweenStart: | |
| case LayoutAlignmentType::spaceBetweenCenter: | |
| case LayoutAlignmentType::spaceBetweenEnd: | |
| ygStyle.justifyContent() = YGJustifySpaceBetween; | |
| break; | |
| } | |
| ygStyle.minDimensions()[YGDimensionWidth] = | |
| YGValue{m_style->minWidth(), m_style->minWidthUnits()}; | |
| ygStyle.minDimensions()[YGDimensionHeight] = | |
| YGValue{m_style->minHeight(), m_style->minHeightUnits()}; | |
| ygStyle.maxDimensions()[YGDimensionWidth] = | |
| YGValue{m_style->maxWidth(), m_style->maxWidthUnits()}; | |
| ygStyle.maxDimensions()[YGDimensionHeight] = | |
| YGValue{m_style->maxHeight(), m_style->maxHeightUnits()}; | |
| ygStyle.gap()[YGGutterColumn] = | |
| YGValue{m_style->gapHorizontal(), m_style->gapHorizontalUnits()}; | |
| ygStyle.gap()[YGGutterRow] = | |
| YGValue{m_style->gapVertical(), m_style->gapVerticalUnits()}; | |
| ygStyle.border()[YGEdgeLeft] = | |
| YGValue{m_style->borderLeft(), m_style->borderLeftUnits()}; | |
| ygStyle.border()[YGEdgeRight] = | |
| YGValue{m_style->borderRight(), m_style->borderRightUnits()}; | |
| ygStyle.border()[YGEdgeTop] = | |
| YGValue{m_style->borderTop(), m_style->borderTopUnits()}; | |
| ygStyle.border()[YGEdgeBottom] = | |
| YGValue{m_style->borderBottom(), m_style->borderBottomUnits()}; | |
| ygStyle.margin()[YGEdgeLeft] = | |
| YGValue{m_style->marginLeft(), m_style->marginLeftUnits()}; | |
| ygStyle.margin()[YGEdgeRight] = | |
| YGValue{m_style->marginRight(), m_style->marginRightUnits()}; | |
| ygStyle.margin()[YGEdgeTop] = | |
| YGValue{m_style->marginTop(), m_style->marginTopUnits()}; | |
| ygStyle.margin()[YGEdgeBottom] = | |
| YGValue{m_style->marginBottom(), m_style->marginBottomUnits()}; | |
| ygStyle.padding()[YGEdgeLeft] = | |
| YGValue{m_style->paddingLeft(), m_style->paddingLeftUnits()}; | |
| ygStyle.padding()[YGEdgeRight] = | |
| YGValue{m_style->paddingRight(), m_style->paddingRightUnits()}; | |
| ygStyle.padding()[YGEdgeTop] = | |
| YGValue{m_style->paddingTop(), m_style->paddingTopUnits()}; | |
| ygStyle.padding()[YGEdgeBottom] = | |
| YGValue{m_style->paddingBottom(), m_style->paddingBottomUnits()}; | |
| ygStyle.position()[YGEdgeLeft] = | |
| YGValue{m_style->positionLeft(), m_style->positionLeftUnits()}; | |
| ygStyle.position()[YGEdgeRight] = | |
| YGValue{m_style->positionRight(), m_style->positionRightUnits()}; | |
| ygStyle.position()[YGEdgeTop] = | |
| YGValue{m_style->positionTop(), m_style->positionTopUnits()}; | |
| ygStyle.position()[YGEdgeBottom] = | |
| YGValue{m_style->positionBottom(), m_style->positionBottomUnits()}; | |
| ygStyle.display() = m_style->display(); | |
| ygStyle.positionType() = m_style->positionType(); | |
| ygStyle.flex() = YGFloatOptional(m_style->flex()); | |
| ygStyle.flexDirection() = m_style->flexDirection(); | |
| ygStyle.flexWrap() = m_style->flexWrap(); | |
| ygNode.setStyle(ygStyle); | |
| } | |
| void LayoutComponent::syncLayoutChildren() | |
| { | |
| YGNode& ourNode = m_layoutData->node; | |
| YGNodeRemoveAllChildren(&ourNode); | |
| int index = 0; | |
| for (auto child : children()) | |
| { | |
| YGNode* node = nullptr; | |
| switch (child->coreType()) | |
| { | |
| case LayoutComponentBase::typeKey: | |
| node = &child->as<LayoutComponent>()->m_layoutData->node; | |
| break; | |
| case NestedArtboardLayoutBase::typeKey: | |
| node = static_cast<YGNode*>( | |
| child->as<NestedArtboardLayout>()->layoutNode()); | |
| break; | |
| } | |
| if (node != nullptr) | |
| { | |
| ourNode.insertChild(node, index++); | |
| node->setOwner(&ourNode); | |
| ourNode.markDirtyAndPropagate(); | |
| } | |
| } | |
| markLayoutNodeDirty(); | |
| } | |
| void LayoutComponent::propagateSize() { propagateSizeToChildren(this); } | |
| void LayoutComponent::propagateSizeToChildren(ContainerComponent* component) | |
| { | |
| for (auto child : component->children()) | |
| { | |
| if (child->is<LayoutComponent>() || | |
| child->coreType() == NodeBase::typeKey) | |
| { | |
| 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); | |
| if (!sizeableChild->shouldPropagateSizeToChildren()) | |
| { | |
| // Do not propagate to its children | |
| continue; | |
| } | |
| } | |
| if (child->is<ContainerComponent>()) | |
| { | |
| propagateSizeToChildren(child->as<ContainerComponent>()); | |
| } | |
| } | |
| } | |
| void LayoutComponent::calculateLayout() | |
| { | |
| YGNodeCalculateLayout(&m_layoutData->node, | |
| width(), | |
| height(), | |
| YGDirection::YGDirectionInherit); | |
| } | |
| 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); | |
| for (auto child : children()) | |
| { | |
| if (child->is<LayoutComponent>()) | |
| { | |
| child->as<LayoutComponent>()->updateLayoutBounds(animate); | |
| } | |
| else if (child->is<NestedArtboardLayout>()) | |
| { | |
| child->as<NestedArtboardLayout>()->updateLayoutBounds(animate); | |
| } | |
| } | |
| auto yogaLayout = node.getLayout(); | |
| Layout newLayout = layoutFromYoga(yogaLayout); | |
| m_layoutPadding = layoutPaddingFromYoga(yogaLayout); | |
| if (animate && animates()) | |
| { | |
| auto animationData = currentAnimationData(); | |
| if (newLayout != animationData->to) | |
| { | |
| 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) | |
| { | |
| 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(); | |
| } | |
| if (m_displayChanged) | |
| { | |
| propagateCollapse(isCollapsed()); | |
| m_displayChanged = false; | |
| } | |
| } | |
| bool LayoutComponent::advanceComponent(float elapsedSeconds, AdvanceFlags flags) | |
| { | |
| 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; | |
| } | |
| } | |
| void LayoutComponent::cascadeAnimationStyle( | |
| LayoutStyleInterpolation inheritedInterpolation, | |
| KeyFrameInterpolator* inheritedInterpolator, | |
| float inheritedInterpolationTime) | |
| { | |
| if (m_style != nullptr && | |
| m_style->animationStyle() == LayoutAnimationStyle::inherit) | |
| { | |
| setInheritedInterpolation(inheritedInterpolation, | |
| inheritedInterpolator, | |
| inheritedInterpolationTime); | |
| } | |
| else | |
| { | |
| clearInheritedInterpolation(); | |
| } | |
| for (auto child : children()) | |
| { | |
| if (child->is<LayoutComponent>()) | |
| { | |
| child->as<LayoutComponent>()->cascadeAnimationStyle( | |
| interpolation(), | |
| interpolator(), | |
| interpolationTime()); | |
| } | |
| } | |
| } | |
| void LayoutComponent::setInheritedInterpolation( | |
| LayoutStyleInterpolation inheritedInterpolation, | |
| KeyFrameInterpolator* inheritedInterpolator, | |
| float inheritedInterpolationTime) | |
| { | |
| m_inheritedInterpolation = inheritedInterpolation; | |
| m_inheritedInterpolator = inheritedInterpolator; | |
| m_inheritedInterpolationTime = inheritedInterpolationTime; | |
| } | |
| 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() | |
| { | |
| 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) | |
| { | |
| m_style->positionLeft(layoutBounds().left()); | |
| 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->widthUnitsValue(m_style->widthScaleType() == LayoutScaleType::fixed | |
| ? YGUnitPoint | |
| : YGUnitAuto); | |
| m_style->heightUnitsValue( | |
| m_style->heightScaleType() == LayoutScaleType::fixed ? YGUnitPoint | |
| : YGUnitAuto); | |
| m_style->intrinsicallySizedValue( | |
| m_style->widthScaleType() == LayoutScaleType::hug || | |
| m_style->heightScaleType() == LayoutScaleType::hug); | |
| markLayoutNodeDirty(); | |
| } | |
| void LayoutComponent::displayChanged() | |
| { | |
| if (m_style == nullptr) | |
| { | |
| return; | |
| } | |
| m_displayChanged = true; | |
| markLayoutNodeDirty(); | |
| } | |
| void LayoutComponent::flexDirectionChanged() | |
| { | |
| markLayoutNodeDirty(); | |
| for (Component* child : children()) | |
| { | |
| if (child->is<LayoutComponent>()) | |
| { | |
| child->as<LayoutComponent>()->markLayoutNodeDirty(); | |
| } | |
| else if (child->is<NestedArtboardLayout>()) | |
| { | |
| child->as<NestedArtboardLayout>()->markLayoutNodeDirty(); | |
| } | |
| } | |
| } | |
| #else | |
| LayoutComponent::LayoutComponent() : | |
| m_layoutData(new LayoutData()), m_proxy(this) | |
| {} | |
| 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() {} | |
| void LayoutComponent::markLayoutStyleDirty() {} | |
| void LayoutComponent::onDirty(ComponentDirt value) {} | |
| bool LayoutComponent::mainAxisIsRow() { return true; } | |
| bool LayoutComponent::mainAxisIsColumn() { return false; } | |
| #endif | |
| LayoutComponent::~LayoutComponent() { delete m_layoutData; } | |
| void LayoutComponent::clipChanged() { markLayoutNodeDirty(); } | |
| void LayoutComponent::widthChanged() { markLayoutNodeDirty(); } | |
| void LayoutComponent::heightChanged() { markLayoutNodeDirty(); } | |
| void LayoutComponent::styleIdChanged() { markLayoutNodeDirty(); } | |
| void LayoutComponent::fractionalWidthChanged() { markLayoutNodeDirty(); } | |
| void LayoutComponent::fractionalHeightChanged() { markLayoutNodeDirty(); } |