From 3760741ba65ab2c5f513001d48f0b44ab783bb18 Mon Sep 17 00:00:00 2001 From: lisyarus Date: Sun, 1 May 2022 13:16:48 +0300 Subject: [PATCH] Refactor ui::selector to support selector hierarchies --- libs/ui/include/psemek/ui/selector.hpp | 24 +++-- libs/ui/source/selector.cpp | 139 +++++++++++++++++++------ 2 files changed, 121 insertions(+), 42 deletions(-) diff --git a/libs/ui/include/psemek/ui/selector.hpp b/libs/ui/include/psemek/ui/selector.hpp index c2b55037..9f283f80 100644 --- a/libs/ui/include/psemek/ui/selector.hpp +++ b/libs/ui/include/psemek/ui/selector.hpp @@ -1,9 +1,10 @@ #pragma once #include -#include #include +#include + namespace psemek::ui { @@ -13,7 +14,7 @@ namespace psemek::ui selector(); ~selector() override; - children_range children() const override { return container_.children(); } + children_range children() const override { return children_range_; } struct shape const & shape() const override { return shape_; } @@ -26,9 +27,10 @@ namespace psemek::ui virtual std::size_t size() const; virtual void resize(std::size_t size); - virtual void add(std::shared_ptr child, bool submenu = false); + virtual std::size_t add(std::shared_ptr child); virtual std::shared_ptr get(std::size_t index) const; - virtual bool submenu(std::size_t index) const; + virtual void set_submenu(std::size_t index, std::shared_ptr submenu); + virtual std::shared_ptr submenu(std::size_t index) const; virtual void clear(); virtual geom::interval y_range(std::size_t index) const; @@ -37,7 +39,6 @@ namespace psemek::ui virtual std::optional selected() const { return selected_; } virtual void on_selected(callback_type callback); - virtual void on_submenu(callback_type callback); virtual callback_type on_selected() const; @@ -49,17 +50,24 @@ namespace psemek::ui // extra distance between successive elements virtual int y_extra() const { return 0; } + virtual void on_submenu(std::size_t index); + + virtual void release_submenu(); + std::vector> child_boxes_; private: - container_impl container_; + std::vector> children_; + std::vector children_range_; + box_shape shape_; - std::vector submenu_; + std::vector> submenu_; + selector * active_submenu_ = nullptr; + float active_submenu_y_ = 0.f; std::optional selected_; callback_type callback_; - callback_type submenu_callback_; }; bool spawn(element * root, std::shared_ptr selector, geom::point const & position); diff --git a/libs/ui/source/selector.cpp b/libs/ui/source/selector.cpp index 62aee842..92e08451 100644 --- a/libs/ui/source/selector.cpp +++ b/libs/ui/source/selector.cpp @@ -3,6 +3,8 @@ #include #include +#include + #include #include @@ -11,7 +13,7 @@ namespace psemek::ui { selector::selector() - : container_(this) + : children_range_(1, nullptr) {} selector::~selector() @@ -22,7 +24,6 @@ namespace psemek::ui void selector::reshape(geom::box const & box) { shape_.box = box; - child_boxes_.clear(); auto st = merged_own_style(); @@ -30,8 +31,9 @@ namespace psemek::ui float y = box[1].min; - for (auto c : children()) + for (std::size_t i = 0; i < children_.size(); ++i) { + auto c = children_[i].get(); float y_start = y; y += (*st->inner_margin)[1]; @@ -44,10 +46,21 @@ namespace psemek::ui y += (*st->inner_margin)[1]; - child_boxes_.push_back({{box[0], {y_start, y}}}); + child_boxes_[i] = {{box[0], {y_start, y}}}; y += y_extra() * *st->scale; } + + if (active_submenu_) + { + auto sc = active_submenu_->size_constraints(); + + geom::box box{{{0.f, sc[0].min}, {0.f, sc[1].min}}}; + box[0] += shape_.box[0].max; + box[1] += active_submenu_y_; + + active_submenu_->reshape(box); + } } geom::box selector::size_constraints() const @@ -75,9 +88,9 @@ namespace psemek::ui x_range += (*st->inner_margin)[0] * 2.f; - y_sum += (*st->inner_margin)[1] * 2.f * container_.size(); - if (!container_.empty()) - y_sum += *st->scale * y_extra() * static_cast(container_.size() - 1); + y_sum += (*st->inner_margin)[1] * 2.f * size(); + if (!children_.empty()) + y_sum += *st->scale * y_extra() * static_cast(size() - 1); return {{x_range, {y_sum, y_sum}}}; } @@ -96,12 +109,12 @@ namespace psemek::ui } } - if (new_selected && new_selected != selected_ && submenu_[*new_selected] && submenu_callback_) - post([cb = submenu_callback_, i = *new_selected]{ cb(i); }); + if (new_selected && new_selected != selected_) + on_submenu(*new_selected); selected_ = new_selected; - return true; + return false; } bool selector::on_event(mouse_click const & e) @@ -118,42 +131,50 @@ namespace psemek::ui std::size_t selector::size() const { - return container_.size(); + return children_.size(); } void selector::resize(std::size_t size) { - container_.resize(size); + children_.resize(size); submenu_.resize(size); child_boxes_.resize(size); + children_range_.resize(size + 1); } - void selector::add(std::shared_ptr child, bool submenu) + std::size_t selector::add(std::shared_ptr child) { - std::size_t index = container_.add(child); - if (index >= submenu_.size()) - { - submenu_.resize(index + 1); - child_boxes_.resize(submenu_.size()); - } - submenu_[index] = submenu; + children_.push_back(child); + submenu_.push_back(nullptr); + child_boxes_.emplace_back(); + children_range_.push_back(child.get()); + child->set_parent(this); + + return children_.size() - 1; } std::shared_ptr selector::get(std::size_t index) const { - return container_.get(index); + return children_[index]; } - bool selector::submenu(std::size_t index) const + void selector::set_submenu(std::size_t index, std::shared_ptr submenu) + { + submenu_[index] = std::move(submenu); + } + + std::shared_ptr selector::submenu(std::size_t index) const { return submenu_[index]; } void selector::clear() { - container_.clear(); + release_children(); + children_.clear(); submenu_.clear(); child_boxes_.clear(); + children_range_.assign(1, nullptr); } geom::interval selector::y_range(std::size_t index) const @@ -166,36 +187,83 @@ namespace psemek::ui callback_ = std::move(callback); } - void selector::on_submenu(callback_type callback) - { - submenu_callback_ = std::move(callback); - } - selector::callback_type selector::on_selected() const { return callback_; } + void selector::on_submenu(std::size_t index) + { + release_submenu(); + + auto new_submenu = submenu_[index].get(); + if (new_submenu) + { + children_range_[0] = new_submenu; + active_submenu_ = new_submenu; + new_submenu->set_parent(this); + active_submenu_y_ = child_boxes_[index][1].min; + post_reshape(); + } + } + + void selector::release_submenu() + { + if (active_submenu_) + { + active_submenu_->release_submenu(); + active_submenu_->set_parent(nullptr); + active_submenu_ = nullptr; + children_range_[0] = nullptr; + } + } + + namespace + { + + struct opaque_event_interceptor + : event_interceptor + { + bool transparent() const override { return false; } + }; + + } + bool spawn(element * root, std::shared_ptr selector, geom::point const & position) { ui::screen * screen = find_last_parent_of_type(root); if (!screen) return false; - auto event_interceptor = std::make_shared(); + auto event_interceptor= std::make_shared(); auto positioner = std::make_shared(); - - event_interceptor->set_child(positioner); positioner->set_child(selector); + event_interceptor->set_child(positioner); - auto close = [event_interceptor = event_interceptor.get(), selector = selector.get()]{ + auto close = [selector_root = event_interceptor.get(), selector = selector.get()]{ selector->on_selected([](std::size_t){}); - auto p = dynamic_cast(event_interceptor->parent()); + auto p = dynamic_cast(selector_root->parent()); if (p) - p->remove_child(event_interceptor); + p->remove_child(selector_root); }; + auto patch_callback = [&](struct selector * target){ + target->on_selected([close, cb = target->on_selected()](std::size_t index){ + close(); + if (cb) + cb(index); + }); + }; + + auto patch_callback_recursive = util::recursive([&](auto && self, struct selector * target) -> void { + patch_callback(target); + for (std::size_t i = 0; i < target->size(); ++i) + if (auto s = target->submenu(i)) + self(s.get()); + }); + patch_callback_recursive(selector.get()); + selector->on_selected([close, cb = selector->on_selected()](std::size_t index){ close(); if (cb) @@ -222,7 +290,10 @@ namespace psemek::ui return false; }); + event_interceptor->on_mouse_move([](mouse_move const &){ return true; }); + send_fake_mouse_move_event(event_interceptor.get(), true); + positioner->set_position(position, positioner::x_align::left, positioner::y_align::top); screen->add_child(event_interceptor, screen::x_policy::fill, screen::y_policy::fill); return true;