#include #include namespace psemek::ui { static float const inf = std::numeric_limits::infinity(); 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 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).get() == c) return true; return false; } std::shared_ptr 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).get() == c) { return remove(i, j); } } } return nullptr; } void grid_layout::set_row_count(std::size_t count) { set_size(count, std::max(1, column_count())); } void grid_layout::set_column_count(std::size_t count) { set_size(std::max(1, 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(); for (std::size_t i = 0; i < old_row_count; ++i) { for (std::size_t j = 0; j < old_column_count; ++j) { if (i >= rows || j >= columns) set(i, j, nullptr); } } 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(); } std::shared_ptr grid_layout::get(std::size_t i, std::size_t j) const { return children_(i, j); } std::shared_ptr grid_layout::set(std::size_t i, std::size_t j, std::shared_ptr 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 grid_layout::remove(std::size_t i, std::size_t j) { return set(i, j, nullptr); } void grid_layout::add_row() { set_row_count(row_count() + 1); } void grid_layout::add_row(std::shared_ptr c) { set_row_count(row_count() + 1); set(row_count() - 1, 0, c); } void grid_layout::add_column() { set_column_count(column_count() + 1); } void grid_layout::add_column(std::shared_ptr c) { set_column_count(column_count() + 1); set(0, column_count() - 1, c); } void grid_layout::set_outer_margin(bool value) { outer_margin_ = value; post_reshape(); } template static std::vector> cell_sizes(util::ndarray, 2> const & children) { std::vector> result(children.dim(Dimension), {0.f, inf}); for (auto idx : children.indices()) { auto c = children(idx); if (!c) continue; result[idx[Dimension]] &= c->size_constraints()[1 - Dimension]; } return result; } template static std::vector> cell_sizes_constrained(util::ndarray, 2> const & children, std::vector> const & other_dimension_shapes) { std::vector> result(children.dim(Dimension), {0.f, inf}); for (auto idx : children.indices()) { auto c = children(idx); if (!c) continue; math::interval constraint; if constexpr (Dimension == 0) { constraint = c->height_constraints(other_dimension_shapes[idx[1]].length()); } else if constexpr (Dimension == 1) { constraint = c->width_constraints(other_dimension_shapes[idx[0]].length()); } result[idx[Dimension]] &= constraint; } return result; } static math::interval combine(std::vector> const & sizes, std::vector const & weights, float margin, bool outer) { math::interval result{0.f, 0.f}; math::interval unit{0.f, inf}; float sum_weights = 0.f; for (std::size_t i = 0; i < sizes.size(); ++i) { bool const minimized = (weights[i] == 0.f); if (minimized) result += sizes[i].min; else unit &= math::interval{sizes[i].min / weights[i], sizes[i].max / weights[i]}; sum_weights += weights[i]; } // prevent (inf * 0.f) if (sum_weights > 0.f) { result.min += unit.min * sum_weights; result.max += unit.max * sum_weights; } int const margin_count = (std::max(1, sizes.size()) + (outer ? 1 : -1)); result += margin_count * margin; return result; } static std::vector> allocate(std::vector> const & sizes, std::vector const & weights, float available, float margin, bool outer) { available -= margin * (static_cast(sizes.size()) + (outer ? 1 : -1)); float const sum_weights = std::accumulate(weights.begin(), weights.end(), 0.f); std::vector> result(sizes.size()); // Allocate minimized for (std::size_t i = 0; i < sizes.size(); ++i) { if (weights[i] == 0.f) { result[i] = {0.f, sizes[i].min}; available -= sizes[i].min; } } available = std::max(available, 0.f); // Allocate scalable for (std::size_t i = 0; i < sizes.size(); ++i) { if (weights[i] == 0.f) continue; float const column_width = available * weights[i] / sum_weights; result[i] = {0.f, column_width}; } return result; } static void move(std::vector> & shapes, math::interval const & available, float margin, bool outer) { // Move cells one after another for (std::size_t i = 0; i < shapes.size(); ++i) { if (i == 0) shapes[i] += available.min + (outer ? margin : 0.f); else shapes[i] += shapes[i - 1].max + margin; } } math::box grid_layout::size_constraints() const { auto st = merged_own_style(); auto row_sizes = cell_sizes<0>(children_); auto column_sizes = cell_sizes<1>(children_); auto height = combine(row_sizes, row_weight_, *st->outer_margin, outer_margin_); auto width = combine(column_sizes, column_weight_, *st->outer_margin, outer_margin_); return {{width, height}}; } math::interval grid_layout::width_constraints(float height) const { auto st = merged_own_style(); auto row_sizes = cell_sizes<0>(children_); auto row_shapes = allocate(row_sizes, row_weight_, height, *st->outer_margin, outer_margin_); auto column_sizes = cell_sizes_constrained<1>(children_, row_shapes); return combine(column_sizes, column_weight_, *st->outer_margin, outer_margin_); } math::interval grid_layout::height_constraints(float width) const { auto st = merged_own_style(); auto column_sizes = cell_sizes<1>(children_); auto column_shapes = allocate(column_sizes, column_weight_, width, *st->outer_margin, outer_margin_); auto row_sizes = cell_sizes_constrained<0>(children_, column_shapes); return combine(row_sizes, row_weight_, *st->outer_margin, outer_margin_); } void grid_layout::reshape(math::box const & bbox) { shape_.box = bbox; auto st = merged_own_style(); if (width_first_) { auto column_sizes = cell_sizes<1>(children_); column_shape_ = allocate(column_sizes, column_weight_, bbox[0].length(), *st->outer_margin, outer_margin_); auto row_sizes = cell_sizes_constrained<0>(children_, column_shape_); row_shape_ = allocate(row_sizes, row_weight_, bbox[1].length(), *st->outer_margin, outer_margin_); } else { auto row_sizes = cell_sizes<0>(children_); row_shape_ = allocate(row_sizes, row_weight_, bbox[1].length(), *st->outer_margin, outer_margin_); auto column_sizes = cell_sizes_constrained<1>(children_, row_shape_); column_shape_ = allocate(column_sizes, column_weight_, bbox[0].length(), *st->outer_margin, outer_margin_); } move(row_shape_, bbox[1], *st->outer_margin, outer_margin_); move(column_shape_, bbox[0], *st->outer_margin, outer_margin_); // Apply calculated shapes 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]}}); } } } grid_layout::~grid_layout() { release_children(); } }