356 lines
7.6 KiB
C++
356 lines
7.6 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/math/camera.hpp>
|
|
#include <psemek/math/contains.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(math::box<float, 2> const & bbox) override
|
|
{
|
|
shape_.box = bbox;
|
|
if (root)
|
|
root->reshape(bbox);
|
|
if (on_reshape)
|
|
post(on_reshape);
|
|
}
|
|
|
|
math::box<float, 2> size_constraints() const override
|
|
{
|
|
return root->size_constraints();
|
|
}
|
|
|
|
bool transparent() const override
|
|
{
|
|
return true;
|
|
}
|
|
|
|
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::weak_ptr<element> focused;
|
|
std::optional<math::point<int, 2>> mouse;
|
|
float hint_delay = 0.f;
|
|
float hint_timer = 0.f;
|
|
util::function<void(element *)> on_hint;
|
|
std::optional<std::weak_ptr<element>> hinted_element = std::nullopt;
|
|
std::optional<std::string> hint_called = std::nullopt;
|
|
util::function<void(element *, struct painter &)> post_draw_effect;
|
|
|
|
impl(async::event_loop * loop);
|
|
|
|
template <typename E>
|
|
element * event(E const & e, bool use_event_bbox = false);
|
|
|
|
element * hinted() 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}, true);
|
|
};
|
|
}
|
|
|
|
template <typename E>
|
|
element * controller::impl::event(E const & e, bool use_event_bbox)
|
|
{
|
|
auto visitor = util::recursive([&](auto && self, element * elem) -> element * {
|
|
if (elem->hidden()) return nullptr;
|
|
|
|
if (!use_event_bbox || math::contains(elem->events_bbox(), math::cast<float>(*mouse)))
|
|
{
|
|
auto cs = elem->children();
|
|
for (auto it = cs.rbegin(); it != cs.rend(); ++it)
|
|
if (*it)
|
|
if (auto p = self(*it))
|
|
return p;
|
|
}
|
|
|
|
if (elem->enabled() && elem->on_event(e)) return elem;
|
|
return nullptr;
|
|
});
|
|
|
|
if (auto p = focused.lock())
|
|
{
|
|
if (p->focused())
|
|
{
|
|
auto result = p->on_event(e);
|
|
if (!p->focused())
|
|
focused.reset();
|
|
if (result)
|
|
return p.get();
|
|
}
|
|
else
|
|
focused.reset();
|
|
}
|
|
|
|
if (root)
|
|
if (auto result = visitor(root.get()))
|
|
{
|
|
if (!focused.lock() && result->focused())
|
|
focused = result->weak_from_this();
|
|
}
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
element * controller::impl::hinted() const
|
|
{
|
|
if (!mouse)
|
|
return nullptr;
|
|
|
|
auto m = math::cast<float>(*mouse);
|
|
auto visitor = util::recursive([&](auto && self, element * elem) -> element * {
|
|
if (elem->hidden()) return nullptr;
|
|
|
|
auto cs = elem->children();
|
|
for (auto it = cs.rbegin(); it != cs.rend(); ++it)
|
|
if (*it)
|
|
if (auto p = self(*it))
|
|
return p;
|
|
|
|
if (elem->enabled() && elem->shape().contains(m) && !elem->transparent())
|
|
return elem;
|
|
|
|
return nullptr;
|
|
});
|
|
|
|
if (root)
|
|
if (auto result = visitor(root.get()); result && result->hint())
|
|
return result;
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
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::set_post_draw_effect(util::function<void(element *, painter &)> effect)
|
|
{
|
|
impl().post_draw_effect = std::move(effect);
|
|
}
|
|
|
|
void controller::reshape(math::box<float, 2> const & shape)
|
|
{
|
|
impl().root->reshape(shape);
|
|
}
|
|
|
|
bool controller::event(mouse_move const & e)
|
|
{
|
|
impl().mouse = e.position;
|
|
auto result = impl().event(e, true);
|
|
|
|
if (impl().hinted_element)
|
|
{
|
|
auto p = impl().hinted_element->lock();
|
|
if (!p)
|
|
{
|
|
if (impl().hint_called && impl().on_hint)
|
|
impl().on_hint(nullptr);
|
|
impl().hinted_element = std::nullopt;
|
|
impl().hint_timer = 0.f;
|
|
impl().hint_called = std::nullopt;
|
|
}
|
|
}
|
|
|
|
auto new_hinted_element = impl().hinted();
|
|
|
|
std::shared_ptr<element> old_hinted_element;
|
|
if (impl().hinted_element)
|
|
old_hinted_element = impl().hinted_element->lock();
|
|
|
|
if (new_hinted_element != old_hinted_element.get())
|
|
{
|
|
if (impl().hint_called && impl().on_hint)
|
|
impl().on_hint(nullptr);
|
|
impl().hint_timer = 0.f;
|
|
impl().hint_called = std::nullopt;
|
|
|
|
if (new_hinted_element)
|
|
impl().hinted_element = new_hinted_element->weak_from_this();
|
|
else
|
|
impl().hinted_element = std::nullopt;
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
bool controller::event(mouse_click const & e)
|
|
{
|
|
return impl().event(e, static_cast<bool>(impl().mouse));
|
|
}
|
|
|
|
bool controller::event(mouse_wheel const & e)
|
|
{
|
|
return impl().event(e, static_cast<bool>(impl().mouse));
|
|
}
|
|
|
|
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);
|
|
|
|
std::shared_ptr<element> p;
|
|
|
|
if (impl().hinted_element)
|
|
{
|
|
p = impl().hinted_element->lock();
|
|
|
|
if (!p)
|
|
{
|
|
if (impl().hint_called && impl().on_hint)
|
|
impl().on_hint(nullptr);
|
|
impl().hinted_element = std::nullopt;
|
|
impl().hint_timer = 0.f;
|
|
impl().hint_called = std::nullopt;
|
|
}
|
|
}
|
|
|
|
if (p && (!impl().hint_called || *impl().hint_called != *p->hint()))
|
|
{
|
|
impl().hint_timer += dt;
|
|
if (impl().hint_timer >= impl().hint_delay)
|
|
{
|
|
if (impl().on_hint)
|
|
impl().on_hint(p.get());
|
|
impl().hint_called = *p->hint();
|
|
}
|
|
}
|
|
}
|
|
|
|
void controller::render(gfx::render_target const & rt)
|
|
{
|
|
impl().painter.start_frame(math::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);
|
|
if (impl().post_draw_effect)
|
|
impl().post_draw_effect(elem, 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(math::window_camera{rt.viewport[0].length(), rt.viewport[1].length()}.transform());
|
|
}
|
|
|
|
}
|