HikoGUI
A low latency retained GUI
Loading...
Searching...
No Matches
grid_layout.hpp
1// Copyright Take Vos 2022.
2// Distributed under the Boost Software License, Version 1.0.
3// (See accompanying file LICENSE_1_0.txt or copy at https://www.boost.org/LICENSE_1_0.txt)
4
5#pragma once
6
7#include "box_constraints.hpp"
8#include "box_shape.hpp"
10#include "../geometry/module.hpp"
11#include "../utility/module.hpp"
12#include <cstdint>
13#include <numeric>
14#include <vector>
15#include <algorithm>
16#include <utility>
17#include <cmath>
18
19namespace hi { inline namespace v1 {
20namespace detail {
21
22template<typename T>
24 using value_type = T;
25
26 size_t first_column = 0;
27 size_t first_row = 0;
28 size_t last_column = 0;
29 size_t last_row = 0;
30 bool beyond_maximum = false;
31 value_type value = {};
32 box_shape shape = {};
33
34 constexpr grid_layout_cell() noexcept = default;
35 constexpr grid_layout_cell(grid_layout_cell const&) noexcept = default;
36 constexpr grid_layout_cell(grid_layout_cell&&) noexcept = default;
37 constexpr grid_layout_cell& operator=(grid_layout_cell const&) noexcept = default;
38 constexpr grid_layout_cell& operator=(grid_layout_cell&&) noexcept = default;
39
40 constexpr grid_layout_cell(
41 size_t first_column,
42 size_t first_row,
43 size_t last_column,
44 size_t last_row,
45 bool beyond_maximum,
46 std::convertible_to<value_type> auto&& value) noexcept :
47 first_column(first_column),
48 first_row(first_row),
49 last_column(last_column),
50 last_row(last_row),
51 beyond_maximum(beyond_maximum),
52 value(hi_forward(value))
53 {
54 hi_assert(first_column < last_column);
55 hi_assert(first_row < last_row);
56 }
57
58 constexpr void set_constraints(box_constraints const& constraints) noexcept
59 {
60 _constraints = constraints;
61 }
62
63 template<hi::axis Axis>
64 [[nodiscard]] constexpr size_t first() const noexcept
65 {
66 if constexpr (Axis == axis::x) {
67 return first_column;
68 } else if constexpr (Axis == axis::y) {
69 return first_row;
70 } else {
72 }
73 }
74
75 template<hi::axis Axis>
76 [[nodiscard]] constexpr size_t last() const noexcept
77 {
78 if constexpr (Axis == axis::x) {
79 return last_column;
80 } else if constexpr (Axis == axis::y) {
81 return last_row;
82 } else {
84 }
85 }
86
87 template<hi::axis Axis>
88 [[nodiscard]] constexpr size_t span() const noexcept
89 {
90 hi_axiom(first<Axis>() < last<Axis>());
91 return last<Axis>() - first<Axis>();
92 }
93
94 template<hi::axis Axis>
95 [[nodiscard]] constexpr auto alignment() const noexcept
96 {
97 if constexpr (Axis == axis::x) {
98 return _constraints.alignment.horizontal();
99 } else if constexpr (Axis == axis::y) {
100 return _constraints.alignment.vertical();
101 } else {
103 }
104 }
105
106 template<hi::axis Axis>
107 [[nodiscard]] constexpr float minimum() const noexcept
108 {
109 if constexpr (Axis == axis::x) {
110 return _constraints.minimum.width();
111 } else if constexpr (Axis == axis::y) {
112 return _constraints.minimum.height();
113 } else {
115 }
116 }
117
118 template<hi::axis Axis>
119 [[nodiscard]] constexpr float preferred() const noexcept
120 {
121 if constexpr (Axis == axis::x) {
122 return _constraints.preferred.width();
123 } else if constexpr (Axis == axis::y) {
124 return _constraints.preferred.height();
125 } else {
127 }
128 }
129
130 template<hi::axis Axis>
131 [[nodiscard]] constexpr float maximum() const noexcept
132 {
133 if constexpr (Axis == axis::x) {
134 return _constraints.maximum.width();
135 } else if constexpr (Axis == axis::y) {
136 return _constraints.maximum.height();
137 } else {
139 }
140 }
141
142 template<hi::axis Axis>
143 [[nodiscard]] constexpr float margin_before(bool forward) const noexcept
144 {
145 if constexpr (Axis == axis::x) {
146 if (forward) {
147 return _constraints.margins.left();
148 } else {
149 return _constraints.margins.right();
150 }
151 } else if constexpr (Axis == axis::y) {
152 if (forward) {
153 return _constraints.margins.bottom();
154 } else {
155 return _constraints.margins.top();
156 }
157 } else {
159 }
160 }
161
162 template<hi::axis Axis>
163 [[nodiscard]] constexpr float margin_after(bool forward) const noexcept
164 {
165 if constexpr (Axis == axis::x) {
166 if (forward) {
167 return _constraints.margins.right();
168 } else {
169 return _constraints.margins.left();
170 }
171 } else if constexpr (Axis == axis::y) {
172 if (forward) {
173 return _constraints.margins.top();
174 } else {
175 return _constraints.margins.bottom();
176 }
177 } else {
179 }
180 }
181
182 template<hi::axis Axis>
183 [[nodiscard]] constexpr float padding_before(bool forward) const noexcept
184 {
185 if constexpr (Axis == axis::x) {
186 if (forward) {
187 return _constraints.padding.left();
188 } else {
189 return _constraints.padding.right();
190 }
191 } else if constexpr (Axis == axis::y) {
192 if (forward) {
193 return _constraints.padding.bottom();
194 } else {
195 return _constraints.padding.top();
196 }
197 } else {
199 }
200 }
201
202 template<hi::axis Axis>
203 [[nodiscard]] constexpr float padding_after(bool forward) const noexcept
204 {
205 if constexpr (Axis == axis::x) {
206 if (forward) {
207 return _constraints.padding.right();
208 } else {
209 return _constraints.padding.left();
210 }
211 } else if constexpr (Axis == axis::y) {
212 if (forward) {
213 return _constraints.padding.top();
214 } else {
215 return _constraints.padding.bottom();
216 }
217 } else {
219 }
220 }
221
222private:
223 box_constraints _constraints;
224};
225
226template<hi::axis Axis, typename T>
228public:
229 constexpr static hi::axis axis = Axis;
230
231 using value_type = T;
232 using alignment_type = std::conditional_t<axis == axis::y, vertical_alignment, horizontal_alignment>;
235
239 float minimum = 0.0f;
240
243 float preferred = 0.0f;
244
248
251 float margin_before = 0.0f;
252
255 float margin_after = 0.0f;
256
259 float padding_before = 0.0f;
260
263 float padding_after = 0.0f;
264
267 alignment_type alignment = alignment_type::none;
268
271 bool beyond_maximum = false;
272
277 float position = 0.0f;
278
283 float extent = 0.0f;
284
289 std::optional<float> guideline = 0.0f;
290 };
292 using iterator = constraint_vector::iterator;
293 using const_iterator = constraint_vector::const_iterator;
294 using reverse_iterator = constraint_vector::reverse_iterator;
295 using reference = constraint_vector::reference;
296 using const_reference = constraint_vector::const_reference;
297
298 constexpr ~grid_layout_axis_constraints() = default;
299 constexpr grid_layout_axis_constraints() noexcept = default;
300 constexpr grid_layout_axis_constraints(grid_layout_axis_constraints const&) noexcept = default;
301 constexpr grid_layout_axis_constraints(grid_layout_axis_constraints&&) noexcept = default;
302 constexpr grid_layout_axis_constraints& operator=(grid_layout_axis_constraints const&) noexcept = default;
303 constexpr grid_layout_axis_constraints& operator=(grid_layout_axis_constraints&&) noexcept = default;
304 [[nodiscard]] constexpr friend bool
305 operator==(grid_layout_axis_constraints const&, grid_layout_axis_constraints const&) noexcept = default;
306
314 constexpr grid_layout_axis_constraints(cell_vector const& cells, size_t num, bool forward) noexcept :
315 _constraints(num), _forward(forward)
316 {
317 for (hilet& cell : cells) {
318 construct_simple_cell(cell);
319 }
320 construct_fixup();
321
322 for (hilet& cell : cells) {
323 construct_span_cell(cell);
324 }
325 construct_fixup();
326 }
327
328 [[nodiscard]] constexpr float margin_before() const noexcept
329 {
330 return empty() ? 0 : _forward ? front().margin_before : back().margin_before;
331 }
332
333 [[nodiscard]] constexpr float margin_after() const noexcept
334 {
335 return empty() ? 0 : _forward ? back().margin_after : front().margin_after;
336 }
337
338 [[nodiscard]] constexpr float padding_before() const noexcept
339 {
340 return empty() ? 0 : _forward ? front().padding_before : back().padding_before;
341 }
342
343 [[nodiscard]] constexpr float padding_after() const noexcept
344 {
345 return empty() ? 0 : _forward ? back().padding_after : front().padding_after;
346 }
347
348 [[nodiscard]] constexpr std::tuple<float, float, float> update_constraints() const noexcept
349 {
350 return constraints(begin(), end());
351 }
352
360 [[nodiscard]] constexpr std::tuple<float, float, float> constraints(cell_type const& cell) const noexcept
361 {
362 return constraints(cell.first<axis>(), cell.last<axis>());
363 }
364
365 [[nodiscard]] constexpr float position(cell_type const& cell) const noexcept
366 {
367 return position(cell.first<axis>(), cell.last<axis>());
368 }
369
370 [[nodiscard]] constexpr float extent(cell_type const& cell) const noexcept
371 {
372 return extent(cell.first<axis>(), cell.last<axis>());
373 }
374
375 [[nodiscard]] constexpr std::optional<float> guideline(cell_type const& cell) const noexcept
376 {
377 if (cell.span<axis>() == 1) {
378 return guideline(cell.first<axis>());
379 } else {
380 return std::nullopt;
381 }
382 }
383
406 constexpr void layout(float new_position, float new_extent, std::optional<float> external_guideline, float guideline_width) noexcept
407 {
408 // Start with the extent of each constraint equal to the preferred extent.
409 for (auto& constraint : _constraints) {
410 constraint.extent = constraint.preferred;
411 }
412
413 // If the total extent is too large, shrink the constraints that allow to be shrunk.
414 auto [total_extent, count] = layout_shrink(begin(), end());
415 while (total_extent > new_extent and count != 0) {
416 // The result may shrink slightly too much, which will be fixed by expanding in the next loop.
417 std::tie(total_extent, count) = layout_shrink(begin(), end(), total_extent - new_extent, count);
418 }
419
420 // If the total extent is too small, expand the constraints that allow to be grown.
421 std::tie(total_extent, count) = layout_expand(begin(), end());
422 while (total_extent < new_extent and count != 0) {
423 // The result may expand slightly too much, we don't care.
424 std::tie(total_extent, count) = layout_expand(begin(), end(), new_extent - total_extent, count);
425 }
426
427 // If the total extent is still too small, expand into the cells that are marked beyond_maximum.
428 if (total_extent < new_extent) {
429 // The result may expand slightly too much, we don't care.
430 count = std::count_if(begin(), end(), [](hilet& item) {
431 return item.beyond_maximum;
432 });
433 if (count) {
434 auto expand = new_extent - total_extent;
435 hilet expand_per = std::ceil(expand / count);
436
437 for (auto& constraint : _constraints) {
438 hilet expand_this = std::min(expand_per, expand);
439 if (constraint.beyond_maximum) {
440 constraint.extent += expand_this;
441 expand -= expand_this;
442 }
443 }
444 }
445 total_extent = extent(cbegin(), cend());
446 }
447
448 // If the total extent is still too small, expand the first constrain above the maximum size.
449 if (total_extent < new_extent and not empty()) {
450 // The result may expand slightly too much, we don't care.
451 front().extent += new_extent - total_extent;
452 }
453
454 if (_forward) {
455 layout_position(begin(), end(), new_position, guideline_width);
456 } else {
457 layout_position(rbegin(), rend(), new_position, guideline_width);
458 }
459
460 if (external_guideline and size() == 1) {
461 // When there is only 1 cell on this axis, the external guideline is used.
462 // XXX If there are more cell, then the external alignment should be taken into account.
463 front().guideline = *external_guideline;
464 }
465 }
466
469 [[nodiscard]] constexpr size_t size() const noexcept
470 {
471 return _constraints.size();
472 }
473
476 [[nodiscard]] constexpr bool empty() const noexcept
477 {
478 return _constraints.empty();
479 }
480
483 [[nodiscard]] constexpr iterator begin() noexcept
484 {
485 return _constraints.begin();
486 }
487
490 [[nodiscard]] constexpr const_iterator begin() const noexcept
491 {
492 return _constraints.begin();
493 }
494
497 [[nodiscard]] constexpr const_iterator cbegin() const noexcept
498 {
499 return _constraints.cbegin();
500 }
501
504 [[nodiscard]] constexpr iterator end() noexcept
505 {
506 return _constraints.end();
507 }
508
511 [[nodiscard]] constexpr const_iterator end() const noexcept
512 {
513 return _constraints.end();
514 }
515
518 [[nodiscard]] constexpr const_iterator cend() const noexcept
519 {
520 return _constraints.cend();
521 }
522
525 [[nodiscard]] constexpr reverse_iterator rbegin() noexcept
526 {
527 return _constraints.rbegin();
528 }
529
532 [[nodiscard]] constexpr reverse_iterator rend() noexcept
533 {
534 return _constraints.rend();
535 }
536
543 [[nodiscard]] constexpr reference operator[](size_t index) noexcept
544 {
545 hi_axiom(index < size());
546 return _constraints[index];
547 }
548
555 [[nodiscard]] constexpr const_reference operator[](size_t index) const noexcept
556 {
557 hi_axiom(index < size());
558 return _constraints[index];
559 }
560
566 [[nodiscard]] constexpr reference front() noexcept
567 {
568 hi_axiom(not empty());
569 return _constraints.front();
570 }
571
577 [[nodiscard]] constexpr const_reference front() const noexcept
578 {
579 hi_axiom(not empty());
580 return _constraints.front();
581 }
582
588 [[nodiscard]] constexpr reference back() noexcept
589 {
590 hi_axiom(not empty());
591 return _constraints.back();
592 }
593
599 [[nodiscard]] constexpr const_reference back() const noexcept
600 {
601 hi_axiom(not empty());
602 return _constraints.back();
603 }
604
605private:
614 constraint_vector _constraints = {};
615
618 bool _forward = true;
619
636 [[nodiscard]] constexpr std::pair<float, size_t>
637 layout_shrink(const_iterator first, const_iterator last, float shrink = 0.0f, size_t count = 1) noexcept
638 {
639 hilet first_ = begin() + std::distance(cbegin(), first);
640 hilet last_ = begin() + std::distance(cbegin(), last);
641
642 hi_axiom(shrink >= 0);
643
644 hilet shrink_per = std::floor(shrink / count);
645
646 auto new_extent = 0.0f;
647 auto new_count = 0_uz;
648 for (auto it = first_; it != last_; ++it) {
649 hilet shrink_this = std::max({shrink_per, shrink, it->extent - it->minimum});
650 it->extent -= shrink_this;
651 shrink -= shrink_this;
652
653 if (it != first_) {
654 new_extent += it->margin_before;
655 }
656 new_extent += it->extent;
657
658 if (it->extent > it->minimum) {
659 ++new_count;
660 }
661 }
662
663 return {new_extent, new_count};
664 }
665
682 [[nodiscard]] constexpr std::pair<float, size_t>
683 layout_expand(const_iterator first, const_iterator last, float expand = 0.0f, size_t count = 1) noexcept
684 {
685 hilet first_ = begin() + std::distance(cbegin(), first);
686 hilet last_ = begin() + std::distance(cbegin(), last);
687
688 hi_axiom(expand >= 0.0f);
689
690 hilet expand_per = std::ceil(expand / count);
691 hi_axiom(expand_per >= 0.0f);
692
693 auto new_extent = 0.0f;
694 auto new_count = 0_uz;
695 for (auto it = first_; it != last_; ++it) {
696 hilet expand_this = std::min({expand_per, expand, it->maximum - it->extent});
697 it->extent += expand_this;
698 expand -= expand_this;
699
700 if (it != first_) {
701 new_extent += it->margin_before;
702 }
703 new_extent += it->extent;
704
705 if (it->extent < it->maximum) {
706 ++new_count;
707 }
708 }
709
710 return {new_extent, new_count};
711 }
712
713 constexpr void layout_position(auto first, auto last, float start_position, float guideline_width) noexcept
714 {
715 auto position = start_position;
716 for (auto it = first; it != last; ++it) {
717 it->position = position;
718 it->guideline = make_guideline(
719 it->alignment, position, position + it->extent, it->padding_before, it->padding_after, guideline_width);
720
721 position += it->extent;
722 position += it->margin_after;
723 }
724 }
725
733 constexpr void construct_simple_cell(cell_type const& cell) noexcept
734 {
735 inplace_max(_constraints[cell.first<axis>()].margin_before, cell.margin_before<axis>(_forward));
736 inplace_max(_constraints[cell.last<axis>() - 1].margin_after, cell.margin_after<axis>(_forward));
737 inplace_max(_constraints[cell.first<axis>()].padding_before, cell.padding_before<axis>(_forward));
738 inplace_max(_constraints[cell.last<axis>() - 1].padding_after, cell.padding_after<axis>(_forward));
739
740 for (auto i = cell.first<axis>(); i != cell.last<axis>(); ++i) {
741 _constraints[i].beyond_maximum |= cell.beyond_maximum;
742 }
743
744 if (cell.span<axis>() == 1) {
745 inplace_max(_constraints[cell.first<axis>()].alignment, cell.alignment<axis>());
746 inplace_max(_constraints[cell.first<axis>()].minimum, cell.minimum<axis>());
747 inplace_max(_constraints[cell.first<axis>()].preferred, cell.preferred<axis>());
748 inplace_min(_constraints[cell.first<axis>()].maximum, cell.maximum<axis>());
749 }
750 }
751
758 constexpr void construct_span_cell(cell_type const& cell) noexcept
759 {
760 auto num_cells = narrow_cast<float>(cell.span<axis>());
761
762 if (cell.span<axis>() > 1) {
763 hilet[span_minimum, span_preferred, span_maximum] = constraints(cell);
764 if (hilet extra = cell.minimum<axis>() - span_minimum; extra > 0) {
765 hilet extra_per_cell = std::floor(extra / num_cells);
766 for (auto i = cell.first<axis>(); i != cell.last<axis>(); ++i) {
767 _constraints[i].minimum += extra_per_cell;
768 }
769 }
770
771 if (hilet extra = cell.preferred<axis>() - span_preferred; extra > 0) {
772 hilet extra_per_cell = std::floor(extra / num_cells);
773 for (auto i = cell.first<axis>(); i != cell.last<axis>(); ++i) {
774 _constraints[i].preferred += extra_per_cell;
775 }
776 }
777
778 if (hilet extra = cell.maximum<axis>() - span_preferred; extra < 0) {
779 hilet extra_per_cell = std::ceil(extra / num_cells);
780 for (auto i = cell.first<axis>(); i != cell.last<axis>(); ++i) {
781 // The maximum could become too low here, fixup() will fix this.
782 _constraints[i].maximum += extra_per_cell;
783 }
784 }
785 }
786 }
787
792 constexpr void construct_fixup() noexcept
793 {
794 for (auto it = begin(); it != end(); ++it) {
795 // Fix the margins so that between two constraints they are equal.
796 if (it + 1 != end()) {
797 it->margin_after = (it + 1)->margin_before = std::max(it->margin_after, (it + 1)->margin_before);
798 }
799
800 // Fix the constraints so that minimum <= preferred <= maximum.
801 inplace_max(it->preferred, it->minimum);
802 inplace_max(it->maximum, it->preferred);
803
804 // Fix the padding, so that it doesn't overlap.
805 if (it->padding_before + it->padding_after > it->minimum) {
806 hilet padding_diff = it->padding_after - it->padding_before;
807 hilet middle = std::clamp(it->minimum / 2.0f + padding_diff, 0.0f, it->minimum);
808 it->padding_after = middle;
809 it->padding_before = it->minimum - middle;
810 }
811 }
812 }
813
822 [[nodiscard]] constexpr std::tuple<float, float, float> constraints(const_iterator first, const_iterator last) const noexcept
823 {
824 auto r_minimum = 0.0f;
825 auto r_preferred = 0.0f;
826 auto r_maximum = 0.0f;
827 auto r_margin = 0.0f;
828
829 if (first != last) {
830 r_minimum = first->minimum;
831 r_preferred = first->preferred;
832 r_maximum = first->maximum;
833 for (auto it = first + 1; it != last; ++it) {
834 r_margin += it->margin_before;
835 r_minimum += it->minimum;
836 r_preferred += it->preferred;
837 r_maximum += it->maximum;
838 }
839 }
840 return {r_minimum + r_margin, r_preferred + r_margin, r_maximum + r_margin};
841 }
842
851 [[nodiscard]] constexpr std::tuple<float, float, float> constraints(size_t first, size_t last) const noexcept
852 {
853 hi_axiom(first <= last);
854 hi_axiom(last <= size());
855 return constraints(begin() + first, begin() + last);
856 }
857
865 [[nodiscard]] constexpr float position(const_iterator first, const_iterator last) const noexcept
866 {
867 hi_axiom(first != last);
868 if (_forward) {
869 return first->position;
870 } else {
871 return (last - 1)->position;
872 }
873 }
874
882 [[nodiscard]] constexpr float position(size_t first, size_t last) const noexcept
883 {
884 hi_axiom(first < last);
885 hi_axiom(last <= size());
886 return position(cbegin() + first, cbegin() + last);
887 }
888
896 [[nodiscard]] constexpr float extent(const_iterator first, const_iterator last) const noexcept
897 {
898 auto r = 0.0f;
899 if (first != last) {
900 r = first->extent;
901 for (auto it = first + 1; it != last; ++it) {
902 r += it->margin_before;
903 r += it->extent;
904 }
905 }
906 return r;
907 }
908
916 [[nodiscard]] constexpr float extent(size_t first, size_t last) const noexcept
917 {
918 hi_axiom(first <= last);
919 hi_axiom(last <= size());
920 return extent(cbegin() + first, cbegin() + last);
921 }
922
923 [[nodiscard]] constexpr std::optional<float> guideline(const_iterator it) const noexcept
924 {
925 return it->guideline;
926 }
927
928 [[nodiscard]] constexpr std::optional<float> guideline(size_t i) const noexcept
929 {
930 return guideline(cbegin() + i);
931 }
932};
933
934} // namespace detail
935
941template<typename T>
943public:
944 using value_type = T;
945
946 using cell_type = detail::grid_layout_cell<value_type>;
947 using cell_vector = std::vector<cell_type>;
948 using iterator = cell_vector::iterator;
949 using const_iterator = cell_vector::const_iterator;
950 using reference = cell_vector::reference;
951 using const_reference = cell_vector::const_reference;
952
953 ~grid_layout() = default;
954 constexpr grid_layout() noexcept = default;
955 constexpr grid_layout(grid_layout const&) noexcept = default;
956 constexpr grid_layout(grid_layout&&) noexcept = default;
957 constexpr grid_layout& operator=(grid_layout const&) noexcept = default;
958 constexpr grid_layout& operator=(grid_layout&&) noexcept = default;
959 [[nodiscard]] constexpr friend bool operator==(grid_layout const&, grid_layout const&) noexcept = default;
960
961 [[nodiscard]] constexpr bool empty() const noexcept
962 {
963 return _cells.empty();
964 }
965
966 [[nodiscard]] constexpr size_t size() const noexcept
967 {
968 return _cells.size();
969 }
970
971 [[nodiscard]] constexpr size_t num_columns() const noexcept
972 {
973 return _num_columns;
974 }
975
976 [[nodiscard]] constexpr size_t num_rows() const noexcept
977 {
978 return _num_rows;
979 }
980
981 [[nodiscard]] constexpr iterator begin() noexcept
982 {
983 return _cells.begin();
984 }
985
986 [[nodiscard]] constexpr iterator end() noexcept
987 {
988 return _cells.end();
989 }
990
991 [[nodiscard]] constexpr const_iterator begin() const noexcept
992 {
993 return _cells.begin();
994 }
995
996 [[nodiscard]] constexpr const_iterator end() const noexcept
997 {
998 return _cells.end();
999 }
1000
1001 [[nodiscard]] constexpr const_iterator cbegin() const noexcept
1002 {
1003 return _cells.cbegin();
1004 }
1005
1006 [[nodiscard]] constexpr const_iterator cend() const noexcept
1007 {
1008 return _cells.cend();
1009 }
1010
1011 [[nodiscard]] constexpr const_reference operator[](size_t i) const noexcept
1012 {
1013 return _cells[i];
1014 }
1015
1016 [[nodiscard]] constexpr reference operator[](size_t i) noexcept
1017 {
1018 return _cells[i];
1019 }
1020
1029 [[nodiscard]] constexpr bool cell_in_use(size_t first_column, size_t first_row, size_t last_column, size_t last_row) noexcept
1030 {
1031 // At least one cell must be in the range.
1032 hi_axiom(first_column < last_column);
1033 hi_axiom(first_row < last_row);
1034
1035 for (hilet& cell : _cells) {
1036 if (first_column >= cell.last_column) {
1037 continue;
1038 }
1039 if (last_column <= cell.first_column) {
1040 continue;
1041 }
1042 if (first_row >= cell.last_row) {
1043 continue;
1044 }
1045 if (last_row <= cell.first_row) {
1046 continue;
1047 }
1048 return true;
1049 }
1050 return false;
1051 }
1052
1063 template<forward_of<value_type> Value>
1064 constexpr reference add_cell(
1065 size_t first_column,
1066 size_t first_row,
1067 size_t last_column,
1068 size_t last_row,
1069 Value&& value,
1070 bool beyond_maximum = false) noexcept
1071 {
1072 // At least one cell must be in the range.
1073 hi_assert(first_column < last_column);
1074 hi_assert(first_row < last_row);
1075 hi_assert(not cell_in_use(first_column, first_row, last_column, last_row));
1076 auto& r = _cells.emplace_back(first_column, first_row, last_column, last_row, beyond_maximum, std::forward<Value>(value));
1077 update_after_insert_or_delete();
1078 return r;
1079 }
1080
1089 template<forward_of<value_type> Value>
1090 constexpr reference add_cell(size_t column, size_t row, Value&& value, bool beyond_maximum = false) noexcept
1091 {
1092 return add_cell(column, row, column + 1, row + 1, std::forward<Value>(value), beyond_maximum);
1093 }
1094
1095 constexpr void clear() noexcept
1096 {
1097 _cells.clear();
1098 update_after_insert_or_delete();
1099 }
1100
1101 [[nodiscard]] constexpr box_constraints constraints(bool left_to_right) const noexcept
1102 {
1103 // Rows in the grid are laid out from top to bottom which is reverse from the y-axis up.
1104 _row_constraints = {_cells, num_rows(), false};
1105 _column_constraints = {_cells, num_columns(), left_to_right};
1106
1107 auto r = box_constraints{};
1108 std::tie(r.minimum.width(), r.preferred.width(), r.maximum.width()) = _column_constraints.update_constraints();
1109 r.margins.left() = _column_constraints.margin_before();
1110 r.margins.right() = _column_constraints.margin_after();
1111 r.padding.left() = _column_constraints.padding_before();
1112 r.padding.right() = _column_constraints.padding_after();
1113
1114 std::tie(r.minimum.height(), r.preferred.height(), r.maximum.height()) = _row_constraints.update_constraints();
1115 r.margins.bottom() = _row_constraints.margin_after();
1116 r.margins.top() = _row_constraints.margin_before();
1117 r.padding.bottom() = _row_constraints.padding_after();
1118 r.padding.top() = _row_constraints.padding_before();
1119
1120 r.alignment = [&] {
1121 if (num_rows() == 1 and num_columns() == 1) {
1122 return hi::alignment{_column_constraints.front().alignment, _row_constraints.front().alignment};
1123 } else if (num_rows() == 1) {
1124 return hi::alignment{_row_constraints.front().alignment};
1125 } else if (num_columns() == 1) {
1126 return hi::alignment{_column_constraints.front().alignment};
1127 } else {
1128 return hi::alignment{};
1129 }
1130 }();
1131
1132 return r;
1133 }
1134
1140 constexpr void set_layout(box_shape const& shape, float baseline_adjustment) noexcept
1141 {
1142 // Rows in the grid are laid out from top to bottom which is reverse from the y-axis up.
1143 _column_constraints.layout(shape.x(), shape.width(), shape.centerline, 0);
1144 _row_constraints.layout(shape.y(), shape.height(), shape.baseline, baseline_adjustment);
1145
1146 // Assign the shape for each cell.
1147 for (auto& cell : _cells) {
1148 cell.shape.rectangle = {
1149 _column_constraints.position(cell),
1150 _row_constraints.position(cell),
1151 _column_constraints.extent(cell),
1152 _row_constraints.extent(cell)};
1153 cell.shape.centerline = _column_constraints.guideline(cell);
1154 cell.shape.baseline = _row_constraints.guideline(cell);
1155 }
1156 }
1157
1158private:
1159 cell_vector _cells = {};
1160 size_t _num_rows = 0;
1161 size_t _num_columns = 0;
1162 mutable detail::grid_layout_axis_constraints<axis::y, value_type> _row_constraints = {};
1163 mutable detail::grid_layout_axis_constraints<axis::x, value_type> _column_constraints = {};
1164
1169 constexpr void sort_cells() noexcept
1170 {
1171 std::sort(_cells.begin(), _cells.end(), [](cell_type const& lhs, cell_type const& rhs) {
1172 if (lhs.first_row != rhs.first_row) {
1173 return lhs.first_row < rhs.first_row;
1174 } else {
1175 return lhs.first_column < rhs.first_column;
1176 }
1177 });
1178 }
1179
1182 constexpr void update_after_insert_or_delete() noexcept
1183 {
1184 sort_cells();
1185
1186 _num_rows = 0;
1187 _num_columns = 0;
1188 for (hilet& cell : _cells) {
1189 inplace_max(_num_rows, cell.last_row);
1190 inplace_max(_num_columns, cell.last_column);
1191 }
1192 }
1193};
1194
1195}} // namespace hi::v1
Utilities for parsing spreadsheet addresses.
#define hi_static_no_default(...)
This part of the code should not be reachable, unless a programming bug.
Definition assert.hpp:323
#define hi_assert(expression,...)
Assert if expression is true.
Definition assert.hpp:199
#define hi_axiom(expression,...)
Specify an axiom; an expression that is true.
Definition assert.hpp:253
#define hilet
Invariant should be the default for variables.
Definition utility.hpp:23
#define hi_forward(x)
Forward a value, based on the decltype of the value.
Definition utility.hpp:29
constexpr std::optional< float > make_guideline(vertical_alignment alignment, float bottom, float top, float padding_bottom, float padding_top, float guideline_width)
Create a guideline between two points.
Definition alignment.hpp:57
axis
An enumeration of the 3 axis for 3D geometry.
Definition axis.hpp:18
@ middle
Align to the vertical-middle.
DOXYGEN BUG.
Definition algorithm.hpp:13
geometry/margins.hpp
Definition cache.hpp:11
Horizontal/Vertical alignment combination.
Definition alignment.hpp:239
constexpr float & width() noexcept
Access the x-as-width element from the extent.
Definition extent2.hpp:101
constexpr float & height() noexcept
Access the y-as-height element from the extent.
Definition extent2.hpp:112
2D constraints.
Definition box_constraints.hpp:22
Definition box_shape.hpp:15
Definition grid_layout.hpp:23
Definition grid_layout.hpp:227
constexpr reference operator[](size_t index) noexcept
Get element.
Definition grid_layout.hpp:543
constexpr size_t size() const noexcept
Number of cell on this axis.
Definition grid_layout.hpp:469
constexpr const_reference back() const noexcept
Get the last element.
Definition grid_layout.hpp:599
constexpr bool empty() const noexcept
Check if this axis is empty.
Definition grid_layout.hpp:476
constexpr reference front() noexcept
Get the first element.
Definition grid_layout.hpp:566
constexpr reverse_iterator rend() noexcept
Iterator to the first cell on this axis.
Definition grid_layout.hpp:532
constexpr reverse_iterator rbegin() noexcept
Iterator to the first cell on this axis.
Definition grid_layout.hpp:525
constexpr reference back() noexcept
Get the last element.
Definition grid_layout.hpp:588
constexpr const_iterator end() const noexcept
Iterator to beyond the last cell on this axis.
Definition grid_layout.hpp:511
constexpr const_reference operator[](size_t index) const noexcept
Get element.
Definition grid_layout.hpp:555
constexpr const_iterator cbegin() const noexcept
Iterator to the first cell on this axis.
Definition grid_layout.hpp:497
constexpr iterator end() noexcept
Iterator to beyond the last cell on this axis.
Definition grid_layout.hpp:504
constexpr const_iterator begin() const noexcept
Iterator to the first cell on this axis.
Definition grid_layout.hpp:490
constexpr const_iterator cend() const noexcept
Iterator to beyond the last cell on this axis.
Definition grid_layout.hpp:518
constexpr iterator begin() noexcept
Iterator to the first cell on this axis.
Definition grid_layout.hpp:483
constexpr void layout(float new_position, float new_extent, std::optional< float > external_guideline, float guideline_width) noexcept
Layout each cell along an axis.
Definition grid_layout.hpp:406
constexpr const_reference front() const noexcept
Get the first element.
Definition grid_layout.hpp:577
constexpr std::tuple< float, float, float > constraints(cell_type const &cell) const noexcept
Get the minimum, preferred, maximum size of the span.
Definition grid_layout.hpp:360
float margin_after
The right/bottom margin of the cells.
Definition grid_layout.hpp:255
float extent
Size of the cell.
Definition grid_layout.hpp:283
float padding_before
The left/top padding of the cells.
Definition grid_layout.hpp:259
float padding_after
The right/bottom padding of the cells.
Definition grid_layout.hpp:263
float minimum
The minimum width/height of the cells.
Definition grid_layout.hpp:239
bool beyond_maximum
Allow this cell to be resized beyond the maximum constraint.
Definition grid_layout.hpp:271
float maximum
The maximum width/height of the cells.
Definition grid_layout.hpp:247
float margin_before
The left/top margin of the cells.
Definition grid_layout.hpp:251
float preferred
The preferred width/height of the cells.
Definition grid_layout.hpp:243
std::optional< float > guideline
The before-position within this cell where to align to.
Definition grid_layout.hpp:289
float position
The position of the cell.
Definition grid_layout.hpp:277
Grid layout algorithm.
Definition grid_layout.hpp:942
constexpr bool cell_in_use(size_t first_column, size_t first_row, size_t last_column, size_t last_row) noexcept
Check if the cell on the grid is already in use.
Definition grid_layout.hpp:1029
constexpr reference add_cell(size_t first_column, size_t first_row, size_t last_column, size_t last_row, Value &&value, bool beyond_maximum=false) noexcept
Check if the cell on the grid is already in use.
Definition grid_layout.hpp:1064
constexpr reference add_cell(size_t column, size_t row, Value &&value, bool beyond_maximum=false) noexcept
Check if the cell on the grid is already in use.
Definition grid_layout.hpp:1090
constexpr void set_layout(box_shape const &shape, float baseline_adjustment) noexcept
Layout the cells based on the width and height.
Definition grid_layout.hpp:1140
T back(T... args)
T begin(T... args)
T ceil(T... args)
T clear(T... args)
T count_if(T... args)
T distance(T... args)
T emplace_back(T... args)
T empty(T... args)
T end(T... args)
T floor(T... args)
T front(T... args)
T max(T... args)
T min(T... args)
T rbegin(T... args)
T rend(T... args)
T size(T... args)
T sort(T... args)
T tie(T... args)