blob: af19fc00c65b237ded3f620d53fbf89d1cafeb18 [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_formatting_context.h"
#include <algorithm>
#include "base/logging.h"
#include "cobalt/cssom/keyword_value.h"
#include "cobalt/layout/line_box.h"
#include "cobalt/layout/used_style.h"
namespace cobalt {
namespace layout {
FlexFormattingContext::FlexFormattingContext(const LayoutParams& layout_params,
bool main_direction_is_horizontal,
bool direction_is_reversed)
: layout_params_(layout_params),
main_direction_is_horizontal_(main_direction_is_horizontal),
direction_is_reversed_(direction_is_reversed),
fit_content_main_size_(LayoutUnit()) {}
FlexFormattingContext::~FlexFormattingContext() {}
void FlexFormattingContext::UpdateRect(Box* child_box) {
DCHECK(!child_box->IsAbsolutelyPositioned());
{
LayoutParams child_layout_params(layout_params_);
child_layout_params.shrink_to_fit_width_forced = true;
child_box->UpdateSize(child_layout_params);
}
// Shrink-to-fit doesn't exists anymore by itself in CSS3. It is called the
// fit-content size, which is derived from the 'min-content' and 'max-content'
// sizes and the 'stretch-fit' size.
// https://www.w3.org/TR/css-sizing-3/#fit-content-size
// which depends on the intrinsic sizes:
// https://www.w3.org/TR/css-flexbox-1/#intrinsic-sizes
// Note that for column flex-direction, this is the intrinsic cross size.
if (main_direction_is_horizontal_) {
fit_content_main_size_ = shrink_to_fit_width() + child_box->width() +
child_box->GetContentToMarginHorizontal();
set_shrink_to_fit_width(fit_content_main_size_);
} else {
fit_content_main_size_ +=
child_box->height() + child_box->GetContentToMarginVertical();
}
set_auto_height(child_box->height());
}
void FlexFormattingContext::CollectItemIntoLine(
LayoutUnit main_space, std::unique_ptr<FlexItem>&& item) {
// Collect flex items into flex lines:
// https://www.w3.org/TR/css-flexbox-1/#algo-line-break
if (lines_.empty()) {
lines_.emplace_back(new FlexLine(layout_params_,
main_direction_is_horizontal_,
direction_is_reversed_, main_space));
fit_content_main_size_ = LayoutUnit();
}
DCHECK(!lines_.empty());
if (multi_line_ && !lines_.back()->CanAddItem(*item)) {
lines_.emplace_back(new FlexLine(layout_params_,
main_direction_is_horizontal_,
direction_is_reversed_, main_space));
}
lines_.back()->AddItem(std::move(item));
fit_content_main_size_ =
std::max(fit_content_main_size_, lines_.back()->items_outer_main_size());
}
void FlexFormattingContext::ResolveFlexibleLengthsAndCrossSizes(
const base::Optional<LayoutUnit>& cross_space,
const base::Optional<LayoutUnit>& min_cross_space,
const base::Optional<LayoutUnit>& max_cross_space,
const scoped_refptr<cssom::PropertyValue>& align_content) {
// Algorithm for Flex Layout:
// https://www.w3.org/TR/css-flexbox-1/#layout-algorithm
// 6. Resolve the flexible lengths.
// Cross Size Determination:
// 7. Determine the hypothetical cross size of each item.
for (auto& line : lines_) {
line->ResolveFlexibleLengthsAndCrossSize();
}
// 8. Calculate the cross size of each flex line.
// If the flex container is single-line and has a definite cross size, the
// cross size of the flex line is the flex container's inner cross size.
if (!multi_line_ && cross_space && !lines_.empty()) {
lines_.front()->set_cross_size(*cross_space);
} else {
// Otherwise, for each flex line:
for (auto& line : lines_) {
line->CalculateCrossSize();
}
}
// If the flex container is single-line, then clamp the line's cross-size
// to be within the container's computed min and max cross sizes.
// https://www.w3.org/TR/css-flexbox-1/#change-201403-clamp-single-line
if (!multi_line_ && !lines_.empty()) {
LayoutUnit line_cross_size = lines_.front()->cross_size();
if (min_cross_space && line_cross_size < *min_cross_space) {
lines_.front()->set_cross_size(*min_cross_space);
} else if (max_cross_space && line_cross_size > *max_cross_space) {
lines_.front()->set_cross_size(*max_cross_space);
}
}
// Note, this is numbered as step 15 in the spec, however it does not rely on
// any calculations done during step 9..14, and the result is needed for
// step 9 below.
// 15. Determine the flex container's used cross size.
LayoutUnit total_cross_size = LayoutUnit();
for (auto& line : lines_) {
total_cross_size += line->cross_size();
}
if (cross_space) {
// If the cross size property is a definite size, use that.
cross_size_ = *cross_space;
} else {
// Otherwise, use the sum of the flex lines' cross sizes.
cross_size_ = total_cross_size;
}
// Clamped by the used min and max cross sizes of the flex container.
if (min_cross_space && cross_size_ < *min_cross_space) {
cross_size_ = *min_cross_space;
} else if (max_cross_space && cross_size_ > *max_cross_space) {
cross_size_ = *max_cross_space;
}
if (lines_.empty()) {
return;
}
LayoutUnit leftover_cross_size = cross_size_ - total_cross_size;
// 9. Handle 'align-content: stretch'.
if (align_content == cssom::KeywordValue::GetStretch()) {
if (leftover_cross_size > LayoutUnit()) {
LayoutUnit leftover_cross_size_per_line =
leftover_cross_size / static_cast<int>(lines_.size());
for (auto& line : lines_) {
line->set_cross_size(line->cross_size() + leftover_cross_size_per_line);
}
leftover_cross_size = LayoutUnit();
}
}
// 10. Collapse visibility:collapse items.
// Cobalt does not implement visibility:collapse.
// 11. Determine the used cross size of each flex item.
for (auto& line : lines_) {
line->DetermineUsedCrossSizes(cross_size_);
}
// Main-Axis Alignment:
// 12. Distribute any remaining free space.
for (auto& line : lines_) {
line->DoMainAxisAlignment();
}
// Cross-Axis Alignment:
// 13. Resolve cross-axis auto margins.
// 14. Align all flex items along the cross-axis.
// 16. Align all flex lines per 'align-content'.
LayoutUnit line_cross_axis_start = LayoutUnit();
LayoutUnit leftover_cross_size_per_line = LayoutUnit();
if (leftover_cross_size > LayoutUnit()) {
if (align_content == cssom::KeywordValue::GetCenter()) {
line_cross_axis_start = leftover_cross_size / 2;
} else if (align_content == cssom::KeywordValue::GetFlexEnd()) {
line_cross_axis_start = leftover_cross_size;
} else if (align_content == cssom::KeywordValue::GetSpaceBetween()) {
leftover_cross_size_per_line =
(lines_.size() < 2)
? LayoutUnit()
: leftover_cross_size / static_cast<int>(lines_.size() - 1);
} else if (align_content == cssom::KeywordValue::GetSpaceAround()) {
leftover_cross_size_per_line =
leftover_cross_size / static_cast<int>(lines_.size());
line_cross_axis_start = leftover_cross_size_per_line / 2;
} else {
DCHECK((align_content == cssom::KeywordValue::GetFlexStart()) ||
(align_content == cssom::KeywordValue::GetStretch()));
}
}
for (auto& line : lines_) {
line->DoCrossAxisAlignment(line_cross_axis_start);
line_cross_axis_start += line->cross_size() + leftover_cross_size_per_line;
}
}
LayoutUnit FlexFormattingContext::GetBaseline() {
LayoutUnit baseline = cross_size_;
if (!lines_.empty()) {
if (direction_is_reversed_ && !main_direction_is_horizontal_) {
baseline = lines_.back()->GetBaseline();
} else {
baseline = lines_.front()->GetBaseline();
}
}
return baseline;
}
} // namespace layout
} // namespace cobalt