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"
9#include "spreadsheet_address.hpp"
11#include "../geometry/axis.hpp"
12#include "../cast.hpp"
13#include <cstdint>
14#include <numeric>
15#include <vector>
16#include <algorithm>
17#include <utility>
18#include <cmath>
19
20namespace hi { inline namespace v1 {
21namespace detail {
22
23template<typename T>
25 using value_type = T;
26
27 size_t first_column = 0;
28 size_t first_row = 0;
29 size_t last_column = 0;
30 size_t last_row = 0;
31 bool beyond_maximum = false;
32 value_type value = {};
33 box_shape shape = {};
34
35 constexpr grid_layout_cell() noexcept = default;
36 constexpr grid_layout_cell(grid_layout_cell const&) noexcept = default;
37 constexpr grid_layout_cell(grid_layout_cell&&) noexcept = default;
38 constexpr grid_layout_cell& operator=(grid_layout_cell const&) noexcept = default;
39 constexpr grid_layout_cell& operator=(grid_layout_cell&&) noexcept = default;
40
41 constexpr grid_layout_cell(
42 size_t first_column,
43 size_t first_row,
44 size_t last_column,
45 size_t last_row,
46 bool beyond_maximum,
47 std::convertible_to<value_type> auto&& value) noexcept :
48 first_column(first_column),
49 first_row(first_row),
50 last_column(last_column),
51 last_row(last_row),
52 beyond_maximum(beyond_maximum),
53 value(hi_forward(value))
54 {
55 hi_assert(first_column < last_column);
56 hi_assert(first_row < last_row);
57 }
58
59 constexpr void set_constraints(box_constraints const& constraints) noexcept
60 {
61 _constraints = constraints;
62 }
63
64 template<hi::axis Axis>
65 [[nodiscard]] constexpr size_t first() const noexcept
66 {
67 if constexpr (Axis == axis::x) {
68 return first_column;
69 } else if constexpr (Axis == axis::y) {
70 return first_row;
71 } else {
73 }
74 }
75
76 template<hi::axis Axis>
77 [[nodiscard]] constexpr size_t last() const noexcept
78 {
79 if constexpr (Axis == axis::x) {
80 return last_column;
81 } else if constexpr (Axis == axis::y) {
82 return last_row;
83 } else {
85 }
86 }
87
88 template<hi::axis Axis>
89 [[nodiscard]] constexpr size_t span() const noexcept
90 {
91 hi_axiom(first<Axis>() < last<Axis>());
92 return last<Axis>() - first<Axis>();
93 }
94
95 template<hi::axis Axis>
96 [[nodiscard]] constexpr auto alignment() const noexcept
97 {
98 if constexpr (Axis == axis::x) {
99 return _constraints.alignment.horizontal();
100 } else if constexpr (Axis == axis::y) {
101 return _constraints.alignment.vertical();
102 } else {
104 }
105 }
106
107 template<hi::axis Axis>
108 [[nodiscard]] constexpr int minimum() const noexcept
109 {
110 if constexpr (Axis == axis::x) {
111 return _constraints.minimum.width();
112 } else if constexpr (Axis == axis::y) {
113 return _constraints.minimum.height();
114 } else {
116 }
117 }
118
119 template<hi::axis Axis>
120 [[nodiscard]] constexpr int preferred() const noexcept
121 {
122 if constexpr (Axis == axis::x) {
123 return _constraints.preferred.width();
124 } else if constexpr (Axis == axis::y) {
125 return _constraints.preferred.height();
126 } else {
128 }
129 }
130
131 template<hi::axis Axis>
132 [[nodiscard]] constexpr int maximum() const noexcept
133 {
134 if constexpr (Axis == axis::x) {
135 return _constraints.maximum.width();
136 } else if constexpr (Axis == axis::y) {
137 return _constraints.maximum.height();
138 } else {
140 }
141 }
142
143 template<hi::axis Axis>
144 [[nodiscard]] constexpr int margin_before(bool forward) const noexcept
145 {
146 if constexpr (Axis == axis::x) {
147 if (forward) {
148 return _constraints.margins.left();
149 } else {
150 return _constraints.margins.right();
151 }
152 } else if constexpr (Axis == axis::y) {
153 if (forward) {
154 return _constraints.margins.bottom();
155 } else {
156 return _constraints.margins.top();
157 }
158 } else {
160 }
161 }
162
163 template<hi::axis Axis>
164 [[nodiscard]] constexpr int margin_after(bool forward) const noexcept
165 {
166 if constexpr (Axis == axis::x) {
167 if (forward) {
168 return _constraints.margins.right();
169 } else {
170 return _constraints.margins.left();
171 }
172 } else if constexpr (Axis == axis::y) {
173 if (forward) {
174 return _constraints.margins.top();
175 } else {
176 return _constraints.margins.bottom();
177 }
178 } else {
180 }
181 }
182
183 template<hi::axis Axis>
184 [[nodiscard]] constexpr int padding_before(bool forward) const noexcept
185 {
186 if constexpr (Axis == axis::x) {
187 if (forward) {
188 return _constraints.padding.left();
189 } else {
190 return _constraints.padding.right();
191 }
192 } else if constexpr (Axis == axis::y) {
193 if (forward) {
194 return _constraints.padding.bottom();
195 } else {
196 return _constraints.padding.top();
197 }
198 } else {
200 }
201 }
202
203 template<hi::axis Axis>
204 [[nodiscard]] constexpr int padding_after(bool forward) const noexcept
205 {
206 if constexpr (Axis == axis::x) {
207 if (forward) {
208 return _constraints.padding.right();
209 } else {
210 return _constraints.padding.left();
211 }
212 } else if constexpr (Axis == axis::y) {
213 if (forward) {
214 return _constraints.padding.top();
215 } else {
216 return _constraints.padding.bottom();
217 }
218 } else {
220 }
221 }
222
223private:
224 box_constraints _constraints;
225};
226
227template<hi::axis Axis, typename T>
229public:
230 constexpr static hi::axis axis = Axis;
231
232 using value_type = T;
233 using alignment_type = std::conditional_t<axis == axis::y, vertical_alignment, horizontal_alignment>;
236
240 int minimum = 0;
241
244 int preferred = 0;
245
249
253
257
261
265
268 alignment_type alignment = alignment_type::none;
269
272 bool beyond_maximum = false;
273
278 int position = 0;
279
284 int extent = 0;
285
290 std::optional<int> guideline = 0;
291 };
293 using iterator = constraint_vector::iterator;
294 using const_iterator = constraint_vector::const_iterator;
295 using reverse_iterator = constraint_vector::reverse_iterator;
296 using reference = constraint_vector::reference;
297 using const_reference = constraint_vector::const_reference;
298
299 constexpr ~grid_layout_axis_constraints() = default;
300 constexpr grid_layout_axis_constraints() noexcept = default;
301 constexpr grid_layout_axis_constraints(grid_layout_axis_constraints const&) noexcept = default;
302 constexpr grid_layout_axis_constraints(grid_layout_axis_constraints&&) noexcept = default;
303 constexpr grid_layout_axis_constraints& operator=(grid_layout_axis_constraints const&) noexcept = default;
304 constexpr grid_layout_axis_constraints& operator=(grid_layout_axis_constraints&&) noexcept = default;
305 [[nodiscard]] constexpr friend bool
306 operator==(grid_layout_axis_constraints const&, grid_layout_axis_constraints const&) noexcept = default;
307
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 int margin_before() const noexcept
329 {
330 return empty() ? 0 : _forward ? front().margin_before : back().margin_before;
331 }
332
333 [[nodiscard]] constexpr int margin_after() const noexcept
334 {
335 return empty() ? 0 : _forward ? back().margin_after : front().margin_after;
336 }
337
338 [[nodiscard]] constexpr int padding_before() const noexcept
339 {
340 return empty() ? 0 : _forward ? front().padding_before : back().padding_before;
341 }
342
343 [[nodiscard]] constexpr int padding_after() const noexcept
344 {
345 return empty() ? 0 : _forward ? back().padding_after : front().padding_after;
346 }
347
348 [[nodiscard]] constexpr std::tuple<int, int, int> update_constraints() const noexcept
349 {
350 return constraints(begin(), end());
351 }
352
360 [[nodiscard]] constexpr std::tuple<int, int, int> constraints(cell_type const& cell) const noexcept
361 {
362 return constraints(cell.first<axis>(), cell.last<axis>());
363 }
364
365 [[nodiscard]] constexpr int position(cell_type const& cell) const noexcept
366 {
367 return position(cell.first<axis>(), cell.last<axis>());
368 }
369
370 [[nodiscard]] constexpr int extent(cell_type const& cell) const noexcept
371 {
372 return extent(cell.first<axis>(), cell.last<axis>());
373 }
374
375 [[nodiscard]] constexpr std::optional<int> 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(int new_position, int new_extent, std::optional<int> external_guideline, int 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 hilet todo = new_extent - total_extent;
435 hilet per_extent = narrow_cast<int>((todo + count - 1) / count);
436 for (auto& constraint : _constraints) {
437 if (constraint.beyond_maximum) {
438 constraint.extent += per_extent;
439 }
440 }
441 }
442 total_extent = extent(cbegin(), cend());
443 }
444
445 // If the total extent is still too small, expand the first constrain above the maximum size.
446 if (total_extent < new_extent and not empty()) {
447 // The result may expand slightly too much, we don't care.
448 front().extent += new_extent - total_extent;
449 }
450
451 if (_forward) {
452 layout_position(begin(), end(), new_position, guideline_width);
453 } else {
454 layout_position(rbegin(), rend(), new_position, guideline_width);
455 }
456
457 if (external_guideline and size() == 1) {
458 // When there is only 1 cell on this axis, the external guideline is used.
459 // XXX If there are more cell, then the external alignment should be taken into account.
460 front().guideline = *external_guideline;
461 }
462 }
463
466 [[nodiscard]] constexpr size_t size() const noexcept
467 {
468 return _constraints.size();
469 }
470
473 [[nodiscard]] constexpr bool empty() const noexcept
474 {
475 return _constraints.empty();
476 }
477
480 [[nodiscard]] constexpr iterator begin() noexcept
481 {
482 return _constraints.begin();
483 }
484
487 [[nodiscard]] constexpr const_iterator begin() const noexcept
488 {
489 return _constraints.begin();
490 }
491
494 [[nodiscard]] constexpr const_iterator cbegin() const noexcept
495 {
496 return _constraints.cbegin();
497 }
498
501 [[nodiscard]] constexpr iterator end() noexcept
502 {
503 return _constraints.end();
504 }
505
508 [[nodiscard]] constexpr const_iterator end() const noexcept
509 {
510 return _constraints.end();
511 }
512
515 [[nodiscard]] constexpr const_iterator cend() const noexcept
516 {
517 return _constraints.cend();
518 }
519
522 [[nodiscard]] constexpr reverse_iterator rbegin() noexcept
523 {
524 return _constraints.rbegin();
525 }
526
529 [[nodiscard]] constexpr reverse_iterator rend() noexcept
530 {
531 return _constraints.rend();
532 }
533
540 [[nodiscard]] constexpr reference operator[](size_t index) noexcept
541 {
542 hi_axiom(index < size());
543 return _constraints[index];
544 }
545
552 [[nodiscard]] constexpr const_reference operator[](size_t index) const noexcept
553 {
554 hi_axiom(index < size());
555 return _constraints[index];
556 }
557
563 [[nodiscard]] constexpr reference front() noexcept
564 {
565 hi_axiom(not empty());
566 return _constraints.front();
567 }
568
574 [[nodiscard]] constexpr const_reference front() const noexcept
575 {
576 hi_axiom(not empty());
577 return _constraints.front();
578 }
579
585 [[nodiscard]] constexpr reference back() noexcept
586 {
587 hi_axiom(not empty());
588 return _constraints.back();
589 }
590
596 [[nodiscard]] constexpr const_reference back() const noexcept
597 {
598 hi_axiom(not empty());
599 return _constraints.back();
600 }
601
602private:
611 constraint_vector _constraints = {};
612
615 bool _forward = true;
616
633 [[nodiscard]] constexpr std::pair<int, size_t>
634 layout_shrink(const_iterator first, const_iterator last, int extra = 0, size_t count = 1) noexcept
635 {
636 hilet first_ = begin() + std::distance(cbegin(), first);
637 hilet last_ = begin() + std::distance(cbegin(), last);
638
639 hi_axiom(extra >= 0);
640
641 hilet extra_per = narrow_cast<int>((extra + count - 1) / count);
642
643 auto new_extent = 0;
644 auto new_count = 0_uz;
645 for (auto it = first_; it != last_; ++it) {
646 it->extent = it->extent - std::max(extra_per, it->extent - it->minimum);
647
648 if (it != first_) {
649 new_extent += it->margin_before;
650 }
651 new_extent += it->extent;
652
653 if (it->extent > it->minimum) {
654 ++new_count;
655 }
656 }
657
658 return {new_extent, new_count};
659 }
660
677 [[nodiscard]] constexpr std::pair<int, size_t>
678 layout_expand(const_iterator first, const_iterator last, int extra = 0, size_t count = 1) noexcept
679 {
680 hilet first_ = begin() + std::distance(cbegin(), first);
681 hilet last_ = begin() + std::distance(cbegin(), last);
682
683 hi_axiom(extra >= 0);
684
685 hilet extra_per = narrow_cast<int>((extra + count - 1) / count);
686
687 auto new_extent = 0;
688 auto new_count = 0_uz;
689 for (auto it = first_; it != last_; ++it) {
690 it->extent = it->extent + std::min(extra_per, it->maximum - it->extent);
691
692 if (it != first_) {
693 new_extent += it->margin_before;
694 }
695 new_extent += it->extent;
696
697 if (it->extent < it->maximum) {
698 ++new_count;
699 }
700 }
701
702 return {new_extent, new_count};
703 }
704
705 constexpr void layout_position(auto first, auto last, int start_position, int guideline_width) noexcept
706 {
707 auto position = start_position;
708 for (auto it = first; it != last; ++it) {
709 it->position = position;
710 it->guideline = make_guideline(
711 it->alignment, position, position + it->extent, it->padding_before, it->padding_after, guideline_width);
712
713 position += it->extent;
714 position += it->margin_after;
715 }
716 }
717
725 constexpr void construct_simple_cell(cell_type const& cell) noexcept
726 {
727 inplace_max(_constraints[cell.first<axis>()].margin_before, cell.margin_before<axis>(_forward));
728 inplace_max(_constraints[cell.last<axis>() - 1].margin_after, cell.margin_after<axis>(_forward));
729 inplace_max(_constraints[cell.first<axis>()].padding_before, cell.padding_before<axis>(_forward));
730 inplace_max(_constraints[cell.last<axis>() - 1].padding_after, cell.padding_after<axis>(_forward));
731
732 for (auto i = cell.first<axis>(); i != cell.last<axis>(); ++i) {
733 _constraints[i].beyond_maximum |= cell.beyond_maximum;
734 }
735
736 if (cell.span<axis>() == 1) {
737 inplace_max(_constraints[cell.first<axis>()].alignment, cell.alignment<axis>());
738 inplace_max(_constraints[cell.first<axis>()].minimum, cell.minimum<axis>());
739 inplace_max(_constraints[cell.first<axis>()].preferred, cell.preferred<axis>());
740 inplace_min(_constraints[cell.first<axis>()].maximum, cell.maximum<axis>());
741 }
742 }
743
750 constexpr void construct_span_cell(cell_type const& cell) noexcept
751 {
752 if (cell.span<axis>() > 1) {
753 hilet[span_minimum, span_preferred, span_maximum] = constraints(cell);
754 if (hilet extra = cell.minimum<axis>() - span_minimum; extra > 0) {
755 hilet extra_per_cell = narrow_cast<int>((extra + cell.span<axis>() - 1) / cell.span<axis>());
756 for (auto i = cell.first<axis>(); i != cell.last<axis>(); ++i) {
757 _constraints[i].minimum += extra_per_cell;
758 }
759 }
760
761 if (hilet extra = cell.preferred<axis>() - span_preferred; extra > 0) {
762 hilet extra_per_cell = narrow_cast<int>((extra + cell.span<axis>() - 1) / cell.span<axis>());
763 for (auto i = cell.first<axis>(); i != cell.last<axis>(); ++i) {
764 _constraints[i].preferred += extra_per_cell;
765 }
766 }
767
768 if (hilet extra = cell.maximum<axis>() - span_preferred; extra < 0) {
769 hilet extra_per_cell = narrow_cast<int>((extra + cell.span<axis>() - 1) / cell.span<axis>());
770 for (auto i = cell.first<axis>(); i != cell.last<axis>(); ++i) {
771 // The maximum could become too low here, fixup() will fix this.
772 _constraints[i].maximum += extra_per_cell;
773 }
774 }
775 }
776 }
777
782 constexpr void construct_fixup() noexcept
783 {
784 for (auto it = begin(); it != end(); ++it) {
785 // Fix the margins so that between two constraints they are equal.
786 if (it + 1 != end()) {
787 it->margin_after = (it + 1)->margin_before = std::max(it->margin_after, (it + 1)->margin_before);
788 }
789
790 // Fix the constraints so that minimum <= preferred <= maximum.
791 inplace_max(it->preferred, it->minimum);
792 inplace_max(it->maximum, it->preferred);
793
794 // Fix the padding, so that it doesn't overlap.
795 if (it->padding_before + it->padding_after > it->minimum) {
796 hilet padding_diff = it->padding_after - it->padding_before;
797 hilet middle = std::clamp(it->minimum / 2 + padding_diff, 0, it->minimum);
798 it->padding_after = middle;
799 it->padding_before = it->minimum - middle;
800 }
801 }
802 }
803
812 [[nodiscard]] constexpr std::tuple<int, int, int> constraints(const_iterator first, const_iterator last) const noexcept
813 {
814 auto r_minimum = 0;
815 auto r_preferred = 0;
816 auto r_maximum = 0;
817 auto r_margin = 0;
818
819 if (first != last) {
820 r_minimum = first->minimum;
821 r_preferred = first->preferred;
822 r_maximum = first->maximum;
823 for (auto it = first + 1; it != last; ++it) {
824 r_margin += it->margin_before;
825 r_minimum += it->minimum;
826 r_preferred += it->preferred;
827 r_maximum += it->maximum;
828 }
829 }
830 return {r_minimum + r_margin, r_preferred + r_margin, r_maximum + r_margin};
831 }
832
841 [[nodiscard]] constexpr std::tuple<int, int, int> constraints(size_t first, size_t last) const noexcept
842 {
843 hi_axiom(first <= last);
844 hi_axiom(last <= size());
845 return constraints(begin() + first, begin() + last);
846 }
847
855 [[nodiscard]] constexpr int position(const_iterator first, const_iterator last) const noexcept
856 {
857 hi_axiom(first != last);
858 if (_forward) {
859 return first->position;
860 } else {
861 return (last - 1)->position;
862 }
863 }
864
872 [[nodiscard]] constexpr int position(size_t first, size_t last) const noexcept
873 {
874 hi_axiom(first < last);
875 hi_axiom(last <= size());
876 return position(cbegin() + first, cbegin() + last);
877 }
878
886 [[nodiscard]] constexpr int extent(const_iterator first, const_iterator last) const noexcept
887 {
888 auto r = 0;
889 if (first != last) {
890 r = first->extent;
891 for (auto it = first + 1; it != last; ++it) {
892 r += it->margin_before;
893 r += it->extent;
894 }
895 }
896 return r;
897 }
898
906 [[nodiscard]] constexpr int extent(size_t first, size_t last) const noexcept
907 {
908 hi_axiom(first <= last);
909 hi_axiom(last <= size());
910 return extent(cbegin() + first, cbegin() + last);
911 }
912
913 [[nodiscard]] constexpr std::optional<int> guideline(const_iterator it) const noexcept
914 {
915 return it->guideline;
916 }
917
918 [[nodiscard]] constexpr std::optional<int> guideline(size_t i) const noexcept
919 {
920 return guideline(cbegin() + i);
921 }
922};
923
924} // namespace detail
925
931template<typename T>
933public:
934 using value_type = T;
935
936 using cell_type = detail::grid_layout_cell<value_type>;
937 using cell_vector = std::vector<cell_type>;
938 using iterator = cell_vector::iterator;
939 using const_iterator = cell_vector::const_iterator;
940 using reference = cell_vector::reference;
941 using const_reference = cell_vector::const_reference;
942
943 ~grid_layout() = default;
944 constexpr grid_layout() noexcept = default;
945 constexpr grid_layout(grid_layout const&) noexcept = default;
946 constexpr grid_layout(grid_layout&&) noexcept = default;
947 constexpr grid_layout& operator=(grid_layout const&) noexcept = default;
948 constexpr grid_layout& operator=(grid_layout&&) noexcept = default;
949 [[nodiscard]] constexpr friend bool operator==(grid_layout const&, grid_layout const&) noexcept = default;
950
951 [[nodiscard]] constexpr bool empty() const noexcept
952 {
953 return _cells.empty();
954 }
955
956 [[nodiscard]] constexpr size_t size() const noexcept
957 {
958 return _cells.size();
959 }
960
961 [[nodiscard]] constexpr size_t num_columns() const noexcept
962 {
963 return _num_columns;
964 }
965
966 [[nodiscard]] constexpr size_t num_rows() const noexcept
967 {
968 return _num_rows;
969 }
970
971 [[nodiscard]] constexpr iterator begin() noexcept
972 {
973 return _cells.begin();
974 }
975
976 [[nodiscard]] constexpr iterator end() noexcept
977 {
978 return _cells.end();
979 }
980
981 [[nodiscard]] constexpr const_iterator begin() const noexcept
982 {
983 return _cells.begin();
984 }
985
986 [[nodiscard]] constexpr const_iterator end() const noexcept
987 {
988 return _cells.end();
989 }
990
991 [[nodiscard]] constexpr const_iterator cbegin() const noexcept
992 {
993 return _cells.cbegin();
994 }
995
996 [[nodiscard]] constexpr const_iterator cend() const noexcept
997 {
998 return _cells.cend();
999 }
1000
1001 [[nodiscard]] constexpr const_reference operator[](size_t i) const noexcept
1002 {
1003 return _cells[i];
1004 }
1005
1006 [[nodiscard]] constexpr reference operator[](size_t i) noexcept
1007 {
1008 return _cells[i];
1009 }
1010
1019 [[nodiscard]] constexpr bool cell_in_use(size_t first_column, size_t first_row, size_t last_column, size_t last_row) noexcept
1020 {
1021 // At least one cell must be in the range.
1022 hi_axiom(first_column < last_column);
1023 hi_axiom(first_row < last_row);
1024
1025 for (hilet& cell : _cells) {
1026 if (first_column >= cell.last_column) {
1027 continue;
1028 }
1029 if (last_column <= cell.first_column) {
1030 continue;
1031 }
1032 if (first_row >= cell.last_row) {
1033 continue;
1034 }
1035 if (last_row <= cell.first_row) {
1036 continue;
1037 }
1038 return true;
1039 }
1040 return false;
1041 }
1042
1053 template<forward_of<value_type> Value>
1054 constexpr reference add_cell(
1055 size_t first_column,
1056 size_t first_row,
1057 size_t last_column,
1058 size_t last_row,
1059 Value&& value,
1060 bool beyond_maximum = false) noexcept
1061 {
1062 // At least one cell must be in the range.
1063 hi_assert(first_column < last_column);
1064 hi_assert(first_row < last_row);
1065 hi_assert(not cell_in_use(first_column, first_row, last_column, last_row));
1066 auto& r = _cells.emplace_back(first_column, first_row, last_column, last_row, beyond_maximum, std::forward<Value>(value));
1067 update_after_insert_or_delete();
1068 return r;
1069 }
1070
1079 template<forward_of<value_type> Value>
1080 constexpr reference add_cell(size_t column, size_t row, Value&& value, bool beyond_maximum = false) noexcept
1081 {
1082 return add_cell(column, row, column + 1, row + 1, std::forward<Value>(value), beyond_maximum);
1083 }
1084
1085 constexpr void clear() noexcept
1086 {
1087 _cells.clear();
1088 update_after_insert_or_delete();
1089 }
1090
1091 [[nodiscard]] constexpr box_constraints constraints(bool left_to_right) const noexcept
1092 {
1093 // Rows in the grid are laid out from top to bottom which is reverse from the y-axis up.
1094 _row_constraints = {_cells, num_rows(), false};
1095 _column_constraints = {_cells, num_columns(), left_to_right};
1096
1097 auto r = box_constraints{};
1098 std::tie(r.minimum.width(), r.preferred.width(), r.maximum.width()) = _column_constraints.update_constraints();
1099 r.margins.left() = _column_constraints.margin_before();
1100 r.margins.right() = _column_constraints.margin_after();
1101 r.padding.left() = _column_constraints.padding_before();
1102 r.padding.right() = _column_constraints.padding_after();
1103
1104 std::tie(r.minimum.height(), r.preferred.height(), r.maximum.height()) = _row_constraints.update_constraints();
1105 r.margins.bottom() = _row_constraints.margin_after();
1106 r.margins.top() = _row_constraints.margin_before();
1107 r.padding.bottom() = _row_constraints.padding_after();
1108 r.padding.top() = _row_constraints.padding_before();
1109
1110 r.alignment = [&] {
1111 if (num_rows() == 1 and num_columns() == 1) {
1112 return hi::alignment{_column_constraints.front().alignment, _row_constraints.front().alignment};
1113 } else if (num_rows() == 1) {
1114 return hi::alignment{_row_constraints.front().alignment};
1115 } else if (num_columns() == 1) {
1116 return hi::alignment{_column_constraints.front().alignment};
1117 } else {
1118 return hi::alignment{};
1119 }
1120 }();
1121
1122 return r;
1123 }
1124
1130 constexpr void set_layout(box_shape const& shape, int baseline_adjustment) noexcept
1131 {
1132 // Rows in the grid are laid out from top to bottom which is reverse from the y-axis up.
1133 _column_constraints.layout(shape.x(), shape.width(), shape.centerline, 0);
1134 _row_constraints.layout(shape.y(), shape.height(), shape.baseline, baseline_adjustment);
1135
1136 // Assign the shape for each cell.
1137 for (auto& cell : _cells) {
1138 cell.shape.rectangle = {
1139 _column_constraints.position(cell),
1140 _row_constraints.position(cell),
1141 _column_constraints.extent(cell),
1142 _row_constraints.extent(cell)};
1143 cell.shape.centerline = _column_constraints.guideline(cell);
1144 cell.shape.baseline = _row_constraints.guideline(cell);
1145 }
1146 }
1147
1148private:
1149 cell_vector _cells = {};
1150 size_t _num_rows = 0;
1151 size_t _num_columns = 0;
1152 mutable detail::grid_layout_axis_constraints<axis::y, value_type> _row_constraints = {};
1153 mutable detail::grid_layout_axis_constraints<axis::x, value_type> _column_constraints = {};
1154
1159 constexpr void sort_cells() noexcept
1160 {
1161 std::sort(_cells.begin(), _cells.end(), [](cell_type const& lhs, cell_type const& rhs) {
1162 if (lhs.first_row != rhs.first_row) {
1163 return lhs.first_row < rhs.first_row;
1164 } else {
1165 return lhs.first_column < rhs.first_column;
1166 }
1167 });
1168 }
1169
1172 constexpr void update_after_insert_or_delete() noexcept
1173 {
1174 sort_cells();
1175
1176 _num_rows = 0;
1177 _num_columns = 0;
1178 for (hilet& cell : _cells) {
1179 inplace_max(_num_rows, cell.last_row);
1180 inplace_max(_num_columns, cell.last_column);
1181 }
1182 }
1183};
1184
1185}} // namespace hi::v1
#define hi_static_no_default(...)
This part of the code should not be reachable, unless a programming bug.
Definition assert.hpp:181
#define hi_assert(expression,...)
Assert if expression is true.
Definition assert.hpp:87
#define hi_axiom(expression,...)
Specify an axiom; an expression that is true.
Definition assert.hpp:133
#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
The axis data type.
constexpr std::optional< T > make_guideline(vertical_alignment alignment, T bottom, T top, T padding_bottom, T padding_top, T guideline_width)
Create a guideline between two points.
Definition alignment.hpp:61
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:15
geometry/margins.hpp
Definition assert.hpp:18
constexpr value_type & width() noexcept
Access the x-as-width element from the extent.
Definition extent.hpp:167
constexpr value_type & height() noexcept
Access the y-as-height element from the extent.
Definition extent.hpp:178
2D constraints.
Definition box_constraints.hpp:25
Definition box_shape.hpp:15
Definition grid_layout.hpp:24
Definition grid_layout.hpp:228
constexpr reference operator[](size_t index) noexcept
Get element.
Definition grid_layout.hpp:540
constexpr size_t size() const noexcept
Number of cell on this axis.
Definition grid_layout.hpp:466
constexpr void layout(int new_position, int new_extent, std::optional< int > external_guideline, int guideline_width) noexcept
Layout each cell along an axis.
Definition grid_layout.hpp:406
constexpr const_reference back() const noexcept
Get the last element.
Definition grid_layout.hpp:596
constexpr std::tuple< int, int, int > constraints(cell_type const &cell) const noexcept
Get the minimum, preferred, maximum size of the span.
Definition grid_layout.hpp:360
constexpr bool empty() const noexcept
Check if this axis is empty.
Definition grid_layout.hpp:473
constexpr reference front() noexcept
Get the first element.
Definition grid_layout.hpp:563
constexpr reverse_iterator rend() noexcept
Iterator to the first cell on this axis.
Definition grid_layout.hpp:529
constexpr reverse_iterator rbegin() noexcept
Iterator to the first cell on this axis.
Definition grid_layout.hpp:522
constexpr reference back() noexcept
Get the last element.
Definition grid_layout.hpp:585
constexpr const_iterator end() const noexcept
Iterator to beyond the last cell on this axis.
Definition grid_layout.hpp:508
constexpr const_reference operator[](size_t index) const noexcept
Get element.
Definition grid_layout.hpp:552
constexpr const_iterator cbegin() const noexcept
Iterator to the first cell on this axis.
Definition grid_layout.hpp:494
constexpr iterator end() noexcept
Iterator to beyond the last cell on this axis.
Definition grid_layout.hpp:501
constexpr const_iterator begin() const noexcept
Iterator to the first cell on this axis.
Definition grid_layout.hpp:487
constexpr const_iterator cend() const noexcept
Iterator to beyond the last cell on this axis.
Definition grid_layout.hpp:515
constexpr iterator begin() noexcept
Iterator to the first cell on this axis.
Definition grid_layout.hpp:480
constexpr const_reference front() const noexcept
Get the first element.
Definition grid_layout.hpp:574
int maximum
The maximum width/height of the cells.
Definition grid_layout.hpp:248
int margin_after
The right/bottom margin of the cells.
Definition grid_layout.hpp:256
bool beyond_maximum
Allow this cell to be resized beyond the maximum constraint.
Definition grid_layout.hpp:272
int extent
Size of the cell.
Definition grid_layout.hpp:284
int margin_before
The left/top margin of the cells.
Definition grid_layout.hpp:252
int minimum
The minimum width/height of the cells.
Definition grid_layout.hpp:240
int preferred
The preferred width/height of the cells.
Definition grid_layout.hpp:244
int padding_before
The left/top padding of the cells.
Definition grid_layout.hpp:260
alignment_type alignment
The alignment of the cells.
Definition grid_layout.hpp:268
int position
The position of the cell.
Definition grid_layout.hpp:278
std::optional< int > guideline
The before-position within this cell where to align to.
Definition grid_layout.hpp:290
int padding_after
The right/bottom padding of the cells.
Definition grid_layout.hpp:264
Grid layout algorithm.
Definition grid_layout.hpp:932
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:1019
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:1054
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:1080
constexpr void set_layout(box_shape const &shape, int baseline_adjustment) noexcept
Layout the cells based on the width and height.
Definition grid_layout.hpp:1130
T back(T... args)
T begin(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 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)