aboutsummaryrefslogtreecommitdiff
path: root/src/ui/render/FlexLayoutRenderObject.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/ui/render/FlexLayoutRenderObject.cpp')
-rw-r--r--src/ui/render/FlexLayoutRenderObject.cpp414
1 files changed, 307 insertions, 107 deletions
diff --git a/src/ui/render/FlexLayoutRenderObject.cpp b/src/ui/render/FlexLayoutRenderObject.cpp
index fcaf6f9a..0c742a64 100644
--- a/src/ui/render/FlexLayoutRenderObject.cpp
+++ b/src/ui/render/FlexLayoutRenderObject.cpp
@@ -1,128 +1,328 @@
#include "cru/ui/render/FlexLayoutRenderObject.hpp"
+#include "cru/common/Logger.hpp"
#include "cru/platform/graph/util/Painter.hpp"
#include <algorithm>
#include <functional>
+#include <type_traits>
namespace cru::ui::render {
-Size FlexLayoutRenderObject::OnMeasureContent(
- const MeasureRequirement& requirement, const MeasureSize& preferred_size) {
- // TODO: Rewrite this.
- CRU_UNUSED(requirement);
- CRU_UNUSED(preferred_size);
- throw std::runtime_error("Not implemented.");
- // const bool horizontal = (direction_ == FlexDirection::Horizontal ||
- // direction_ == FlexDirection::HorizontalReverse);
-
- // const auto main_max_length =
- // horizontal ? requirement.max_width : requirement.max_height;
- // const auto cross_max_length =
- // horizontal ? requirement.max_height : requirement.max_width;
-
- // std::function<float(const Size&)> get_main_length;
- // std::function<float(const Size&)> get_cross_length;
- // std::function<void(Size&, const Size&)> calculate_result_size;
- // std::function<MeasureRequirement(MeasureLength main, MeasureLength cross)>
- // create_requirement;
-
- // if (horizontal) {
- // get_main_length = [](const Size& size) { return size.width; };
- // get_cross_length = [](const Size& size) { return size.height; };
- // calculate_result_size = [](Size& result, const Size& child_size) {
- // result.width += child_size.width;
- // result.height = std::max(result.height, child_size.height);
- // };
- // create_requirement = [](MeasureLength main, MeasureLength cross) {
- // return MeasureRequirement{main, cross};
- // };
- // } else {
- // get_main_length = [](const Size& size) { return size.height; };
- // get_cross_length = [](const Size& size) { return size.width; };
- // calculate_result_size = [](Size& result, const Size& child_size) {
- // result.height += child_size.height;
- // result.width = std::max(result.width, child_size.width);
- // };
- // create_requirement = [](MeasureLength main, MeasureLength cross) {
- // return MeasureRequirement{cross, main};
- // };
- // }
+struct tag_horizontal_t {};
+struct tag_vertical_t {};
- // const auto& children = GetChildren();
- // Index children_count = children.size();
+template <typename TSize>
+constexpr auto GetMain(const TSize& size, tag_horizontal_t) {
+ return size.width;
+}
- // if (!main_max_length.IsNotSpecify()) {
- // float remain_main_length = main_max_length.GetLengthOrMax();
+template <typename TSize>
+constexpr auto GetCross(const TSize& size, tag_horizontal_t) {
+ return size.height;
+}
- // for (Index i = 0; i < children_count; i++) {
- // const auto child = children[i];
- // child->Measure(create_requirement(remain_main_length,
- // cross_max_length)); const auto measure_result =
- // child->GetMeasuredSize(); remain_main_length -=
- // get_main_length(measure_result);
- // }
+template <typename TSize>
+constexpr auto GetMain(const TSize& size, tag_vertical_t) {
+ return size.height;
+}
- // if (remain_main_length > 0) {
- // std::vector<Index> expand_children;
- // float total_expand_factor = 0;
+template <typename TSize>
+constexpr auto GetCross(const TSize& size, tag_vertical_t) {
+ return size.width;
+}
- // for (Index i = 0; i < children_count; i++) {
- // const auto factor = GetChildLayoutData(i)->expand_factor;
- // if (factor > 0) {
- // expand_children.push_back(i);
- // total_expand_factor += factor;
- // }
- // }
+template <typename TSize>
+constexpr auto& GetMain(TSize& size, tag_horizontal_t) {
+ return size.width;
+}
- // for (const Index i : expand_children) {
- // const float distributed_grow_length =
- // remain_main_length *
- // (GetChildLayoutData(i)->expand_factor / total_expand_factor);
- // const auto child = children[i];
- // const float new_main_length =
- // get_main_length(child->GetMeasuredSize()) +
- // distributed_grow_length;
- // child->Measure(create_requirement(new_main_length,
- // cross_max_length));
- // }
- // } else if (remain_main_length < 0) {
- // std::vector<Index> shrink_children;
- // float total_shrink_factor = 0;
-
- // for (Index i = 0; i < children_count; i++) {
- // const auto factor = GetChildLayoutData(i)->shrink_factor;
- // if (factor > 0) {
- // shrink_children.push_back(i);
- // total_shrink_factor += factor;
- // }
- // }
+template <typename TSize>
+constexpr auto& GetCross(TSize& size, tag_horizontal_t) {
+ return size.height;
+}
- // for (const Index i : shrink_children) {
- // const float distributed_shrink_length = // negative
- // remain_main_length *
- // (GetChildLayoutData(i)->shrink_factor / total_shrink_factor);
- // const auto child = children[i];
- // float new_main_length = get_main_length(child->GetMeasuredSize()) +
- // distributed_shrink_length;
- // new_main_length = new_main_length > 0 ? new_main_length : 0;
- // child->Measure(create_requirement(new_main_length,
- // cross_max_length));
- // }
- // }
- // } else {
- // for (Index i = 0; i < children_count; i++) {
- // const auto child = children[i];
- // child->Measure(requirement);
- // }
- // }
+template <typename TSize>
+constexpr auto& GetMain(TSize& size, tag_vertical_t) {
+ return size.height;
+}
- // Size result;
- // for (auto child : children) {
- // calculate_result_size(result, child->GetMeasuredSize());
- // }
+template <typename TSize>
+constexpr auto& GetCross(TSize& size, tag_vertical_t) {
+ return size.width;
+}
+
+template <typename TSize>
+constexpr TSize CreateTSize(decltype(std::declval<TSize>().width) main,
+ decltype(std::declval<TSize>().height) cross,
+ tag_horizontal_t) {
+ return TSize{main, cross};
+}
+
+template <typename TSize>
+constexpr TSize CreateTSize(decltype(std::declval<TSize>().width) main,
+ decltype(std::declval<TSize>().height) cross,
+ tag_vertical_t) {
+ return TSize{main, cross};
+}
+
+enum class FlexLayoutAdjustType { None, Expand, Shrink };
+
+namespace {
+void Remove(std::vector<Index>& v, const std::vector<Index>& to_remove_v) {
+ Index current = 0;
+ for (auto to_remove : to_remove_v) {
+ while (v[current] != to_remove) {
+ current++;
+ }
+ v.erase(v.cbegin() + current);
+ }
+}
+
+template <typename direction_tag_t,
+ typename = std::enable_if_t<
+ std::is_same_v<direction_tag_t, tag_horizontal_t> ||
+ std::is_same_v<direction_tag_t, tag_vertical_t>>>
+Size FlexLayoutMeasureContentImpl(
+ const MeasureRequirement& requirement, const MeasureSize& preferred_size,
+ const std::vector<RenderObject*>& children,
+ const std::vector<FlexChildLayoutData>& layout_data) {
+ Expects(children.size() == layout_data.size());
+
+ direction_tag_t direction_tag;
+
+ const Index child_count = children.size();
+
+ MeasureLength preferred_main_length = GetMain(preferred_size, direction_tag);
+ MeasureLength preferred_cross_length =
+ GetCross(preferred_size, direction_tag);
+ MeasureLength max_main_length = GetMain(requirement.max, direction_tag);
+ MeasureLength max_cross_length = GetCross(requirement.max, direction_tag);
+ MeasureLength min_main_length = GetMain(requirement.min, direction_tag);
+ MeasureLength min_cross_length = GetCross(requirement.min, direction_tag);
- // return result;
+ // step 1.
+ for (auto child : children) {
+ child->Measure(MeasureRequirement{CreateTSize<MeasureSize>(
+ MeasureLength::NotSpecified(),
+ max_cross_length, direction_tag),
+ MeasureSize::NotSpecified()},
+ MeasureSize::NotSpecified());
+ }
+
+ float total_length = 0.f;
+ for (auto child : children) {
+ total_length += GetMain(child->GetSize(), direction_tag);
+ }
+
+ // step 2.
+ FlexLayoutAdjustType adjust_type = FlexLayoutAdjustType::None;
+ float target_length =
+ -1.f; // Use a strange value to indicate error. This value should be
+ // assigned before usage. Or program has a bug.
+
+ if (preferred_main_length.IsSpecified()) {
+ const float preferred_main_length_value =
+ preferred_main_length.GetLengthOrUndefined();
+ target_length = preferred_main_length_value;
+ if (total_length > preferred_main_length_value) {
+ adjust_type = FlexLayoutAdjustType::Shrink;
+ } else if (total_length < preferred_main_length_value) {
+ adjust_type = FlexLayoutAdjustType::Expand;
+ }
+ } else {
+ if (max_main_length.IsSpecified()) {
+ const float max_main_length_value =
+ max_main_length.GetLengthOrUndefined();
+ if (max_main_length_value < total_length) {
+ adjust_type = FlexLayoutAdjustType::Shrink;
+ target_length = max_main_length_value;
+ } else if (max_main_length_value > total_length) {
+ for (auto data : layout_data) {
+ if (data.expand_factor > 0) {
+ adjust_type = FlexLayoutAdjustType::Expand;
+ target_length = max_main_length_value;
+ break;
+ }
+ }
+ }
+ }
+ // Do not use else here.
+ if (adjust_type != FlexLayoutAdjustType::None &&
+ min_main_length.IsSpecified() &&
+ min_main_length.GetLengthOrUndefined() > total_length) {
+ adjust_type = FlexLayoutAdjustType::Expand;
+ target_length = min_main_length.GetLengthOrUndefined();
+ }
+ }
+
+ // step 3.
+ if (adjust_type == FlexLayoutAdjustType::Shrink) {
+ std::vector<Index> shrink_list;
+
+ for (Index i = 0; i < child_count; i++) {
+ if (layout_data[i].shrink_factor > 0) {
+ shrink_list.push_back(i);
+ }
+ }
+
+ while (!shrink_list.empty()) {
+ const float total_shrink_length = total_length - target_length;
+
+ float total_shrink_factor = 0.f;
+ std::vector<Index> to_remove;
+
+ for (Index i : shrink_list) {
+ total_shrink_factor += layout_data[i].shrink_factor;
+ }
+
+ for (Index i : shrink_list) {
+ const auto child = children[i];
+ const float shrink_length = layout_data[i].shrink_factor /
+ total_shrink_factor * total_shrink_length;
+ float new_measure_length =
+ GetMain(child->GetSize(), direction_tag) - shrink_length;
+
+ MeasureLength child_min_main_length =
+ GetMain(child->GetMinSize(), direction_tag);
+
+ if (child_min_main_length.IsSpecified() &&
+ new_measure_length < child_min_main_length.GetLengthOrUndefined()) {
+ new_measure_length = child_min_main_length.GetLengthOrUndefined();
+ } else if (new_measure_length < 0.f) {
+ new_measure_length = 0.f;
+ }
+
+ child->Measure(MeasureRequirement{CreateTSize<MeasureSize>(
+ new_measure_length,
+ max_cross_length, direction_tag),
+ MeasureSize::NotSpecified()},
+ CreateTSize<MeasureSize>(new_measure_length,
+ MeasureLength::NotSpecified(),
+ direction_tag));
+
+ const Size new_size = child->GetSize();
+ const float new_main_length = GetMain(new_size, direction_tag);
+ if (new_main_length == 0.f ||
+ (child_min_main_length.IsSpecified() &&
+ new_main_length == child_min_main_length.GetLengthOrUndefined())) {
+ to_remove.push_back(i);
+ }
+ }
+
+ total_length = 0.f;
+ for (auto child : children) {
+ total_length += GetMain(child->GetSize(), direction_tag);
+ }
+
+ if (total_length <= target_length) break;
+
+ Remove(shrink_list, to_remove);
+ }
+ } else if (adjust_type == FlexLayoutAdjustType::Expand) {
+ std::vector<Index> expand_list;
+
+ for (Index i = 0; i < child_count; i++) {
+ if (layout_data[i].expand_factor > 0) {
+ expand_list.push_back(i);
+ }
+ }
+
+ while (!expand_list.empty()) {
+ const float total_expand_length = target_length - total_length;
+
+ float total_expand_factor = 0.f;
+ std::vector<Index> to_remove;
+
+ for (Index i : expand_list) {
+ total_expand_factor += layout_data[i].expand_factor;
+ }
+
+ for (Index i : expand_list) {
+ const auto child = children[i];
+ const float expand_length = layout_data[i].expand_factor /
+ total_expand_factor * total_expand_length;
+ float new_measure_length =
+ GetMain(child->GetSize(), direction_tag) + expand_length;
+
+ MeasureLength child_max_main_length =
+ GetMain(child->GetMaxSize(), direction_tag);
+
+ if (child_max_main_length.IsSpecified() &&
+ new_measure_length > child_max_main_length.GetLengthOrUndefined()) {
+ new_measure_length = child_max_main_length.GetLengthOrUndefined();
+ }
+
+ child->Measure(
+ MeasureRequirement{
+ CreateTSize<MeasureSize>(MeasureLength::NotSpecified(),
+ max_cross_length, direction_tag),
+ CreateTSize<MeasureSize>(new_measure_length,
+ MeasureLength::NotSpecified(),
+ direction_tag)},
+ CreateTSize<MeasureSize>(new_measure_length,
+ MeasureLength::NotSpecified(),
+ direction_tag));
+
+ const Size new_size = child->GetSize();
+ const float new_main_length = GetMain(new_size, direction_tag);
+ if (child_max_main_length.IsSpecified() &&
+ new_main_length == child_max_main_length.GetLengthOrUndefined()) {
+ to_remove.push_back(i);
+ }
+ }
+
+ total_length = 0.f;
+ for (auto child : children) {
+ total_length += GetMain(child->GetSize(), direction_tag);
+ }
+
+ if (total_length >= target_length) break;
+
+ Remove(expand_list, to_remove);
+ }
+ }
+
+ float child_max_cross_length = 0.f;
+
+ for (auto child : children) {
+ const float cross_length = GetCross(child->GetSize(), direction_tag);
+ if (cross_length > child_max_cross_length) {
+ child_max_cross_length = cross_length;
+ }
+ }
+
+ if (max_main_length.IsSpecified() &&
+ total_length > max_main_length.GetLengthOrUndefined()) {
+ log::Warn(
+ "FlexLayoutRenderObject: Children's main axis length exceeds required "
+ "max length.");
+ total_length = max_main_length.GetLengthOrUndefined();
+ } else if (min_main_length.IsSpecified() &&
+ total_length < min_main_length.GetLengthOrUndefined()) {
+ total_length = min_main_length.GetLengthOrUndefined();
+ }
+
+ if (min_main_length.IsSpecified() &&
+ child_max_cross_length < min_main_length.GetLengthOrUndefined()) {
+ child_max_cross_length = min_main_length.GetLengthOrUndefined();
+ }
+
+ return CreateTSize<Size>(total_length, child_max_cross_length, direction_tag);
+}
+} // namespace
+
+Size FlexLayoutRenderObject::OnMeasureContent(
+ const MeasureRequirement& requirement, const MeasureSize& preferred_size) {
+ const bool horizontal = (direction_ == FlexDirection::Horizontal ||
+ direction_ == FlexDirection::HorizontalReverse);
+ if (horizontal) {
+ return FlexLayoutMeasureContentImpl<tag_horizontal_t>(
+ requirement, preferred_size, GetChildren(), GetChildLayoutDataList());
+ } else {
+ return FlexLayoutMeasureContentImpl<tag_vertical_t>(
+ requirement, preferred_size, GetChildren(), GetChildLayoutDataList());
+ }
}
void FlexLayoutRenderObject::OnLayoutContent(const Rect& content_rect) {