psemek/libs/ui/source/controller.cpp

277 lines
5.9 KiB
C++

#include <psemek/ui/controller.hpp>
#include <psemek/ui/painter_impl.hpp>
#include <psemek/ui/box_shape.hpp>
#include <psemek/gfx/gl.hpp>
#include <psemek/util/recursive.hpp>
#include <psemek/geom/camera.hpp>
namespace psemek::ui
{
namespace
{
struct root_proxy
: element
{
children_range children() const override
{
return children_range{&root_raw, &root_raw + 1};
}
struct shape const & shape() const override
{
return shape_;
}
using element::reshape;
void reshape(geom::box<float, 2> const & bbox) override
{
shape_.box = bbox;
if (root)
root->reshape(bbox);
if (on_reshape)
post(on_reshape);
}
geom::box<float, 2> size_constraints() const override
{
return root->size_constraints();
}
void draw(painter &) const override {}
std::shared_ptr<element> root;
element * root_raw = nullptr;
box_shape shape_;
std::function<void()> on_reshape;
};
}
struct controller::impl
{
async::event_loop * loop;
painter_impl painter;
std::shared_ptr<root_proxy> root;
std::optional<geom::point<int, 2>> mouse;
float hint_delay = 0.f;
float hint_timer = 0.f;
util::function<void(element *)> on_hint;
element * hinted_element = nullptr;
std::optional<std::string> hint_called = std::nullopt;
impl(async::event_loop * loop);
template <typename E>
bool event(E const & e);
std::pair<bool, element *> event(mouse_move const & e);
element * hint() const;
void update(float dt);
};
controller::impl::impl(async::event_loop * loop)
: loop(loop)
, root{std::make_shared<root_proxy>()}
{
root->set_loop(loop);
root->on_reshape = [this]{
if (mouse)
event(mouse_move{*mouse});
};
}
template <typename E>
bool controller::impl::event(E const & e)
{
auto visitor = util::recursive([&](auto && self, element * elem) -> bool {
if (elem->hidden()) return false;
auto cs = elem->children();
for (auto it = cs.rbegin(); it != cs.rend(); ++it)
if (*it && self(*it)) return true;
if (elem->enabled() && elem->on_event(e)) return true;
return false;
});
if (root)
return visitor(root.get());
return false;
}
std::pair<bool, element *> controller::impl::event(mouse_move const & e)
{
element * hinted = nullptr;
auto m = geom::cast<float>(e.position);
auto visitor = util::recursive([&](auto && self, element * elem) -> bool {
if (elem->hidden()) return false;
auto cs = elem->children();
for (auto it = cs.rbegin(); it != cs.rend(); ++it)
if (*it && self(*it)) return true;
if (elem->enabled())
{
if (elem->hint() && elem->shape().contains(m) && !hinted)
hinted = elem;
if (elem->on_event(e))
return true;
}
return false;
});
bool result = false;
if (root)
result = visitor(root.get());
return {result, hinted};
}
void controller::impl::update(float dt)
{
auto visitor = util::recursive([dt](auto && self, element * elem) -> void {
if (elem->enabled()) elem->update(dt);
for (auto c : elem->children())
if (c) self(c);
});
if (root)
visitor(root.get());
}
controller::controller(async::event_loop * loop)
: pimpl_{make_impl(loop)}
{}
async::event_loop * controller::loop() const
{
return impl().loop;
}
controller::~controller() = default;
std::shared_ptr<element> controller::set_root(std::shared_ptr<element> r)
{
auto old = std::move(impl().root->root);
if (old)
old->set_parent(nullptr);
impl().root->root = std::move(r);
impl().root->root_raw = impl().root->root.get();
if (impl().root->root)
impl().root->root->set_parent(impl().root.get());
impl().root->reshape();
return old;
}
element * controller::root()
{
return impl().root->root.get();
}
void controller::set_hint_delay(float seconds)
{
impl().hint_delay = seconds;
}
void controller::on_hint(util::function<void(element *)> callback)
{
impl().on_hint = std::move(callback);
}
void controller::reshape(geom::box<float, 2> const & shape)
{
impl().root->reshape(shape);
}
bool controller::event(mouse_move const & e)
{
impl().mouse = e.position;
auto [ result, new_hinted_element ] = impl().event(e);
if (new_hinted_element != impl().hinted_element)
{
if (impl().hint_called && impl().on_hint)
impl().on_hint(nullptr);
impl().hint_timer = 0.f;
impl().hint_called = std::nullopt;
}
impl().hinted_element = new_hinted_element;
return result;
}
bool controller::event(mouse_click const & e)
{
return impl().event(e);
}
bool controller::event(mouse_wheel const & e)
{
return impl().event(e);
}
bool controller::event(key_press const & e)
{
return impl().event(e);
}
bool controller::event(text_input const & e)
{
return impl().event(e);
}
void controller::update(float dt)
{
impl().update(dt);
if (impl().hinted_element && (!impl().hint_called || *impl().hint_called != *impl().hinted_element->hint()))
{
impl().hint_timer += dt;
if (impl().hint_timer >= impl().hint_delay)
{
if (impl().on_hint)
impl().on_hint(impl().hinted_element);
impl().hint_called = *impl().hinted_element->hint();
}
}
}
void controller::render(gfx::render_target const & rt)
{
impl().painter.start_frame(geom::cast<float>(rt.viewport));
auto visitor = util::recursive([&](auto && self, element * elem) -> void {
if (elem->hidden()) return;
bool const visible = !(impl().painter.current_bbox() & elem->shape().bbox()).empty();
if (visible)
elem->draw(impl().painter);
for (auto c : elem->children())
if (c) self(c);
if (visible)
elem->post_draw(impl().painter);
});
visitor(impl().root.get());
rt.bind();
gl::Enable(gl::DEPTH_TEST);
gl::DepthFunc(gl::LEQUAL);
gl::Enable(gl::BLEND);
gl::BlendFunc(gl::SRC_ALPHA, gl::ONE_MINUS_SRC_ALPHA);
gl::Disable(gl::CULL_FACE);
impl().painter.render(geom::window_camera{rt.viewport[0].length(), rt.viewport[1].length()}.transform());
}
}