Refactor ui::selector to support selector hierarchies

This commit is contained in:
Nikita Lisitsa 2022-05-01 13:16:48 +03:00
parent 7b63614787
commit 3760741ba6
2 changed files with 121 additions and 42 deletions

View file

@ -1,9 +1,10 @@
#pragma once #pragma once
#include <psemek/ui/element.hpp> #include <psemek/ui/element.hpp>
#include <psemek/ui/container_impl.hpp>
#include <psemek/ui/box_shape.hpp> #include <psemek/ui/box_shape.hpp>
#include <vector>
namespace psemek::ui namespace psemek::ui
{ {
@ -13,7 +14,7 @@ namespace psemek::ui
selector(); selector();
~selector() override; ~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_; } struct shape const & shape() const override { return shape_; }
@ -26,9 +27,10 @@ namespace psemek::ui
virtual std::size_t size() const; virtual std::size_t size() const;
virtual void resize(std::size_t size); virtual void resize(std::size_t size);
virtual void add(std::shared_ptr<element> child, bool submenu = false); virtual std::size_t add(std::shared_ptr<element> child);
virtual std::shared_ptr<element> get(std::size_t index) const; virtual std::shared_ptr<element> 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<selector> submenu);
virtual std::shared_ptr<selector> submenu(std::size_t index) const;
virtual void clear(); virtual void clear();
virtual geom::interval<float> y_range(std::size_t index) const; virtual geom::interval<float> y_range(std::size_t index) const;
@ -37,7 +39,6 @@ namespace psemek::ui
virtual std::optional<std::size_t> selected() const { return selected_; } virtual std::optional<std::size_t> selected() const { return selected_; }
virtual void on_selected(callback_type callback); virtual void on_selected(callback_type callback);
virtual void on_submenu(callback_type callback);
virtual callback_type on_selected() const; virtual callback_type on_selected() const;
@ -49,17 +50,24 @@ namespace psemek::ui
// extra distance between successive elements // extra distance between successive elements
virtual int y_extra() const { return 0; } virtual int y_extra() const { return 0; }
virtual void on_submenu(std::size_t index);
virtual void release_submenu();
std::vector<geom::box<float, 2>> child_boxes_; std::vector<geom::box<float, 2>> child_boxes_;
private: private:
container_impl container_; std::vector<std::shared_ptr<element>> children_;
std::vector<element *> children_range_;
box_shape shape_; box_shape shape_;
std::vector<bool> submenu_; std::vector<std::shared_ptr<selector>> submenu_;
selector * active_submenu_ = nullptr;
float active_submenu_y_ = 0.f;
std::optional<std::size_t> selected_; std::optional<std::size_t> selected_;
callback_type callback_; callback_type callback_;
callback_type submenu_callback_;
}; };
bool spawn(element * root, std::shared_ptr<selector> selector, geom::point<float, 2> const & position); bool spawn(element * root, std::shared_ptr<selector> selector, geom::point<float, 2> const & position);

View file

@ -3,6 +3,8 @@
#include <psemek/ui/positioner.hpp> #include <psemek/ui/positioner.hpp>
#include <psemek/ui/event_interceptor.hpp> #include <psemek/ui/event_interceptor.hpp>
#include <psemek/util/recursive.hpp>
#include <psemek/geom/contains.hpp> #include <psemek/geom/contains.hpp>
#include <stdexcept> #include <stdexcept>
@ -11,7 +13,7 @@ namespace psemek::ui
{ {
selector::selector() selector::selector()
: container_(this) : children_range_(1, nullptr)
{} {}
selector::~selector() selector::~selector()
@ -22,7 +24,6 @@ namespace psemek::ui
void selector::reshape(geom::box<float, 2> const & box) void selector::reshape(geom::box<float, 2> const & box)
{ {
shape_.box = box; shape_.box = box;
child_boxes_.clear();
auto st = merged_own_style(); auto st = merged_own_style();
@ -30,8 +31,9 @@ namespace psemek::ui
float y = box[1].min; 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; float y_start = y;
y += (*st->inner_margin)[1]; y += (*st->inner_margin)[1];
@ -44,10 +46,21 @@ namespace psemek::ui
y += (*st->inner_margin)[1]; 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; y += y_extra() * *st->scale;
} }
if (active_submenu_)
{
auto sc = active_submenu_->size_constraints();
geom::box<float, 2> 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<float, 2> selector::size_constraints() const geom::box<float, 2> selector::size_constraints() const
@ -75,9 +88,9 @@ namespace psemek::ui
x_range += (*st->inner_margin)[0] * 2.f; x_range += (*st->inner_margin)[0] * 2.f;
y_sum += (*st->inner_margin)[1] * 2.f * container_.size(); y_sum += (*st->inner_margin)[1] * 2.f * size();
if (!container_.empty()) if (!children_.empty())
y_sum += *st->scale * y_extra() * static_cast<int>(container_.size() - 1); y_sum += *st->scale * y_extra() * static_cast<int>(size() - 1);
return {{x_range, {y_sum, y_sum}}}; 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_) if (new_selected && new_selected != selected_)
post([cb = submenu_callback_, i = *new_selected]{ cb(i); }); on_submenu(*new_selected);
selected_ = new_selected; selected_ = new_selected;
return true; return false;
} }
bool selector::on_event(mouse_click const & e) bool selector::on_event(mouse_click const & e)
@ -118,42 +131,50 @@ namespace psemek::ui
std::size_t selector::size() const std::size_t selector::size() const
{ {
return container_.size(); return children_.size();
} }
void selector::resize(std::size_t size) void selector::resize(std::size_t size)
{ {
container_.resize(size); children_.resize(size);
submenu_.resize(size); submenu_.resize(size);
child_boxes_.resize(size); child_boxes_.resize(size);
children_range_.resize(size + 1);
} }
void selector::add(std::shared_ptr<element> child, bool submenu) std::size_t selector::add(std::shared_ptr<element> child)
{ {
std::size_t index = container_.add(child); children_.push_back(child);
if (index >= submenu_.size()) submenu_.push_back(nullptr);
{ child_boxes_.emplace_back();
submenu_.resize(index + 1); children_range_.push_back(child.get());
child_boxes_.resize(submenu_.size()); child->set_parent(this);
}
submenu_[index] = submenu; return children_.size() - 1;
} }
std::shared_ptr<element> selector::get(std::size_t index) const std::shared_ptr<element> 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<selector> submenu)
{
submenu_[index] = std::move(submenu);
}
std::shared_ptr<selector> selector::submenu(std::size_t index) const
{ {
return submenu_[index]; return submenu_[index];
} }
void selector::clear() void selector::clear()
{ {
container_.clear(); release_children();
children_.clear();
submenu_.clear(); submenu_.clear();
child_boxes_.clear(); child_boxes_.clear();
children_range_.assign(1, nullptr);
} }
geom::interval<float> selector::y_range(std::size_t index) const geom::interval<float> selector::y_range(std::size_t index) const
@ -166,36 +187,83 @@ namespace psemek::ui
callback_ = std::move(callback); callback_ = std::move(callback);
} }
void selector::on_submenu(callback_type callback)
{
submenu_callback_ = std::move(callback);
}
selector::callback_type selector::on_selected() const selector::callback_type selector::on_selected() const
{ {
return callback_; 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> selector, geom::point<float, 2> const & position) bool spawn(element * root, std::shared_ptr<selector> selector, geom::point<float, 2> const & position)
{ {
ui::screen * screen = find_last_parent_of_type<struct screen>(root); ui::screen * screen = find_last_parent_of_type<struct screen>(root);
if (!screen) if (!screen)
return false; return false;
auto event_interceptor = std::make_shared<struct event_interceptor>(); auto event_interceptor= std::make_shared<opaque_event_interceptor>();
auto positioner = std::make_shared<struct positioner>(); auto positioner = std::make_shared<struct positioner>();
event_interceptor->set_child(positioner);
positioner->set_child(selector); 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){}); selector->on_selected([](std::size_t){});
auto p = dynamic_cast<struct screen *>(event_interceptor->parent()); auto p = dynamic_cast<struct screen *>(selector_root->parent());
if (p) 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){ selector->on_selected([close, cb = selector->on_selected()](std::size_t index){
close(); close();
if (cb) if (cb)
@ -222,7 +290,10 @@ namespace psemek::ui
return false; return false;
}); });
event_interceptor->on_mouse_move([](mouse_move const &){ return true; });
send_fake_mouse_move_event(event_interceptor.get(), true); send_fake_mouse_move_event(event_interceptor.get(), true);
positioner->set_position(position, positioner::x_align::left, positioner::y_align::top); 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); screen->add_child(event_interceptor, screen::x_policy::fill, screen::y_policy::fill);
return true; return true;