/* * Copyright (c) Meta Platforms, Inc. and affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. */ #include #include #include #include #include #include #include namespace facebook::yoga { Node::Node() : Node{&Config::getDefault()} {} Node::Node(const yoga::Config* config) : config_{config} { yoga::assertFatal( config != nullptr, "Attempting to construct Node with null config"); if (config->useWebDefaults()) { useWebDefaults(); } } Node::Node(Node&& node) noexcept : hasNewLayout_(node.hasNewLayout_), isReferenceBaseline_(node.isReferenceBaseline_), isDirty_(node.isDirty_), alwaysFormsContainingBlock_(node.alwaysFormsContainingBlock_), nodeType_(node.nodeType_), context_(node.context_), measureFunc_(node.measureFunc_), baselineFunc_(node.baselineFunc_), dirtiedFunc_(node.dirtiedFunc_), style_(std::move(node.style_)), layout_(node.layout_), lineIndex_(node.lineIndex_), owner_(node.owner_), children_(std::move(node.children_)), config_(node.config_), resolvedDimensions_(node.resolvedDimensions_) { for (auto c : children_) { c->setOwner(this); } } YGSize Node::measure( float availableWidth, MeasureMode widthMode, float availableHeight, MeasureMode heightMode) { const auto size = measureFunc_( this, availableWidth, unscopedEnum(widthMode), availableHeight, unscopedEnum(heightMode)); if (yoga::isUndefined(size.height) || size.height < 0 || yoga::isUndefined(size.width) || size.width < 0) { yoga::log( this, LogLevel::Warn, "Measure function returned an invalid dimension to Yoga: [width=%f, height=%f]", size.width, size.height); return { .width = maxOrDefined(0.0f, size.width), .height = maxOrDefined(0.0f, size.height)}; } return size; } float Node::baseline(float width, float height) const { return baselineFunc_(this, width, height); } float Node::dimensionWithMargin( const FlexDirection axis, const float widthSize) { return getLayout().measuredDimension(dimension(axis)) + style_.computeMarginForAxis(axis, widthSize); } bool Node::isLayoutDimensionDefined(const FlexDirection axis) { const float value = getLayout().measuredDimension(dimension(axis)); return yoga::isDefined(value) && value >= 0.0f; } // Setters void Node::setMeasureFunc(YGMeasureFunc measureFunc) { if (measureFunc == nullptr) { // TODO: t18095186 Move nodeType to opt-in function and mark appropriate // places in Litho setNodeType(NodeType::Default); } else { yoga::assertFatalWithNode( this, children_.empty(), "Cannot set measure function: Nodes with measure functions cannot have " "children."); // TODO: t18095186 Move nodeType to opt-in function and mark appropriate // places in Litho setNodeType(NodeType::Text); } measureFunc_ = measureFunc; } void Node::replaceChild(Node* child, size_t index) { children_[index] = child; } void Node::replaceChild(Node* oldChild, Node* newChild) { std::replace(children_.begin(), children_.end(), oldChild, newChild); } void Node::insertChild(Node* child, size_t index) { children_.insert(children_.begin() + static_cast(index), child); } void Node::setConfig(yoga::Config* config) { yoga::assertFatal( config != nullptr, "Attempting to set a null config on a Node"); yoga::assertFatalWithConfig( config, config->useWebDefaults() == config_->useWebDefaults(), "UseWebDefaults may not be changed after constructing a Node"); if (yoga::configUpdateInvalidatesLayout(*config_, *config)) { markDirtyAndPropagate(); } config_ = config; } void Node::setDirty(bool isDirty) { if (static_cast(isDirty) == isDirty_) { return; } isDirty_ = isDirty; if (isDirty && (dirtiedFunc_ != nullptr)) { dirtiedFunc_(this); } } bool Node::removeChild(Node* child) { auto p = std::find(children_.begin(), children_.end(), child); if (p != children_.end()) { children_.erase(p); return true; } return false; } void Node::removeChild(size_t index) { children_.erase(children_.begin() + static_cast(index)); } void Node::setLayoutDirection(Direction direction) { layout_.setDirection(direction); } void Node::setLayoutMargin(float margin, PhysicalEdge edge) { layout_.setMargin(edge, margin); } void Node::setLayoutBorder(float border, PhysicalEdge edge) { layout_.setBorder(edge, border); } void Node::setLayoutPadding(float padding, PhysicalEdge edge) { layout_.setPadding(edge, padding); } void Node::setLayoutLastOwnerDirection(Direction direction) { layout_.lastOwnerDirection = direction; } void Node::setLayoutComputedFlexBasis(const FloatOptional computedFlexBasis) { layout_.computedFlexBasis = computedFlexBasis; } void Node::setLayoutPosition(float position, PhysicalEdge edge) { layout_.setPosition(edge, position); } void Node::setLayoutComputedFlexBasisGeneration( uint32_t computedFlexBasisGeneration) { layout_.computedFlexBasisGeneration = computedFlexBasisGeneration; } void Node::setLayoutMeasuredDimension( float measuredDimension, Dimension dimension) { layout_.setMeasuredDimension(dimension, measuredDimension); } void Node::setLayoutHadOverflow(bool hadOverflow) { layout_.setHadOverflow(hadOverflow); } void Node::setLayoutDimension(float LengthValue, Dimension dimension) { layout_.setDimension(dimension, LengthValue); } // If both left and right are defined, then use left. Otherwise return +left or // -right depending on which is defined. Ignore statically positioned nodes as // insets do not apply to them. float Node::relativePosition( FlexDirection axis, Direction direction, float axisSize) const { if (style_.positionType() == PositionType::Static) { return 0; } if (style_.isInlineStartPositionDefined(axis, direction)) { return style_.computeInlineStartPosition(axis, direction, axisSize); } return -1 * style_.computeInlineEndPosition(axis, direction, axisSize); } void Node::setPosition( const Direction direction, const float ownerWidth, const float ownerHeight) { /* Root nodes should be always layouted as LTR, so we don't return negative * values. */ const Direction directionRespectingRoot = owner_ != nullptr ? direction : Direction::LTR; const FlexDirection mainAxis = yoga::resolveDirection(style_.flexDirection(), directionRespectingRoot); const FlexDirection crossAxis = yoga::resolveCrossDirection(mainAxis, directionRespectingRoot); // In the case of position static these are just 0. See: // https://www.w3.org/TR/css-position-3/#valdef-position-static const float relativePositionMain = relativePosition( mainAxis, directionRespectingRoot, isRow(mainAxis) ? ownerWidth : ownerHeight); const float relativePositionCross = relativePosition( crossAxis, directionRespectingRoot, isRow(mainAxis) ? ownerHeight : ownerWidth); const auto mainAxisLeadingEdge = inlineStartEdge(mainAxis, direction); const auto mainAxisTrailingEdge = inlineEndEdge(mainAxis, direction); const auto crossAxisLeadingEdge = inlineStartEdge(crossAxis, direction); const auto crossAxisTrailingEdge = inlineEndEdge(crossAxis, direction); setLayoutPosition( (style_.computeInlineStartMargin(mainAxis, direction, ownerWidth) + relativePositionMain), mainAxisLeadingEdge); setLayoutPosition( (style_.computeInlineEndMargin(mainAxis, direction, ownerWidth) + relativePositionMain), mainAxisTrailingEdge); setLayoutPosition( (style_.computeInlineStartMargin(crossAxis, direction, ownerWidth) + relativePositionCross), crossAxisLeadingEdge); setLayoutPosition( (style_.computeInlineEndMargin(crossAxis, direction, ownerWidth) + relativePositionCross), crossAxisTrailingEdge); } Style::Length Node::resolveFlexBasisPtr() const { Style::Length flexBasis = style_.flexBasis(); if (flexBasis.unit() != Unit::Auto && flexBasis.unit() != Unit::Undefined) { return flexBasis; } if (style_.flex().isDefined() && style_.flex().unwrap() > 0.0f) { return config_->useWebDefaults() ? value::ofAuto() : value::points(0); } return value::ofAuto(); } void Node::resolveDimension() { for (auto dim : {Dimension::Width, Dimension::Height}) { if (style_.maxDimension(dim).isDefined() && yoga::inexactEquals( style_.maxDimension(dim), style_.minDimension(dim))) { resolvedDimensions_[yoga::to_underlying(dim)] = style_.maxDimension(dim); } else { resolvedDimensions_[yoga::to_underlying(dim)] = style_.dimension(dim); } } } Direction Node::resolveDirection(const Direction ownerDirection) { if (style_.direction() == Direction::Inherit) { return ownerDirection != Direction::Inherit ? ownerDirection : Direction::LTR; } else { return style_.direction(); } } void Node::clearChildren() { children_.clear(); children_.shrink_to_fit(); } // Other Methods void Node::cloneChildrenIfNeeded() { size_t i = 0; for (Node*& child : children_) { if (child->getOwner() != this) { child = resolveRef(config_->cloneNode(child, this, i)); child->setOwner(this); } i += 1; } } void Node::markDirtyAndPropagate() { if (!isDirty_) { setDirty(true); setLayoutComputedFlexBasis(FloatOptional()); if (owner_ != nullptr) { owner_->markDirtyAndPropagate(); } } } float Node::resolveFlexGrow() const { // Root nodes flexGrow should always be 0 if (owner_ == nullptr) { return 0.0; } if (style_.flexGrow().isDefined()) { return style_.flexGrow().unwrap(); } if (style_.flex().isDefined() && style_.flex().unwrap() > 0.0f) { return style_.flex().unwrap(); } return Style::DefaultFlexGrow; } float Node::resolveFlexShrink() const { if (owner_ == nullptr) { return 0.0; } if (style_.flexShrink().isDefined()) { return style_.flexShrink().unwrap(); } if (!config_->useWebDefaults() && style_.flex().isDefined() && style_.flex().unwrap() < 0.0f) { return -style_.flex().unwrap(); } return config_->useWebDefaults() ? Style::WebDefaultFlexShrink : Style::DefaultFlexShrink; } bool Node::isNodeFlexible() { return ( (style_.positionType() != PositionType::Absolute) && (resolveFlexGrow() != 0 || resolveFlexShrink() != 0)); } void Node::reset() { yoga::assertFatalWithNode( this, children_.empty(), "Cannot reset a node which still has children attached"); yoga::assertFatalWithNode( this, owner_ == nullptr, "Cannot reset a node still attached to a owner"); *this = Node{getConfig()}; } } // namespace facebook::yoga