Introduce ui::element::{width,height}_constraint - constraints on one dimension given the size in another dimension

This commit is contained in:
Nikita Lisitsa 2022-04-16 17:16:09 +03:00
parent 8f6d48c605
commit 6aebd8c931
6 changed files with 203 additions and 135 deletions

View file

@ -44,8 +44,15 @@ namespace psemek::ui
virtual void reshape(geom::box<float, 2> const & bbox) = 0;
virtual void reshape();
// Constraints on the element's width & height, assuming unlimited available space
virtual geom::box<float, 2> size_constraints() const;
// Constraints on the element's width, assuming fixed height
virtual geom::interval<float> width_constraints(float height) const;
// Constraints on the element's height, assuming fixed width
virtual geom::interval<float> height_constraints(float width) const;
virtual bool enabled() const { return enabled_; }
virtual void set_enabled(bool value) { enabled_ = value; }
virtual void enable() { set_enabled(true); }

View file

@ -40,8 +40,15 @@ namespace psemek::ui
virtual void set_outer_margin(bool value);
virtual bool outer_margin() const { return outer_margin_; }
virtual void set_width_first(bool value);
virtual bool width_first() const { return width_first_; }
geom::box<float, 2> size_constraints() const override;
geom::interval<float> width_constraints(float height) const override;
geom::interval<float> height_constraints(float width) const override;
struct shape const & shape() const override { return shape_; }
void reshape(geom::box<float, 2> const & bbox) override;
@ -61,6 +68,7 @@ namespace psemek::ui
std::vector<float> column_weight_;
bool outer_margin_ = true;
bool width_first_ = true;
box_shape shape_;
};

View file

@ -69,6 +69,9 @@ namespace psemek::ui
geom::box<float, 2> size_constraints() const override;
geom::interval<float> width_constraints(float height) const override;
geom::interval<float> height_constraints(float width) const override;
void style_updated() override;
void own_style_updated() override;

View file

@ -61,6 +61,16 @@ namespace psemek::ui
return {{{0.f, inf}, {0.f, inf}}};
}
geom::interval<float> element::width_constraints(float) const
{
return size_constraints()[0];
}
geom::interval<float> element::height_constraints(float) const
{
return size_constraints()[1];
}
std::shared_ptr<style const> element::set_style(std::shared_ptr<struct style const> st)
{
if (style_)

View file

@ -149,86 +149,167 @@ namespace psemek::ui
post_reshape();
}
static std::pair<std::vector<geom::interval<float>>, std::vector<geom::interval<float>>> row_column_sizes(util::array<std::shared_ptr<element>, 2> const & children)
void grid_layout::set_width_first(bool value)
{
std::vector<geom::interval<float>> row_size(children.width(), {0.f, inf});
std::vector<geom::interval<float>> column_size(children.height(), {0.f, inf});
width_first_ = value;
post_reshape();
}
for (std::size_t i = 0; i < children.width(); ++i)
template <int Dimension>
static std::vector<geom::interval<float>> cell_sizes(util::array<std::shared_ptr<element>, 2> const & children)
{
std::vector<geom::interval<float>> result(children.dim(Dimension), {0.f, inf});
for (auto idx : children.indices())
{
for (std::size_t j = 0; j < children.height(); ++j)
{
auto c = children(i, j);
if (!c) continue;
auto c = children(idx);
if (!c) continue;
auto sc = c->size_constraints();
row_size[i] &= sc[1];
column_size[j] &= sc[0];
result[idx[Dimension]] &= c->size_constraints()[1 - Dimension];
}
return result;
}
template <int Dimension>
static std::vector<geom::interval<float>> cell_sizes_constrained(util::array<std::shared_ptr<element>, 2> const & children, std::vector<geom::interval<float>> const & other_dimension_shapes)
{
std::vector<geom::interval<float>> result(children.dim(Dimension), {0.f, inf});
for (auto idx : children.indices())
{
auto c = children(idx);
if (!c) continue;
geom::interval<float> 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 geom::interval<float> combine(std::vector<geom::interval<float>> const & sizes, std::vector<float> const & weights, float margin, bool outer)
{
geom::interval<float> result{0.f, 0.f};
geom::interval<float> 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 &= geom::interval<float>{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<int>(1, sizes.size()) + (outer ? 1 : -1));
result += margin_count * margin;
return result;
}
static std::vector<geom::interval<float>> allocate(std::vector<geom::interval<float>> const & sizes, std::vector<float> const & weights, float available, float margin, bool outer)
{
available -= margin * (static_cast<int>(sizes.size()) + (outer ? 1 : -1));
float const sum_weights = std::accumulate(weights.begin(), weights.end(), 0.f);
std::vector<geom::interval<float>> 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;
}
}
return {std::move(row_size), std::move(column_size)};
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<geom::interval<float>> & shapes, geom::interval<float> 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;
}
}
geom::box<float, 2> 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;
auto row_sizes = cell_sizes<0>(children_);
auto column_sizes = cell_sizes<1>(children_);
geom::box<float, 2> result{{{0.f, 0.f}, {0.f, 0.f}}};
geom::box<float, 2> unit{{{0.f, inf}, {0.f, inf}}};
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}};
}
float sum_row_weights = 0.f;
float sum_column_weights = 0.f;
geom::interval<float> grid_layout::width_constraints(float height) const
{
auto st = merged_own_style();
for (std::size_t i = 0; i < row_count(); ++i)
{
bool const minimized = (row_weight_[i] == 0.f);
auto row_sizes = cell_sizes<0>(children_);
auto row_shapes = allocate(row_sizes, row_weight_, height, *st->outer_margin, outer_margin_);
if (minimized)
result[1] += row_size[i].min;
else
unit[1] &= geom::interval<float>{row_size[i].min / row_weight_[i], row_size[i].max / row_weight_[i]};
auto column_sizes = cell_sizes_constrained<1>(children_, row_shapes);
sum_row_weights += row_weight_[i];
}
return combine(column_sizes, column_weight_, *st->outer_margin, outer_margin_);
}
for (std::size_t i = 0; i < column_count(); ++i)
{
bool const minimized = (column_weight_[i] == 0.f);
geom::interval<float> grid_layout::height_constraints(float width) const
{
auto st = merged_own_style();
if (minimized)
result[0] += column_size[i].min;
else
unit[0] &= geom::interval<float>{column_size[i].min / column_weight_[i], column_size[i].max / column_weight_[i]};
auto column_sizes = cell_sizes<1>(children_);
auto column_shapes = allocate(column_sizes, column_weight_, width, *st->outer_margin, outer_margin_);
sum_column_weights += column_weight_[i];
}
auto row_sizes = cell_sizes_constrained<0>(children_, column_shapes);
// prevent (inf * 0.f)
if (sum_row_weights > 0.f)
{
result[1].min += unit[1].min * sum_row_weights;
result[1].max += unit[1].max * sum_row_weights;
}
if (sum_column_weights > 0.f)
{
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<int>(1, row_count()) + (outer_margin_ ? 1 : -1));
int const margin_column_count = (std::max<int>(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;
return combine(row_sizes, row_weight_, *st->outer_margin, outer_margin_);
}
void grid_layout::reshape(geom::box<float, 2> const & bbox)
@ -236,87 +317,30 @@ namespace psemek::ui
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<int>(1, row_count()) + (outer_margin_ ? 1 : -1));
int const margin_column_count = (std::max<int>(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 (width_first_)
{
if (row_weight_[i] == 0.f)
{
row_shape_[i] = {0.f, row_size[i].min};
available_height -= row_size[i].min;
}
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_);
}
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;
}
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)

View file

@ -68,6 +68,22 @@ namespace psemek::ui
return {{{cached_state_inf_->size[0], inf}, {cached_state_inf_->size[1], inf}}};
}
geom::interval<float> label::width_constraints(float height) const
{
static float const inf = std::numeric_limits<float>::infinity();
auto state = cached_state_for({{{0.f, inf}, {0.f, height}}});
return {state.size[0], inf};
}
geom::interval<float> label::height_constraints(float width) const
{
static float const inf = std::numeric_limits<float>::infinity();
auto state = cached_state_for({{{0.f, width}, {0.f, inf}}});
return {state.size[1], inf};
}
void label::style_updated()
{
element::style_updated();