Farewell to reactive ui library

This commit is contained in:
Nikita Lisitsa 2024-08-12 23:16:34 +03:00
parent 4fde43313c
commit dbd479d413
64 changed files with 0 additions and 3263 deletions

View file

@ -1,8 +0,0 @@
file(GLOB_RECURSE PSEMEK_UI_HEADERS RELATIVE "${CMAKE_CURRENT_SOURCE_DIR}" "include/*.hpp")
file(GLOB_RECURSE PSEMEK_UI_SOURCES RELATIVE "${CMAKE_CURRENT_SOURCE_DIR}" "source/*.cpp")
psemek_add_library(psemek-ui ${PSEMEK_UI_HEADERS} ${PSEMEK_UI_SOURCES})
target_include_directories(psemek-ui PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/include")
target_link_libraries(psemek-ui PUBLIC psemek-util psemek-react psemek-geom psemek-fonts psemek-async psemek-app)
psemek_glob_tests(psemek-ui tests)

View file

@ -1,18 +0,0 @@
#pragma once
#include <psemek/ui/alignment.hpp>
#include <psemek/react/value.hpp>
#include <any>
namespace psemek::ui
{
struct aligned
{
react::value<std::any> child = {};
react::value<halignment> halign = halignment::center;
react::value<valignment> valign = valignment::center;
};
}

View file

@ -1,62 +0,0 @@
#pragma once
#include <psemek/geom/interval.hpp>
#include <psemek/util/enum.hpp>
namespace psemek::ui
{
enum class halignment
{
left,
center,
right,
};
enum class valignment
{
top,
center,
bottom,
};
template <typename T>
concept any_alignment = std::is_same_v<T, halignment> || std::is_same_v<T, valignment>;
inline float lerp_factor(halignment h)
{
switch (h)
{
case halignment::left: return 0;
case halignment::center: return 0.5f;
case halignment::right: return 1.f;
}
throw util::unknown_enum_value_exception{h};
}
inline float lerp_factor(valignment h)
{
switch (h)
{
case valignment::top: return 0.f;
case valignment::center: return 0.5f;
case valignment::bottom: return 1.f;
}
throw util::unknown_enum_value_exception{h};
}
template <any_alignment Alignment>
inline float align(geom::interval<float> const & range, Alignment a)
{
return geom::lerp(range, lerp_factor(a));
}
template <any_alignment Alignment>
inline geom::interval<float> align(geom::interval<float> const & range, float size, Alignment a)
{
return geom::interval{range.min, range.min + size} + (range.length() - size) * lerp_factor(a);
}
}

View file

@ -1,48 +0,0 @@
#pragma once
#include <psemek/ui/key.hpp>
#include <psemek/react/value.hpp>
#include <any>
#include <variant>
namespace psemek::ui
{
namespace box_layout
{
struct minimized
{};
struct weight
{
float value = 1.f;
};
using size_policy = std::variant<minimized, weight>;
struct element
{
react::value<std::any> element;
react::value<size_policy> policy = {};
std::optional<ui::key> key = {};
};
struct horizontal
{
using element = box_layout::element;
react::value<std::vector<element>> children = {};
react::value<float> padding = 0.f;
};
struct vertical
{
using element = box_layout::element;
react::value<std::vector<element>> children = {};
react::value<float> padding = 0.f;
};
}
}

View file

@ -1,17 +0,0 @@
#pragma once
#include <psemek/react/value.hpp>
#include <psemek/react/source.hpp>
namespace psemek::ui
{
struct button
{
react::source<bool> mouseover = {};
react::source<bool> mousedown = {};
react::source<bool> on_mouseover = {};
react::source<bool> on_mousedown = {};
};
}

View file

@ -1,19 +0,0 @@
#pragma once
#include <psemek/react/value.hpp>
#include <any>
namespace psemek::ui
{
struct extend
{
react::value<std::any> child = {};
react::value<float> left = 0.f;
react::value<float> right = 0.f;
react::value<float> top = 0.f;
react::value<float> bottom = 0.f;
};
}

View file

@ -1,29 +0,0 @@
#pragma once
#include <psemek/react/value.hpp>
#include <psemek/geom/vector.hpp>
#include <any>
namespace psemek::ui
{
struct fixed_size
{
react::value<std::any> child = {};
react::value<geom::vector<float, 2>> size = {0.f, 0.f};
};
struct fixed_width
{
react::value<std::any> child = {};
react::value<float> width = 0.f;
};
struct fixed_height
{
react::value<std::any> child = {};
react::value<float> height = 0.f;
};
}

View file

@ -1,20 +0,0 @@
#pragma once
#include <psemek/ui/aligned.hpp>
#include <psemek/react/value.hpp>
#include <psemek/geom/point.hpp>
#include <any>
namespace psemek::ui
{
struct floating
{
react::value<std::any> child = {};
react::value<geom::point<float, 2>> anchor = {0.f, 0.f};
react::value<halignment> halign = halignment::center;
react::value<valignment> valign = valignment::center;
};
}

View file

@ -1,16 +0,0 @@
#pragma once
#include <psemek/react/value.hpp>
#include <any>
namespace psemek::ui
{
struct frame
{
react::value<std::any> child = {};
react::value<float> margin = 0.f;
};
}

View file

@ -1,29 +0,0 @@
#pragma once
#include <psemek/ui/aligned.hpp>
#include <psemek/ui/impl/single_container.hpp>
#include <psemek/react/source.hpp>
namespace psemek::ui::impl
{
struct aligned_base
: single_container
{
aligned_base();
void reshape(geom::box<float, 2> const & new_shape) override;
react::value<struct size_constraints> size_constraints() const override;
void set_child(std::unique_ptr<component> child) override;
std::unique_ptr<component> release_child() override;
void update(aligned const & value);
private:
react::source<react::value<std::pair<halignment, valignment>>> align_;
react::source<react::value<struct size_constraints>> child_size_constraints_;
react::value<struct size_constraints> size_constraints_;
};
}

View file

@ -1,57 +0,0 @@
#pragma once
#include <psemek/ui/impl/container.hpp>
#include <psemek/ui/box_layout.hpp>
#include <psemek/react/value.hpp>
#include <psemek/react/source.hpp>
namespace psemek::ui::impl
{
namespace detail
{
template <int Dimension>
struct box_layout_type;
template <>
struct box_layout_type<0>
{
using type = box_layout::horizontal;
};
template <>
struct box_layout_type<1>
{
using type = box_layout::vertical;
};
}
// Dimension == 0 is horizontal layout
// Dimension == 1 is vertical layout
template <int Dimension>
struct box_layout_base
: container
{
box_layout_base();
void reshape(geom::box<float, 2> const & new_shape) override;
react::value<struct size_constraints> size_constraints() const override;
void set_children(std::vector<std::unique_ptr<component>> children) override;
std::vector<std::unique_ptr<component>> release_children() override;
void update(typename detail::box_layout_type<Dimension>::type const & box_layout);
private:
react::source<react::value<float>> padding_;
react::source<react::value<std::vector<react::value<box_layout::size_policy>>>> size_policies_;
react::source<std::vector<react::value<struct size_constraints>>> children_size_constraints_;
react::value<struct size_constraints> size_constraints_;
};
extern template struct box_layout_base<0>;
extern template struct box_layout_base<1>;
}

View file

@ -1,29 +0,0 @@
#pragma once
#include <psemek/ui/button.hpp>
#include <psemek/ui/impl/component.hpp>
namespace psemek::ui::impl
{
struct button_base
: component
{
button_base();
bool on_event(mouse_move_event const & event) override;
bool on_event(mouse_button_event const & event) override;
void update(button const & value);
private:
react::source<bool> mouseover_;
react::source<bool> mousedown_;
react::source<bool> on_mouseover_;
react::source<bool> on_mousedown_;
bool is_mouseover_ = false;
bool is_mousedown_ = false;
};
}

View file

@ -1,46 +0,0 @@
#pragma once
#include <psemek/ui/impl/size_constraints.hpp>
#include <psemek/ui/impl/events.hpp>
#include <psemek/ui/impl/renderer.hpp>
#include <psemek/react/value.hpp>
#include <psemek/geom/box.hpp>
#include <psemek/geom/interval.hpp>
#include <psemek/util/span.hpp>
#include <memory>
#include <string>
#include <optional>
namespace psemek::ui::impl
{
struct component
{
virtual util::span<std::unique_ptr<component> const> children() const;
virtual geom::box<float, 2> const & shape() const;
virtual void reshape(geom::box<float, 2> const & new_shape);
virtual react::value<struct size_constraints> size_constraints() const;
virtual bool on_event(resize_event const &) { return false; }
virtual bool on_event(mouse_move_event const &) { return false; }
virtual bool on_event(mouse_wheel_event const &) { return false; }
virtual bool on_event(mouse_button_event const &) { return false; }
virtual bool on_event(key_event const &) { return false; }
virtual void animate(float) {}
virtual void draw(renderer &) {}
virtual void post_draw(renderer &) {}
virtual std::optional<std::string> cursor() const { return std::nullopt; }
virtual ~component() {}
private:
geom::box<float, 2> shape_;
};
}

View file

@ -1,22 +0,0 @@
#pragma once
#include <psemek/ui/impl/component.hpp>
#include <psemek/ui/impl/event_state.hpp>
#include <psemek/async/executor.hpp>
#include <memory>
#include <any>
#include <functional>
namespace psemek::ui::impl
{
struct component_factory
{
virtual std::unique_ptr<component> reconciliate(std::unique_ptr<component> root, std::any const & value,
event_state const & state, async::executor & delayed_executor, std::function<void()> request_reshape) = 0;
virtual ~component_factory() {}
};
}

View file

@ -1,194 +0,0 @@
#pragma once
#include <psemek/ui/impl/component_factory.hpp>
#include <psemek/ui/impl/single_container.hpp>
#include <psemek/ui/impl/container.hpp>
#include <psemek/react/value.hpp>
#include <psemek/util/to_string.hpp>
#include <psemek/util/type_name.hpp>
#include <psemek/util/function.hpp>
#include <psemek/util/hash_table.hpp>
#include <psemek/util/exception.hpp>
#include <typeindex>
namespace psemek::ui::impl
{
struct component_not_supported_exception
: util::exception
{
component_not_supported_exception(std::type_index type, boost::stacktrace::stacktrace stacktrace = {})
: util::exception(util::to_string("UI component ", util::type_name(type), " is not supported"), std::move(stacktrace))
, type_(type)
{}
std::type_index type() const
{
return type_;
}
private:
std::type_index type_;
};
struct component_factory_base
: component_factory
{
std::unique_ptr<component> reconciliate(std::unique_ptr<component> root, std::any const & value, event_state const & state, async::executor & delayed_executor, std::function<void()> request_reshape) override
{
std::type_index type(value.type());
if (auto it = type_factories_.find(type); it != type_factories_.end())
return it->second(std::move(root), value, state, delayed_executor, request_reshape);
throw component_not_supported_exception(type);
}
template <typename Type, typename ImplType, typename Factory>
void register_type(Factory && factory)
{
type_factories_[typeid(Type)] = [this, factory = std::move(factory)](std::unique_ptr<component> root, std::any const & value,
event_state const & state, async::executor & delayed_executor, std::function<void()> request_reshape)
{
std::unique_ptr<ImplType> impl;
if (root)
{
if (auto impl_raw = dynamic_cast<ImplType *>(root.get()))
{
impl.reset(impl_raw);
root.release();
}
}
root.reset();
// prevent 'lambda capture is unused' warning
(void)this;
if (!impl)
{
if constexpr (std::is_invocable_v<Factory, event_state const &>)
impl = factory(state);
else
impl = factory();
impl->size_constraints().subscribe([request_reshape](auto const &){ request_reshape(); }, react::forever);
request_reshape();
}
auto const & typed_value = *std::any_cast<Type>(&value);
impl->update(typed_value);
if constexpr (std::is_base_of_v<single_container, ImplType>)
{
impl->release_child_token();
if (typed_value.child)
{
impl->set_child_token(typed_value.child.subscribe([this, impl = impl.get(), &state, &delayed_executor, request_reshape, holder = typed_value.child](std::any const & child){
delayed_executor.post([this, impl, &state, &delayed_executor, request_reshape, child]{
if (child.has_value())
impl->set_child(reconciliate(impl->release_child(), child, state, delayed_executor, request_reshape));
else
impl->release_child();
request_reshape();
});
}, true));
}
else
{
impl->release_child();
}
}
else if constexpr (std::is_base_of_v<container, ImplType>)
{
if (typed_value.children)
{
impl->set_children_token(typed_value.children.subscribe([this, impl = impl.get(), &state, &delayed_executor, request_reshape, holder = typed_value.children](std::vector<typename Type::element> const & children_values){
delayed_executor.post([this, impl, &state, &delayed_executor, request_reshape, children_values]{
impl->release_child_tokens();
util::hash_map<key, std::unique_ptr<component>> child_by_key;
auto old_children = impl->release_children();
{
auto keys = impl->release_child_keys();
for (std::size_t i = 0; i < old_children.size(); ++i)
if (keys[i])
child_by_key[std::move(*keys[i])] = std::move(old_children[i]);
}
std::vector<std::unique_ptr<component>> children(children_values.size());
std::vector<std::optional<key>> child_keys(children_values.size());
for (std::size_t i = 0; i < children_values.size(); ++i)
{
if (auto const & key = children_values[i].key)
{
if (auto it = child_by_key.find(*key); it != child_by_key.end())
children[i] = std::move(it->second);
child_keys[i] = *key;
}
if (!children[i] && i < old_children.size() && old_children[i] && children_values[i].element)
children[i] = std::move(old_children[i]);
}
impl->set_children(std::move(children));
impl->set_child_keys(std::move(child_keys));
std::vector<util::signal<std::any>::subscription_token> child_tokens(children_values.size());
for (std::size_t i = 0; i < children_values.size(); ++i)
{
if (children_values[i].element)
{
child_tokens[i] = children_values[i].element.subscribe([this, impl, &state, &delayed_executor, request_reshape, i, holder = children_values[i].element](std::any const & value){
delayed_executor.post([this, impl, &state, &delayed_executor, request_reshape, i, value]{
auto children = impl->release_children();
if (value.has_value())
children[i] = reconciliate(std::move(children[i]), value, state, delayed_executor, request_reshape);
else
children[i] = nullptr;
impl->set_children(std::move(children));
request_reshape();
});
}, true);
}
}
impl->set_child_tokens(std::move(child_tokens));
request_reshape();
});
}, true));
}
else
{
impl->release_child_tokens();
impl->release_child_keys();
impl->release_children();
}
}
return impl;
};
}
template <typename Type, typename ImplType>
void register_type()
{
register_type<Type, ImplType>([](event_state const & state){
if constexpr (std::is_constructible_v<ImplType, event_state const &>)
return std::make_unique<ImplType>(state);
else
return std::make_unique<ImplType>();
});
}
private:
util::hash_map<std::type_index, util::function<std::unique_ptr<component>(std::unique_ptr<component>, std::any const &, event_state const &, async::executor &, std::function<void()>)>> type_factories_;
};
}

View file

@ -1,36 +0,0 @@
#pragma once
#include <psemek/ui/impl/component.hpp>
#include <psemek/ui/key.hpp>
#include <psemek/util/signal.hpp>
#include <any>
#include <optional>
#include <vector>
namespace psemek::ui::impl
{
struct container
: component
{
virtual void set_children(std::vector<std::unique_ptr<component>> children);
virtual void set_children_token(util::signal<std::vector<std::any>>::subscription_token token);
virtual void set_child_tokens(std::vector<util::signal<std::any>::subscription_token> tokens);
virtual void set_child_keys(std::vector<std::optional<key>> keys);
virtual std::vector<std::unique_ptr<component>> release_children();
virtual util::signal<>::subscription_token release_children_token();
virtual std::vector<util::signal<>::subscription_token> release_child_tokens();
virtual std::vector<std::optional<key>> release_child_keys();
util::span<std::unique_ptr<component> const> children() const override;
private:
util::signal<>::subscription_token children_token_;
std::vector<std::unique_ptr<component>> children_;
std::vector<util::signal<>::subscription_token> child_tokens_;
std::vector<std::optional<key>> child_keys_;
};
}

View file

@ -1,40 +0,0 @@
#pragma once
#include <psemek/ui/impl/component_factory.hpp>
#include <psemek/ui/impl/renderer.hpp>
#include <psemek/ui/impl/event_state.hpp>
#include <psemek/react/value.hpp>
#include <psemek/util/pimpl.hpp>
#include <any>
namespace psemek::ui::impl
{
struct controller
{
controller(component_factory & factory);
~controller();
void set_ui(react::value<std::any> ui);
component * root();
bool on_event(resize_event const & event);
bool on_event(mouse_move_event const & event);
bool on_event(mouse_wheel_event const & event);
bool on_event(mouse_button_event const & event);
bool on_event(key_event const & event);
event_state const & state();
void update(float dt);
void draw(renderer & renderer);
std::optional<std::string> cursor();
private:
psemek_declare_pimpl
};
}

View file

@ -1,14 +0,0 @@
#pragma once
#include <psemek/ui/impl/component_factory_base.hpp>
namespace psemek::ui::impl
{
struct default_component_factory
: component_factory_base
{
default_component_factory();
};
}

View file

@ -1,12 +0,0 @@
#pragma once
#include <psemek/app/event_state.hpp>
namespace psemek::ui::impl
{
using event_state = app::event_state;
using app::apply;
}

View file

@ -1,14 +0,0 @@
#pragma once
#include <psemek/app/events.hpp>
namespace psemek::ui::impl
{
using app::resize_event;
using app::mouse_move_event;
using app::mouse_button_event;
using app::mouse_wheel_event;
using app::key_event;
}

View file

@ -1,32 +0,0 @@
#pragma once
#include <psemek/ui/extend.hpp>
#include <psemek/ui/impl/single_container.hpp>
#include <psemek/react/source.hpp>
namespace psemek::ui::impl
{
struct extend_base
: single_container
{
extend_base();
void reshape(geom::box<float, 2> const & new_shape) override;
react::value<struct size_constraints> size_constraints() const override;
void set_child(std::unique_ptr<component> child) override;
std::unique_ptr<component> release_child() override;
void update(extend const & value);
private:
react::source<react::value<float>> left_;
react::source<react::value<float>> right_;
react::source<react::value<float>> top_;
react::source<react::value<float>> bottom_;
react::source<react::value<struct size_constraints>> child_size_constraints_;
react::value<struct size_constraints> size_constraints_;
};
}

View file

@ -1,28 +0,0 @@
#pragma once
#include <psemek/ui/fixed_size.hpp>
#include <psemek/ui/impl/single_container.hpp>
#include <psemek/react/source.hpp>
namespace psemek::ui::impl
{
struct fixed_size_base
: single_container
{
fixed_size_base();
void reshape(geom::box<float, 2> const & new_shape) override;
react::value<struct size_constraints> size_constraints() const override;
void update(fixed_size const & value);
void update(fixed_width const & value);
void update(fixed_height const & value);
private:
react::source<react::value<geom::vector<std::optional<float>, 2>>> size_;
react::source<react::value<struct size_constraints>> child_size_constraints_;
react::value<struct size_constraints> size_constraints_;
};
}

View file

@ -1,28 +0,0 @@
#pragma once
#include <psemek/ui/floating.hpp>
#include <psemek/ui/impl/single_container.hpp>
#include <psemek/react/source.hpp>
namespace psemek::ui::impl
{
struct floating_base
: single_container
{
floating_base();
void reshape(geom::box<float, 2> const & new_shape) override;
react::value<struct size_constraints> size_constraints() const override;
void update(floating const & value);
private:
react::value<geom::point<float, 2>> anchor_ = geom::point{0.f, 0.f};
react::value<halignment> halign_ = halignment::center;
react::value<valignment> valign_ = valignment::center;
react::value<struct size_constraints> size_constraints_;
};
}

View file

@ -1,29 +0,0 @@
#pragma once
#include <psemek/ui/frame.hpp>
#include <psemek/ui/impl/single_container.hpp>
#include <psemek/react/source.hpp>
namespace psemek::ui::impl
{
struct frame_base
: single_container
{
frame_base();
void reshape(geom::box<float, 2> const & new_shape) override;
react::value<struct size_constraints> size_constraints() const override;
void set_child(std::unique_ptr<component> child) override;
std::unique_ptr<component> release_child() override;
void update(frame const & value);
private:
react::source<react::value<float>> margin_;
react::source<react::value<struct size_constraints>> child_size_constraints_;
react::value<struct size_constraints> size_constraints_;
};
}

View file

@ -1,31 +0,0 @@
#pragma once
#include <psemek/ui/label.hpp>
#include <psemek/ui/impl/component.hpp>
#include <psemek/react/source.hpp>
namespace psemek::ui::impl
{
struct label_base
: component
{
label_base();
react::value<struct size_constraints> size_constraints() const override;
void draw(renderer & renderer) override;
void update(label const & value);
private:
react::source<react::value<fonts::font *>> font_;
react::source<react::value<std::vector<fonts::shaped_glyph>>> glyphs_;
react::value<gfx::color_rgba> color_ = gfx::color_rgba{0, 0, 0, 255};
react::value<halignment> halign_ = halignment::center;
react::value<valignment> valign_ = valignment::center;
react::value<geom::vector<float, 2>> size_;
react::value<struct size_constraints> size_constraints_;
};
}

View file

@ -1,33 +0,0 @@
#pragma once
#include <psemek/ui/move.hpp>
#include <psemek/ui/impl/single_container.hpp>
#include <psemek/react/source.hpp>
namespace psemek::ui::impl
{
struct move_base
: single_container
{
move_base();
void reshape(geom::box<float, 2> const & new_shape) override;
react::value<struct size_constraints> size_constraints() const override;
void set_child(std::unique_ptr<component> child) override;
std::unique_ptr<component> release_child() override;
void animate(float dt) override;
void update(move const & value);
private:
react::value<geom::vector<float, 2>> offset_;
react::value<float> animation_speed_;
react::source<react::value<struct size_constraints>> child_size_constraints_;
react::value<struct size_constraints> size_constraints_;
geom::vector<float, 2> current_offset_;
};
}

View file

@ -1,17 +0,0 @@
#pragma once
#include <psemek/fonts/font_v2.hpp>
#include <psemek/gfx/color.hpp>
#include <psemek/geom/box.hpp>
namespace psemek::ui::impl
{
struct renderer
{
virtual void draw_glyph(fonts::texture_type const & texture, geom::box<float, 2> const & position, geom::box<float, 2> const & texcoord, gfx::color_rgba const & color) = 0;
virtual ~renderer() {}
};
}

View file

@ -1,23 +0,0 @@
#pragma once
#include <psemek/ui/shape_reader.hpp>
#include <psemek/ui/impl/single_container.hpp>
namespace psemek::ui::impl
{
struct shape_reader_base
: single_container
{
shape_reader_base();
void reshape(geom::box<float, 2> const & new_shape) override;
react::value<struct size_constraints> size_constraints() const override;
void update(shape_reader const & value);
private:
react::source<geom::box<float, 2>> shape_ = {};
};
}

View file

@ -1,26 +0,0 @@
#pragma once
#include <psemek/ui/impl/component.hpp>
#include <psemek/ui/key.hpp>
#include <psemek/util/signal.hpp>
namespace psemek::ui::impl
{
struct single_container
: component
{
util::span<std::unique_ptr<component> const> children() const override;
virtual void set_child(std::unique_ptr<component> child);
virtual void set_child_token(util::signal<>::subscription_token token);
virtual component * child() const;
virtual std::unique_ptr<component> release_child();
virtual util::signal<>::subscription_token release_child_token();
private:
util::signal<>::subscription_token child_token_;
std::unique_ptr<component> child_;
};
}

View file

@ -1,27 +0,0 @@
#pragma once
#include <psemek/ui/impl/single_container.hpp>
#include <psemek/react/value.hpp>
#include <psemek/react/source.hpp>
namespace psemek::ui::impl
{
struct single_container_base
: single_container
{
single_container_base(react::value<geom::vector<float, 2>> margin);
void reshape(geom::box<float, 2> const & new_shape) override;
react::value<struct size_constraints> size_constraints() const override;
void set_child(std::unique_ptr<component> child) override;
std::unique_ptr<component> release_child() override;
private:
react::value<geom::vector<float, 2>> margin_;
react::source<react::value<struct size_constraints>> child_size_constraints_;
react::value<struct size_constraints> size_constraints_;
};
}

View file

@ -1,32 +0,0 @@
#pragma once
#include <psemek/geom/box.hpp>
#include <limits>
namespace psemek::ui::impl
{
struct size_constraints
{
geom::box<float, 2> box;
static constexpr float infinity = std::numeric_limits<float>::infinity();
static size_constraints max();
};
size_constraints shift(size_constraints const & constraints, geom::vector<float, 2> const & delta);
size_constraints scale(size_constraints const & constraints, float factor, int dimension);
float min(size_constraints const & constraints, int dimension);
size_constraints intersect(size_constraints const & constraints1, size_constraints const & constraints2);
geom::interval<float> range(size_constraints const & constraints, int dimension);
size_constraints cut(size_constraints const & constraints, int dimension, geom::interval<float> const & range);
size_constraints sum_along(size_constraints const & constraints1, size_constraints const & constraints2, int dimension);
}

View file

@ -1,30 +0,0 @@
#pragma once
#include <psemek/ui/impl/container.hpp>
#include <psemek/ui/stack_layout.hpp>
#include <psemek/react/value.hpp>
#include <psemek/react/source.hpp>
namespace psemek::ui::impl
{
struct stack_layout_base
: container
{
stack_layout_base();
void reshape(geom::box<float, 2> const & new_shape) override;
react::value<struct size_constraints> size_constraints() const override;
void set_children(std::vector<std::unique_ptr<component>> children) override;
std::vector<std::unique_ptr<component>> release_children() override;
void update(stack_layout const &)
{}
private:
react::source<std::vector<react::value<struct size_constraints>>> children_size_constraints_;
react::value<struct size_constraints> size_constraints_;
};
}

View file

@ -1,29 +0,0 @@
#pragma once
#include <psemek/ui/storage.hpp>
#include <psemek/ui/impl/single_container.hpp>
#include <psemek/react/source.hpp>
namespace psemek::ui::impl
{
struct storage_base
: single_container
{
storage_base();
void reshape(geom::box<float, 2> const & new_shape) override;
react::value<struct size_constraints> size_constraints() const override;
void set_child(std::unique_ptr<component> child) override;
std::unique_ptr<component> release_child() override;
void update(storage const & value);
private:
util::hash_map<std::string, react::source<std::any>> values_;
react::source<react::value<struct size_constraints>> child_size_constraints_;
react::value<struct size_constraints> size_constraints_;
};
}

View file

@ -1,10 +0,0 @@
#pragma once
#include <string>
namespace psemek::ui
{
using key = std::string;
}

View file

@ -1,22 +0,0 @@
#pragma once
#include <psemek/ui/alignment.hpp>
#include <psemek/fonts/font_v2.hpp>
#include <psemek/gfx/color.hpp>
#include <psemek/react/value.hpp>
#include <string>
namespace psemek::ui
{
struct label
{
react::value<std::string> text = {};
react::value<fonts::font *> font = {};
react::value<gfx::color_rgba> color = gfx::color_rgba{0, 0, 0, 255};
react::value<halignment> halign = halignment::center;
react::value<valignment> valign = valignment::center;
};
}

View file

@ -1,18 +0,0 @@
#pragma once
#include <psemek/react/value.hpp>
#include <psemek/geom/vector.hpp>
#include <any>
namespace psemek::ui
{
struct move
{
react::value<std::any> child = {};
react::value<geom::vector<float, 2>> offset = geom::vector{0.f, 0.f};
react::value<float> animation_speed = 0.f;
};
}

View file

@ -1,14 +0,0 @@
#pragma once
#include <psemek/react/value.hpp>
#include <psemek/gfx/color.hpp>
namespace psemek::ui
{
struct rectangle
{
react::value<gfx::color_rgba> color = {};
};
}

View file

@ -1,17 +0,0 @@
#pragma once
#include <psemek/react/source.hpp>
#include <psemek/geom/box.hpp>
#include <any>
namespace psemek::ui
{
struct shape_reader
{
react::value<std::any> child = {};
react::source<geom::box<float, 2>> shape = {};
};
}

View file

@ -1,16 +0,0 @@
#pragma once
#include <psemek/react/value.hpp>
#include <psemek/react/source.hpp>
#include <psemek/geom/interval.hpp>
namespace psemek::ui
{
struct slider
{
react::value<geom::interval<int>> range = {};
react::source<int> value = {};
};
}

View file

@ -1,22 +0,0 @@
#pragma once
#include <psemek/ui/key.hpp>
#include <psemek/react/value.hpp>
#include <any>
namespace psemek::ui
{
struct stack_layout
{
struct element
{
react::value<std::any> element;
std::optional<ui::key> key = {};
};
react::value<std::vector<element>> children = {};
};
}

View file

@ -1,19 +0,0 @@
#pragma once
#include <psemek/react/source.hpp>
#include <psemek/geom/box.hpp>
#include <psemek/util/hash_table.hpp>
#include <any>
namespace psemek::ui
{
struct storage
{
react::value<std::any> child = {};
util::hash_map<std::string, react::source<std::any>> values = {};
};
}

View file

@ -1,71 +0,0 @@
#include <psemek/ui/impl/aligned_base.hpp>
#include <psemek/react/join.hpp>
#include <psemek/react/map.hpp>
namespace psemek::ui::impl
{
namespace
{
size_constraints compute_size_constraints(size_constraints const & child_size_constraints)
{
return {
.box = {{
{child_size_constraints.box[0].min, size_constraints::infinity},
{child_size_constraints.box[1].min, size_constraints::infinity},
}}
};
}
}
aligned_base::aligned_base()
: align_({halignment::center, valignment::center})
, child_size_constraints_(size_constraints::max())
, size_constraints_(react::map(compute_size_constraints, react::join(child_size_constraints_)))
{}
void aligned_base::reshape(geom::box<float, 2> const & new_shape)
{
single_container::reshape(new_shape);
auto [ halign, valign ] = **align_;
auto child_size_constraints = **child_size_constraints_;
if (auto child = this->child())
child->reshape({
align(new_shape[0], child_size_constraints.box[0].min, halign),
align(new_shape[1], child_size_constraints.box[1].min, valign),
});
}
react::value<struct size_constraints> aligned_base::size_constraints() const
{
return size_constraints_;
}
void aligned_base::set_child(std::unique_ptr<component> child)
{
if (child)
child_size_constraints_.set(child->size_constraints());
else
child_size_constraints_.set(size_constraints::max());
single_container::set_child(std::move(child));
}
std::unique_ptr<component> aligned_base::release_child()
{
child_size_constraints_.set(size_constraints::max());
return single_container::release_child();
}
void aligned_base::update(aligned const & value)
{
align_.set(react::map([](auto const & halign, auto const & valign){ return std::pair{halign, valign}; }, value.halign, value.valign));
}
}

View file

@ -1,188 +0,0 @@
#include <psemek/ui/impl/box_layout_base.hpp>
#include <psemek/react/map.hpp>
#include <psemek/react/join.hpp>
namespace psemek::ui::impl
{
namespace
{
static constexpr box_layout::size_policy default_policy = box_layout::minimized{};
template <int Dimension>
size_constraints compute_size_constraints(std::vector<box_layout::size_policy> const & size_policies,
std::vector<size_constraints> const & children_size_constraints, float padding)
{
size_constraints minimized = size_constraints::max();
size_constraints weight_unit = minimized;
float weight_sum = 0.f;
for (std::size_t i = 0; i < std::min(size_policies.size(), children_size_constraints.size()); ++i)
{
if (std::get_if<box_layout::minimized>(&size_policies[i]))
{
minimized = sum_along(minimized, children_size_constraints[i], Dimension);
}
else if (auto weight = std::get_if<box_layout::weight>(&size_policies[i]))
{
weight_unit = intersect(weight_unit, scale(children_size_constraints[i], 1.f / weight->value, Dimension));
weight_sum += weight->value;
}
}
auto result = std::move(minimized);
if (weight_sum > 0.f)
result = sum_along(result, scale(weight_unit, weight_sum, Dimension), Dimension);
if (size_policies.size() > 0)
{
geom::vector shift_delta{0.f, 0.f};
shift_delta[Dimension] = padding * (size_policies.size() - 1);
result = shift(std::move(result), shift_delta);
}
return result;
}
template <int Dimension>
std::vector<float> allocate(float total_size, std::vector<react::value<box_layout::size_policy>> const & size_policies,
util::span<std::unique_ptr<component> const> children, float padding)
{
std::vector<float> result(std::min(size_policies.size(), children.size()), 0.f);
if (!size_policies.empty())
total_size -= padding * (result.size() - 1);
float total_weight = 0.f;
// First, allocate minimized elements
// Also compute the total weight for weighted elements
for (std::size_t i = 0; i < result.size(); ++i)
{
if (!children[i])
continue;
auto const policy = size_policies[i] ? *size_policies[i] : default_policy;
if (std::get_if<box_layout::minimized>(&policy))
{
result[i] = min(*children[i]->size_constraints(), Dimension);
total_size -= result[i];
}
else if (auto weight = std::get_if<box_layout::weight>(&policy))
{
total_weight += weight->value;
}
}
// Next, allocate weighted elements
for (std::size_t i = 0; i < result.size(); ++i)
{
auto const policy = size_policies[i] ? *size_policies[i] : default_policy;
if (auto weight = std::get_if<box_layout::weight>(&policy))
{
result[i] = total_size * weight->value / total_weight;
}
}
return result;
}
}
template <int Dimension>
box_layout_base<Dimension>::box_layout_base()
: padding_(0.f)
, size_policies_(std::vector<react::value<box_layout::size_policy>>{})
, children_size_constraints_()
, size_constraints_(
react::map(
[](auto const & size_policies, auto const & children_size_constraints, auto const & padding){
return compute_size_constraints<Dimension>(size_policies, children_size_constraints, padding);
},
react::join(react::map(react::unpack_with_default(default_policy), react::join(size_policies_))),
react::join(react::map(react::unpack, children_size_constraints_)),
react::join(padding_)
)
)
{}
template <int Dimension>
void box_layout_base<Dimension>::reshape(geom::box<float, 2> const & new_shape)
{
container::reshape(new_shape);
if (children().empty())
return;
auto padding = **padding_;
auto sizes = allocate<Dimension>(new_shape[Dimension].length(), **size_policies_, children(), padding);
float pen = new_shape[Dimension].min;
for (std::size_t i = 0; i < children().size(); ++i)
{
if (children()[i])
{
geom::box<float, 2> child_shape;
child_shape[Dimension] = {pen, pen + sizes[i]};
child_shape[Dimension ^ 1] = new_shape[Dimension ^ 1];
children()[i]->reshape(child_shape);
}
pen += sizes[i];
pen += padding;
}
}
template <int Dimension>
react::value<size_constraints> box_layout_base<Dimension>::size_constraints() const
{
return size_constraints_;
}
template <int Dimension>
void box_layout_base<Dimension>::set_children(std::vector<std::unique_ptr<component>> children)
{
container::set_children(std::move(children));
std::vector<react::value<struct size_constraints>> children_size_constraints;
for (auto const & child : this->children())
{
if (child)
children_size_constraints.push_back(child->size_constraints());
else
children_size_constraints.push_back(size_constraints::max());
}
children_size_constraints_.set(std::move(children_size_constraints));
}
template <int Dimension>
std::vector<std::unique_ptr<component>> box_layout_base<Dimension>::release_children()
{
auto result = container::release_children();
children_size_constraints_.set({});
return result;
}
template <int Dimension>
void box_layout_base<Dimension>::update(typename detail::box_layout_type<Dimension>::type const & box_layout)
{
size_policies_.set(react::map([](auto const & children){
std::vector<react::value<box_layout::size_policy>> size_policies;
size_policies.reserve(children.size());
for (auto const & child : children)
size_policies.push_back(child.policy);
return size_policies;
}, box_layout.children));
padding_.set(box_layout.padding);
}
template struct box_layout_base<0>;
template struct box_layout_base<1>;
}

View file

@ -1,55 +0,0 @@
#include <psemek/ui/impl/button_base.hpp>
#include <psemek/geom/contains.hpp>
namespace psemek::ui::impl
{
button_base::button_base()
{}
bool button_base::on_event(mouse_move_event const & event)
{
bool const mouseover = geom::contains(shape(), geom::cast<float>(event.position));
if (is_mouseover_ != mouseover)
{
is_mouseover_ = mouseover;
mouseover_.set(is_mouseover_);
on_mouseover_.set(is_mouseover_);
}
return false;
}
bool button_base::on_event(mouse_button_event const & event)
{
bool const mousedown = event.button == app::mouse_button::left && event.down;
if (is_mouseover_ || !mousedown)
{
if (is_mousedown_ != mousedown)
{
is_mousedown_ = mousedown;
mousedown_.set(is_mousedown_);
on_mousedown_.set(is_mousedown_);
}
return is_mouseover_ && is_mousedown_;
}
return false;
}
void button_base::update(button const & value)
{
mouseover_ = value.mouseover;
mousedown_ = value.mousedown;
on_mouseover_ = value.on_mouseover;
on_mousedown_ = value.on_mousedown;
mouseover_.set(is_mouseover_);
mousedown_.set(is_mousedown_);
}
}

View file

@ -1,27 +0,0 @@
#include <psemek/ui/impl/component.hpp>
namespace psemek::ui::impl
{
util::span<std::unique_ptr<component> const> component::children() const
{
return {};
}
geom::box<float, 2> const & component::shape() const
{
return shape_;
}
void component::reshape(geom::box<float, 2> const & new_shape)
{
shape_ = new_shape;
}
react::value<size_constraints> component::size_constraints() const
{
static react::value<struct size_constraints> const result{size_constraints::max()};
return result;
}
}

View file

@ -1,52 +0,0 @@
#include <psemek/ui/impl/container.hpp>
#include <psemek/util/range.hpp>
namespace psemek::ui::impl
{
void container::set_children(std::vector<std::unique_ptr<component>> children)
{
children_ = std::move(children);
}
void container::set_children_token(util::signal<std::vector<std::any>>::subscription_token token)
{
children_token_ = std::move(token);
}
void container::set_child_tokens(std::vector<util::signal<std::any>::subscription_token> tokens)
{
child_tokens_ = std::move(tokens);
}
void container::set_child_keys(std::vector<std::optional<key>> keys)
{
child_keys_ = std::move(keys);
}
std::vector<std::unique_ptr<component>> container::release_children()
{
return std::move(children_);
}
util::signal<>::subscription_token container::release_children_token()
{
return std::move(children_token_);
}
std::vector<util::signal<>::subscription_token> container::release_child_tokens()
{
return std::move(child_tokens_);
}
std::vector<std::optional<key>> container::release_child_keys()
{
return std::move(child_keys_);
}
util::span<std::unique_ptr<component> const> container::children() const
{
return children_;
}
}

View file

@ -1,180 +0,0 @@
#include <psemek/ui/impl/controller.hpp>
#include <psemek/ui/impl/event_state.hpp>
#include <psemek/async/event_loop.hpp>
#include <psemek/util/range.hpp>
namespace psemek::ui::impl
{
struct controller::impl
{
component_factory & factory;
event_state state;
util::signal<>::subscription_token root_token;
std::unique_ptr<ui::impl::component> root;
async::event_loop delayed_queue;
impl(component_factory & factory)
: factory(factory)
{}
void set_ui(react::value<std::any> ui)
{
root_token = ui.subscribe([this](std::any const & value)
{
root = factory.reconciliate(std::move(root), value, state, delayed_queue, [this]{ delayed_queue.post([this]{ reshape(); }); });
}, true);
}
void reshape()
{
if (!root)
return;
root->reshape({{{0.f, state.size[0]}, {0.f, state.size[1]}}});
on_event_impl(mouse_move_event{state.mouse, {0, 0}}, root.get());
}
template <typename Event>
bool on_event(Event const & event)
{
apply(state, event);
return on_event_impl(event, root.get());
}
bool on_event(resize_event const & event)
{
apply(state, event);
delayed_queue.post([this]{ reshape(); });
return on_event_impl(event, root.get());
}
template <typename Event>
bool on_event_impl(Event const & event, component * element)
{
if (!element)
return false;
for (auto const & child : util::reversed(element->children()))
if (on_event_impl(event, child.get()))
return true;
return element->on_event(event);
}
void update(float dt)
{
delayed_queue.pump();
animate_impl(dt, root.get());
}
void animate_impl(float dt, component * element)
{
if (!element)
return;
element->animate(dt);
for (auto const & child : element->children())
animate_impl(dt, child.get());
}
void draw(renderer & renderer)
{
draw_impl(renderer, root.get());
}
void draw_impl(renderer & renderer, component * element)
{
if (!element)
return;
element->draw(renderer);
for (auto const & child : element->children())
draw_impl(renderer, child.get());
element->post_draw(renderer);
}
std::optional<std::string> cursor()
{
return cursor_impl(root.get());
}
std::optional<std::string> cursor_impl(component * element)
{
if (!element)
return std::nullopt;
for (auto const & child : util::reversed(element->children()))
if (auto cursor = cursor_impl(child.get()))
return cursor;
return element->cursor();
}
};
controller::controller(component_factory & factory)
: pimpl_(make_impl(factory))
{}
controller::~controller() = default;
void controller::set_ui(react::value<std::any> ui)
{
impl().set_ui(std::move(ui));
}
component * controller::root()
{
return impl().root.get();
}
bool controller::on_event(resize_event const & event)
{
return impl().on_event(event);
}
bool controller::on_event(mouse_move_event const & event)
{
return impl().on_event(event);
}
bool controller::on_event(mouse_wheel_event const & event)
{
return impl().on_event(event);
}
bool controller::on_event(mouse_button_event const & event)
{
return impl().on_event(event);
}
bool controller::on_event(key_event const & event)
{
return impl().on_event(event);
}
event_state const & controller::state()
{
return impl().state;
}
void controller::update(float dt)
{
impl().update(dt);
}
void controller::draw(renderer & renderer)
{
impl().draw(renderer);
}
std::optional<std::string> controller::cursor()
{
return impl().cursor();
}
}

View file

@ -1,41 +0,0 @@
#include <psemek/ui/impl/default_component_factory.hpp>
#include <psemek/ui/stack_layout.hpp>
#include <psemek/ui/box_layout.hpp>
#include <psemek/ui/aligned.hpp>
#include <psemek/ui/fixed_size.hpp>
#include <psemek/ui/button.hpp>
#include <psemek/ui/impl/stack_layout_base.hpp>
#include <psemek/ui/impl/box_layout_base.hpp>
#include <psemek/ui/impl/aligned_base.hpp>
#include <psemek/ui/impl/fixed_size_base.hpp>
#include <psemek/ui/impl/frame_base.hpp>
#include <psemek/ui/impl/extend_base.hpp>
#include <psemek/ui/impl/move_base.hpp>
#include <psemek/ui/impl/button_base.hpp>
#include <psemek/ui/impl/label_base.hpp>
#include <psemek/ui/impl/floating_base.hpp>
#include <psemek/ui/impl/shape_reader_base.hpp>
#include <psemek/ui/impl/storage_base.hpp>
namespace psemek::ui::impl
{
default_component_factory::default_component_factory()
{
register_type<stack_layout, impl::stack_layout_base>();
register_type<box_layout::horizontal, impl::box_layout_base<0>>();
register_type<box_layout::vertical, impl::box_layout_base<1>>();
register_type<aligned, impl::aligned_base>();
register_type<fixed_size, impl::fixed_size_base>();
register_type<fixed_width, impl::fixed_size_base>();
register_type<fixed_height, impl::fixed_size_base>();
register_type<frame, impl::frame_base>();
register_type<extend, impl::extend_base>();
register_type<move, impl::move_base>();
register_type<button, impl::button_base>();
register_type<label, impl::label_base>();
register_type<floating, impl::floating_base>();
register_type<storage, impl::storage_base>();
}
}

View file

@ -1,67 +0,0 @@
#include <psemek/ui/impl/extend_base.hpp>
#include <psemek/react/join.hpp>
#include <psemek/react/map.hpp>
namespace psemek::ui::impl
{
namespace
{
geom::box<float, 2> compute_child_shape(geom::box<float, 2> shape, float left, float right, float top, float bottom)
{
shape[0].min -= left;
shape[0].max += right;
shape[1].min -= top;
shape[1].max += bottom;
return shape;
}
}
extend_base::extend_base()
: left_(0.f)
, right_(0.f)
, top_(0.f)
, bottom_(0.f)
, child_size_constraints_(size_constraints::max())
, size_constraints_(react::join(child_size_constraints_))
{}
void extend_base::reshape(geom::box<float, 2> const & new_shape)
{
single_container::reshape(new_shape);
if (auto child = this->child())
child->reshape(compute_child_shape(new_shape, **left_, **right_, **top_, **bottom_));
}
react::value<struct size_constraints> extend_base::size_constraints() const
{
return size_constraints_;
}
void extend_base::set_child(std::unique_ptr<component> child)
{
if (child)
child_size_constraints_.set(child->size_constraints());
else
child_size_constraints_.set(size_constraints::max());
single_container::set_child(std::move(child));
}
std::unique_ptr<component> extend_base::release_child()
{
child_size_constraints_.set(size_constraints::max());
return single_container::release_child();
}
void extend_base::update(extend const & value)
{
left_.set(value.left);
right_.set(value.right);
top_.set(value.top);
bottom_.set(value.bottom);
}
}

View file

@ -1,65 +0,0 @@
#include <psemek/ui/impl/fixed_size_base.hpp>
#include <psemek/ui/alignment.hpp>
#include <psemek/react/join.hpp>
#include <psemek/react/map.hpp>
namespace psemek::ui::impl
{
namespace
{
size_constraints compute_size_constraints(geom::vector<std::optional<float>, 2> const & size)
{
return {
.box = {{
size[0] ? geom::interval<float>::singleton(*(size[0])) : geom::interval<float>(0.f, size_constraints::infinity),
size[1] ? geom::interval<float>::singleton(*(size[1])) : geom::interval<float>(0.f, size_constraints::infinity),
}},
};
}
geom::box<float, 2> compute_shape(geom::box<float, 2> const & shape, geom::vector<std::optional<float>, 2> const & size)
{
return {{
size[0] ? align(shape[0], *(size[0]), halignment::center) : shape[0],
size[1] ? align(shape[1], *(size[1]), valignment::center) : shape[1],
}};
}
}
fixed_size_base::fixed_size_base()
: size_({0.f, 0.f})
, size_constraints_(react::map(compute_size_constraints, react::join(size_)))
{}
void fixed_size_base::reshape(geom::box<float, 2> const & new_shape)
{
single_container::reshape(new_shape);
if (auto child = this->child())
child->reshape(compute_shape(new_shape, **size_));
}
react::value<struct size_constraints> fixed_size_base::size_constraints() const
{
return size_constraints_;
}
void fixed_size_base::update(fixed_size const & value)
{
size_.set(react::map([](geom::vector<float, 2> const & size){ return geom::vector<std::optional<float>, 2>{size[0], size[1]}; }, value.size));
}
void fixed_size_base::update(fixed_width const & value)
{
size_.set(react::map([](float width){ return geom::vector<std::optional<float>, 2>{width, std::nullopt}; }, value.width));
}
void fixed_size_base::update(fixed_height const & value)
{
size_.set(react::map([](float height){ return geom::vector<std::optional<float>, 2>{std::nullopt, height}; }, value.height));
}
}

View file

@ -1,49 +0,0 @@
#include <psemek/ui/impl/floating_base.hpp>
#include <psemek/ui/alignment.hpp>
#include <psemek/react/join.hpp>
#include <psemek/react/map.hpp>
namespace psemek::ui::impl
{
namespace
{
geom::box<float, 2> compute_child_shape(geom::box<float, 2> const & size_constraints, geom::point<float, 2> const & anchor, halignment halign, valignment valign)
{
geom::vector size{size_constraints[0].min, size_constraints[1].min};
geom::box<float, 2> result;
result[0] = geom::interval{- size[0], 0.f} + (1.f - lerp_factor(halign)) * size[0] + anchor[0];
result[1] = geom::interval{- size[1], 0.f} + (1.f - lerp_factor(valign)) * size[1] + anchor[1];
return result;
}
}
floating_base::floating_base()
: size_constraints_(impl::size_constraints::max())
{}
void floating_base::reshape(geom::box<float, 2> const & new_shape)
{
single_container::reshape(new_shape);
if (auto child = this->child())
child->reshape(compute_child_shape((*(child->size_constraints())).box, *anchor_, *halign_, *valign_));
}
react::value<struct size_constraints> floating_base::size_constraints() const
{
return size_constraints_;
}
void floating_base::update(floating const & value)
{
anchor_ = value.anchor;
halign_ = value.halign;
valign_ = value.valign;
}
}

View file

@ -1,62 +0,0 @@
#include <psemek/ui/impl/frame_base.hpp>
#include <psemek/react/join.hpp>
#include <psemek/react/map.hpp>
namespace psemek::ui::impl
{
namespace
{
size_constraints compute_size_constraints(size_constraints const & child_size_constraints, float margin)
{
return { child_size_constraints.box + geom::vector{2.f * margin, 2.f * margin} };
}
geom::box<float, 2> compute_child_shape(geom::box<float, 2> const & shape, float margin)
{
return geom::expand(shape, -margin);
}
}
frame_base::frame_base()
: margin_(0.f)
, child_size_constraints_(size_constraints::max())
, size_constraints_(react::map(compute_size_constraints, react::join(child_size_constraints_), react::join(margin_)))
{}
void frame_base::reshape(geom::box<float, 2> const & new_shape)
{
single_container::reshape(new_shape);
if (auto child = this->child())
child->reshape(compute_child_shape(new_shape, **margin_));
}
react::value<struct size_constraints> frame_base::size_constraints() const
{
return size_constraints_;
}
void frame_base::set_child(std::unique_ptr<component> child)
{
if (child)
child_size_constraints_.set(child->size_constraints());
else
child_size_constraints_.set(size_constraints::max());
single_container::set_child(std::move(child));
}
std::unique_ptr<component> frame_base::release_child()
{
child_size_constraints_.set(size_constraints::max());
return single_container::release_child();
}
void frame_base::update(frame const & value)
{
margin_.set(value.margin);
}
}

View file

@ -1,64 +0,0 @@
#include <psemek/ui/impl/label_base.hpp>
#include <psemek/react/map.hpp>
#include <psemek/react/join.hpp>
namespace psemek::ui::impl
{
label_base::label_base()
: font_(react::value<fonts::font *>(nullptr))
, glyphs_(react::value<std::vector<fonts::shaped_glyph>>(std::vector<fonts::shaped_glyph>{}))
, size_(react::map([](std::vector<fonts::shaped_glyph> const & glyphs, fonts::font * font){
if (!font)
return geom::vector{0.f, 0.f};
geom::box<float, 2> bbox;
for (auto const & glyph : glyphs)
bbox |= glyph.position;
return geom::vector{bbox[0].length(), font->size()[1]};
}, react::join(glyphs_), react::join(font_)))
, size_constraints_(react::map([](geom::vector<float, 2> const & size){
geom::box<float, 2> result;
result[0].min = size[0];
result[0].max = size_constraints::infinity;
result[1].min = size[1];
result[1].max = size_constraints::infinity;
return impl::size_constraints{result};
}, size_))
{}
react::value<size_constraints> label_base::size_constraints() const
{
return size_constraints_;
}
void label_base::draw(renderer & renderer)
{
if (!glyphs_ || !*glyphs_)
return;
auto const size = *size_;
auto const shape = this->shape();
geom::vector<float, 2> origin;
origin[0] = std::round(geom::lerp(shape[0].min, shape[0].max - size[0], lerp_factor(*halign_)));
origin[1] = std::round(geom::lerp(shape[1].min, shape[1].max - size[1], lerp_factor(*valign_)));
origin[1] += std::round((size[1] + (**font_)->xheight()) / 2.f);
for (auto const & glyph : **glyphs_)
renderer.draw_glyph(*glyph.texture, glyph.position + origin, glyph.texcoords, *color_);
}
void label_base::update(label const & value)
{
font_.set(value.font);
glyphs_.set(react::map([](std::string const & str, fonts::font * font) -> std::vector<fonts::shaped_glyph> {
if (!font)
return {};
return font->shape(str, {});
}, value.text, value.font));
color_ = value.color;
halign_ = value.halign;
valign_ = value.valign;
}
}

View file

@ -1,85 +0,0 @@
#include <psemek/ui/impl/move_base.hpp>
#include <psemek/react/join.hpp>
#include <psemek/react/map.hpp>
namespace psemek::ui::impl
{
namespace
{
geom::box<float, 2> compute_child_shape(geom::box<float, 2> const & shape, geom::vector<float, 2> const & offset)
{
return shape + offset;
}
}
move_base::move_base()
: offset_({0.f, 0.f})
, animation_speed_(0.f)
, child_size_constraints_(size_constraints::max())
, size_constraints_(react::join(child_size_constraints_))
, current_offset_{0.f, 0.f}
{}
void move_base::reshape(geom::box<float, 2> const & new_shape)
{
single_container::reshape(new_shape);
if (auto child = this->child())
child->reshape(compute_child_shape(new_shape, current_offset_));
}
react::value<struct size_constraints> move_base::size_constraints() const
{
return size_constraints_;
}
void move_base::set_child(std::unique_ptr<component> child)
{
if (child)
child_size_constraints_.set(child->size_constraints());
else
child_size_constraints_.set(size_constraints::max());
single_container::set_child(std::move(child));
}
std::unique_ptr<component> move_base::release_child()
{
child_size_constraints_.set(size_constraints::max());
return single_container::release_child();
}
void move_base::animate(float dt)
{
if (*animation_speed_ == 0.f)
{
if (current_offset_ != *offset_)
{
current_offset_ = *offset_;
reshape(shape());
}
}
else
{
auto factor = - std::expm1(- dt * (*animation_speed_));
auto new_offset = geom::lerp(current_offset_, *offset_, factor);
if (geom::length(current_offset_ - new_offset) < 1.f / 16.f)
new_offset = *offset_;
if (current_offset_ != *offset_)
{
current_offset_ = new_offset;
reshape(shape());
}
}
}
void move_base::update(move const & value)
{
offset_ = value.offset;
animation_speed_ = value.animation_speed;
}
}

View file

@ -1,35 +0,0 @@
#include <psemek/ui/impl/shape_reader_base.hpp>
namespace psemek::ui::impl
{
shape_reader_base::shape_reader_base()
: shape_(geom::box<float, 2>{{{0.f, 0.f}, {0.f, 0.f}}})
{}
void shape_reader_base::reshape(geom::box<float, 2> const & new_shape)
{
single_container::reshape(new_shape);
if (auto child = this->child())
child->reshape(new_shape);
shape_.set(new_shape);
}
react::value<struct size_constraints> shape_reader_base::size_constraints() const
{
if (auto child = this->child())
return child->size_constraints();
return size_constraints::max();
}
void shape_reader_base::update(shape_reader const & value)
{
shape_ = value.shape;
shape_.set(shape());
}
}

View file

@ -1,36 +0,0 @@
#include <psemek/ui/impl/single_container.hpp>
namespace psemek::ui::impl
{
util::span<std::unique_ptr<component> const> single_container::children() const
{
return {&child_, 1};
}
void single_container::set_child(std::unique_ptr<component> 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<component> single_container::release_child()
{
return std::move(child_);
}
util::signal<>::subscription_token single_container::release_child_token()
{
return std::move(child_token_);
}
}

View file

@ -1,42 +0,0 @@
#include <psemek/ui/impl/single_container_base.hpp>
#include <psemek/react/map.hpp>
#include <psemek/react/join.hpp>
namespace psemek::ui::impl
{
single_container_base::single_container_base(react::value<geom::vector<float, 2>> margin)
: margin_(margin)
, child_size_constraints_(size_constraints::max())
, size_constraints_(react::map([](struct size_constraints const & child_constraints, geom::vector<float, 2> const & margin){
return shift(child_constraints, margin);
}, react::join(child_size_constraints_), margin))
{}
void single_container_base::reshape(geom::box<float, 2> const & new_shape)
{
single_container::reshape(new_shape);
if (child())
child()->reshape(geom::shrink(new_shape, *margin_));
}
react::value<size_constraints> single_container_base::size_constraints() const
{
return size_constraints_;
}
void single_container_base::set_child(std::unique_ptr<component> child)
{
single_container::set_child(std::move(child));
child_size_constraints_.set(this->child() ? this->child()->size_constraints() : size_constraints::max());
}
std::unique_ptr<component> single_container_base::release_child()
{
auto child = single_container::release_child();
child_size_constraints_.set(size_constraints::max());
return child;
}
}

View file

@ -1,59 +0,0 @@
#include <psemek/ui/impl/size_constraints.hpp>
namespace psemek::ui::impl
{
size_constraints size_constraints::max()
{
return
{{{
{0.f, infinity},
{0.f, infinity},
}}};
}
size_constraints shift(size_constraints const & constraints, geom::vector<float, 2> const & delta)
{
return {constraints.box + delta};
}
size_constraints scale(size_constraints const & constraints, float factor, int dimension)
{
auto result = constraints;
result.box[dimension].min *= factor;
result.box[dimension].max *= factor;
return result;
}
float min(size_constraints const & constraints, int dimension)
{
return constraints.box[dimension].min;
}
size_constraints intersect(size_constraints const & constraints1, size_constraints const & constraints2)
{
return {constraints1.box & constraints2.box};
}
geom::interval<float> range(size_constraints const & constraints, int dimension)
{
return constraints.box[dimension];
}
size_constraints cut(size_constraints const & constraints, int dimension, geom::interval<float> const & range)
{
size_constraints result = constraints;
result.box[dimension] &= range;
return result;
}
size_constraints sum_along(size_constraints const & constraints1, size_constraints const & constraints2, int dimension)
{
size_constraints result;
result.box[dimension].min = constraints1.box[dimension].min + constraints2.box[dimension].min;
result.box[dimension].max = constraints1.box[dimension].max + constraints2.box[dimension].max;
result.box[dimension ^ 1] = constraints1.box[dimension ^ 1] & constraints2.box[dimension ^ 1];
return result;
}
}

View file

@ -1,63 +0,0 @@
#include <psemek/ui/impl/stack_layout_base.hpp>
#include <psemek/react/join.hpp>
#include <psemek/react/map.hpp>
namespace psemek::ui::impl
{
namespace
{
size_constraints compute_size_constraints(std::vector<size_constraints> const & children_size_constraints)
{
size_constraints result = size_constraints::max();
for (auto const & constraints : children_size_constraints)
result = intersect(result, constraints);
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, children_size_constraints_))))
{}
void stack_layout_base::reshape(geom::box<float, 2> const & new_shape)
{
container::reshape(new_shape);
for (auto const & child : children())
if (child)
child->reshape(new_shape);
}
react::value<size_constraints> stack_layout_base::size_constraints() const
{
return size_constraints_;
}
void stack_layout_base::set_children(std::vector<std::unique_ptr<component>> children)
{
container::set_children(std::move(children));
std::vector<react::value<struct size_constraints>> children_size_constraints;
for (auto const & child : this->children())
{
if (child)
children_size_constraints.push_back(child->size_constraints());
else
children_size_constraints.push_back(size_constraints::max());
}
children_size_constraints_.set(std::move(children_size_constraints));
}
std::vector<std::unique_ptr<component>> stack_layout_base::release_children()
{
auto result = container::release_children();
children_size_constraints_.set({});
return result;
}
}

View file

@ -1,59 +0,0 @@
#include <psemek/ui/impl/storage_base.hpp>
#include <psemek/react/join.hpp>
namespace psemek::ui::impl
{
storage_base::storage_base()
: child_size_constraints_(size_constraints::max())
, size_constraints_(react::join(child_size_constraints_))
{}
void storage_base::reshape(geom::box<float, 2> const & new_shape)
{
single_container::reshape(new_shape);
if (auto child = this->child())
child->reshape(new_shape);
}
react::value<struct size_constraints> storage_base::size_constraints() const
{
if (auto child = this->child())
return child->size_constraints();
return impl::size_constraints::max();
}
void storage_base::set_child(std::unique_ptr<component> child)
{
if (child)
child_size_constraints_.set(child->size_constraints());
else
child_size_constraints_.set(size_constraints::max());
single_container::set_child(std::move(child));
}
std::unique_ptr<component> storage_base::release_child()
{
child_size_constraints_.set(size_constraints::max());
return single_container::release_child();
}
void storage_base::update(storage const & value)
{
util::hash_map<std::string, react::source<std::any>> new_values;
for (auto const & p : value.values)
{
auto & v = (new_values[p.first] = p.second);
if (auto it = values_.find(p.first); it != values_.end())
v.set(*(it->second));
}
values_ = std::move(new_values);
}
}

View file

@ -1,38 +0,0 @@
#include <psemek/test/test.hpp>
#include <psemek/ui/impl/component_factory_base.hpp>
#include <psemek/ui/impl/single_container_base.hpp>
#include <psemek/ui/impl/stack_layout_base.hpp>
#include <psemek/ui/impl/box_layout_base.hpp>
#include <psemek/ui/rectangle.hpp>
#include <psemek/react/source.hpp>
using namespace psemek;
using namespace psemek::ui;
namespace
{
constexpr float layout_margin = 5.f;
struct rectangle_impl
: impl::component
{
void update(rectangle const &)
{}
};
struct test_component_factory
: impl::component_factory_base
{
test_component_factory()
{
register_type<rectangle, rectangle_impl>();
register_type<stack_layout, impl::stack_layout_base>();
register_type<box_layout::horizontal, impl::box_layout_base<0>>([]{ return std::make_unique<impl::box_layout_base<0>>(layout_margin); });
register_type<box_layout::vertical, impl::box_layout_base<1>>([]{ return std::make_unique<impl::box_layout_base<1>>(layout_margin); });
}
};
}

View file

@ -1,364 +0,0 @@
#include <psemek/test/test.hpp>
#include <psemek/ui/impl/component_factory_base.hpp>
#include <psemek/ui/label.hpp>
#include <psemek/ui/rectangle.hpp>
#include <psemek/ui/button.hpp>
#include <psemek/ui/stack_layout.hpp>
#include <type_traits>
using namespace psemek;
using namespace psemek::ui;
namespace
{
struct rectangle_impl
: impl::component
{
void update(rectangle const &)
{}
};
struct label_impl
: impl::component
{
void update(label const &)
{}
};
struct button_impl
: impl::single_container
{
void update(button const &)
{}
};
struct stack_layout_impl
: impl::container
{
void update(stack_layout const &)
{}
};
struct test_component_factory
: impl::component_factory_base
{
int rectangle_count = 0;
int label_count = 0;
int button_count = 0;
int stack_layout_count = 0;
test_component_factory()
{
register_type<rectangle, rectangle_impl>([this]{ ++rectangle_count; return std::make_unique<rectangle_impl>(); });
register_type<label, label_impl>([this]{ ++label_count; return std::make_unique<label_impl>(); });
register_type<button, button_impl>([this]{ ++button_count; return std::make_unique<button_impl>(); });
register_type<stack_layout, stack_layout_impl>([this]{ ++stack_layout_count; return std::make_unique<stack_layout_impl>(); });
}
};
struct check_children_helper
{
impl::component & component;
std::size_t i = 0;
template <typename Type>
void check()
{
if constexpr (std::is_same_v<Type, std::nullptr_t>)
{
expect_null(component.children()[i]);
}
else
{
expect_non_null(component.children()[i]);
expect_dynamic_type(*component.children()[i], Type);
}
++i;
}
};
template <typename ... Types>
void check_children(impl::component & component)
{
expect_equal(component.children().size(), sizeof...(Types));
check_children_helper helper{component};
(void)helper;
(helper.check<Types>(), ...);
}
}
test_case(ui_impl_factory_element)
{
test_component_factory factory;
auto test_ui = label{};
auto ui_root = factory.reconciliate(nullptr, test_ui);
expect_non_null(ui_root);
expect_dynamic_type(*ui_root, label_impl);
expect_equal(factory.label_count, 1);
}
test_case(ui_impl_factory_single__container)
{
test_component_factory factory;
auto test_ui = button{
.child = label{}
};
auto ui_root = factory.reconciliate(nullptr, test_ui);
expect_non_null(ui_root);
expect_dynamic_type(*ui_root, button_impl);
check_children<label_impl>(*ui_root);
expect_equal(factory.label_count, 1);
expect_equal(factory.button_count, 1);
}
test_case(ui_impl_factory_single__container__null)
{
test_component_factory factory;
auto test_ui = button{};
auto ui_root = factory.reconciliate(nullptr, test_ui);
expect_non_null(ui_root);
expect_dynamic_type(*ui_root, button_impl);
check_children<std::nullptr_t>(*ui_root);
expect_equal(factory.button_count, 1);
}
test_case(ui_impl_factory_container)
{
test_component_factory factory;
auto test_ui = stack_layout{{
{label{}},
{button{.child = label{}}},
{label{}},
}};
auto ui_root = factory.reconciliate(nullptr, test_ui);
expect_non_null(ui_root);
expect_dynamic_type(*ui_root, stack_layout_impl);
check_children<label_impl, button_impl, label_impl>(*ui_root);
check_children<label_impl>(*ui_root->children()[1]);
expect_equal(factory.label_count, 3);
expect_equal(factory.button_count, 1);
expect_equal(factory.stack_layout_count, 1);
}
test_case(ui_impl_factory_container__null)
{
test_component_factory factory;
auto test_ui = stack_layout{};
auto ui_root = factory.reconciliate(nullptr, test_ui);
expect_non_null(ui_root);
expect_dynamic_type(*ui_root, stack_layout_impl);
check_children<>(*ui_root);
expect_equal(factory.stack_layout_count, 1);
}
test_case(ui_impl_factory_container__null__child)
{
test_component_factory factory;
auto test_ui = stack_layout{{
{},
{},
}};
auto ui_root = factory.reconciliate(nullptr, test_ui);
expect_non_null(ui_root);
expect_dynamic_type(*ui_root, stack_layout_impl);
check_children<std::nullptr_t, std::nullptr_t>(*ui_root);
expect_equal(factory.stack_layout_count, 1);
}
test_case(ui_impl_reconciliate_single__container)
{
test_component_factory factory;
auto child = react::source<std::any>(label{});
expect_equal((*child).type(), typeid(label{}));
auto test_ui = button{
.child = child
};
expect_equal((*test_ui.child).type(), typeid(label{}));
auto ui_root = factory.reconciliate(nullptr, test_ui);
expect_non_null(ui_root);
expect_dynamic_type(*ui_root, button_impl);
check_children<label_impl>(*ui_root);
expect_equal(factory.rectangle_count, 0);
expect_equal(factory.label_count, 1);
expect_equal(factory.button_count, 1);
child.set(label{});
check_children<label_impl>(*ui_root);
expect_equal(factory.rectangle_count, 0);
expect_equal(factory.label_count, 1);
expect_equal(factory.button_count, 1);
child.set(rectangle{});
check_children<rectangle_impl>(*ui_root);
expect_equal(factory.rectangle_count, 1);
expect_equal(factory.label_count, 1);
expect_equal(factory.button_count, 1);
child.set(label{});
check_children<label_impl>(*ui_root);
expect_equal(factory.rectangle_count, 1);
expect_equal(factory.label_count, 2);
expect_equal(factory.button_count, 1);
}
test_case(ui_impl_reconciliate_container__no__keys)
{
test_component_factory factory;
react::source<std::vector<stack_layout::element>> children;
auto test_ui = stack_layout{children};
auto ui_root = factory.reconciliate(nullptr, test_ui);
expect_equal(factory.stack_layout_count, 1);
expect_equal(factory.label_count, 0);
expect_equal(factory.button_count, 0);
expect_non_null(ui_root);
expect_dynamic_type(*ui_root, stack_layout_impl);
check_children<>(*ui_root);
children.set({{label{}}});
check_children<label_impl>(*ui_root);
expect_equal(factory.stack_layout_count, 1);
expect_equal(factory.label_count, 1);
expect_equal(factory.button_count, 0);
children.set({{label{}}, {label{}}});
check_children<label_impl, label_impl>(*ui_root);
expect_equal(factory.stack_layout_count, 1);
expect_equal(factory.label_count, 2);
expect_equal(factory.button_count, 0);
children.set({{label{}}, {label{}}, {label{}}});
expect_equal(factory.stack_layout_count, 1);
expect_equal(factory.label_count, 3);
expect_equal(factory.button_count, 0);
children.set({{label{}}, {button{}}, {label{}}});
expect_equal(factory.stack_layout_count, 1);
expect_equal(factory.label_count, 3);
expect_equal(factory.button_count, 1);
children.set({{button{}}, {button{}}, {label{}}});
expect_equal(factory.stack_layout_count, 1);
expect_equal(factory.label_count, 3);
expect_equal(factory.button_count, 2);
children.set({{button{}}, {button{}}});
expect_equal(factory.stack_layout_count, 1);
expect_equal(factory.label_count, 3);
expect_equal(factory.button_count, 2);
children.set({{button{}}});
expect_equal(factory.stack_layout_count, 1);
expect_equal(factory.label_count, 3);
expect_equal(factory.button_count, 2);
children.set({});
expect_equal(factory.stack_layout_count, 1);
expect_equal(factory.label_count, 3);
expect_equal(factory.button_count, 2);
}
test_case(ui_impl_reconciliate_container__keys)
{
test_component_factory factory;
react::source<std::vector<stack_layout::element>> children;
auto test_ui = stack_layout{children};
auto ui_root = factory.reconciliate(nullptr, test_ui);
expect_equal(factory.stack_layout_count, 1);
expect_equal(factory.label_count, 0);
expect_non_null(ui_root);
expect_dynamic_type(*ui_root, stack_layout_impl);
check_children<>(*ui_root);
children.set({{label{}, "Label 0"}});
check_children<label_impl>(*ui_root);
auto label0 = ui_root->children()[0].get();
expect_equal(factory.stack_layout_count, 1);
expect_equal(factory.label_count, 1);
children.set({{label{}, "Label 0"}, {label{}, "Label 1"}});
check_children<label_impl, label_impl>(*ui_root);
expect_equal(ui_root->children()[0].get(), label0);
auto label1 = ui_root->children()[1].get();
expect_equal(factory.stack_layout_count, 1);
expect_equal(factory.label_count, 2);
children.set({{label{}, "Label 1"}, {label{}, "Label 0"}});
check_children<label_impl, label_impl>(*ui_root);
expect_equal(ui_root->children()[0].get(), label1);
expect_equal(ui_root->children()[1].get(), label0);
expect_equal(factory.stack_layout_count, 1);
expect_equal(factory.label_count, 2);
children.set({{label{}, "Label 0"}, {label{}, "Label 2"}});
check_children<label_impl, label_impl>(*ui_root);
expect_equal(ui_root->children()[0].get(), label0);
expect_equal(factory.stack_layout_count, 1);
expect_equal(factory.label_count, 3);
}

View file

@ -1,233 +0,0 @@
#include <psemek/test/test.hpp>
#include <psemek/ui/impl/size_polygon_utils.hpp>
#include <algorithm>
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<float> 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;
}

View file

@ -1,95 +0,0 @@
#include <psemek/test/test.hpp>
#include <psemek/ui/impl/size_polygon_utils.hpp>
#include <algorithm>
using namespace psemek;
using namespace psemek::ui::impl;
namespace
{
template <typename T>
std::set<T> as_set(std::vector<T> const & v)
{
std::set<T> 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);
}