blob: 231594b812edf3d1915607440a252e14d0d909bb [file] [log] [blame]
// Copyright 2019 The Cobalt Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "cobalt/layout/flex_item.h"
#include <algorithm>
#include "base/logging.h"
#include "base/memory/ptr_util.h"
#include "cobalt/cssom/css_computed_style_data.h"
#include "cobalt/cssom/number_value.h"
#include "cobalt/layout/block_container_box.h"
#include "cobalt/layout/box.h"
#include "cobalt/layout/container_box.h"
#include "cobalt/layout/used_style.h"
namespace cobalt {
namespace layout {
// Class for Flex Items in a container with a horizontal main axis.
// Since Cobalt does not support vertical writing modes, this is used for row
// flex containers.
class MainAxisHorizontalFlexItem : public FlexItem {
public:
explicit MainAxisHorizontalFlexItem(Box* box) : FlexItem(box, true) {}
~MainAxisHorizontalFlexItem() override {}
LayoutUnit GetContentToMarginMainAxis() const override;
LayoutUnit GetContentToMarginCrossAxis() const override;
base::Optional<LayoutUnit> GetUsedMainAxisSizeIfNotAuto(
const SizeLayoutUnit& containing_block_size) const override;
base::Optional<LayoutUnit> GetUsedMinMainAxisSizeIfNotAuto(
const SizeLayoutUnit& containing_block_size) const override;
base::Optional<LayoutUnit> GetUsedMinCrossAxisSizeIfNotAuto(
const SizeLayoutUnit& containing_block_size) const override;
base::Optional<LayoutUnit> GetUsedMaxMainAxisSizeIfNotNone(
const SizeLayoutUnit& containing_block_size) const override;
base::Optional<LayoutUnit> GetUsedMaxCrossAxisSizeIfNotNone(
const SizeLayoutUnit& containing_block_size) const override;
void DetermineHypotheticalCrossSize(
const LayoutParams& layout_params) override;
LayoutUnit GetContentBoxMainSize() const override;
LayoutUnit GetMarginBoxMainSize() const override;
LayoutUnit GetMarginBoxCrossSize() const override;
bool CrossSizeIsAuto() const override;
bool MarginMainStartIsAuto() const override;
bool MarginMainEndIsAuto() const override;
bool MarginCrossStartIsAuto() const override;
bool MarginCrossEndIsAuto() const override;
void SetCrossSize(LayoutUnit cross_size) override;
void SetMainAxisStart(LayoutUnit position) override;
void SetCrossAxisStart(LayoutUnit position) override;
};
LayoutUnit MainAxisHorizontalFlexItem::GetContentToMarginMainAxis() const {
return box()->GetContentToMarginHorizontal();
}
LayoutUnit MainAxisHorizontalFlexItem::GetContentToMarginCrossAxis() const {
return box()->GetContentToMarginVertical();
}
base::Optional<LayoutUnit>
MainAxisHorizontalFlexItem::GetUsedMainAxisSizeIfNotAuto(
const SizeLayoutUnit& containing_block_size) const {
return GetUsedWidthIfNotAuto(computed_style(), containing_block_size, NULL);
}
base::Optional<LayoutUnit>
MainAxisHorizontalFlexItem::GetUsedMinMainAxisSizeIfNotAuto(
const SizeLayoutUnit& containing_block_size) const {
base::Optional<LayoutUnit> maybe_used_min_space =
GetUsedMinWidthIfNotAuto(computed_style(), containing_block_size, NULL);
return maybe_used_min_space;
}
base::Optional<LayoutUnit>
MainAxisHorizontalFlexItem::GetUsedMinCrossAxisSizeIfNotAuto(
const SizeLayoutUnit& containing_block_size) const {
return GetUsedMinHeightIfNotAuto(computed_style(), containing_block_size);
}
base::Optional<LayoutUnit>
MainAxisHorizontalFlexItem::GetUsedMaxMainAxisSizeIfNotNone(
const SizeLayoutUnit& containing_block_size) const {
return GetUsedMaxWidthIfNotNone(computed_style(), containing_block_size,
NULL);
}
base::Optional<LayoutUnit>
MainAxisHorizontalFlexItem::GetUsedMaxCrossAxisSizeIfNotNone(
const SizeLayoutUnit& containing_block_size) const {
return GetUsedMaxHeightIfNotNone(computed_style(), containing_block_size);
}
void MainAxisHorizontalFlexItem::DetermineHypotheticalCrossSize(
const LayoutParams& layout_params) {
// 5. Set each item's used main size to its target main size.
// https://www.w3.org/TR/css-flexbox-1/#resolve-flexible-lengths
// Also, algorithm for Flex Layout continued from step 7:
// Cross Size Determination:
// 7. Determine the hypothetical cross size of each item
// By performing layout with the used main size and the available space.
// https://www.w3.org/TR/css-flexbox-1/#algo-cross-item
LayoutParams child_layout_params(layout_params);
child_layout_params.shrink_to_fit_width_forced = false;
child_layout_params.freeze_width = true;
box()->set_width(target_main_size());
box()->UpdateSize(child_layout_params);
}
LayoutUnit MainAxisHorizontalFlexItem::GetContentBoxMainSize() const {
return box()->width();
}
LayoutUnit MainAxisHorizontalFlexItem::GetMarginBoxMainSize() const {
return box()->GetMarginBoxWidth();
}
LayoutUnit MainAxisHorizontalFlexItem::GetMarginBoxCrossSize() const {
return box()->GetMarginBoxHeight();
}
bool MainAxisHorizontalFlexItem::CrossSizeIsAuto() const {
return computed_style()->height() == cssom::KeywordValue::GetAuto();
}
bool MainAxisHorizontalFlexItem::MarginMainStartIsAuto() const {
return computed_style()->margin_left() == cssom::KeywordValue::GetAuto();
}
bool MainAxisHorizontalFlexItem::MarginMainEndIsAuto() const {
return computed_style()->margin_right() == cssom::KeywordValue::GetAuto();
}
bool MainAxisHorizontalFlexItem::MarginCrossStartIsAuto() const {
return computed_style()->margin_top() == cssom::KeywordValue::GetAuto();
}
bool MainAxisHorizontalFlexItem::MarginCrossEndIsAuto() const {
return computed_style()->margin_bottom() == cssom::KeywordValue::GetAuto();
}
void MainAxisHorizontalFlexItem::SetCrossSize(LayoutUnit cross_size) {
box()->set_height(cross_size);
}
void MainAxisHorizontalFlexItem::SetMainAxisStart(LayoutUnit position) {
box()->set_left(position);
}
void MainAxisHorizontalFlexItem::SetCrossAxisStart(LayoutUnit position) {
box()->set_top(position);
}
// Class for Flex Items in a container with a vertical main axis.
// Since Cobalt does not support vertical writing modes, this is used for column
// flex containers.
class MainAxisVerticalFlexItem : public FlexItem {
public:
explicit MainAxisVerticalFlexItem(Box* box) : FlexItem(box, false) {}
~MainAxisVerticalFlexItem() override {}
LayoutUnit GetContentToMarginMainAxis() const override;
LayoutUnit GetContentToMarginCrossAxis() const override;
base::Optional<LayoutUnit> GetUsedMainAxisSizeIfNotAuto(
const SizeLayoutUnit& containing_block_size) const override;
base::Optional<LayoutUnit> GetUsedMinMainAxisSizeIfNotAuto(
const SizeLayoutUnit& containing_block_size) const override;
base::Optional<LayoutUnit> GetUsedMinCrossAxisSizeIfNotAuto(
const SizeLayoutUnit& containing_block_size) const override;
base::Optional<LayoutUnit> GetUsedMaxMainAxisSizeIfNotNone(
const SizeLayoutUnit& containing_block_size) const override;
base::Optional<LayoutUnit> GetUsedMaxCrossAxisSizeIfNotNone(
const SizeLayoutUnit& containing_block_size) const override;
void DetermineHypotheticalCrossSize(
const LayoutParams& layout_params) override;
LayoutUnit GetContentBoxMainSize() const override;
LayoutUnit GetMarginBoxMainSize() const override;
LayoutUnit GetMarginBoxCrossSize() const override;
bool CrossSizeIsAuto() const override;
bool MarginMainStartIsAuto() const override;
bool MarginMainEndIsAuto() const override;
bool MarginCrossStartIsAuto() const override;
bool MarginCrossEndIsAuto() const override;
void SetCrossSize(LayoutUnit cross_size) override;
void SetMainAxisStart(LayoutUnit position) override;
void SetCrossAxisStart(LayoutUnit position) override;
};
LayoutUnit MainAxisVerticalFlexItem::GetContentToMarginMainAxis() const {
return box()->GetContentToMarginVertical();
}
LayoutUnit MainAxisVerticalFlexItem::GetContentToMarginCrossAxis() const {
return box()->GetContentToMarginHorizontal();
}
base::Optional<LayoutUnit>
MainAxisVerticalFlexItem::GetUsedMainAxisSizeIfNotAuto(
const SizeLayoutUnit& containing_block_size) const {
return GetUsedHeightIfNotAuto(computed_style(), containing_block_size, NULL);
}
base::Optional<LayoutUnit>
MainAxisVerticalFlexItem::GetUsedMinMainAxisSizeIfNotAuto(
const SizeLayoutUnit& containing_block_size) const {
return GetUsedMinHeightIfNotAuto(computed_style(), containing_block_size);
}
base::Optional<LayoutUnit>
MainAxisVerticalFlexItem::GetUsedMinCrossAxisSizeIfNotAuto(
const SizeLayoutUnit& containing_block_size) const {
return GetUsedMinWidthIfNotAuto(computed_style(), containing_block_size,
NULL);
}
base::Optional<LayoutUnit>
MainAxisVerticalFlexItem::GetUsedMaxMainAxisSizeIfNotNone(
const SizeLayoutUnit& containing_block_size) const {
return GetUsedMaxHeightIfNotNone(computed_style(), containing_block_size);
}
base::Optional<LayoutUnit>
MainAxisVerticalFlexItem::GetUsedMaxCrossAxisSizeIfNotNone(
const SizeLayoutUnit& containing_block_size) const {
return GetUsedMaxWidthIfNotNone(computed_style(), containing_block_size,
NULL);
}
void MainAxisVerticalFlexItem::DetermineHypotheticalCrossSize(
const LayoutParams& layout_params) {
// 7. Determine the hypothetical cross size of each item
// By performing layout with the used main size and the available space.
// https://www.w3.org/TR/css-flexbox-1/#algo-cross-item
LayoutParams child_layout_params(layout_params);
child_layout_params.shrink_to_fit_width_forced = true;
box()->UpdateSize(child_layout_params);
box()->set_height(target_main_size());
}
LayoutUnit MainAxisVerticalFlexItem::GetContentBoxMainSize() const {
return box()->height();
}
LayoutUnit MainAxisVerticalFlexItem::GetMarginBoxMainSize() const {
return box()->GetMarginBoxHeight();
}
LayoutUnit MainAxisVerticalFlexItem::GetMarginBoxCrossSize() const {
return box()->GetMarginBoxWidth();
}
bool MainAxisVerticalFlexItem::CrossSizeIsAuto() const {
return computed_style()->width() == cssom::KeywordValue::GetAuto();
}
bool MainAxisVerticalFlexItem::MarginMainStartIsAuto() const {
return computed_style()->margin_top() == cssom::KeywordValue::GetAuto();
}
bool MainAxisVerticalFlexItem::MarginMainEndIsAuto() const {
return computed_style()->margin_bottom() == cssom::KeywordValue::GetAuto();
}
bool MainAxisVerticalFlexItem::MarginCrossStartIsAuto() const {
return computed_style()->margin_left() == cssom::KeywordValue::GetAuto();
}
bool MainAxisVerticalFlexItem::MarginCrossEndIsAuto() const {
return computed_style()->margin_right() == cssom::KeywordValue::GetAuto();
}
void MainAxisVerticalFlexItem::SetCrossSize(LayoutUnit cross_size) {
box()->set_width(cross_size);
}
void MainAxisVerticalFlexItem::SetMainAxisStart(LayoutUnit position) {
box()->set_top(position);
}
void MainAxisVerticalFlexItem::SetCrossAxisStart(LayoutUnit position) {
box()->set_left(position);
}
FlexItem::FlexItem(Box* box, bool main_direction_is_horizontal)
: box_(box), main_direction_is_horizontal_(main_direction_is_horizontal) {}
std::unique_ptr<FlexItem> FlexItem::Create(Box* box,
bool main_direction_is_horizontal) {
if (main_direction_is_horizontal) {
return base::WrapUnique(new MainAxisHorizontalFlexItem(box));
} else {
return base::WrapUnique(new MainAxisVerticalFlexItem(box));
}
}
void FlexItem::DetermineFlexFactor(bool flex_factor_is_grow) {
auto flex_factor_property = flex_factor_is_grow
? box_->computed_style()->flex_grow()
: box_->computed_style()->flex_shrink();
flex_factor_ = base::polymorphic_downcast<const cssom::NumberValue*>(
flex_factor_property.get())
->value();
}
const scoped_refptr<cobalt::cssom::PropertyValue>&
FlexItem::GetUsedAlignSelfPropertyValue() {
DCHECK(box()->parent());
return GetUsedAlignSelf(computed_style(), box()->parent()->computed_style());
}
const scoped_refptr<cobalt::cssom::PropertyValue>&
FlexItem::GetUsedJustifyContentPropertyValue() {
DCHECK(box()->parent());
return box()->parent()->computed_style()->justify_content();
}
bool FlexItem::OverflowIsVisible() const {
return computed_style()->overflow() == cssom::KeywordValue::GetVisible();
}
void FlexItem::DetermineFlexBaseSize(
const base::Optional<LayoutUnit>& main_space,
bool container_shrink_to_fit_width_forced) {
// Absolutely positioned boxes are not flex items.
DCHECK(!box()->IsAbsolutelyPositioned());
// All flex items are block container boxes.
DCHECK(box()->AsBlockContainerBox());
// Algorithm for determine the flex base size and hypothetical main size of
// each item.
// https://www.w3.org/TR/css-flexbox-1/#algo-main-item
// A. If the item has a definite used flex basis, that's the flex base size.
bool flex_basis_depends_on_available_space;
base::Optional<LayoutUnit> flex_basis = GetUsedFlexBasisIfNotContent(
computed_style(), main_direction_is_horizontal_,
main_space.value_or(LayoutUnit()),
&flex_basis_depends_on_available_space);
bool flex_basis_is_definite =
flex_basis && (!flex_basis_depends_on_available_space || main_space);
if (flex_basis_is_definite) {
flex_base_size_ = *flex_basis;
return;
}
// B. If the flex item has an intrinsic aspect ratio, a used flex basis of
// content, and a definite cross size, then the flex base size is
// calculated from its inner cross size and the flex item's intrinsic
// aspect ratio.
// Sizing from intrinsic ratio is not supported.
bool flex_basis_is_content_or_depends_on_available_space =
!flex_basis || flex_basis_depends_on_available_space;
// C. If the used flex basis is content or depends on its available space, and
// the flex container is being sized under a min-content or max-content
// constraint, size the item under that constraint. The flex base size is
// the item's resulting main size.
if (flex_basis_is_content_or_depends_on_available_space &&
container_shrink_to_fit_width_forced) {
flex_base_size_ =
main_direction_is_horizontal_ ? box()->width() : box()->height();
return;
}
// D. Otherwise, if the used flex basis is content or depends on its available
// space, the available main size is infinite, and the flex item's inline
// axis is parallel to the main axis, lay the item out using the rules for
// a box in an orthogonal flow. The flex base size is the item's
// max-content main size.
if (flex_basis_is_content_or_depends_on_available_space &&
main_direction_is_horizontal_ && !main_space) {
flex_base_size_ = box()->width();
return;
}
// E. Otherwise, size the item into the available space using its used flex
// basis in place of its main size, treating a value of content as
// max-content. If a cross size is needed to determine the main size (e.g.
// when the flex item's main size is in its block axis) and the flex item's
// cross size is auto and not definite, in this calculation use fit-content
// as the flex item's cross size. The flex base size is the item's
// resulting main size.
// TODO: handle 'if (!main_direction_is_horizontal)' and auto
// height, see above.
flex_base_size_ =
main_direction_is_horizontal_ ? box()->width() : box()->height();
}
void FlexItem::DetermineHypotheticalMainSize(
const SizeLayoutUnit& available_space) {
// The hypothetical main size is the item's flex base size clamped
// according to its used min and max main sizes.
base::Optional<LayoutUnit> maybe_min_main_size =
GetUsedMinMainAxisSizeIfNotAuto(available_space);
LayoutUnit main_size =
std::max(maybe_min_main_size.value_or(LayoutUnit()), flex_base_size());
base::Optional<LayoutUnit> maybe_max_main_size =
GetUsedMaxMainAxisSizeIfNotNone(available_space);
if (maybe_max_main_size) {
main_size = std::min(*maybe_max_main_size, main_size);
}
hypothetical_main_size_ = main_size;
}
base::Optional<LayoutUnit> FlexItem::GetContentBasedMinimumSize(
const SizeLayoutUnit& containing_block_size) const {
// Automatic Minimum Size of Flex Items.
// https://www.w3.org/TR/css-flexbox-1/#min-size-auto
// If the item's computed main size property is definite, then the specified
// size suggestion is that size (clamped by its max main size property if
// it's definite). It is otherwise undefined.
// https://www.w3.org/TR/css-flexbox-1/#specified-size-suggestion
base::Optional<LayoutUnit> specified_size_suggestion =
GetUsedMainAxisSizeIfNotAuto(containing_block_size);
base::Optional<LayoutUnit> maybe_max_main_size =
GetUsedMaxMainAxisSizeIfNotNone(containing_block_size);
if (maybe_max_main_size.has_value() &&
specified_size_suggestion.has_value()) {
specified_size_suggestion =
std::min(*maybe_max_main_size, *specified_size_suggestion);
}
// The content size suggestion is the min-content size in the main axis,
// clamped by the max main size property if that is definite.
// https://www.w3.org/TR/css-flexbox-1/#content-size-suggestion
base::Optional<LayoutUnit> content_size_suggestion;
if (OverflowIsVisible()) {
if (main_direction_is_horizontal_) {
base::Optional<LayoutUnit> maybe_height =
GetUsedHeightIfNotAuto(computed_style(), containing_block_size, NULL);
content_size_suggestion =
box()->AsBlockContainerBox()->GetShrinkToFitWidth(
containing_block_size.width(), maybe_height);
} else {
content_size_suggestion = GetContentBoxMainSize();
}
if (maybe_max_main_size.has_value()) {
content_size_suggestion =
std::min(*maybe_max_main_size, *content_size_suggestion);
}
}
// If the box has neither a specified size suggestion nor an aspect ratio,
// its content-based minimum size is the content size suggestion.
base::Optional<LayoutUnit> content_based_minimum_size =
content_size_suggestion;
// In general, the content-based minimum size of a flex item is the smaller
// of its content size suggestion and its specified size suggestion.
if (content_size_suggestion.has_value() &&
specified_size_suggestion.has_value()) {
content_based_minimum_size =
std::min(*content_size_suggestion, *specified_size_suggestion);
}
base::Optional<LayoutUnit> specified_min_size_suggestion =
GetUsedMinMainAxisSizeIfNotAuto(containing_block_size);
if (specified_min_size_suggestion.has_value()) {
content_based_minimum_size = specified_min_size_suggestion;
}
return content_based_minimum_size;
}
} // namespace layout
} // namespace cobalt