New UI library internals redesign: add global event_state, add delayed events queue, fix reshaping & subscribing to size_constraints

This commit is contained in:
Nikita Lisitsa 2024-07-31 15:55:23 +03:00
parent 7151fef7a8
commit 367f82a01f
6 changed files with 107 additions and 82 deletions

View file

@ -3,6 +3,6 @@ file(GLOB_RECURSE PSEMEK_UI_SOURCES RELATIVE "${CMAKE_CURRENT_SOURCE_DIR}" "sour
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 psemek-app)
target_link_libraries(psemek-ui PUBLIC psemek-util psemek-react psemek-geom psemek-async psemek-app)
psemek_glob_tests(psemek-ui tests)

View file

@ -1,16 +1,20 @@
#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) = 0;
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

@ -35,18 +35,19 @@ namespace psemek::ui::impl
struct component_factory_base
: component_factory
{
std::unique_ptr<component> reconciliate(std::unique_ptr<component> root, std::any const & value) override
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);
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)
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)
@ -64,7 +65,16 @@ namespace psemek::ui::impl
(void)this;
if (!impl)
impl = factory();
{
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);
@ -75,8 +85,11 @@ namespace psemek::ui::impl
impl->release_child_token();
if (typed_value.child)
{
impl->set_child_token(typed_value.child.subscribe([this, impl = impl.get()](std::any const & child){
impl->set_child(reconciliate(impl->release_child(), child));
impl->set_child_token(typed_value.child.subscribe([this, impl = impl.get(), &state, &delayed_executor, request_reshape](std::any const & child){
delayed_executor.post([this, impl, &state, &delayed_executor, request_reshape, child]{
impl->set_child(reconciliate(impl->release_child(), child, state, delayed_executor, request_reshape));
request_reshape();
});
}, true));
}
else
@ -88,55 +101,61 @@ namespace psemek::ui::impl
{
if (typed_value.children)
{
impl->set_children_token(typed_value.children.subscribe([this, impl = impl.get()](std::vector<typename Type::element> const & children_values){
impl->release_child_tokens();
impl->set_children_token(typed_value.children.subscribe([this, impl = impl.get(), &state, &delayed_executor, request_reshape](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;
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)
auto old_children = impl->release_children();
{
if (auto it = child_by_key.find(*key); it != child_by_key.end())
children[i] = std::move(it->second);
auto keys = impl->release_child_keys();
child_keys[i] = *key;
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]);
}
if (!children[i] && i < old_children.size() && old_children[i])
children[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());
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)
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);
}
}
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);
impl->set_child_tokens(std::move(child_tokens));
child_keys[i] = *key;
}
if (!children[i] && i < old_children.size() && old_children[i])
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](std::any const & value){
delayed_executor.post([this, impl, &state, &delayed_executor, request_reshape, i, value]{
auto children = impl->release_children();
children[i] = reconciliate(std::move(children[i]), value, state, delayed_executor, request_reshape);
impl->set_children(std::move(children));
request_reshape();
});
}, true);
}
}
impl->set_child_tokens(std::move(child_tokens));
request_reshape();
});
}, true));
}
else
@ -154,11 +173,16 @@ namespace psemek::ui::impl
template <typename Type, typename ImplType>
void register_type()
{
register_type<Type, ImplType>([]{ return std::make_unique<ImplType>(); });
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 &)>> type_factories_;
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

@ -25,7 +25,7 @@ namespace psemek::ui::impl
bool on_event(mouse_button_event const & event);
bool on_event(key_event const & event);
void animate(float dt);
void update(float dt);
void draw(renderer & renderer);
private:

View file

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

View file

@ -1,4 +1,6 @@
#include <psemek/ui/impl/controller.hpp>
#include <psemek/ui/impl/event_state.hpp>
#include <psemek/async/event_loop.hpp>
namespace psemek::ui::impl
{
@ -6,13 +8,13 @@ namespace psemek::ui::impl
struct controller::impl
{
component_factory & factory;
geom::vector<int, 2> screen_size = {0, 0};
event_state state;
util::signal<>::subscription_token root_token;
util::signal<>::subscription_token root_reshape_token;
std::unique_ptr<ui::impl::component> root;
async::event_loop delayed_queue;
impl(component_factory & factory)
: factory(factory)
{}
@ -21,44 +23,26 @@ namespace psemek::ui::impl
{
root_token = ui.subscribe([this](std::any const & value)
{
root = factory.reconciliate(std::move(root), value);
subscribe_reshape();
root = factory.reconciliate(std::move(root), value, state, delayed_queue, [this]{ delayed_queue.post([this]{ reshape(); }); });
}, true);
on_event(resize_event{screen_size});
}
void subscribe_reshape()
void reshape()
{
root_reshape_token = nullptr;
if (!root)
return;
if (root)
root_reshape_token = root->size_constraints().subscribe([this](psemek::ui::impl::size_constraints const &)
{
do_reshape();
});
}
void do_reshape()
{
if (root)
root->reshape({{{0.f, screen_size[0]}, {0.f, screen_size[1]}}});
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)
{
screen_size = event.size;
bool result = on_event_impl(event, root.get());
do_reshape();
return result;
}
template <typename Event>
bool on_event_impl(Event const & event, component * element)
{
@ -72,8 +56,9 @@ namespace psemek::ui::impl
return element->on_event(event);
}
void animate(float dt)
void update(float dt)
{
delayed_queue.pump();
animate_impl(dt, root.get());
}
@ -147,9 +132,9 @@ namespace psemek::ui::impl
return impl().on_event(event);
}
void controller::animate(float dt)
void controller::update(float dt)
{
impl().animate(dt);
impl().update(dt);
}
void controller::draw(renderer & renderer)