#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(); 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(); } static std::pair>, std::vector>> row_column_sizes(util::array, 2> const & children) { std::vector> row_size(children.width(), {0.f, inf}); std::vector> column_size(children.height(), {0.f, inf}); for (std::size_t i = 0; i < children.width(); ++i) { for (std::size_t j = 0; j < children.height(); ++j) { auto c = children(i, j); if (!c) continue; auto sc = c->size_constraints(); row_size[i] &= sc[1]; column_size[j] &= sc[0]; } } return {std::move(row_size), std::move(column_size)}; } geom::box grid_layout::size_constraints() const { auto st = merged_own_style(); if (!st) return element::size_constraints(); auto row_column_size = row_column_sizes(children_); auto const & row_size = row_column_size.first; auto const & column_size = row_column_size.second; geom::box result{{{0.f, 0.f}, {0.f, 0.f}}}; geom::box unit = geom::box::full(); float sum_row_weights = 0.f; float sum_column_weights = 0.f; for (std::size_t i = 0; i < row_count(); ++i) { bool const minimized = (row_weight_[i] == 0.f); if (minimized) result[1] += row_size[i].min; else unit[1] &= geom::interval{row_size[i].min / row_weight_[i], row_size[i].max / row_weight_[i]}; sum_row_weights += row_weight_[i]; } for (std::size_t i = 0; i < column_count(); ++i) { bool const minimized = (column_weight_[i] == 0.f); if (minimized) result[0] += column_size[i].min; else unit[0] &= geom::interval{column_size[i].min / column_weight_[i], column_size[i].max / column_weight_[i]}; sum_column_weights += column_weight_[i]; } result[1].min += unit[1].min * sum_row_weights; result[1].max += unit[1].max * sum_row_weights; result[0].min += unit[0].min * sum_column_weights; result[0].max += unit[0].max * sum_column_weights; int const margin_row_count = (std::max(1, row_count()) + (outer_margin_ ? 1 : -1)); int const margin_column_count = (std::max(1, column_count()) + (outer_margin_ ? 1 : -1)); result[1] += margin_row_count * (*st->outer_margin); result[0] += margin_column_count * (*st->outer_margin); return result; } void grid_layout::reshape(geom::box const & bbox) { shape_.box = bbox; auto st = merged_own_style(); if (!st) return; float const margin = *st->outer_margin; auto row_column_size = row_column_sizes(children_); auto const & row_size = row_column_size.first; auto const & column_size = row_column_size.second; int const margin_row_count = (std::max(1, row_count()) + (outer_margin_ ? 1 : -1)); int const margin_column_count = (std::max(1, column_count()) + (outer_margin_ ? 1 : -1)); float available_height = std::max(0.f, bbox[1].length() - margin * margin_row_count); float available_width = std::max(0.f, bbox[0].length() - margin * margin_column_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); row_shape_.resize(row_count()); column_shape_.resize(column_count()); // First, allocate minimized rows for (std::size_t i = 0; i < row_count(); ++i) { if (row_weight_[i] == 0.f) { row_shape_[i] = {0.f, row_size[i].min}; available_height -= row_size[i].min; } } available_height = std::max(available_height, 0.f); // Next, allocate scalable rows for (std::size_t i = 0; i < row_count(); ++i) { if (row_weight_[i] == 0.f) continue; float const row_height = available_height * row_weight_[i] / row_weight_sum; row_shape_[i] = {0.f, row_height}; } // Finally, move rows one after another for (std::size_t i = 0; i < row_count(); ++i) { if (i == 0) row_shape_[i] += bbox[1].min + (outer_margin_ ? margin : 0.f); else row_shape_[i] += row_shape_[i - 1].max + margin; } // First, allocate minimized columns for (std::size_t i = 0; i < column_count(); ++i) { if (column_weight_[i] == 0.f) { column_shape_[i] = {0.f, column_size[i].min}; available_width -= column_size[i].min; } } available_width = std::max(available_width, 0.f); // Next, allocate scalable columns for (std::size_t i = 0; i < column_count(); ++i) { if (column_weight_[i] == 0.f) continue; float const column_width = available_width * column_weight_[i] / column_weight_sum; column_shape_[i] = {0.f, column_width}; } // Finally, move rows one after another for (std::size_t i = 0; i < column_count(); ++i) { if (i == 0) column_shape_[i] += bbox[0].min + (outer_margin_ ? margin : 0.f); else column_shape_[i] += column_shape_[i - 1].max + 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(); } }