New UI library wip

This commit is contained in:
Nikita Lisitsa 2023-04-18 15:30:38 +03:00
parent e21692743c
commit 1246985763
18 changed files with 508 additions and 0 deletions

6
libs/ui/CMakeLists.txt Normal file
View file

@ -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)

View file

@ -0,0 +1,20 @@
#pragma once
namespace psemek::ui
{
enum class halignment
{
left,
center,
right,
};
enum class valignment
{
top,
center,
bottom,
};
}

View file

@ -0,0 +1,17 @@
#pragma once
#include <psemek/react/value.hpp>
#include <psemek/react/source.hpp>
#include <any>
namespace psemek::ui
{
struct button
{
react::source<void> on_click = {};
react::value<std::any> child = {};
};
}

View file

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

View file

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

View file

@ -0,0 +1,11 @@
#pragma once
namespace psemek::ui::impl
{
struct component
{
virtual ~component() {}
};
}

View file

@ -0,0 +1,18 @@
#pragma once
#include <psemek/ui/impl/component.hpp>
#include <memory>
#include <any>
namespace psemek::ui::impl
{
struct component_factory
{
virtual std::unique_ptr<component> reconciliate(std::unique_ptr<component> root, std::any const & value) = 0;
virtual ~component_factory() {}
};
}

View file

@ -0,0 +1,141 @@
#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 <exception>
#include <typeindex>
#include <unordered_map>
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<component> reconciliate(std::unique_ptr<component> 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 <typename Type, typename Factory>
void register_factory(Factory factory)
{
type_factories_[typeid(Type)] = [factory = std::move(factory)](std::unique_ptr<component> root, std::any const & value)
{
return factory(std::move(root), *std::any_cast<Type>(&value));
};
}
template <typename Type, typename ImplType>
void register_type()
{
type_factories_[typeid(Type)] = [this](std::unique_ptr<component> root, std::any const & value)
{
std::unique_ptr<ImplType> impl;
if (root)
{
if (auto impl_raw = dynamic_cast<ImplType *>(root.get()))
{
impl.reset(impl_raw);
root.release();
}
}
root.reset();
if (!impl)
impl = std::make_unique<ImplType>();
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();
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<container, ImplType>)
{
impl->set_children_token(typed_value.children.subscribe([this, impl = impl.get()](std::vector<typename Type::element> const & children_values){
impl->release_child_tokens();
std::unordered_map<key, std::unique_ptr<component>> 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<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;
}
}
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)
{
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::type_index, util::function<std::unique_ptr<component>(std::unique_ptr<component>, std::any const &)>> type_factories_;
};
}

View file

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

View file

@ -0,0 +1,23 @@
#pragma once
#include <psemek/ui/impl/component_factory.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);
private:
psemek_declare_pimpl
};
}

View file

@ -0,0 +1,40 @@
#pragma once
#include <psemek/ui/impl/component.hpp>
#include <psemek/ui/key.hpp>
#include <psemek/util/signal.hpp>
#include <any>
namespace psemek::ui::impl
{
struct single_container
: component
{
void set_child(std::unique_ptr<component> child)
{
child_ = std::move(child);
}
void set_child_token(util::signal<void>::subscription_token token)
{
child_token_ = std::move(token);
}
std::unique_ptr<component> release_child()
{
return std::move(child_);
}
util::signal<void>::subscription_token release_child_token()
{
return std::move(child_token_);
}
private:
util::signal<void>::subscription_token child_token_;
std::unique_ptr<component> child_;
};
}

View file

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

View file

@ -0,0 +1,19 @@
#pragma once
#include <psemek/ui/alignment.hpp>
#include <psemek/react/value.hpp>
#include <string>
namespace psemek::ui
{
struct label
{
react::value<std::string> text = {};
react::value<halignment> halign = {};
react::value<valignment> valign = {};
react::value<std::string> must_fit = {};
};
}

View file

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

View file

@ -0,0 +1,16 @@
#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

@ -0,0 +1,22 @@
#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

@ -0,0 +1,26 @@
#include <psemek/ui/impl/controller.hpp>
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<std::any> ui)
{
(void)ui;
}
}

View file