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}) psemek_add_library(psemek-ui ${PSEMEK_UI_HEADERS} ${PSEMEK_UI_SOURCES})
target_include_directories(psemek-ui PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/include") 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) psemek_glob_tests(psemek-ui tests)

View file

@ -1,16 +1,20 @@
#pragma once #pragma once
#include <psemek/ui/impl/component.hpp> #include <psemek/ui/impl/component.hpp>
#include <psemek/ui/impl/event_state.hpp>
#include <psemek/async/executor.hpp>
#include <memory> #include <memory>
#include <any> #include <any>
#include <functional>
namespace psemek::ui::impl namespace psemek::ui::impl
{ {
struct component_factory 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() {} virtual ~component_factory() {}
}; };

View file

@ -35,18 +35,19 @@ namespace psemek::ui::impl
struct component_factory_base struct component_factory_base
: component_factory : 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()); std::type_index type(value.type());
if (auto it = type_factories_.find(type); it != type_factories_.end()) 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); throw component_not_supported_exception(type);
} }
template <typename Type, typename ImplType, typename Factory> template <typename Type, typename ImplType, typename Factory>
void register_type(Factory && 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; std::unique_ptr<ImplType> impl;
if (root) if (root)
@ -64,8 +65,17 @@ namespace psemek::ui::impl
(void)this; (void)this;
if (!impl) if (!impl)
{
if constexpr (std::is_invocable_v<Factory, event_state const &>)
impl = factory(state);
else
impl = factory(); 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); auto const & typed_value = *std::any_cast<Type>(&value);
impl->update(typed_value); impl->update(typed_value);
@ -75,8 +85,11 @@ namespace psemek::ui::impl
impl->release_child_token(); impl->release_child_token();
if (typed_value.child) if (typed_value.child)
{ {
impl->set_child_token(typed_value.child.subscribe([this, impl = impl.get()](std::any const & child){ impl->set_child_token(typed_value.child.subscribe([this, impl = impl.get(), &state, &delayed_executor, request_reshape](std::any const & child){
impl->set_child(reconciliate(impl->release_child(), 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)); }, true));
} }
else else
@ -88,7 +101,8 @@ namespace psemek::ui::impl
{ {
if (typed_value.children) 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->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(); 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;
@ -128,15 +142,20 @@ namespace psemek::ui::impl
{ {
if (children_values[i].element) if (children_values[i].element)
{ {
child_tokens[i] = children_values[i].element.subscribe([this, impl, i](std::any const & value){ 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(); auto children = impl->release_children();
children[i] = reconciliate(std::move(children[i]), value); children[i] = reconciliate(std::move(children[i]), value, state, delayed_executor, request_reshape);
impl->set_children(std::move(children)); impl->set_children(std::move(children));
request_reshape();
});
}, true); }, true);
} }
} }
impl->set_child_tokens(std::move(child_tokens)); impl->set_child_tokens(std::move(child_tokens));
request_reshape();
});
}, true)); }, true));
} }
else else
@ -154,11 +173,16 @@ namespace psemek::ui::impl
template <typename Type, typename ImplType> template <typename Type, typename ImplType>
void register_type() 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: 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(mouse_button_event const & event);
bool on_event(key_event const & event); bool on_event(key_event const & event);
void animate(float dt); void update(float dt);
void draw(renderer & renderer); void draw(renderer & renderer);
private: 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/controller.hpp>
#include <psemek/ui/impl/event_state.hpp>
#include <psemek/async/event_loop.hpp>
namespace psemek::ui::impl namespace psemek::ui::impl
{ {
@ -6,13 +8,13 @@ namespace psemek::ui::impl
struct controller::impl struct controller::impl
{ {
component_factory & factory; component_factory & factory;
event_state state;
geom::vector<int, 2> screen_size = {0, 0};
util::signal<>::subscription_token root_token; util::signal<>::subscription_token root_token;
util::signal<>::subscription_token root_reshape_token;
std::unique_ptr<ui::impl::component> root; std::unique_ptr<ui::impl::component> root;
async::event_loop delayed_queue;
impl(component_factory & factory) impl(component_factory & factory)
: factory(factory) : factory(factory)
{} {}
@ -21,44 +23,26 @@ namespace psemek::ui::impl
{ {
root_token = ui.subscribe([this](std::any const & value) root_token = ui.subscribe([this](std::any const & value)
{ {
root = factory.reconciliate(std::move(root), value); root = factory.reconciliate(std::move(root), value, state, delayed_queue, [this]{ delayed_queue.post([this]{ reshape(); }); });
subscribe_reshape();
}, true); }, true);
on_event(resize_event{screen_size});
} }
void subscribe_reshape() void reshape()
{ {
root_reshape_token = nullptr; if (!root)
return;
if (root) root->reshape({{{0.f, state.size[0]}, {0.f, state.size[1]}}});
root_reshape_token = root->size_constraints().subscribe([this](psemek::ui::impl::size_constraints const &) on_event_impl(mouse_move_event{state.mouse, {0, 0}}, root.get());
{
do_reshape();
});
}
void do_reshape()
{
if (root)
root->reshape({{{0.f, screen_size[0]}, {0.f, screen_size[1]}}});
} }
template <typename Event> template <typename Event>
bool on_event(Event const & event) bool on_event(Event const & event)
{ {
apply(state, event);
return on_event_impl(event, root.get()); 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> template <typename Event>
bool on_event_impl(Event const & event, component * element) bool on_event_impl(Event const & event, component * element)
{ {
@ -72,8 +56,9 @@ namespace psemek::ui::impl
return element->on_event(event); return element->on_event(event);
} }
void animate(float dt) void update(float dt)
{ {
delayed_queue.pump();
animate_impl(dt, root.get()); animate_impl(dt, root.get());
} }
@ -147,9 +132,9 @@ namespace psemek::ui::impl
return impl().on_event(event); 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) void controller::draw(renderer & renderer)