#include "taitank_flexline.h"
#include <cmath>
#include "taitank_node.h"
#include "taitank_util.h"
namespace taitank {
FlexLine::FlexLine(TaitankNodeRef container) {
ASSERT(container != nullptr);
flex_container_ = container;
sum_hypothetical_main_size_ = 0;
total_flex_grow_ = 0;
total_flex_shrink_ = 0;
total_weighted_flex_shrink_ = 0;
line_cross_size_ = 0;
initial_free_space_ = 0;
remaining_free_space_ = 0;
container_main_inner_size_ = 0;
}
void FlexLine::AddItem(TaitankNodeRef item) {
if (item == nullptr) {
return;
}
sum_hypothetical_main_size_ += item->layout_result_.hypothetical_main_axis_margin_boxsize;
total_flex_grow_ += item->style_.flex_grow_;
total_flex_shrink_ += item->style_.flex_shrink_;
total_weighted_flex_shrink_ += item->style_.flex_shrink_ * item->layout_result_.flex_base_size;
items_.push_back(item);
}
bool FlexLine::IsEmpty() { return items_.size() == 0; }
void FlexLine::FreezeInflexibleItems(FlexLayoutAction layoutAction) {
FlexDirection mainAxis = flex_container_->style_.flex_direction_;
FlexSign flexSign = Sign();
remaining_free_space_ = container_main_inner_size_ - sum_hypothetical_main_size_;
std::vector<TaitankNodeRef> inFlexibleItems;
for (size_t i = 0; i < items_.size(); i++) {
TaitankNodeRef item = items_[i];
if (layoutAction == LAYOUT_ACTION_LAYOUT) {
item->is_frozen_ = false;
}
float flexFactor =
flexSign == PositiveFlexibility ? item->style_.flex_grow_ : item->style_.flex_shrink_;
if (flexFactor == 0 ||
(flexSign == PositiveFlexibility &&
item->layout_result_.flex_base_size > item->layout_result_.hypothetical_main_axis_size) ||
(flexSign == NegativeFlexibility &&
item->layout_result_.flex_base_size < item->layout_result_.hypothetical_main_axis_size)) {
item->SetLayoutDimension(mainAxis, item->layout_result_.hypothetical_main_axis_size);
inFlexibleItems.push_back(item);
}
}
FreezeViolations(inFlexibleItems);
initial_free_space_ = remaining_free_space_;
}
void FlexLine::FreezeViolations(std::vector<TaitankNode*>& violations) {
FlexDirection mainAxis = flex_container_->style_.flex_direction_;
for (size_t i = 0; i < violations.size(); i++) {
TaitankNodeRef item = violations[i];
if (item->is_frozen_) continue;
remaining_free_space_ -=
(item->GetLayoutDimension(mainAxis) - item->layout_result_.hypothetical_main_axis_size);
total_flex_grow_ -= item->style_.flex_grow_;
total_flex_shrink_ -= item->style_.flex_shrink_;
total_weighted_flex_shrink_ -= item->style_.flex_shrink_ * item->layout_result_.flex_base_size;
total_weighted_flex_shrink_ = fmax(total_weighted_flex_shrink_, 0.0f);
item->is_frozen_ = true;
}
}
bool FlexLine::ResolveFlexibleLengths() {
FlexDirection mainAxis = flex_container_->style_.flex_direction_;
float usedFreeSpace = 0;
float totalViolation = 0;
std::vector<TaitankNodeRef> minViolations;
std::vector<TaitankNodeRef> maxViolations;
FlexSign flexSign = Sign();
float sumFlexFactors = (flexSign == PositiveFlexibility) ? total_flex_grow_ : total_flex_shrink_;
if (sumFlexFactors > 0 && sumFlexFactors < 1) {
float value = initial_free_space_ * sumFlexFactors;
if (value < remaining_free_space_) {
remaining_free_space_ = value;
}
}
for (size_t i = 0; i < items_.size(); i++) {
TaitankNodeRef item = items_[i];
if (item->is_frozen_) continue;
float extraSpace = 0;
if (remaining_free_space_ > 0 && total_flex_grow_ > 0 && flexSign == PositiveFlexibility) {
extraSpace = remaining_free_space_ * item->style_.flex_grow_ / total_flex_grow_;
} else if (remaining_free_space_ < 0 && total_weighted_flex_shrink_ > 0 &&
flexSign == NegativeFlexibility) {
extraSpace = remaining_free_space_ * item->style_.flex_shrink_ *
item->layout_result_.flex_base_size / total_weighted_flex_shrink_;
}
float violation = 0;
if (std::isfinite(extraSpace)) {
float itemMainSize = item->layout_result_.hypothetical_main_axis_size + extraSpace;
float adjustItemMainSize = item->GetBoundAxis(mainAxis, itemMainSize);
item->SetLayoutDimension(mainAxis, adjustItemMainSize);
usedFreeSpace += adjustItemMainSize - item->layout_result_.hypothetical_main_axis_size;
violation = adjustItemMainSize - itemMainSize;
}
if (violation > 0) {
minViolations.push_back(item);
} else if (violation < 0) {
maxViolations.push_back(item);
}
totalViolation += violation;
}
if (static_cast<bool>(totalViolation)) {
FreezeViolations(totalViolation < 0 ? maxViolations : minViolations);
} else {
remaining_free_space_ -= usedFreeSpace;
}
return !static_cast<bool>(totalViolation);;
}
void FlexLine::AlignItems() {
FlexDirection mainAxis = flex_container_->ResolveMainAxis();
auto itemsSize = items_.size();
remaining_free_space_ = container_main_inner_size_;
int autoMarginCount = 0;
for (size_t i = 0; i < itemsSize; i++) {
TaitankNodeRef item = items_[i];
remaining_free_space_ -= (item->GetLayoutDimension(mainAxis) + item->GetMargin(mainAxis));
if (item->IsAutoStartMargin(mainAxis)) {
autoMarginCount++;
}
if (item->IsAutoEndMargin(mainAxis)) {
autoMarginCount++;
}
}
float autoMargin = 0;
if (remaining_free_space_ > 0 && autoMarginCount > 0) {
autoMargin = remaining_free_space_ / static_cast<float>(autoMarginCount);
remaining_free_space_ = 0;
}
for (size_t i = 0; i < itemsSize; i++) {
TaitankNodeRef item = items_[i];
if (item->IsAutoStartMargin(mainAxis)) {
item->SetLayoutStartMargin(mainAxis, autoMargin);
} else {
item->SetLayoutStartMargin(mainAxis, item->GetStartMargin(mainAxis));
}
if (item->IsAutoEndMargin(mainAxis)) {
item->SetLayoutEndMargin(mainAxis, autoMargin);
} else {
item->SetLayoutEndMargin(mainAxis, item->GetEndMargin(mainAxis));
}
}
float offset = flex_container_->GetStartPaddingAndBorder(mainAxis);
TaitankStyle style = flex_container_->GetStyle();
float space = 0;
switch (style.justify_content_) {
case FLEX_ALIGN_START:
break;
case FLEX_ALIGN_CENTER:
offset += remaining_free_space_ / 2;
break;
case FLEX_ALIGN_END:
offset += remaining_free_space_;
break;
case FLEX_ALIGN_SPACE_BETWEEN:
space = remaining_free_space_ / (static_cast<float>(itemsSize - 1));
break;
case FLEX_ALIGN_SPACE_AROUND:
space = remaining_free_space_ / static_cast<float>(itemsSize);
offset += space / 2;
break;
case FLEX_ALIGN_SPACE_EVENLY:
space = remaining_free_space_ / (static_cast<float>(itemsSize + 1));
offset += space;
break;
default:
break;
}
for (size_t i = 0; i < itemsSize; i++) {
TaitankNodeRef item = items_[i];
offset += item->GetLayoutStartMargin(mainAxis);
item->SetLayoutStartPosition(mainAxis, offset);
item->SetLayoutEndPosition(mainAxis, flex_container_->GetLayoutDimension(mainAxis) -
item->GetLayoutDimension(mainAxis) - offset);
offset += item->GetLayoutDimension(mainAxis) + item->GetLayoutEndMargin(mainAxis) + space;
}
}
}