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/utility.hpp"
12#include "../macros.hpp"
13#include <cstdint>
14#include <numeric>
15#include <vector>
16#include <algorithm>
17#include <utility>
18#include <cmath>
19
20
21
22namespace hi { inline namespace v1 {
23namespace detail {
24
25template<typename T>
27 using value_type = T;
28
29 size_t first_column = 0;
30 size_t first_row = 0;
31 size_t last_column = 0;
32 size_t last_row = 0;
33 bool beyond_maximum = false;
34 value_type value = {};
35 box_shape shape = {};
36
37 constexpr grid_layout_cell() noexcept = default;
38 constexpr grid_layout_cell(grid_layout_cell const&) noexcept = default;
39 constexpr grid_layout_cell(grid_layout_cell&&) noexcept = default;
40 constexpr grid_layout_cell& operator=(grid_layout_cell const&) noexcept = default;
41 constexpr grid_layout_cell& operator=(grid_layout_cell&&) noexcept = default;
42
43 constexpr grid_layout_cell(
44 size_t first_column,
45 size_t first_row,
46 size_t last_column,
47 size_t last_row,
48 bool beyond_maximum,
49 std::convertible_to<value_type> auto&& value) noexcept :
50 first_column(first_column),
51 first_row(first_row),
52 last_column(last_column),
53 last_row(last_row),
54 beyond_maximum(beyond_maximum),
55 value(hi_forward(value))
56 {
57 hi_assert(first_column < last_column);
58 hi_assert(first_row < last_row);
59 }
60
61 constexpr void set_constraints(box_constraints const& constraints) noexcept
62 {
63 _constraints = constraints;
64 }
65
66 template<hi::axis Axis>
67 [[nodiscard]] constexpr size_t first() const noexcept
68 {
69 if constexpr (Axis == axis::x) {
70 return first_column;
71 } else if constexpr (Axis == axis::y) {
72 return first_row;
73 } else {
74 hi_static_no_default();
75 }
76 }
77
78 template<hi::axis Axis>
79 [[nodiscard]] constexpr size_t last() const noexcept
80 {
81 if constexpr (Axis == axis::x) {
82 return last_column;
83 } else if constexpr (Axis == axis::y) {
84 return last_row;
85 } else {
86 hi_static_no_default();
87 }
88 }
89
90 template<hi::axis Axis>
91 [[nodiscard]] constexpr size_t span() const noexcept
92 {
93 hi_axiom(first<Axis>() < last<Axis>());
94 return last<Axis>() - first<Axis>();
95 }
96
97 template<hi::axis Axis>
98 [[nodiscard]] constexpr auto alignment() const noexcept
99 {
100 if constexpr (Axis == axis::x) {
101 return _constraints.alignment.horizontal();
102 } else if constexpr (Axis == axis::y) {
103 return _constraints.alignment.vertical();
104 } else {
105 hi_static_no_default();
106 }
107 }
108
109 template<hi::axis Axis>
110 [[nodiscard]] constexpr float minimum() const noexcept
111 {
112 if constexpr (Axis == axis::x) {
113 return _constraints.minimum.width();
114 } else if constexpr (Axis == axis::y) {
115 return _constraints.minimum.height();
116 } else {
117 hi_static_no_default();
118 }
119 }
120
121 template<hi::axis Axis>
122 [[nodiscard]] constexpr float preferred() const noexcept
123 {
124 if constexpr (Axis == axis::x) {
125 return _constraints.preferred.width();
126 } else if constexpr (Axis == axis::y) {
127 return _constraints.preferred.height();
128 } else {
129 hi_static_no_default();
130 }
131 }
132
133 template<hi::axis Axis>
134 [[nodiscard]] constexpr float maximum() const noexcept
135 {
136 if constexpr (Axis == axis::x) {
137 return _constraints.maximum.width();
138 } else if constexpr (Axis == axis::y) {
139 return _constraints.maximum.height();
140 } else {
141 hi_static_no_default();
142 }
143 }
144
145 template<hi::axis Axis>
146 [[nodiscard]] constexpr float margin_before(bool forward) const noexcept
147 {
148 if constexpr (Axis == axis::x) {
149 if (forward) {
150 return _constraints.margins.left();
151 } else {
152 return _constraints.margins.right();
153 }
154 } else if constexpr (Axis == axis::y) {
155 if (forward) {
156 return _constraints.margins.bottom();
157 } else {
158 return _constraints.margins.top();
159 }
160 } else {
161 hi_static_no_default();
162 }
163 }
164
165 template<hi::axis Axis>
166 [[nodiscard]] constexpr float margin_after(bool forward) const noexcept
167 {
168 if constexpr (Axis == axis::x) {
169 if (forward) {
170 return _constraints.margins.right();
171 } else {
172 return _constraints.margins.left();
173 }
174 } else if constexpr (Axis == axis::y) {
175 if (forward) {
176 return _constraints.margins.top();
177 } else {
178 return _constraints.margins.bottom();
179 }
180 } else {
181 hi_static_no_default();
182 }
183 }
184
185 template<hi::axis Axis>
186 [[nodiscard]] constexpr float padding_before(bool forward) const noexcept
187 {
188 if constexpr (Axis == axis::x) {
189 if (forward) {
190 return _constraints.padding.left();
191 } else {
192 return _constraints.padding.right();
193 }
194 } else if constexpr (Axis == axis::y) {
195 if (forward) {
196 return _constraints.padding.bottom();
197 } else {
198 return _constraints.padding.top();
199 }
200 } else {
201 hi_static_no_default();
202 }
203 }
204
205 template<hi::axis Axis>
206 [[nodiscard]] constexpr float padding_after(bool forward) const noexcept
207 {
208 if constexpr (Axis == axis::x) {
209 if (forward) {
210 return _constraints.padding.right();
211 } else {
212 return _constraints.padding.left();
213 }
214 } else if constexpr (Axis == axis::y) {
215 if (forward) {
216 return _constraints.padding.top();
217 } else {
218 return _constraints.padding.bottom();
219 }
220 } else {
221 hi_static_no_default();
222 }
223 }
224
225private:
226 box_constraints _constraints;
227};
228
229template<hi::axis Axis, typename T>
231public:
232 constexpr static hi::axis axis = Axis;
233
234 using value_type = T;
235 using alignment_type = std::conditional_t<axis == axis::y, vertical_alignment, horizontal_alignment>;
238
242 float minimum = 0.0f;
243
246 float preferred = 0.0f;
247
251
254 float margin_before = 0.0f;
255
258 float margin_after = 0.0f;
259
262 float padding_before = 0.0f;
263
266 float padding_after = 0.0f;
267
270 alignment_type alignment = alignment_type::none;
271
274 bool beyond_maximum = false;
275
280 float position = 0.0f;
281
286 float extent = 0.0f;
287
292 std::optional<float> guideline = 0.0f;
293 };
295 using iterator = constraint_vector::iterator;
296 using const_iterator = constraint_vector::const_iterator;
297 using reverse_iterator = constraint_vector::reverse_iterator;
298 using reference = constraint_vector::reference;
299 using const_reference = constraint_vector::const_reference;
300
301 constexpr ~grid_layout_axis_constraints() = default;
307 [[nodiscard]] constexpr friend bool
309
318 _constraints(num), _forward(forward)
319 {
320 for (hilet& cell : cells) {
321 construct_simple_cell(cell);
322 }
323 construct_fixup();
324
325 for (hilet& cell : cells) {
326 construct_span_cell(cell);
327 }
328 construct_fixup();
329 }
330
331 [[nodiscard]] constexpr float margin_before() const noexcept
332 {
333 return empty() ? 0 : _forward ? front().margin_before : back().margin_before;
334 }
335
336 [[nodiscard]] constexpr float margin_after() const noexcept
337 {
338 return empty() ? 0 : _forward ? back().margin_after : front().margin_after;
339 }
340
341 [[nodiscard]] constexpr float padding_before() const noexcept
342 {
343 return empty() ? 0 : _forward ? front().padding_before : back().padding_before;
344 }
345
346 [[nodiscard]] constexpr float padding_after() const noexcept
347 {
348 return empty() ? 0 : _forward ? back().padding_after : front().padding_after;
349 }
350
351 [[nodiscard]] constexpr std::tuple<float, float, float> update_constraints() const noexcept
352 {
353 return constraints(begin(), end());
354 }
355
364 {
365 return constraints(cell.first<axis>(), cell.last<axis>());
366 }
367
368 [[nodiscard]] constexpr float position(cell_type const& cell) const noexcept
369 {
370 return position(cell.first<axis>(), cell.last<axis>());
371 }
372
373 [[nodiscard]] constexpr float extent(cell_type const& cell) const noexcept
374 {
375 return extent(cell.first<axis>(), cell.last<axis>());
376 }
377
378 [[nodiscard]] constexpr std::optional<float> guideline(cell_type const& cell) const noexcept
379 {
380 if (cell.span<axis>() == 1) {
381 return guideline(cell.first<axis>());
382 } else {
383 return std::nullopt;
384 }
385 }
386
409 constexpr void layout(float new_position, float new_extent, std::optional<float> external_guideline, float guideline_width) noexcept
410 {
411 // Start with the extent of each constraint equal to the preferred extent.
412 for (auto& constraint : _constraints) {
413 constraint.extent = constraint.preferred;
414 }
415
416 // If the total extent is too large, shrink the constraints that allow to be shrunk.
417 auto [total_extent, count] = layout_shrink(begin(), end());
418 while (total_extent > new_extent and count != 0) {
419 // The result may shrink slightly too much, which will be fixed by expanding in the next loop.
420 std::tie(total_extent, count) = layout_shrink(begin(), end(), total_extent - new_extent, count);
421 }
422
423 // If the total extent is too small, expand the constraints that allow to be grown.
424 std::tie(total_extent, count) = layout_expand(begin(), end());
425 while (total_extent < new_extent and count != 0) {
426 // The result may expand slightly too much, we don't care.
427 std::tie(total_extent, count) = layout_expand(begin(), end(), new_extent - total_extent, count);
428 }
429
430 // If the total extent is still too small, expand into the cells that are marked beyond_maximum.
431 if (total_extent < new_extent) {
432 // The result may expand slightly too much, we don't care.
433 count = std::count_if(begin(), end(), [](hilet& item) {
434 return item.beyond_maximum;
435 });
436 if (count) {
437 auto expand = new_extent - total_extent;
438 hilet expand_per = std::ceil(expand / count);
439
440 for (auto& constraint : _constraints) {
441 hilet expand_this = std::min(expand_per, expand);
442 if (constraint.beyond_maximum) {
443 constraint.extent += expand_this;
444 expand -= expand_this;
445 }
446 }
447 }
448 total_extent = extent(cbegin(), cend());
449 }
450
451 // If the total extent is still too small, expand the first constrain above the maximum size.
453 // The result may expand slightly too much, we don't care.
454 front().extent += new_extent - total_extent;
455 }
456
457 if (_forward) {
458 layout_position(begin(), end(), new_position, guideline_width);
459 } else {
460 layout_position(rbegin(), rend(), new_position, guideline_width);
461 }
462
463 if (external_guideline and size() == 1) {
464 // When there is only 1 cell on this axis, the external guideline is used.
465 // XXX If there are more cell, then the external alignment should be taken into account.
466 front().guideline = *external_guideline;
467 }
468 }
469
472 [[nodiscard]] constexpr size_t size() const noexcept
473 {
474 return _constraints.size();
475 }
476
479 [[nodiscard]] constexpr bool empty() const noexcept
480 {
481 return _constraints.empty();
482 }
483
486 [[nodiscard]] constexpr iterator begin() noexcept
487 {
488 return _constraints.begin();
489 }
490
493 [[nodiscard]] constexpr const_iterator begin() const noexcept
494 {
495 return _constraints.begin();
496 }
497
500 [[nodiscard]] constexpr const_iterator cbegin() const noexcept
501 {
502 return _constraints.cbegin();
503 }
504
507 [[nodiscard]] constexpr iterator end() noexcept
508 {
509 return _constraints.end();
510 }
511
514 [[nodiscard]] constexpr const_iterator end() const noexcept
515 {
516 return _constraints.end();
517 }
518
521 [[nodiscard]] constexpr const_iterator cend() const noexcept
522 {
523 return _constraints.cend();
524 }
525
528 [[nodiscard]] constexpr reverse_iterator rbegin() noexcept
529 {
530 return _constraints.rbegin();
531 }
532
535 [[nodiscard]] constexpr reverse_iterator rend() noexcept
536 {
537 return _constraints.rend();
538 }
539
546 [[nodiscard]] constexpr reference operator[](size_t index) noexcept
547 {
548 hi_axiom(index < size());
549 return _constraints[index];
550 }
551
558 [[nodiscard]] constexpr const_reference operator[](size_t index) const noexcept
559 {
560 hi_axiom(index < size());
561 return _constraints[index];
562 }
563
570 {
571 hi_axiom(not empty());
572 return _constraints.front();
573 }
574
580 [[nodiscard]] constexpr const_reference front() const noexcept
581 {
582 hi_axiom(not empty());
583 return _constraints.front();
584 }
585
592 {
593 hi_axiom(not empty());
594 return _constraints.back();
595 }
596
602 [[nodiscard]] constexpr const_reference back() const noexcept
603 {
604 hi_axiom(not empty());
605 return _constraints.back();
606 }
607
608private:
617 constraint_vector _constraints = {};
618
621 bool _forward = true;
622
640 layout_shrink(const_iterator first, const_iterator last, float shrink = 0.0f, size_t count = 1) noexcept
641 {
642 hilet first_ = begin() + std::distance(cbegin(), first);
643 hilet last_ = begin() + std::distance(cbegin(), last);
644
645 hi_axiom(shrink >= 0);
646
647 hilet shrink_per = std::floor(shrink / count);
648
649 auto new_extent = 0.0f;
650 auto new_count = 0_uz;
651 for (auto it = first_; it != last_; ++it) {
652 hilet shrink_this = std::max({shrink_per, shrink, it->extent - it->minimum});
653 it->extent -= shrink_this;
654 shrink -= shrink_this;
655
656 if (it != first_) {
657 new_extent += it->margin_before;
658 }
659 new_extent += it->extent;
660
661 if (it->extent > it->minimum) {
662 ++new_count;
663 }
664 }
665
666 return {new_extent, new_count};
667 }
668
686 layout_expand(const_iterator first, const_iterator last, float expand = 0.0f, size_t count = 1) noexcept
687 {
688 hilet first_ = begin() + std::distance(cbegin(), first);
689 hilet last_ = begin() + std::distance(cbegin(), last);
690
691 hi_axiom(expand >= 0.0f);
692
693 hilet expand_per = std::ceil(expand / count);
694 hi_axiom(expand_per >= 0.0f);
695
696 auto new_extent = 0.0f;
697 auto new_count = 0_uz;
698 for (auto it = first_; it != last_; ++it) {
699 hilet expand_this = std::min({expand_per, expand, it->maximum - it->extent});
700 it->extent += expand_this;
701 expand -= expand_this;
702
703 if (it != first_) {
704 new_extent += it->margin_before;
705 }
706 new_extent += it->extent;
707
708 if (it->extent < it->maximum) {
709 ++new_count;
710 }
711 }
712
713 return {new_extent, new_count};
714 }
715
716 constexpr void layout_position(auto first, auto last, float start_position, float guideline_width) noexcept
717 {
718 auto position = start_position;
719 for (auto it = first; it != last; ++it) {
720 it->position = position;
721 it->guideline = make_guideline(
722 it->alignment, position, position + it->extent, it->padding_before, it->padding_after, guideline_width);
723
724 position += it->extent;
725 position += it->margin_after;
726 }
727 }
728
736 constexpr void construct_simple_cell(cell_type const& cell) noexcept
737 {
738 inplace_max(_constraints[cell.first<axis>()].margin_before, cell.margin_before<axis>(_forward));
739 inplace_max(_constraints[cell.last<axis>() - 1].margin_after, cell.margin_after<axis>(_forward));
740 inplace_max(_constraints[cell.first<axis>()].padding_before, cell.padding_before<axis>(_forward));
741 inplace_max(_constraints[cell.last<axis>() - 1].padding_after, cell.padding_after<axis>(_forward));
742
743 for (auto i = cell.first<axis>(); i != cell.last<axis>(); ++i) {
744 _constraints[i].beyond_maximum |= cell.beyond_maximum;
745 }
746
747 if (cell.span<axis>() == 1) {
748 inplace_max(_constraints[cell.first<axis>()].alignment, cell.alignment<axis>());
749 inplace_max(_constraints[cell.first<axis>()].minimum, cell.minimum<axis>());
750 inplace_max(_constraints[cell.first<axis>()].preferred, cell.preferred<axis>());
751 inplace_min(_constraints[cell.first<axis>()].maximum, cell.maximum<axis>());
752 }
753 }
754
761 constexpr void construct_span_cell(cell_type const& cell) noexcept
762 {
763 auto num_cells = narrow_cast<float>(cell.span<axis>());
764
765 if (cell.span<axis>() > 1) {
767 if (hilet extra = cell.minimum<axis>() - span_minimum; extra > 0) {
769 for (auto i = cell.first<axis>(); i != cell.last<axis>(); ++i) {
770 _constraints[i].minimum += extra_per_cell;
771 }
772 }
773
774 if (hilet extra = cell.preferred<axis>() - span_preferred; extra > 0) {
776 for (auto i = cell.first<axis>(); i != cell.last<axis>(); ++i) {
777 _constraints[i].preferred += extra_per_cell;
778 }
779 }
780
781 if (hilet extra = cell.maximum<axis>() - span_preferred; extra < 0) {
783 for (auto i = cell.first<axis>(); i != cell.last<axis>(); ++i) {
784 // The maximum could become too low here, fixup() will fix this.
785 _constraints[i].maximum += extra_per_cell;
786 }
787 }
788 }
789 }
790
795 constexpr void construct_fixup() noexcept
796 {
797 for (auto it = begin(); it != end(); ++it) {
798 // Fix the margins so that between two constraints they are equal.
799 if (it + 1 != end()) {
800 it->margin_after = (it + 1)->margin_before = std::max(it->margin_after, (it + 1)->margin_before);
801 }
802
803 // Fix the constraints so that minimum <= preferred <= maximum.
804 inplace_max(it->preferred, it->minimum);
805 inplace_max(it->maximum, it->preferred);
806
807 // Fix the padding, so that it doesn't overlap.
808 if (it->padding_before + it->padding_after > it->minimum) {
809 hilet padding_diff = it->padding_after - it->padding_before;
810 hilet middle = std::clamp(it->minimum / 2.0f + padding_diff, 0.0f, it->minimum);
811 it->padding_after = middle;
812 it->padding_before = it->minimum - middle;
813 }
814 }
815 }
816
825 [[nodiscard]] constexpr std::tuple<float, float, float> constraints(const_iterator first, const_iterator last) const noexcept
826 {
827 auto r_minimum = 0.0f;
828 auto r_preferred = 0.0f;
829 auto r_maximum = 0.0f;
830 auto r_margin = 0.0f;
831
832 if (first != last) {
833 r_minimum = first->minimum;
834 r_preferred = first->preferred;
835 r_maximum = first->maximum;
836 for (auto it = first + 1; it != last; ++it) {
837 r_margin += it->margin_before;
838 r_minimum += it->minimum;
839 r_preferred += it->preferred;
840 r_maximum += it->maximum;
841 }
842 }
844 }
845
854 [[nodiscard]] constexpr std::tuple<float, float, float> constraints(size_t first, size_t last) const noexcept
855 {
856 hi_axiom(first <= last);
857 hi_axiom(last <= size());
858 return constraints(begin() + first, begin() + last);
859 }
860
868 [[nodiscard]] constexpr float position(const_iterator first, const_iterator last) const noexcept
869 {
870 hi_axiom(first != last);
871 if (_forward) {
872 return first->position;
873 } else {
874 return (last - 1)->position;
875 }
876 }
877
885 [[nodiscard]] constexpr float position(size_t first, size_t last) const noexcept
886 {
887 hi_axiom(first < last);
888 hi_axiom(last <= size());
889 return position(cbegin() + first, cbegin() + last);
890 }
891
899 [[nodiscard]] constexpr float extent(const_iterator first, const_iterator last) const noexcept
900 {
901 auto r = 0.0f;
902 if (first != last) {
903 r = first->extent;
904 for (auto it = first + 1; it != last; ++it) {
905 r += it->margin_before;
906 r += it->extent;
907 }
908 }
909 return r;
910 }
911
919 [[nodiscard]] constexpr float extent(size_t first, size_t last) const noexcept
920 {
921 hi_axiom(first <= last);
922 hi_axiom(last <= size());
923 return extent(cbegin() + first, cbegin() + last);
924 }
925
926 [[nodiscard]] constexpr std::optional<float> guideline(const_iterator it) const noexcept
927 {
928 return it->guideline;
929 }
930
931 [[nodiscard]] constexpr std::optional<float> guideline(size_t i) const noexcept
932 {
933 return guideline(cbegin() + i);
934 }
935};
936
937} // namespace detail
938
944template<typename T>
946public:
947 using value_type = T;
948
949 using cell_type = detail::grid_layout_cell<value_type>;
950 using cell_vector = std::vector<cell_type>;
951 using iterator = cell_vector::iterator;
952 using const_iterator = cell_vector::const_iterator;
953 using reference = cell_vector::reference;
954 using const_reference = cell_vector::const_reference;
955
956 ~grid_layout() = default;
957 constexpr grid_layout() noexcept = default;
958 constexpr grid_layout(grid_layout const&) noexcept = default;
959 constexpr grid_layout(grid_layout&&) noexcept = default;
960 constexpr grid_layout& operator=(grid_layout const&) noexcept = default;
961 constexpr grid_layout& operator=(grid_layout&&) noexcept = default;
962 [[nodiscard]] constexpr friend bool operator==(grid_layout const&, grid_layout const&) noexcept = default;
963
964 [[nodiscard]] constexpr bool empty() const noexcept
965 {
966 return _cells.empty();
967 }
968
969 [[nodiscard]] constexpr size_t size() const noexcept
970 {
971 return _cells.size();
972 }
973
974 [[nodiscard]] constexpr size_t num_columns() const noexcept
975 {
976 return _num_columns;
977 }
978
979 [[nodiscard]] constexpr size_t num_rows() const noexcept
980 {
981 return _num_rows;
982 }
983
984 [[nodiscard]] constexpr iterator begin() noexcept
985 {
986 return _cells.begin();
987 }
988
989 [[nodiscard]] constexpr iterator end() noexcept
990 {
991 return _cells.end();
992 }
993
994 [[nodiscard]] constexpr const_iterator begin() const noexcept
995 {
996 return _cells.begin();
997 }
998
999 [[nodiscard]] constexpr const_iterator end() const noexcept
1000 {
1001 return _cells.end();
1002 }
1003
1004 [[nodiscard]] constexpr const_iterator cbegin() const noexcept
1005 {
1006 return _cells.cbegin();
1007 }
1008
1009 [[nodiscard]] constexpr const_iterator cend() const noexcept
1010 {
1011 return _cells.cend();
1012 }
1013
1014 [[nodiscard]] constexpr const_reference operator[](size_t i) const noexcept
1015 {
1016 return _cells[i];
1017 }
1018
1019 [[nodiscard]] constexpr reference operator[](size_t i) noexcept
1020 {
1021 return _cells[i];
1022 }
1023
1032 [[nodiscard]] constexpr bool cell_in_use(size_t first_column, size_t first_row, size_t last_column, size_t last_row) noexcept
1033 {
1034 // At least one cell must be in the range.
1035 hi_axiom(first_column < last_column);
1036 hi_axiom(first_row < last_row);
1037
1038 for (hilet& cell : _cells) {
1039 if (first_column >= cell.last_column) {
1040 continue;
1041 }
1042 if (last_column <= cell.first_column) {
1043 continue;
1044 }
1045 if (first_row >= cell.last_row) {
1046 continue;
1047 }
1048 if (last_row <= cell.first_row) {
1049 continue;
1050 }
1051 return true;
1052 }
1053 return false;
1054 }
1055
1066 template<forward_of<value_type> Value>
1068 size_t first_column,
1069 size_t first_row,
1070 size_t last_column,
1071 size_t last_row,
1072 Value&& value,
1073 bool beyond_maximum = false) noexcept
1074 {
1075 // At least one cell must be in the range.
1076 hi_assert(first_column < last_column);
1077 hi_assert(first_row < last_row);
1078 hi_assert(not cell_in_use(first_column, first_row, last_column, last_row));
1079 auto& r = _cells.emplace_back(first_column, first_row, last_column, last_row, beyond_maximum, std::forward<Value>(value));
1080 update_after_insert_or_delete();
1081 return r;
1082 }
1083
1092 template<forward_of<value_type> Value>
1093 constexpr reference add_cell(size_t column, size_t row, Value&& value, bool beyond_maximum = false) noexcept
1094 {
1095 return add_cell(column, row, column + 1, row + 1, std::forward<Value>(value), beyond_maximum);
1096 }
1097
1098 constexpr void clear() noexcept
1099 {
1100 _cells.clear();
1101 update_after_insert_or_delete();
1102 }
1103
1104 [[nodiscard]] constexpr box_constraints constraints(bool left_to_right) const noexcept
1105 {
1106 // Rows in the grid are laid out from top to bottom which is reverse from the y-axis up.
1107 _row_constraints = {_cells, num_rows(), false};
1108 _column_constraints = {_cells, num_columns(), left_to_right};
1109
1110 auto r = box_constraints{};
1111 std::tie(r.minimum.width(), r.preferred.width(), r.maximum.width()) = _column_constraints.update_constraints();
1112 r.margins.left() = _column_constraints.margin_before();
1113 r.margins.right() = _column_constraints.margin_after();
1114 r.padding.left() = _column_constraints.padding_before();
1115 r.padding.right() = _column_constraints.padding_after();
1116
1117 std::tie(r.minimum.height(), r.preferred.height(), r.maximum.height()) = _row_constraints.update_constraints();
1118 r.margins.bottom() = _row_constraints.margin_after();
1119 r.margins.top() = _row_constraints.margin_before();
1120 r.padding.bottom() = _row_constraints.padding_after();
1121 r.padding.top() = _row_constraints.padding_before();
1122
1123 r.alignment = [&] {
1124 if (num_rows() == 1 and num_columns() == 1) {
1125 return hi::alignment{_column_constraints.front().alignment, _row_constraints.front().alignment};
1126 } else if (num_rows() == 1) {
1127 return hi::alignment{_row_constraints.front().alignment};
1128 } else if (num_columns() == 1) {
1129 return hi::alignment{_column_constraints.front().alignment};
1130 } else {
1131 return hi::alignment{};
1132 }
1133 }();
1134
1135 return r;
1136 }
1137
1143 constexpr void set_layout(box_shape const& shape, float baseline_adjustment) noexcept
1144 {
1145 // Rows in the grid are laid out from top to bottom which is reverse from the y-axis up.
1146 _column_constraints.layout(shape.x(), shape.width(), shape.centerline, 0);
1147 _row_constraints.layout(shape.y(), shape.height(), shape.baseline, baseline_adjustment);
1148
1149 // Assign the shape for each cell.
1150 for (auto& cell : _cells) {
1151 cell.shape.rectangle = {
1152 _column_constraints.position(cell),
1153 _row_constraints.position(cell),
1154 _column_constraints.extent(cell),
1155 _row_constraints.extent(cell)};
1156 cell.shape.centerline = _column_constraints.guideline(cell);
1157 cell.shape.baseline = _row_constraints.guideline(cell);
1158 }
1159 }
1160
1161private:
1162 cell_vector _cells = {};
1163 size_t _num_rows = 0;
1164 size_t _num_columns = 0;
1165 mutable detail::grid_layout_axis_constraints<axis::y, value_type> _row_constraints = {};
1166 mutable detail::grid_layout_axis_constraints<axis::x, value_type> _column_constraints = {};
1167
1172 constexpr void sort_cells() noexcept
1173 {
1174 std::sort(_cells.begin(), _cells.end(), [](cell_type const& lhs, cell_type const& rhs) {
1175 if (lhs.first_row != rhs.first_row) {
1176 return lhs.first_row < rhs.first_row;
1177 } else {
1178 return lhs.first_column < rhs.first_column;
1179 }
1180 });
1181 }
1182
1185 constexpr void update_after_insert_or_delete() noexcept
1186 {
1187 sort_cells();
1188
1189 _num_rows = 0;
1190 _num_columns = 0;
1191 for (hilet& cell : _cells) {
1192 inplace_max(_num_rows, cell.last_row);
1193 inplace_max(_num_columns, cell.last_column);
1194 }
1195 }
1196};
1197
1198}} // namespace hi::v1
Utilities for parsing spreadsheet addresses.
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:60
axis
An enumeration of the 3 axis for 3D geometry.
Definition axis.hpp:19
@ middle
Align to the vertical-middle.
DOXYGEN BUG.
Definition algorithm.hpp:16
geometry/margins.hpp
Definition lookahead_iterator.hpp:5
constexpr Out narrow_cast(In const &rhs) noexcept
Cast numeric values without loss of precision.
Definition cast.hpp:377
Horizontal/Vertical alignment combination.
Definition alignment.hpp:242
constexpr float & width() noexcept
Access the x-as-width element from the extent.
Definition extent2.hpp:104
constexpr float & height() noexcept
Access the y-as-height element from the extent.
Definition extent2.hpp:115
2D constraints.
Definition box_constraints.hpp:25
Definition box_shape.hpp:18
Definition grid_layout.hpp:26
Definition grid_layout.hpp:230
constexpr reference operator[](size_t index) noexcept
Get element.
Definition grid_layout.hpp:546
constexpr size_t size() const noexcept
Number of cell on this axis.
Definition grid_layout.hpp:472
constexpr const_reference back() const noexcept
Get the last element.
Definition grid_layout.hpp:602
constexpr bool empty() const noexcept
Check if this axis is empty.
Definition grid_layout.hpp:479
constexpr reference front() noexcept
Get the first element.
Definition grid_layout.hpp:569
constexpr reverse_iterator rend() noexcept
Iterator to the first cell on this axis.
Definition grid_layout.hpp:535
constexpr reverse_iterator rbegin() noexcept
Iterator to the first cell on this axis.
Definition grid_layout.hpp:528
constexpr reference back() noexcept
Get the last element.
Definition grid_layout.hpp:591
constexpr const_iterator end() const noexcept
Iterator to beyond the last cell on this axis.
Definition grid_layout.hpp:514
constexpr const_reference operator[](size_t index) const noexcept
Get element.
Definition grid_layout.hpp:558
constexpr const_iterator cbegin() const noexcept
Iterator to the first cell on this axis.
Definition grid_layout.hpp:500
constexpr iterator end() noexcept
Iterator to beyond the last cell on this axis.
Definition grid_layout.hpp:507
constexpr const_iterator begin() const noexcept
Iterator to the first cell on this axis.
Definition grid_layout.hpp:493
constexpr const_iterator cend() const noexcept
Iterator to beyond the last cell on this axis.
Definition grid_layout.hpp:521
constexpr iterator begin() noexcept
Iterator to the first cell on this axis.
Definition grid_layout.hpp:486
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:409
constexpr const_reference front() const noexcept
Get the first element.
Definition grid_layout.hpp:580
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:363
float margin_after
The right/bottom margin of the cells.
Definition grid_layout.hpp:258
float extent
Size of the cell.
Definition grid_layout.hpp:286
float padding_before
The left/top padding of the cells.
Definition grid_layout.hpp:262
float padding_after
The right/bottom padding of the cells.
Definition grid_layout.hpp:266
float minimum
The minimum width/height of the cells.
Definition grid_layout.hpp:242
bool beyond_maximum
Allow this cell to be resized beyond the maximum constraint.
Definition grid_layout.hpp:274
float maximum
The maximum width/height of the cells.
Definition grid_layout.hpp:250
float margin_before
The left/top margin of the cells.
Definition grid_layout.hpp:254
float preferred
The preferred width/height of the cells.
Definition grid_layout.hpp:246
std::optional< float > guideline
The before-position within this cell where to align to.
Definition grid_layout.hpp:292
float position
The position of the cell.
Definition grid_layout.hpp:280
Grid layout algorithm.
Definition grid_layout.hpp:945
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:1032
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:1067
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:1093
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:1143
Definition concepts.hpp:42
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)