#include #include #include #include #include #include #include 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 const & bbox) override { shape_.box = bbox; if (root) root->reshape(bbox); if (on_reshape) post(on_reshape); } geom::box size_constraints() const override { return root->size_constraints(); } bool transparent() const override { return true; } void draw(painter &) const override {} std::shared_ptr root; element * root_raw = nullptr; box_shape shape_; std::function on_reshape; }; } struct controller::impl { async::event_loop * loop; painter_impl painter; std::shared_ptr root; std::weak_ptr focused; std::optional> mouse; float hint_delay = 0.f; float hint_timer = 0.f; util::function on_hint; std::optional> hinted_element = std::nullopt; std::optional hint_called = std::nullopt; util::function post_draw_effect; impl(async::event_loop * loop); template 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->set_loop(loop); root->on_reshape = [this]{ if (mouse) event(mouse_move{*mouse}, true); }; } template 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 || geom::contains(elem->events_bbox(), geom::cast(*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 = geom::cast(*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 controller::set_root(std::shared_ptr 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 callback) { impl().on_hint = std::move(callback); } void controller::set_post_draw_effect(util::function effect) { impl().post_draw_effect = std::move(effect); } void controller::reshape(geom::box 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 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(impl().mouse)); } bool controller::event(mouse_wheel const & e) { return impl().event(e, static_cast(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 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(geom::cast(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(geom::window_camera{rt.viewport[0].length(), rt.viewport[1].length()}.transform()); } }