Implement ui grid layout
This commit is contained in:
parent
a139067a3e
commit
1e9eb51911
6 changed files with 299 additions and 23 deletions
|
|
@ -7,10 +7,12 @@
|
|||
#include <psemek/ui/default_element_factory.hpp>
|
||||
#include <psemek/ui/font.hpp>
|
||||
#include <psemek/ui/frame.hpp>
|
||||
#include <psemek/ui/grid_layout.hpp>
|
||||
|
||||
#include <psemek/async/event_loop.hpp>
|
||||
|
||||
#include <psemek/util/recursive.hpp>
|
||||
#include <psemek/util/to_string.hpp>
|
||||
|
||||
#include <psemek/geom/camera.hpp>
|
||||
|
||||
|
|
@ -43,30 +45,35 @@ struct ui_example
|
|||
|
||||
auto screen = element_factory.make_screen();
|
||||
|
||||
// auto button = element_factory.make_button("Test");
|
||||
// button->on_click([button = button.get()]{
|
||||
// button->label()->set_text(std::string(button->label()->text()) + "0");
|
||||
// });
|
||||
// screen->add(button, ui::screen::x_policy::center, ui::screen::y_policy::center);
|
||||
auto grid = element_factory.make_grid_layout();
|
||||
grid->set_size(1, 1);
|
||||
auto button = element_factory.make_button("0,0");
|
||||
grid->set(0, 0, button);
|
||||
button->on_click(util::recursive([this, grid](auto && self) -> void {
|
||||
while (true)
|
||||
{
|
||||
for (std::size_t i = 0; i < grid->row_count(); ++i)
|
||||
{
|
||||
for (std::size_t j = 0; j < grid->column_count(); ++j)
|
||||
{
|
||||
if (!grid->get(i, j))
|
||||
{
|
||||
auto button = element_factory.make_button(util::to_string(i, ",", j));
|
||||
grid->set(i, j, button);
|
||||
button->on_click(self);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// auto text = element_factory.make_label(lorem_ipsum);
|
||||
// screen->add(text, ui::screen::x_policy::center, ui::screen::y_policy::center);
|
||||
if (grid->row_count() <= grid->column_count())
|
||||
grid->set_row_count(grid->row_count() + 1);
|
||||
else
|
||||
grid->set_column_count(grid->column_count() + 1);
|
||||
}
|
||||
}));
|
||||
|
||||
auto text = element_factory.make_label(to_be);
|
||||
text->set_overflow(ui::label::overflow_mode::ellipsis);
|
||||
text->set_multiline(ui::label::multiline_mode::minimize_lines);
|
||||
auto frame = element_factory.make_frame();
|
||||
frame->set_child(text);
|
||||
screen->add_child(frame, ui::screen::x_policy::center, ui::screen::y_policy::center);
|
||||
|
||||
auto updater = util::recursive([this, text, i = 0](auto && self) mutable -> void {
|
||||
text->set_halign(static_cast<ui::label::halignment>(i % 3));
|
||||
text->set_valign(static_cast<ui::label::valignment>(i / 3));
|
||||
|
||||
i = (i + 1) % 9;
|
||||
loop.post_at(async::clock::now() + std::chrono::milliseconds{1000}, self);
|
||||
});
|
||||
updater();
|
||||
screen->add_child(grid, ui::screen::x_policy::center, ui::screen::y_policy::center);
|
||||
|
||||
ui_controller.set_root(std::move(screen));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@
|
|||
#include <psemek/ui/label.hpp>
|
||||
#include <psemek/ui/frame.hpp>
|
||||
#include <psemek/ui/screen.hpp>
|
||||
#include <psemek/ui/grid_layout.hpp>
|
||||
|
||||
#include <psemek/util/pimpl.hpp>
|
||||
|
||||
|
|
@ -23,6 +24,7 @@ namespace psemek::ui
|
|||
std::shared_ptr<label> make_label(std::string text);
|
||||
std::shared_ptr<frame> make_frame();
|
||||
std::shared_ptr<screen> make_screen();
|
||||
std::shared_ptr<grid_layout> make_grid_layout();
|
||||
|
||||
private:
|
||||
psemek_declare_pimpl
|
||||
|
|
|
|||
52
libs/ui/include/psemek/ui/grid_layout.hpp
Normal file
52
libs/ui/include/psemek/ui/grid_layout.hpp
Normal file
|
|
@ -0,0 +1,52 @@
|
|||
#pragma once
|
||||
|
||||
#include <psemek/ui/element.hpp>
|
||||
#include <psemek/ui/container.hpp>
|
||||
#include <psemek/ui/box_shape.hpp>
|
||||
|
||||
#include <psemek/util/array.hpp>
|
||||
|
||||
namespace psemek::ui
|
||||
{
|
||||
|
||||
struct grid_layout
|
||||
: container
|
||||
{
|
||||
children_range children() const override;
|
||||
|
||||
virtual bool add_child(std::shared_ptr<element> c) override;
|
||||
virtual bool has_child(element * c) const override;
|
||||
virtual std::shared_ptr<element> remove_child(element * c) override;
|
||||
|
||||
virtual std::size_t row_count() const { return children_.dim(0); }
|
||||
virtual std::size_t column_count() const { return children_.dim(1); }
|
||||
|
||||
virtual void set_row_count(std::size_t count);
|
||||
virtual void set_column_count(std::size_t count);
|
||||
virtual void set_size(std::size_t rows, std::size_t columns);
|
||||
|
||||
virtual void set_row_weight(std::size_t i, float w);
|
||||
virtual void set_column_weight(std::size_t i, float w);
|
||||
|
||||
virtual element * get(std::size_t i, std::size_t j) const;
|
||||
virtual std::shared_ptr<element> set(std::size_t i, std::size_t j, std::shared_ptr<element> c);
|
||||
virtual std::shared_ptr<element> remove(std::size_t i, std::size_t j);
|
||||
|
||||
geom::box<float, 2> size_constraints() const override;
|
||||
|
||||
struct shape const & shape() const override { return shape_; }
|
||||
void reshape(geom::box<float, 2> const & bbox) override;
|
||||
|
||||
void draw(painter &) const override {}
|
||||
|
||||
private:
|
||||
util::array<std::shared_ptr<element>, 2> children_;
|
||||
std::vector<element *> children_range_;
|
||||
|
||||
std::vector<float> row_weight_;
|
||||
std::vector<float> column_weight_;
|
||||
|
||||
box_shape shape_;
|
||||
};
|
||||
|
||||
}
|
||||
|
|
@ -199,4 +199,9 @@ namespace psemek::ui
|
|||
return impl().create<screen>();
|
||||
}
|
||||
|
||||
std::shared_ptr<grid_layout> default_element_factory::make_grid_layout()
|
||||
{
|
||||
return impl().create<grid_layout>();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
211
libs/ui/source/grid_layout.cpp
Normal file
211
libs/ui/source/grid_layout.cpp
Normal file
|
|
@ -0,0 +1,211 @@
|
|||
#include <psemek/ui/grid_layout.hpp>
|
||||
|
||||
#include <numeric>
|
||||
|
||||
namespace psemek::ui
|
||||
{
|
||||
|
||||
element::children_range grid_layout::children() const
|
||||
{
|
||||
return children_range{children_range_.data(), children_range_.data() + children_range_.size()};
|
||||
}
|
||||
|
||||
bool grid_layout::add_child(std::shared_ptr<element> c)
|
||||
{
|
||||
for (std::size_t i = 0; i < row_count(); ++i)
|
||||
{
|
||||
for (std::size_t j = 0; j < column_count(); ++j)
|
||||
{
|
||||
if (!get(i, j))
|
||||
{
|
||||
set(i, j, std::move(c));
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool grid_layout::has_child(element * c) const
|
||||
{
|
||||
for (std::size_t i = 0; i < row_count(); ++i)
|
||||
for (std::size_t j = 0; j < column_count(); ++j)
|
||||
if (get(i, j) == c) return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
std::shared_ptr<element> grid_layout::remove_child(element * c)
|
||||
{
|
||||
for (std::size_t i = 0; i < row_count(); ++i)
|
||||
{
|
||||
for (std::size_t j = 0; j < column_count(); ++j)
|
||||
{
|
||||
if (get(i, j) == c)
|
||||
{
|
||||
return remove(i, j);
|
||||
}
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void grid_layout::set_row_count(std::size_t count)
|
||||
{
|
||||
set_size(count, column_count());
|
||||
}
|
||||
|
||||
void grid_layout::set_column_count(std::size_t count)
|
||||
{
|
||||
set_size(row_count(), count);
|
||||
}
|
||||
|
||||
void grid_layout::set_size(std::size_t rows, std::size_t columns)
|
||||
{
|
||||
std::size_t const old_row_count = row_count();
|
||||
std::size_t const old_column_count = column_count();
|
||||
|
||||
children_.resize({rows, columns});
|
||||
|
||||
children_range_.resize(rows * columns);
|
||||
for (std::size_t i = 0; i < rows; ++i)
|
||||
{
|
||||
for (std::size_t j = 0; j < columns; ++j)
|
||||
{
|
||||
children_range_[i * columns + j] = children_(i, j).get();
|
||||
}
|
||||
}
|
||||
|
||||
row_weight_.resize(rows);
|
||||
for (std::size_t i = old_row_count; i < rows; ++i)
|
||||
row_weight_[i] = 1.f;
|
||||
|
||||
column_weight_.resize(columns);
|
||||
for (std::size_t i = old_column_count; i < columns; ++i)
|
||||
column_weight_[i] = 1.f;
|
||||
|
||||
post_reshape();
|
||||
}
|
||||
|
||||
void grid_layout::set_row_weight(std::size_t i, float w)
|
||||
{
|
||||
row_weight_.at(i) = w;
|
||||
post_reshape();
|
||||
}
|
||||
|
||||
void grid_layout::set_column_weight(std::size_t i, float w)
|
||||
{
|
||||
column_weight_.at(i) = w;
|
||||
post_reshape();
|
||||
}
|
||||
|
||||
element * grid_layout::get(std::size_t i, std::size_t j) const
|
||||
{
|
||||
return children_(i, j).get();
|
||||
}
|
||||
|
||||
std::shared_ptr<element> grid_layout::set(std::size_t i, std::size_t j, std::shared_ptr<element> c)
|
||||
{
|
||||
auto old = std::move(children_(i, j));
|
||||
if (old) old->set_parent(nullptr);
|
||||
if (c) c->set_parent(this);
|
||||
children_range_[i * column_count() + j] = c.get();
|
||||
children_(i, j) = std::move(c);
|
||||
post_reshape();
|
||||
return old;
|
||||
}
|
||||
|
||||
std::shared_ptr<element> grid_layout::remove(std::size_t i, std::size_t j)
|
||||
{
|
||||
return set(i, j, nullptr);
|
||||
}
|
||||
|
||||
geom::box<float, 2> grid_layout::size_constraints() const
|
||||
{
|
||||
auto st = style();
|
||||
if (!st) return element::size_constraints();
|
||||
|
||||
static float const inf = std::numeric_limits<float>::infinity();
|
||||
|
||||
std::vector<geom::interval<float>> row_size(row_count(), {0.f, inf});
|
||||
std::vector<geom::interval<float>> column_size(column_count(), {0.f, inf});
|
||||
|
||||
for (std::size_t i = 0; i < row_count(); ++i)
|
||||
{
|
||||
for (std::size_t j = 0; j < column_count(); ++j)
|
||||
{
|
||||
if (!get(i, j)) continue;
|
||||
|
||||
auto sc = get(i, j)->size_constraints();
|
||||
row_size[i] &= sc[1];
|
||||
column_size[j] &= sc[0];
|
||||
}
|
||||
}
|
||||
|
||||
geom::box<float, 2> result{{{0.f, 0.f}, {0.f, 0.f}}};
|
||||
|
||||
for (std::size_t i = 0; i < row_count(); ++i)
|
||||
{
|
||||
result[1].min += row_size[i].min;
|
||||
result[1].max += row_size[i].max;
|
||||
}
|
||||
|
||||
for (std::size_t i = 0; i < column_count(); ++i)
|
||||
{
|
||||
result[0].min += column_size[i].min;
|
||||
result[0].max += column_size[i].max;
|
||||
}
|
||||
|
||||
result[1] += (row_count() + 1) * st->outer_margin;
|
||||
result[0] += (column_count() + 1) * st->outer_margin;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
void grid_layout::reshape(geom::box<float, 2> const & bbox)
|
||||
{
|
||||
auto st = style();
|
||||
if (!st) return;
|
||||
|
||||
float const margin = st->outer_margin;
|
||||
|
||||
float const available_width = std::max(0.f, bbox[0].length() - margin * (1 + column_count()));
|
||||
float const available_height = std::max(0.f, bbox[1].length() - margin * (1 + row_count()));
|
||||
|
||||
float const row_weight_sum = std::accumulate(row_weight_.begin(), row_weight_.end(), 0.f);
|
||||
float const column_weight_sum = std::accumulate(column_weight_.begin(), column_weight_.end(), 0.f);
|
||||
|
||||
std::vector<geom::interval<float>> row_shape(row_count());
|
||||
std::vector<geom::interval<float>> column_shape(column_count());
|
||||
|
||||
for (std::size_t i = 0; i < row_count(); ++i)
|
||||
{
|
||||
float const row_height = available_height * row_weight_[i] / row_weight_sum;
|
||||
row_shape[i] = {margin, margin + row_height};
|
||||
if (i == 0)
|
||||
row_shape[i] += bbox[1].min;
|
||||
else
|
||||
row_shape[i] += row_shape[i - 1].max;
|
||||
}
|
||||
|
||||
for (std::size_t i = 0; i < column_count(); ++i)
|
||||
{
|
||||
float const column_width = available_width * column_weight_[i] / column_weight_sum;
|
||||
column_shape[i] = {margin, margin + column_width};
|
||||
if (i == 0)
|
||||
column_shape[i] += bbox[0].min;
|
||||
else
|
||||
column_shape[i] += column_shape[i - 1].max;
|
||||
}
|
||||
|
||||
for (std::size_t i = 0; i < row_count(); ++i)
|
||||
{
|
||||
for (std::size_t j = 0; j < column_count(); ++j)
|
||||
{
|
||||
if (!get(i, j)) continue;
|
||||
|
||||
get(i, j)->reshape({{column_shape[j], row_shape[i]}});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
1
todo.md
1
todo.md
|
|
@ -23,7 +23,6 @@
|
|||
* overflow checks for number parsers
|
||||
* recursive parsers
|
||||
* ui
|
||||
* grid layout element with equal cells
|
||||
* grid layout quadratic optimization
|
||||
* draggable window element
|
||||
* screen children sorting
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue