diff --git a/libs/ui/CMakeLists.txt b/libs/ui/CMakeLists.txt new file mode 100644 index 00000000..3b9538e9 --- /dev/null +++ b/libs/ui/CMakeLists.txt @@ -0,0 +1,6 @@ +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-gfx) diff --git a/libs/ui/include/psemek/ui/alignment.hpp b/libs/ui/include/psemek/ui/alignment.hpp new file mode 100644 index 00000000..8304d99c --- /dev/null +++ b/libs/ui/include/psemek/ui/alignment.hpp @@ -0,0 +1,20 @@ +#pragma once + +namespace psemek::ui +{ + + enum class halignment + { + left, + center, + right, + }; + + enum class valignment + { + top, + center, + bottom, + }; + +} diff --git a/libs/ui/include/psemek/ui/button.hpp b/libs/ui/include/psemek/ui/button.hpp new file mode 100644 index 00000000..5dbcf9ae --- /dev/null +++ b/libs/ui/include/psemek/ui/button.hpp @@ -0,0 +1,17 @@ +#pragma once + +#include +#include + +#include + +namespace psemek::ui +{ + + struct button + { + react::source on_click = {}; + react::value child = {}; + }; + +} diff --git a/libs/ui/include/psemek/ui/frame.hpp b/libs/ui/include/psemek/ui/frame.hpp new file mode 100644 index 00000000..17d10050 --- /dev/null +++ b/libs/ui/include/psemek/ui/frame.hpp @@ -0,0 +1,15 @@ +#pragma once + +#include + +#include + +namespace psemek::ui +{ + + struct frame + { + react::value child = {}; + }; + +} diff --git a/libs/ui/include/psemek/ui/grid_layout.hpp b/libs/ui/include/psemek/ui/grid_layout.hpp new file mode 100644 index 00000000..3690fcec --- /dev/null +++ b/libs/ui/include/psemek/ui/grid_layout.hpp @@ -0,0 +1,46 @@ +#pragma once + +#include +#include + +#include +#include + +namespace psemek::ui +{ + + namespace grid_layout + { + + struct minimized + {}; + + struct weight + { + float value = 1.f; + }; + + using size_policy = std::variant; + + struct element + { + react::value element; + react::value policy = {}; + std::optional key = {}; + }; + + struct horizontal + { + using element = grid_layout::element; + react::value> children = {}; + }; + + struct vertical + { + using element = grid_layout::element; + react::value> children = {}; + }; + + } + +} diff --git a/libs/ui/include/psemek/ui/impl/component.hpp b/libs/ui/include/psemek/ui/impl/component.hpp new file mode 100644 index 00000000..a7741b5c --- /dev/null +++ b/libs/ui/include/psemek/ui/impl/component.hpp @@ -0,0 +1,11 @@ +#pragma once + +namespace psemek::ui::impl +{ + + struct component + { + virtual ~component() {} + }; + +} diff --git a/libs/ui/include/psemek/ui/impl/component_factory.hpp b/libs/ui/include/psemek/ui/impl/component_factory.hpp new file mode 100644 index 00000000..8b95602d --- /dev/null +++ b/libs/ui/include/psemek/ui/impl/component_factory.hpp @@ -0,0 +1,18 @@ +#pragma once + +#include + +#include +#include + +namespace psemek::ui::impl +{ + + struct component_factory + { + virtual std::unique_ptr reconciliate(std::unique_ptr root, std::any const & value) = 0; + + virtual ~component_factory() {} + }; + +} diff --git a/libs/ui/include/psemek/ui/impl/component_factory_base.hpp b/libs/ui/include/psemek/ui/impl/component_factory_base.hpp new file mode 100644 index 00000000..01e8e568 --- /dev/null +++ b/libs/ui/include/psemek/ui/impl/component_factory_base.hpp @@ -0,0 +1,141 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +namespace psemek::ui::impl +{ + + struct component_not_supported_exception + : std::exception + { + component_not_supported_exception(std::type_index type) + : type_(type) + , message_(util::to_string("UI component ", util::type_name(type_), " is not supported")) + {} + + const char * what() const noexcept override + { + return message_.data(); + } + + private: + std::type_index type_; + std::string message_; + }; + + struct component_factory_base + : component_factory + { + std::unique_ptr reconciliate(std::unique_ptr root, std::any const & value) 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); + throw component_not_supported_exception(type); + } + + template + void register_factory(Factory factory) + { + type_factories_[typeid(Type)] = [factory = std::move(factory)](std::unique_ptr root, std::any const & value) + { + return factory(std::move(root), *std::any_cast(&value)); + }; + } + + template + void register_type() + { + type_factories_[typeid(Type)] = [this](std::unique_ptr root, std::any const & value) + { + std::unique_ptr impl; + if (root) + { + if (auto impl_raw = dynamic_cast(root.get())) + { + impl.reset(impl_raw); + root.release(); + } + } + + root.reset(); + + if (!impl) + impl = std::make_unique(); + + auto const & typed_value = *std::any_cast(&value); + + impl->update(typed_value); + + if constexpr (std::is_base_of_v) + { + impl->release_child_token(); + impl->set_child_token(typed_value.child.subscribe([this, impl = impl.get()](std::any const & child){ + impl->set_child(reconciliate(impl->release_child(), child)); + }, true)); + } + else if constexpr (std::is_base_of_v) + { + impl->set_children_token(typed_value.children.subscribe([this, impl = impl.get()](std::vector const & children_values){ + impl->release_child_tokens(); + + std::unordered_map> child_by_key; + { + auto children = impl->release_children(); + auto keys = impl->release_child_keys(); + + for (std::size_t i = 0; i < children.size(); ++i) + if (keys[i]) + child_by_key[std::move(*keys[i])] = std::move(children[i]); + } + + std::vector> children(children_values.size()); + std::vector> 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; + } + } + + impl->set_children(std::move(children)); + impl->set_child_keys(std::move(child_keys)); + + std::vector::subscription_token> child_tokens(children_values.size()); + + for (std::size_t i = 0; i < children_values.size(); ++i) + { + child_tokens[i] = children_values[i].element.subscribe([this, impl, i](std::any const & value){ + auto children = impl->release_children(); + children[i] = reconciliate(std::move(children[i]), value); + impl->set_children(std::move(children)); + }, true); + } + + impl->set_child_tokens(std::move(child_tokens)); + }, true)); + } + + return impl; + }; + } + + private: + std::unordered_map(std::unique_ptr, std::any const &)>> type_factories_; + }; + +} diff --git a/libs/ui/include/psemek/ui/impl/container.hpp b/libs/ui/include/psemek/ui/impl/container.hpp new file mode 100644 index 00000000..2a63a339 --- /dev/null +++ b/libs/ui/include/psemek/ui/impl/container.hpp @@ -0,0 +1,63 @@ +#pragma once + +#include +#include +#include + +#include +#include + +namespace psemek::ui::impl +{ + + struct container + : component + { + void set_children(std::vector> children) + { + children_ = std::move(children); + } + + void set_children_token(util::signal>::subscription_token token) + { + children_token_ = std::move(token); + } + + 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_); + } + + private: + util::signal::subscription_token children_token_; + std::vector> children_; + std::vector::subscription_token> child_tokens_; + std::vector> child_keys_; + }; + +} diff --git a/libs/ui/include/psemek/ui/impl/controller.hpp b/libs/ui/include/psemek/ui/impl/controller.hpp new file mode 100644 index 00000000..7df64682 --- /dev/null +++ b/libs/ui/include/psemek/ui/impl/controller.hpp @@ -0,0 +1,23 @@ +#pragma once + +#include +#include +#include + +#include + +namespace psemek::ui::impl +{ + + struct controller + { + controller(component_factory & factory); + ~controller(); + + void set_ui(react::value ui); + + private: + psemek_declare_pimpl + }; + +} diff --git a/libs/ui/include/psemek/ui/impl/single_container.hpp b/libs/ui/include/psemek/ui/impl/single_container.hpp new file mode 100644 index 00000000..1eaf2295 --- /dev/null +++ b/libs/ui/include/psemek/ui/impl/single_container.hpp @@ -0,0 +1,40 @@ +#pragma once + +#include +#include +#include + +#include + +namespace psemek::ui::impl +{ + + struct single_container + : component + { + void set_child(std::unique_ptr child) + { + child_ = std::move(child); + } + + void set_child_token(util::signal::subscription_token token) + { + child_token_ = std::move(token); + } + + std::unique_ptr release_child() + { + return std::move(child_); + } + + util::signal::subscription_token release_child_token() + { + return std::move(child_token_); + } + + private: + util::signal::subscription_token child_token_; + std::unique_ptr child_; + }; + +} diff --git a/libs/ui/include/psemek/ui/key.hpp b/libs/ui/include/psemek/ui/key.hpp new file mode 100644 index 00000000..889ee31d --- /dev/null +++ b/libs/ui/include/psemek/ui/key.hpp @@ -0,0 +1,10 @@ +#pragma once + +#include + +namespace psemek::ui +{ + + using key = std::string; + +} diff --git a/libs/ui/include/psemek/ui/label.hpp b/libs/ui/include/psemek/ui/label.hpp new file mode 100644 index 00000000..938ab748 --- /dev/null +++ b/libs/ui/include/psemek/ui/label.hpp @@ -0,0 +1,19 @@ +#pragma once + +#include +#include + +#include + +namespace psemek::ui +{ + + struct label + { + react::value text = {}; + react::value halign = {}; + react::value valign = {}; + react::value must_fit = {}; + }; + +} diff --git a/libs/ui/include/psemek/ui/rectangle.hpp b/libs/ui/include/psemek/ui/rectangle.hpp new file mode 100644 index 00000000..28698053 --- /dev/null +++ b/libs/ui/include/psemek/ui/rectangle.hpp @@ -0,0 +1,15 @@ +#pragma once + +#include +#include + +namespace psemek::ui +{ + + struct rectagle + { + react::value color = {}; + react::value square = {}; + }; + +} diff --git a/libs/ui/include/psemek/ui/slider.hpp b/libs/ui/include/psemek/ui/slider.hpp new file mode 100644 index 00000000..fc3b36c7 --- /dev/null +++ b/libs/ui/include/psemek/ui/slider.hpp @@ -0,0 +1,16 @@ +#pragma once + +#include +#include +#include + +namespace psemek::ui +{ + + struct slider + { + react::value> range = {}; + react::source value = {}; + }; + +} diff --git a/libs/ui/include/psemek/ui/stack_layout.hpp b/libs/ui/include/psemek/ui/stack_layout.hpp new file mode 100644 index 00000000..2f9fb109 --- /dev/null +++ b/libs/ui/include/psemek/ui/stack_layout.hpp @@ -0,0 +1,22 @@ +#pragma once + +#include +#include + +#include + +namespace psemek::ui +{ + + struct stack_layout + { + struct element + { + react::value element; + std::optional key = {}; + }; + + react::value> children = {}; + }; + +} diff --git a/libs/ui/source/impl/controller.cpp b/libs/ui/source/impl/controller.cpp new file mode 100644 index 00000000..4ee790d3 --- /dev/null +++ b/libs/ui/source/impl/controller.cpp @@ -0,0 +1,26 @@ +#include + +namespace psemek::ui::impl +{ + + struct controller::impl + { + component_factory & factory; + + impl(component_factory & factory) + : factory(factory) + {} + }; + + controller::controller(component_factory & factory) + : pimpl_(make_impl(factory)) + {} + + controller::~controller() = default; + + void controller::set_ui(react::value ui) + { + (void)ui; + } + +} diff --git a/libs/ui/source/impl/reconciliator.cpp b/libs/ui/source/impl/reconciliator.cpp new file mode 100644 index 00000000..e69de29b