From 933734bd2e069822cd1a5889940003c63b43f520 Mon Sep 17 00:00:00 2001 From: lisyarus Date: Sat, 6 May 2023 12:54:36 +0300 Subject: [PATCH] New UI library wip --- libs/react/include/psemek/react/map.hpp | 45 +++- libs/ui/include/psemek/ui/box_layout.hpp | 7 +- .../psemek/ui/impl/box_layout_base.hpp | 8 +- libs/ui/include/psemek/ui/impl/component.hpp | 11 +- libs/ui/include/psemek/ui/impl/container.hpp | 51 +--- .../include/psemek/ui/impl/rectangle_base.hpp | 21 -- .../psemek/ui/impl/single_container.hpp | 34 +-- .../psemek/ui/impl/single_container_base.hpp | 8 +- .../include/psemek/ui/impl/size_polygon.hpp | 15 ++ .../psemek/ui/impl/size_polygon_utils.hpp | 19 ++ .../psemek/ui/impl/stack_layout_base.hpp | 12 +- libs/ui/include/psemek/ui/rectangle.hpp | 1 - libs/ui/source/impl/box_layout_base.cpp | 157 ++++++------ libs/ui/source/impl/component.cpp | 22 +- libs/ui/source/impl/container.cpp | 51 ++++ libs/ui/source/impl/rectangle_base.cpp | 19 -- libs/ui/source/impl/single_container.cpp | 36 +++ libs/ui/source/impl/single_container_base.cpp | 32 ++- libs/ui/source/impl/size_polygon_utils.cpp | 135 ++++++++++ libs/ui/source/impl/stack_layout_base.cpp | 49 +++- libs/ui/tests/layout.cpp | 72 +----- libs/ui/tests/size_polygon_cut.cpp | 233 ++++++++++++++++++ libs/ui/tests/size_polygon_min.cpp | 95 +++++++ 23 files changed, 840 insertions(+), 293 deletions(-) delete mode 100644 libs/ui/include/psemek/ui/impl/rectangle_base.hpp create mode 100644 libs/ui/include/psemek/ui/impl/size_polygon.hpp create mode 100644 libs/ui/include/psemek/ui/impl/size_polygon_utils.hpp create mode 100644 libs/ui/source/impl/container.cpp delete mode 100644 libs/ui/source/impl/rectangle_base.cpp create mode 100644 libs/ui/source/impl/single_container.cpp create mode 100644 libs/ui/source/impl/size_polygon_utils.cpp create mode 100644 libs/ui/tests/size_polygon_cut.cpp create mode 100644 libs/ui/tests/size_polygon_min.cpp diff --git a/libs/react/include/psemek/react/map.hpp b/libs/react/include/psemek/react/map.hpp index 44a9f716..b7b81f42 100644 --- a/libs/react/include/psemek/react/map.hpp +++ b/libs/react/include/psemek/react/map.hpp @@ -48,10 +48,11 @@ namespace psemek::react return value(detail::internal_tag{}, std::move(node)); } - template - auto map_impl(F func, std::vector> args) + template + auto map_vector_impl(F func, std::vector> args, G transform) { - using R = std::decay_t>()))>; + using H = std::decay_t()))>; + using R = std::decay_t>()))>; auto node = std::make_shared>(); auto weak_node = std::weak_ptr{node}; @@ -88,11 +89,14 @@ namespace psemek::react func = std::move(func), internal_tokens = std::move(internal_tokens), external_tokens = std::move(external_tokens), - args = std::move(args)]() -> R { - std::vector arg_values; + args = std::move(args), + transform = std::move(transform)]() -> R { + std::vector arg_values; arg_values.reserve(args.size()); for (auto const & arg : args) - arg_values.push_back(*arg); + { + arg_values.push_back(transform(arg)); + } return func(std::move(arg_values)); }; @@ -108,15 +112,34 @@ namespace psemek::react } template - auto map(F func, std::vector> args) + auto map_vector(F func, std::vector> args) { - return detail::map_impl(std::move(func), std::move(args)); + return detail::map_vector_impl(std::move(func), std::move(args), [](T const & value){ return value; }); } - template - value> unpack(std::vector> args) + template + auto map_vector(F func, std::vector> args, G transform) { - return map([](std::vector x) -> std::vector { return x; }, std::move(args)); + return detail::map_vector_impl(std::move(func), std::move(args), transform); } + constexpr auto unpack = [](std::vector> args) + { + return map_vector([](std::vector x) -> std::vector { return x; }, std::move(args), [](value const & arg){ return arg ? *arg : T{}; }); + }; + + constexpr auto unpack_with_default = [](T const & default_value) + { + return [default_value](std::vector> args){ + return map_vector([](std::vector x) -> std::vector { return x; }, std::move(args), [default_value](value const & arg){ return arg ? *arg : default_value; }); + }; + }; + + constexpr auto unpack_with_transform = [](G transform) + { + return [transform = std::move(transform)](std::vector> args){ + return map_vector([](std::vector x) -> std::vector { return x; }, std::move(args), transform); + }; + }; + } diff --git a/libs/ui/include/psemek/ui/box_layout.hpp b/libs/ui/include/psemek/ui/box_layout.hpp index 9ca357d6..ef48f6e8 100644 --- a/libs/ui/include/psemek/ui/box_layout.hpp +++ b/libs/ui/include/psemek/ui/box_layout.hpp @@ -15,17 +15,12 @@ namespace psemek::ui struct minimized {}; - struct fixed - { - float value = 0.f; - }; - struct weight { float value = 1.f; }; - using size_policy = std::variant; + using size_policy = std::variant; struct element { diff --git a/libs/ui/include/psemek/ui/impl/box_layout_base.hpp b/libs/ui/include/psemek/ui/impl/box_layout_base.hpp index 913cce96..9f0973cc 100644 --- a/libs/ui/include/psemek/ui/impl/box_layout_base.hpp +++ b/libs/ui/include/psemek/ui/impl/box_layout_base.hpp @@ -3,6 +3,7 @@ #include #include #include +#include namespace psemek::ui::impl { @@ -36,13 +37,18 @@ namespace psemek::ui::impl box_layout_base(react::value margin); void reshape(geom::box const & new_shape) override; - geom::interval size_constraints(int dimension, float other_dimension_size) const override; + react::value size_constraints() const override; + + void set_children(std::vector> children) override; + std::vector> release_children() override; void update(typename detail::box_layout_type::type const & box_layout); private: react::value margin_; react::value>> size_policies_; + react::source>> children_size_constraints_; + react::value size_constraints_; }; extern template struct box_layout_base<0>; diff --git a/libs/ui/include/psemek/ui/impl/component.hpp b/libs/ui/include/psemek/ui/impl/component.hpp index 66a9900c..55868c49 100644 --- a/libs/ui/include/psemek/ui/impl/component.hpp +++ b/libs/ui/include/psemek/ui/impl/component.hpp @@ -1,8 +1,10 @@ #pragma once -#include +#include +#include #include #include +#include #include @@ -13,14 +15,15 @@ namespace psemek::ui::impl { static constexpr float infinity = std::numeric_limits::infinity(); + static size_polygon const & default_size_polygon(); + static react::value default_size_constraints(); + virtual util::span const> children() const; virtual geom::box const & shape() const; virtual void reshape(geom::box const & new_shape); - // size_constraints(0, height) is width_constraints(height) - // size_constraints(1, width) is height_constraints(width) - virtual geom::interval size_constraints(int dimension, float other_dimension_size) const; + virtual react::value size_constraints() const; virtual ~component() {} diff --git a/libs/ui/include/psemek/ui/impl/container.hpp b/libs/ui/include/psemek/ui/impl/container.hpp index 6ef9dcfa..fda743b4 100644 --- a/libs/ui/include/psemek/ui/impl/container.hpp +++ b/libs/ui/include/psemek/ui/impl/container.hpp @@ -14,50 +14,17 @@ namespace psemek::ui::impl struct container : component { - void set_children(std::vector> children) - { - children_ = std::move(children); - } + virtual void set_children(std::vector> children); + virtual void set_children_token(util::signal>::subscription_token token); + virtual void set_child_tokens(std::vector::subscription_token> tokens); + virtual void set_child_keys(std::vector> keys); - void set_children_token(util::signal>::subscription_token token) - { - children_token_ = std::move(token); - } + virtual std::vector> release_children(); + virtual util::signal::subscription_token release_children_token(); + virtual std::vector::subscription_token> release_child_tokens(); + virtual std::vector> release_child_keys(); - void set_child_tokens(std::vector::subscription_token> tokens) - { - child_tokens_ = std::move(tokens); - } - - void set_child_keys(std::vector> keys) - { - child_keys_ = std::move(keys); - } - - std::vector> release_children() - { - return std::move(children_); - } - - util::signal::subscription_token release_children_token() - { - return std::move(children_token_); - } - - std::vector::subscription_token> release_child_tokens() - { - return std::move(child_tokens_); - } - - std::vector> release_child_keys() - { - return std::move(child_keys_); - } - - util::span const> children() const override - { - return children_; - } + util::span const> children() const override; private: util::signal::subscription_token children_token_; diff --git a/libs/ui/include/psemek/ui/impl/rectangle_base.hpp b/libs/ui/include/psemek/ui/impl/rectangle_base.hpp deleted file mode 100644 index ce177d09..00000000 --- a/libs/ui/include/psemek/ui/impl/rectangle_base.hpp +++ /dev/null @@ -1,21 +0,0 @@ -#pragma once - -#include -#include -#include - -namespace psemek::ui::impl -{ - - struct rectangle_base - : component - { - geom::interval size_constraints(int dimension, float other_dimension_size) const override; - - void update(rectangle const & value); - - private: - react::value square_; - }; - -} diff --git a/libs/ui/include/psemek/ui/impl/single_container.hpp b/libs/ui/include/psemek/ui/impl/single_container.hpp index 2efae3cb..3c685d15 100644 --- a/libs/ui/include/psemek/ui/impl/single_container.hpp +++ b/libs/ui/include/psemek/ui/impl/single_container.hpp @@ -12,35 +12,13 @@ namespace psemek::ui::impl struct single_container : component { - void set_child(std::unique_ptr child) - { - child_ = std::move(child); - } + util::span const> children() const override; - void set_child_token(util::signal::subscription_token token) - { - child_token_ = std::move(token); - } - - component * child() const - { - return child_.get(); - } - - std::unique_ptr release_child() - { - return std::move(child_); - } - - util::signal::subscription_token release_child_token() - { - return std::move(child_token_); - } - - util::span const> children() const override - { - return {&child_, 1}; - } + virtual void set_child(std::unique_ptr child); + virtual void set_child_token(util::signal::subscription_token token); + virtual component * child() const; + virtual std::unique_ptr release_child(); + virtual util::signal::subscription_token release_child_token(); private: util::signal::subscription_token child_token_; diff --git a/libs/ui/include/psemek/ui/impl/single_container_base.hpp b/libs/ui/include/psemek/ui/impl/single_container_base.hpp index da154183..8f8b2f44 100644 --- a/libs/ui/include/psemek/ui/impl/single_container_base.hpp +++ b/libs/ui/include/psemek/ui/impl/single_container_base.hpp @@ -2,6 +2,7 @@ #include #include +#include namespace psemek::ui::impl { @@ -12,10 +13,15 @@ namespace psemek::ui::impl single_container_base(react::value> margin); void reshape(geom::box const & new_shape) override; - geom::interval size_constraints(int dimension, float other_dimension_size) const override; + react::value size_constraints() const override; + + void set_child(std::unique_ptr child) override; + std::unique_ptr release_child() override; private: react::value> margin_; + react::source> child_size_constraints_; + react::value size_constraints_; }; } diff --git a/libs/ui/include/psemek/ui/impl/size_polygon.hpp b/libs/ui/include/psemek/ui/impl/size_polygon.hpp new file mode 100644 index 00000000..bbd48976 --- /dev/null +++ b/libs/ui/include/psemek/ui/impl/size_polygon.hpp @@ -0,0 +1,15 @@ +#pragma once + +#include + +#include + +namespace psemek::ui::impl +{ + + constexpr float max_size = 16384.f; + + // Convex, in CCW order + using size_polygon = std::vector>; + +} diff --git a/libs/ui/include/psemek/ui/impl/size_polygon_utils.hpp b/libs/ui/include/psemek/ui/impl/size_polygon_utils.hpp new file mode 100644 index 00000000..e9cdb60f --- /dev/null +++ b/libs/ui/include/psemek/ui/impl/size_polygon_utils.hpp @@ -0,0 +1,19 @@ +#pragma once + +#include +#include + +namespace psemek::ui::impl +{ + + size_polygon shift(size_polygon polygon, geom::vector const & delta); + + size_polygon min(size_polygon const & polygon, int dimension); + + size_polygon intersect(size_polygon const & polygon1, size_polygon const & polygon2); + + geom::interval range(size_polygon const & polygon, int dimension); + size_polygon cut(size_polygon const & polygon, int dimension, geom::interval const & range); + size_polygon sum_along(size_polygon const & polygon1, size_polygon const & polygon2, int dimension); + +} diff --git a/libs/ui/include/psemek/ui/impl/stack_layout_base.hpp b/libs/ui/include/psemek/ui/impl/stack_layout_base.hpp index 0b33aeeb..e5e0ca43 100644 --- a/libs/ui/include/psemek/ui/impl/stack_layout_base.hpp +++ b/libs/ui/include/psemek/ui/impl/stack_layout_base.hpp @@ -3,6 +3,7 @@ #include #include #include +#include namespace psemek::ui::impl { @@ -10,11 +11,20 @@ namespace psemek::ui::impl struct stack_layout_base : container { + stack_layout_base(); + void reshape(geom::box const & new_shape) override; - geom::interval size_constraints(int dimension, float other_dimension_size) const override; + react::value size_constraints() const override; + + void set_children(std::vector> children) override; + std::vector> release_children() override; void update(stack_layout const &) {} + + private: + react::source>> children_size_constraints_; + react::value size_constraints_; }; } diff --git a/libs/ui/include/psemek/ui/rectangle.hpp b/libs/ui/include/psemek/ui/rectangle.hpp index 0ad49f85..2f6de278 100644 --- a/libs/ui/include/psemek/ui/rectangle.hpp +++ b/libs/ui/include/psemek/ui/rectangle.hpp @@ -9,7 +9,6 @@ namespace psemek::ui struct rectangle { react::value color = {}; - react::value square = {}; }; } diff --git a/libs/ui/source/impl/box_layout_base.cpp b/libs/ui/source/impl/box_layout_base.cpp index e778fb5a..23adaa36 100644 --- a/libs/ui/source/impl/box_layout_base.cpp +++ b/libs/ui/source/impl/box_layout_base.cpp @@ -1,4 +1,5 @@ #include +#include #include #include @@ -11,14 +12,55 @@ namespace psemek::ui::impl static constexpr box_layout::size_policy default_policy = box_layout::weight{}; template - std::vector allocate(float total_size, float other_dimension_size, - std::vector> const & policies, util::span const> children) + size_polygon compute_size_constraints(std::vector const & size_policies, + std::vector const & children_size_constraints, float margin) + { + size_polygon minimized{{0.f, 0.f}, {0.f, max_size}}; + size_polygon weight_unit = minimized; + float weight_sum = 0.f; + + for (std::size_t i = 0; i < size_policies.size(); ++i) + { + if (std::get_if(&size_policies[i])) + { + minimized = sum_along(minimized, *children_size_constraints[i], Dimension); + } + else if (auto weight = std::get_if(&size_policies[i])) + { + size_polygon child = *children_size_constraints[i]; + for (auto & p : child) + p[Dimension] /= weight->value; + weight_unit = sum_along(weight_unit, child, Dimension); + weight_sum += weight->value; + } + } + + auto result = std::move(minimized); + + if (weight_sum > 0.f) + { + for (auto & p : weight_unit) + p[Dimension] *= weight_sum; + result = sum_along(result, weight_unit, Dimension); + } + + geom::vector shift_delta{0.f, 0.f}; + shift_delta[Dimension] = margin; + + result = shift(std::move(result), shift_delta); + + return result; + } + + template + std::vector allocate(float total_size, std::vector> const & policies, + util::span const> children) { std::vector result(policies.size(), 0.f); float total_weight = 0.f; - // First, allocate minimized & fixed elements + // First, allocate minimized elements // Also compute the total weight for weighted elements for (std::size_t i = 0; i < policies.size(); ++i) { @@ -26,12 +68,7 @@ namespace psemek::ui::impl if (std::get_if(&policy)) { - result[i] = children[i]->size_constraints(Dimension, other_dimension_size).min; - total_size -= result[i]; - } - else if (auto fixed = std::get_if(&policy)) - { - result[i] = fixed->value; + result[i] = min(*children[i]->size_constraints(), Dimension).front()[Dimension]; total_size -= result[i]; } else if (auto weight = std::get_if(&policy)) @@ -59,6 +96,14 @@ namespace psemek::ui::impl template box_layout_base::box_layout_base(react::value margin) : margin_(margin) + , size_policies_({}) + , children_size_constraints_() + , size_constraints_(react::map([](auto const & size_policies, auto const & children_size_constraints, auto const & margin){ + return compute_size_constraints(size_policies, children_size_constraints, margin); + }, + react::join(react::map(react::unpack_with_default(default_policy), size_policies_)), + react::join(react::map(react::unpack_with_transform([](react::value const & arg){ return &(*arg); }), children_size_constraints_)), + margin_)) {} template @@ -71,7 +116,7 @@ namespace psemek::ui::impl float const margin = *margin_; - auto sizes = allocate(new_shape[Dimension].length() - (children().size() - 1) * margin, new_shape[Dimension ^ 1].length(), *size_policies_, children()); + auto sizes = allocate(new_shape[Dimension].length() - (children().size() - 1) * margin, *size_policies_, children()); float pen = new_shape[Dimension].min; for (std::size_t i = 0; i < children().size(); ++i) @@ -89,83 +134,33 @@ namespace psemek::ui::impl } template - geom::interval box_layout_base::size_constraints(int dimension, float other_dimension_size) const + react::value box_layout_base::size_constraints() const { - if (children().empty()) - return container::size_constraints(dimension, other_dimension_size); + return size_constraints_; + } - float const margin = *margin_; + template + void box_layout_base::set_children(std::vector> children) + { + container::set_children(std::move(children)); - if (dimension == Dimension) + std::vector> children_size_constraints; + for (auto const & child : this->children()) { - auto result = geom::interval{0.f, 0.f}; - - auto const & policies = *size_policies_; - - auto weighted_unit_constraints = geom::interval{0.f, infinity}; - float weight_sum = 0.f; - - for (std::size_t i = 0; i < children().size(); ++i) - { - auto const & child = children()[i]; - auto const policy = policies[i] ? *policies[i] : default_policy; - - if (auto fixed = std::get_if(&policy)) - result += fixed->value; - else if (std::get_if(&policy)) - { - if (child) - { - auto child_constraints = child->size_constraints(dimension, other_dimension_size); - result.min += child_constraints.min; - result.max += child_constraints.max; - } - else - { - result.max = infinity; - } - } - else if (auto weight = std::get_if(&policy)) - { - if (child) - { - auto child_constraints = child->size_constraints(dimension, other_dimension_size); - child_constraints.min /= weight->value; - child_constraints.max /= weight->value; - weighted_unit_constraints &= child_constraints; - } - - weight_sum += weight->value; - } - } - - // Prevent NaN in result.max (inf * 0) - if (weight_sum > 0.f) - { - result.min += weighted_unit_constraints.min * weight_sum; - result.max += weighted_unit_constraints.max * weight_sum; - } - - result += (children().size() - 1) * margin; - - return result; + if (child) + children_size_constraints.push_back(child->size_constraints()); + else + children_size_constraints.push_back(default_size_constraints()); } - else - { - float const margin = *margin_; + children_size_constraints_.set(std::move(children_size_constraints)); + } - auto sizes = allocate(other_dimension_size - (children().size() - 1) * margin, shape()[1].length(), *size_policies_, children()); - - auto result = container::size_constraints(dimension, other_dimension_size); - - for (std::size_t i = 0; i < children().size(); ++i) - { - if (!children()[i]) continue; - result &= children()[i]->size_constraints(dimension, sizes[i]); - } - - return result; - } + template + std::vector> box_layout_base::release_children() + { + auto result = container::release_children(); + children_size_constraints_.set({}); + return result; } template diff --git a/libs/ui/source/impl/component.cpp b/libs/ui/source/impl/component.cpp index c425f382..42056643 100644 --- a/libs/ui/source/impl/component.cpp +++ b/libs/ui/source/impl/component.cpp @@ -3,6 +3,24 @@ namespace psemek::ui::impl { + size_polygon const & component::default_size_polygon() + { + static size_polygon const result{{ + {max_size, 0.f}, + {0.f, 0.f}, + {0.f, max_size}, + {max_size, max_size} + }}; + + return result; + } + + react::value component::default_size_constraints() + { + static react::value const result{default_size_polygon()}; + return result; + } + util::span const> component::children() const { return {}; @@ -18,9 +36,9 @@ namespace psemek::ui::impl shape_ = new_shape; } - geom::interval component::size_constraints(int, float) const + react::value component::size_constraints() const { - return {0.f, infinity}; + return default_size_constraints(); } } diff --git a/libs/ui/source/impl/container.cpp b/libs/ui/source/impl/container.cpp new file mode 100644 index 00000000..6ba94b8b --- /dev/null +++ b/libs/ui/source/impl/container.cpp @@ -0,0 +1,51 @@ +#include + +namespace psemek::ui::impl +{ + + void container::set_children(std::vector> children) + { + children_ = std::move(children); + } + + void container::set_children_token(util::signal>::subscription_token token) + { + children_token_ = std::move(token); + } + + void container::set_child_tokens(std::vector::subscription_token> tokens) + { + child_tokens_ = std::move(tokens); + } + + void container::set_child_keys(std::vector> keys) + { + child_keys_ = std::move(keys); + } + + std::vector> container::release_children() + { + return std::move(children_); + } + + util::signal::subscription_token container::release_children_token() + { + return std::move(children_token_); + } + + std::vector::subscription_token> container::release_child_tokens() + { + return std::move(child_tokens_); + } + + std::vector> container::release_child_keys() + { + return std::move(child_keys_); + } + + util::span const> container::children() const + { + return children_; + } + +} diff --git a/libs/ui/source/impl/rectangle_base.cpp b/libs/ui/source/impl/rectangle_base.cpp deleted file mode 100644 index a70040d0..00000000 --- a/libs/ui/source/impl/rectangle_base.cpp +++ /dev/null @@ -1,19 +0,0 @@ -#include - -namespace psemek::ui::impl -{ - - geom::interval rectangle_base::size_constraints(int dimension, float other_dimension_size) const - { - if (square_ && *square_) - return {other_dimension_size, other_dimension_size}; - else - return component::size_constraints(dimension, other_dimension_size); - } - - void rectangle_base::update(rectangle const & value) - { - square_ = value.square; - } - -} diff --git a/libs/ui/source/impl/single_container.cpp b/libs/ui/source/impl/single_container.cpp new file mode 100644 index 00000000..d6478103 --- /dev/null +++ b/libs/ui/source/impl/single_container.cpp @@ -0,0 +1,36 @@ +#include + +namespace psemek::ui::impl +{ + + util::span const> single_container::children() const + { + return {&child_, 1}; + } + + void single_container::set_child(std::unique_ptr child) + { + child_ = std::move(child); + } + + void single_container::set_child_token(util::signal::subscription_token token) + { + child_token_ = std::move(token); + } + + component * single_container::child() const + { + return child_.get(); + } + + std::unique_ptr single_container::release_child() + { + return std::move(child_); + } + + util::signal::subscription_token single_container::release_child_token() + { + return std::move(child_token_); + } + +} diff --git a/libs/ui/source/impl/single_container_base.cpp b/libs/ui/source/impl/single_container_base.cpp index 81cc1b98..7b37379b 100644 --- a/libs/ui/source/impl/single_container_base.cpp +++ b/libs/ui/source/impl/single_container_base.cpp @@ -1,10 +1,17 @@ #include +#include +#include +#include namespace psemek::ui::impl { single_container_base::single_container_base(react::value> margin) - : margin_(std::move(margin)) + : margin_(margin) + , child_size_constraints_(default_size_constraints()) + , size_constraints_(react::map([](size_polygon const & child_constraints, geom::vector const & margin){ + return shift(child_constraints, margin); + }, react::join(child_size_constraints_), margin)) {} void single_container_base::reshape(geom::box const & new_shape) @@ -15,14 +22,23 @@ namespace psemek::ui::impl child()->reshape(geom::shrink(new_shape, *margin_)); } - geom::interval single_container_base::size_constraints(int dimension, float other_dimension_size) const + react::value single_container_base::size_constraints() const { - geom::interval result = single_container::size_constraints(dimension, other_dimension_size); - - if (child()) - result = child()->size_constraints(dimension, other_dimension_size - 2.f * (*margin_)[dimension ^ 1]); - - return result + 2.f * (*margin_)[dimension]; + return size_constraints_; } + void single_container_base::set_child(std::unique_ptr child) + { + single_container::set_child(std::move(child)); + child_size_constraints_.set(this->child() ? this->child()->size_constraints() : default_size_constraints()); + } + + std::unique_ptr single_container_base::release_child() + { + auto child = single_container::release_child(); + child_size_constraints_.set(default_size_constraints()); + return child; + } + + } diff --git a/libs/ui/source/impl/size_polygon_utils.cpp b/libs/ui/source/impl/size_polygon_utils.cpp new file mode 100644 index 00000000..37d80af0 --- /dev/null +++ b/libs/ui/source/impl/size_polygon_utils.cpp @@ -0,0 +1,135 @@ +#include +#include +#include +#include + +namespace psemek::ui::impl +{ + + size_polygon shift(size_polygon polygon, geom::vector const & delta) + { + for (auto & p : polygon) + p += delta; + return polygon; + } + + size_polygon min(size_polygon const & polygon, int dimension) + { + if (polygon.empty()) + return {}; + + size_polygon result; + + auto cit = util::make_cyclic_iterator(polygon); + std::size_t iterations = 0; + + for (auto next = std::next(cit); (*next)[dimension] <= (*cit)[dimension] && iterations < polygon.size(); cit = next++, ++iterations); + + if (iterations == polygon.size()) + return polygon; + + for (auto prev = std::prev(cit); (*prev)[dimension] <= (*cit)[dimension]; cit = prev--); + + for (auto cjt = cit; (*cjt)[dimension] == (*cit)[dimension]; ++cjt) + result.push_back(*cjt); + + return result; + } + + size_polygon intersect(size_polygon const & polygon1, size_polygon const & polygon2) + { + // TODO: https://www.cs.jhu.edu/~misha/Spring16/ORourke82.pdf + (void)polygon1; + (void)polygon2; + util::not_implemented(); + } + + geom::interval range(size_polygon const & polygon, int dimension) + { + geom::interval result; + for (auto const & p : polygon) + result |= p[dimension]; + return result; + } + + size_polygon cut(size_polygon const & polygon, int dimension, geom::interval const & range) + { + size_polygon result; + std::size_t iterations = 0; + for (auto it = util::make_cyclic_iterator(polygon), next_it = std::next(it); iterations != polygon.size(); (it = next_it++), ++iterations) + { + auto const & curr = *it; + auto const & next = *next_it; + + auto intersection = [&](float value) + { + // curr + (next - curr) * t = value + float t = (value - curr[dimension]) / (next[dimension] - curr[dimension]); + geom::point p; + p[dimension] = value; + p[dimension ^ 1] = geom::lerp(curr[dimension ^ 1], next[dimension ^ 1], t); + return p; + }; + + auto classify = [&](float value) + { + if (value < range.min) + return -2; + else if (value == range.min) + return -1; + else if (value < range.max) + return 0; + else if (value == range.max) + return 1; + else // (value > range.max) + return 2; + }; + + int const curr_class = classify(curr[dimension]); + int const next_class = classify(next[dimension]); + + if (curr_class >= -1 && curr_class <= 1) + result.push_back(curr); + + bool const min = (curr_class == -2 && next_class >= 0) || (curr_class >= 0 && next_class == -2); + bool const max = (curr_class == 2 && next_class <= 0) || (curr_class <= 0 && next_class == 2); + + + if (range.min == range.max) + { + if (min) result.push_back(intersection(range.min)); + } + else + { + if (min) result.push_back(intersection(range.min)); + if (max) result.push_back(intersection(range.max)); + + if (min && max && curr_class == 2) + std::swap(result.back(), result[result.size() - 2]); + } + } + + return result; + } + + size_polygon sum_along(size_polygon const & polygon1, size_polygon const & polygon2, int dimension) + { + if (polygon1.empty() || polygon2.empty()) + return size_polygon{}; + + int const other_dimension = dimension ^ 1; + + auto range1 = range(polygon1, other_dimension); + auto range2 = range(polygon2, other_dimension); + + auto common_range = range1 & range2; + if (common_range.empty()) + return size_polygon{}; + + auto cut1 = cut(polygon1, other_dimension, common_range); + auto cut2 = cut(polygon2, other_dimension, common_range); + + util::not_implemented(); + } + +} diff --git a/libs/ui/source/impl/stack_layout_base.cpp b/libs/ui/source/impl/stack_layout_base.cpp index 676a0387..95a7f648 100644 --- a/libs/ui/source/impl/stack_layout_base.cpp +++ b/libs/ui/source/impl/stack_layout_base.cpp @@ -1,8 +1,33 @@ #include +#include +#include +#include namespace psemek::ui::impl { + namespace + { + + size_polygon compute_size_constraints(std::vector const & children_size_constraints) + { + if (children_size_constraints.empty()) + return component::default_size_polygon(); + + auto result = *children_size_constraints.front(); + for (std::size_t i = 1; i < children_size_constraints.size(); ++i) + result = intersect(result, *children_size_constraints[i]); + return result; + } + + } + + stack_layout_base::stack_layout_base() + : size_constraints_(react::map([](auto const & children_size_constraints){ + return compute_size_constraints(children_size_constraints); + }, react::join(react::map(react::unpack_with_transform([](react::value const & arg){ return &(*arg); }), children_size_constraints_)))) + {} + void stack_layout_base::reshape(geom::box const & new_shape) { for (auto const & child : children()) @@ -10,15 +35,31 @@ namespace psemek::ui::impl child->reshape(new_shape); } - geom::interval stack_layout_base::size_constraints(int dimension, float other_dimension_size) const + react::value stack_layout_base::size_constraints() const { - auto result = container::size_constraints(dimension, other_dimension_size); + return size_constraints_; + } - for (auto const & child : children()) + void stack_layout_base::set_children(std::vector> children) + { + container::set_children(std::move(children)); + std::vector> children_size_constraints; + for (auto const & child : this->children()) + { if (child) - result &= child->size_constraints(dimension, other_dimension_size); + children_size_constraints.push_back(child->size_constraints()); + else + children_size_constraints.push_back(default_size_constraints()); + } + children_size_constraints_.set(std::move(children_size_constraints)); + } + std::vector> stack_layout_base::release_children() + { + auto result = container::release_children(); + children_size_constraints_.set({}); return result; } + } diff --git a/libs/ui/tests/layout.cpp b/libs/ui/tests/layout.cpp index 65a53b9a..5d898e41 100644 --- a/libs/ui/tests/layout.cpp +++ b/libs/ui/tests/layout.cpp @@ -1,10 +1,10 @@ #include #include -#include #include #include #include +#include #include @@ -15,14 +15,20 @@ namespace { constexpr float layout_margin = 5.f; - constexpr float infinity = impl::component::infinity; + + struct rectangle_impl + : impl::component + { + void update(rectangle const &) + {} + }; struct test_component_factory : impl::component_factory_base { test_component_factory() { - register_type(); + register_type(); register_type(); register_type>([]{ return std::make_unique>(layout_margin); }); register_type>([]{ return std::make_unique>(layout_margin); }); @@ -30,63 +36,3 @@ namespace }; } - -test_case(ui_layout_rectangle) -{ - test_component_factory factory; - - auto square = react::source(false); - - auto test_ui = rectangle{.square = square}; - - auto ui_root = factory.reconciliate(nullptr, test_ui); - - expect_equal(ui_root->size_constraints(0, 100.f), geom::interval(0.f, infinity)); - expect_equal(ui_root->size_constraints(1, 100.f), geom::interval(0.f, infinity)); - - square.set(true); - - expect_equal(ui_root->size_constraints(0, 100.f), geom::interval(100.f, 100.f)); - expect_equal(ui_root->size_constraints(1, 100.f), geom::interval(100.f, 100.f)); - - ui_root->reshape({{{0.f, 10.f}, {0.f, 10.f}}}); - - expect_equal(ui_root->shape(), (geom::box{{{0.f, 10.f}, {0.f, 10.f}}})); -} - - -test_case(ui_layout_hbox__2__rectangles) -{ - test_component_factory factory; - - auto square0 = react::source(false); - auto square1 = react::source(false); - - auto policy0 = react::source(box_layout::minimized{}); - auto policy1 = react::source(box_layout::minimized{}); - - auto test_ui = box_layout::horizontal{{ - { - .element = rectangle{.square = square0}, - .policy = policy0, - }, - { - .element = rectangle{.square = square1}, - .policy = policy1, - } - }}; - - auto ui_root = factory.reconciliate(nullptr, test_ui); - - expect_equal(ui_root->size_constraints(0, 100.f), geom::interval(layout_margin, infinity)); - expect_equal(ui_root->size_constraints(1, 100.f), geom::interval(0.f, infinity)); - - square0.set(true); - - expect_equal(ui_root->size_constraints(0, 100.f), geom::interval(100.f + layout_margin, infinity)); - expect_equal(ui_root->size_constraints(1, 100.f), geom::interval(100.f, 100.f)); - - ui_root->reshape({{{0.f, 10.f}, {0.f, 10.f}}}); - - expect_equal(ui_root->shape(), (geom::box{{{0.f, 10.f}, {0.f, 10.f}}})); -} diff --git a/libs/ui/tests/size_polygon_cut.cpp b/libs/ui/tests/size_polygon_cut.cpp new file mode 100644 index 00000000..06c8045c --- /dev/null +++ b/libs/ui/tests/size_polygon_cut.cpp @@ -0,0 +1,233 @@ +#include + +#include + +#include + +using namespace psemek; +using namespace psemek::ui::impl; + +namespace +{ + + size_polygon normalize(size_polygon p) + { + if (p.empty()) + return p; + auto it = std::min_element(p.begin(), p.end()); + std::rotate(p.begin(), it, p.end()); + return p; + } + + void check_polygon_cut(size_polygon & polygon, int dimension, geom::interval const & range, size_polygon const & result) + { + for (int i = 0; i < polygon.size(); ++i) + { + expect_equal(normalize(cut(polygon, dimension, range)), normalize(result)); + std::rotate(polygon.begin(), polygon.end() - 1, polygon.end()); + } + } + + void check_polygon_sum(size_polygon & polygon1, size_polygon & polygon2, size_polygon const & result_x, size_polygon const & result_y) + { + for (int i1 = 0; i1 < polygon1.size(); ++i1) + { + for (int i2 = 0; i2 < polygon2.size(); ++i2) + { + expect_equal(normalize(sum_along(polygon1, polygon2, 0)), normalize(result_x)); + expect_equal(normalize(sum_along(polygon1, polygon2, 1)), normalize(result_y)); + + std::rotate(polygon2.begin(), polygon2.end() - 1, polygon2.end()); + } + + std::rotate(polygon1.begin(), polygon1.end() - 1, polygon1.end()); + } + } + +} + +test_case(ui_impl_size__polygon_cut_empty) +{ + expect_equal(cut(size_polygon{}, 0, {0.f, 10.f}), size_polygon{}); +} + +test_case(ui_impl_size__polygon_cut_point__x) +{ + size_polygon polygon{{0.f, 0.f}}; + + check_polygon_cut(polygon, 0, {-5.f, -5.f}, size_polygon{}); + check_polygon_cut(polygon, 0, {-5.f, 0.f}, polygon); + check_polygon_cut(polygon, 0, {-5.f, 5.f}, polygon); + check_polygon_cut(polygon, 0, {0.f, 0.f}, polygon); + check_polygon_cut(polygon, 0, {0.f, 5.f}, polygon); + check_polygon_cut(polygon, 0, {5.f, 5.f}, size_polygon{}); +} + +test_case(ui_impl_size__polygon_cut_point__y) +{ + size_polygon polygon{{0.f, 0.f}}; + + check_polygon_cut(polygon, 1, {-5.f, -5.f}, size_polygon{}); + check_polygon_cut(polygon, 1, {-5.f, 0.f}, polygon); + check_polygon_cut(polygon, 1, {-5.f, 5.f}, polygon); + check_polygon_cut(polygon, 1, {0.f, 0.f}, polygon); + check_polygon_cut(polygon, 1, {0.f, 5.f}, polygon); + check_polygon_cut(polygon, 1, {5.f, 5.f}, size_polygon{}); +} + +test_case(ui_impl_size__polygon_cut_segmentx__x__simple) +{ + size_polygon polygon{{0.f, 0.f}, {10.f, 0.f}}; + + check_polygon_cut(polygon, 0, {-5.f, -5.f}, size_polygon{}); + check_polygon_cut(polygon, 0, {-5.f, 5.f}, size_polygon{{0.f, 0.f}, {5.f, 0.f}}); + check_polygon_cut(polygon, 0, {-5.f, 15.f}, polygon); + check_polygon_cut(polygon, 0, {5.f, 15.f}, size_polygon{{5.f, 0.f}, {10.f, 0.f}}); + check_polygon_cut(polygon, 0, {15.f, 15.f}, size_polygon{}); +} + +test_case(ui_impl_size__polygon_cut_square__x__simple) +{ + size_polygon polygon{{0.f, 0.f}, {10.f, 0.f}, {10.f, 10.f}, {0.f, 10.f}}; + + check_polygon_cut(polygon, 0, {-10.f, -2.f}, size_polygon{}); + check_polygon_cut(polygon, 0, {-2.f, -2.f}, size_polygon{}); + check_polygon_cut(polygon, 0, {-2.f, 2.f}, size_polygon{{0.f, 0.f}, {2.f, 0.f}, {2.f, 10.f}, {0.f, 10.f}}); + check_polygon_cut(polygon, 0, {2.f, 8.f}, size_polygon{{2.f, 0.f}, {8.f, 0.f}, {8.f, 10.f}, {2.f, 10.f}}); + check_polygon_cut(polygon, 0, {5.f, 5.f}, size_polygon{{5.f, 0.f}, {5.f, 10.f}}); + check_polygon_cut(polygon, 0, {8.f, 12.f}, size_polygon{{8.f, 0.f}, {10.f, 0.f}, {10.f, 10.f}, {8.f, 10.f}}); + check_polygon_cut(polygon, 0, {12.f, 20.f}, size_polygon{}); + check_polygon_cut(polygon, 0, {12.f, 12.f}, size_polygon{}); + check_polygon_cut(polygon, 0, {-2.f, 12.f}, polygon); +} + +test_case(ui_impl_size__polygon_cut_square__x__degenerate) +{ + size_polygon polygon{{0.f, 0.f}, {10.f, 0.f}, {10.f, 10.f}, {0.f, 10.f}}; + + check_polygon_cut(polygon, 0, {-10.f, 0.f}, size_polygon{{0.f, 0.f}, {0.f, 10.f}}); + check_polygon_cut(polygon, 0, {0.f, 0.f}, size_polygon{{0.f, 0.f}, {0.f, 10.f}}); + check_polygon_cut(polygon, 0, {-2.f, 10.f}, polygon); + check_polygon_cut(polygon, 0, {0.f, 10.f}, polygon); + check_polygon_cut(polygon, 0, {0.f, 12.f}, polygon); + check_polygon_cut(polygon, 0, {10.f, 10.f}, size_polygon{{10.f, 0.f}, {10.f, 10.f}}); + check_polygon_cut(polygon, 0, {10.f, 20.f}, size_polygon{{10.f, 0.f}, {10.f, 10.f}}); +} + +test_case(ui_impl_size__polygon_cut_square__y__simple) +{ + size_polygon polygon{{0.f, 0.f}, {10.f, 0.f}, {10.f, 10.f}, {0.f, 10.f}}; + + check_polygon_cut(polygon, 1, {-10.f, -2.f}, size_polygon{}); + check_polygon_cut(polygon, 1, {-2.f, -2.f}, size_polygon{}); + check_polygon_cut(polygon, 1, {-2.f, 2.f}, size_polygon{{0.f, 0.f}, {10.f, 0.f}, {10.f, 2.f}, {0.f, 2.f}}); + check_polygon_cut(polygon, 1, {2.f, 8.f}, size_polygon{{0.f, 2.f}, {10.f, 2.f}, {10.f, 8.f}, {0.f, 8.f}}); + check_polygon_cut(polygon, 1, {5.f, 5.f}, size_polygon{{0.f, 5.f}, {10.f, 5.f}}); + check_polygon_cut(polygon, 1, {8.f, 12.f}, size_polygon{{0.f, 8.f}, {10.f, 8.f}, {10.f, 10.f}, {0.f, 10.f}}); + check_polygon_cut(polygon, 1, {12.f, 20.f}, size_polygon{}); + check_polygon_cut(polygon, 1, {12.f, 12.f}, size_polygon{}); + check_polygon_cut(polygon, 1, {-2.f, 12.f}, polygon); +} + +test_case(ui_impl_size__polygon_cut_square__y__degenerate) +{ + size_polygon polygon{{0.f, 0.f}, {10.f, 0.f}, {10.f, 10.f}, {0.f, 10.f}}; + + check_polygon_cut(polygon, 1, {-10.f, 0.f}, size_polygon{{0.f, 0.f}, {10.f, 0.f}}); + check_polygon_cut(polygon, 1, {0.f, 0.f}, size_polygon{{0.f, 0.f}, {10.f, 0.f}}); + check_polygon_cut(polygon, 1, {-2.f, 10.f}, polygon); + check_polygon_cut(polygon, 1, {0.f, 10.f}, polygon); + check_polygon_cut(polygon, 1, {0.f, 12.f}, polygon); + check_polygon_cut(polygon, 1, {10.f, 10.f}, size_polygon{{0.f, 10.f}, {10.f, 10.f}}); + check_polygon_cut(polygon, 1, {10.f, 20.f}, size_polygon{{0.f, 10.f}, {10.f, 10.f}}); +} + +test_case(ui_impl_size__polygon_cut_diamond__x__simple) +{ + size_polygon polygon{{10.f, 0.f}, {20.f, 10.f}, {10.f, 20.f}, {0.f, 10.f}}; + + check_polygon_cut(polygon, 0, {-10.f, -5.f}, size_polygon{}); + check_polygon_cut(polygon, 0, {-10.f, 5.f}, size_polygon{{0.f, 10.f}, {5.f, 5.f}, {5.f, 15.f}}); + check_polygon_cut(polygon, 0, {-10.f, 15.f}, size_polygon{{0.f, 10.f}, {10.f, 0.f}, {15.f, 5.f}, {15.f, 15.f}, {10.f, 20.f}}); + check_polygon_cut(polygon, 0, {5.f, 5.f}, size_polygon{{5.f, 5.f}, {5.f, 15.f}}); + check_polygon_cut(polygon, 0, {5.f, 15.f}, size_polygon{{5.f, 15.f}, {5.f, 5.f}, {10.f, 0.f}, {15.f, 5.f}, {15.f, 15.f}, {10.f, 20.f}}); + check_polygon_cut(polygon, 0, {5.f, 25.f}, size_polygon{{5.f, 15.f}, {5.f, 5.f}, {10.f, 0.f}, {20.f, 10.f}, {10.f, 20.f}}); + check_polygon_cut(polygon, 0, {15.f, 15.f}, size_polygon{{15.f, 5.f}, {15.f, 15.f}}); + check_polygon_cut(polygon, 0, {15.f, 25.f}, size_polygon{{15.f, 5.f}, {20.f, 10.f}, {15.f, 15.f}}); + check_polygon_cut(polygon, 0, {25.f, 30.f}, size_polygon{}); + check_polygon_cut(polygon, 0, {-5.f, 25.f}, polygon); +} + +test_case(ui_impl_size__polygon_cut_diamond__x__degenerate) +{ + size_polygon polygon{{10.f, 0.f}, {20.f, 10.f}, {10.f, 20.f}, {0.f, 10.f}}; + + check_polygon_cut(polygon, 0, {-10.f, 0.f}, size_polygon{{0.f, 10.f}}); + check_polygon_cut(polygon, 0, {-10.f, 10.f}, size_polygon{{0.f, 10.f}, {10.f, 0.f}, {10.f, 20.f}}); + check_polygon_cut(polygon, 0, {-10.f, 20.f}, polygon); + check_polygon_cut(polygon, 0, {0.f, 0.f}, size_polygon{{0.f, 10.f}}); + check_polygon_cut(polygon, 0, {0.f, 5.f}, size_polygon{{0.f, 10.f}, {5.f, 5.f}, {5.f, 15.f}}); + check_polygon_cut(polygon, 0, {0.f, 10.f}, size_polygon{{0.f, 10.f}, {10.f, 0.f}, {10.f, 20.f}}); + check_polygon_cut(polygon, 0, {0.f, 15.f}, size_polygon{{0.f, 10.f}, {10.f, 0.f}, {15.f, 5.f}, {15.f, 15.f}, {10.f, 20.f}}); + check_polygon_cut(polygon, 0, {0.f, 20.f}, polygon); + check_polygon_cut(polygon, 0, {0.f, 25.f}, polygon); + check_polygon_cut(polygon, 0, {5.f, 10.f}, size_polygon{{5.f, 5.f}, {10.f, 0.f}, {10.f, 20.f}, {5.f, 15.f}}); + check_polygon_cut(polygon, 0, {5.f, 20.f}, size_polygon{{5.f, 5.f}, {10.f, 0.f}, {20.f, 10.f}, {10.f, 20.f}, {5.f, 15.f}}); + check_polygon_cut(polygon, 0, {10.f, 10.f}, size_polygon{{10.f, 0.f}, {10.f, 20.f}}); + check_polygon_cut(polygon, 0, {10.f, 15.f}, size_polygon{{10.f, 0.f}, {15.f, 5.f}, {15.f, 15.f}, {10.f, 20.f}}); + check_polygon_cut(polygon, 0, {10.f, 20.f}, size_polygon{{10.f, 0.f}, {20.f, 10.f}, {10.f, 20.f}}); + check_polygon_cut(polygon, 0, {10.f, 25.f}, size_polygon{{10.f, 0.f}, {20.f, 10.f}, {10.f, 20.f}}); + check_polygon_cut(polygon, 0, {15.f, 20.f}, size_polygon{{15.f, 5.f}, {20.f, 10.f}, {15.f, 15.f}}); + check_polygon_cut(polygon, 0, {20.f, 20.f}, size_polygon{{20.f, 10.f}}); + check_polygon_cut(polygon, 0, {20.f, 25.f}, size_polygon{{20.f, 10.f}}); +} + +test_case(ui_impl_size__polygon_cut_diamond__y__simple) +{ + size_polygon polygon{{10.f, 0.f}, {20.f, 10.f}, {10.f, 20.f}, {0.f, 10.f}}; + + check_polygon_cut(polygon, 1, {-10.f, -5.f}, size_polygon{}); + check_polygon_cut(polygon, 1, {-10.f, 5.f}, size_polygon{{15.f, 5.f}, {5.f, 5.f}, {10.f, 0.f}}); + check_polygon_cut(polygon, 1, {-10.f, 15.f}, size_polygon{{20.f, 10.f}, {15.f, 15.f}, {5.f, 15.f}, {0.f, 10.f}, {10.f, 0.f}}); + check_polygon_cut(polygon, 1, {5.f, 5.f}, size_polygon{{15.f, 5.f}, {5.f, 5.f}}); + check_polygon_cut(polygon, 1, {5.f, 15.f}, size_polygon{{20.f, 10.f}, {15.f, 15.f}, {5.f, 15.f}, {0.f, 10.f}, {5.f, 5.f}, {15.f, 5.f}}); + check_polygon_cut(polygon, 1, {5.f, 25.f}, size_polygon{{20.f, 10.f}, {10.f, 20.f}, {0.f, 10.f}, {5.f, 5.f}, {15.f, 5.f}}); + check_polygon_cut(polygon, 1, {15.f, 15.f}, size_polygon{{15.f, 15.f}, {5.f, 15.f}}); + check_polygon_cut(polygon, 1, {15.f, 25.f}, size_polygon{{15.f, 15.f}, {10.f, 20.f}, {5.f, 15.f}}); + check_polygon_cut(polygon, 1, {25.f, 30.f}, size_polygon{}); + check_polygon_cut(polygon, 1, {-5.f, 25.f}, polygon); +} + +test_case(ui_impl_size__polygon_cut_diamond__y__degenerate) +{ + size_polygon polygon{{10.f, 0.f}, {20.f, 10.f}, {10.f, 20.f}, {0.f, 10.f}}; + + check_polygon_cut(polygon, 1, {-10.f, 0.f}, size_polygon{{10.f, 0.f}}); + check_polygon_cut(polygon, 1, {-10.f, 10.f}, size_polygon{{20.f, 10.f}, {0.f, 10.f}, {10.f, 0.f}}); + check_polygon_cut(polygon, 1, {-10.f, 20.f}, polygon); + check_polygon_cut(polygon, 1, {0.f, 0.f}, size_polygon{{10.f, 0.f}}); + check_polygon_cut(polygon, 1, {0.f, 5.f}, size_polygon{{10.f, 0.f}, {15.f, 5.f}, {5.f, 5.f}}); + check_polygon_cut(polygon, 1, {0.f, 10.f}, size_polygon{{0.f, 10.f}, {10.f, 0.f}, {20.f, 10.f}}); + check_polygon_cut(polygon, 1, {0.f, 15.f}, size_polygon{{0.f, 10.f}, {10.f, 0.f}, {20.f, 10.f}, {15.f, 15.f}, {5.f, 15.f}}); + check_polygon_cut(polygon, 1, {0.f, 20.f}, polygon); + check_polygon_cut(polygon, 1, {0.f, 25.f}, polygon); + check_polygon_cut(polygon, 1, {5.f, 10.f}, size_polygon{{5.f, 5.f}, {15.f, 5.f}, {20.f, 10.f}, {0.f, 10.f}}); + check_polygon_cut(polygon, 1, {5.f, 20.f}, size_polygon{{5.f, 5.f}, {15.f, 5.f}, {20.f, 10.f}, {10.f, 20.f}, {0.f, 10.f}}); + check_polygon_cut(polygon, 1, {10.f, 10.f}, size_polygon{{0.f, 10.f}, {20.f, 10.f}}); + check_polygon_cut(polygon, 1, {10.f, 15.f}, size_polygon{{0.f, 10.f}, {20.f, 10.f}, {15.f, 15.f}, {5.f, 15.f}}); + check_polygon_cut(polygon, 1, {10.f, 20.f}, size_polygon{{0.f, 10.f}, {20.f, 10.f}, {10.f, 20.f}}); + check_polygon_cut(polygon, 1, {10.f, 25.f}, size_polygon{{0.f, 10.f}, {20.f, 10.f}, {10.f, 20.f}}); + check_polygon_cut(polygon, 1, {15.f, 20.f}, size_polygon{{5.f, 15.f}, {15.f, 15.f}, {10.f, 20.f}}); + check_polygon_cut(polygon, 1, {20.f, 20.f}, size_polygon{{10.f, 20.f}}); + check_polygon_cut(polygon, 1, {20.f, 25.f}, size_polygon{{10.f, 20.f}}); +} + +test_case(ui_impl_size__polygon_sum_empty) +{ + size_polygon polygon; + + expect_equal(sum_along(polygon, polygon, 0), polygon); + expect_equal(sum_along(polygon, polygon, 1), polygon); + + (void)check_polygon_sum; +} diff --git a/libs/ui/tests/size_polygon_min.cpp b/libs/ui/tests/size_polygon_min.cpp new file mode 100644 index 00000000..d3bbabfb --- /dev/null +++ b/libs/ui/tests/size_polygon_min.cpp @@ -0,0 +1,95 @@ +#include + +#include + +#include + +using namespace psemek; +using namespace psemek::ui::impl; + +namespace +{ + + template + std::set as_set(std::vector const & v) + { + std::set result; + for (auto const & x : v) + result.insert(x); + return result; + } + + void check_polygon_min(size_polygon & polygon, size_polygon const & min_x, size_polygon const & min_y) + { + for (int i = 0; i < polygon.size(); ++i) + { + expect_equal(as_set(min(polygon, 0)), as_set(min_x)); + expect_equal(as_set(min(polygon, 1)), as_set(min_y)); + + std::rotate(polygon.begin(), polygon.end() - 1, polygon.end()); + } + } + +} + +test_case(ui_impl_size__polygon_min_empty) +{ + size_polygon polygon; + + expect_equal(min(polygon, 0), size_polygon{}); + expect_equal(min(polygon, 1), size_polygon{}); +} + +test_case(ui_impl_size__polygon_min_edge__X) +{ + size_polygon polygon{{0.f, 0.f}, {10.f, 0.f}}; + size_polygon min_x{{0.f, 0.f}}; + size_polygon min_y{{0.f, 0.f}, {10.f, 0.f}}; + + check_polygon_min(polygon, min_x, min_y); +} + +test_case(ui_impl_size__polygon_min_edge__Y) +{ + size_polygon polygon{{0.f, 0.f}, {0.f, 10.f}}; + size_polygon min_x{{0.f, 0.f}, {0.f, 10.f}}; + size_polygon min_y{{0.f, 0.f}}; + + check_polygon_min(polygon, min_x, min_y); +} + +test_case(ui_impl_size__polygon_min_edge__XY) +{ + size_polygon polygon{{0.f, 0.f}, {10.f, 10.f}}; + size_polygon min_x{{0.f, 0.f}}; + size_polygon min_y{{0.f, 0.f}}; + + check_polygon_min(polygon, min_x, min_y); +} + +test_case(ui_impl_size__polygon_min_square) +{ + size_polygon polygon{{0.f, 0.f}, {10.f, 0.f}, {10.f, 10.f}, {0.f, 10.f}}; + size_polygon min_x{{0.f, 0.f}, {0.f, 10.f}}; + size_polygon min_y{{0.f, 0.f}, {10.f, 0.f}}; + + check_polygon_min(polygon, min_x, min_y); +} + +test_case(ui_impl_size__polygon_min_diamond) +{ + size_polygon polygon{{0.f, 10.f}, {10.f, 0.f}, {20.f, 10.f}, {10.f, 20.f}}; + size_polygon min_x{{0.f, 10.f}}; + size_polygon min_y{{10.f, 0.f}}; + + check_polygon_min(polygon, min_x, min_y); +} + +test_case(ui_impl_size__polygon_min_octahedron) +{ + size_polygon polygon{{10.f, 0.f}, {20.f, 0.f}, {30.f, 10.f}, {30.f, 20.f}, {20.f, 30.f}, {10.f, 30.f}, {0.f, 20.f}, {0.f, 10.f}}; + size_polygon min_x{{0.f, 10.f}, {0.f, 20.f}}; + size_polygon min_y{{10.f, 0.f}, {20.f, 0.f}}; + + check_polygon_min(polygon, min_x, min_y); +}