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/geometry.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
20hi_export_module(hikogui.layout.grid_layout);
21
22hi_export namespace 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 template<std::convertible_to<value_type> Value>
44 constexpr grid_layout_cell(
45 size_t first_column,
46 size_t first_row,
47 size_t last_column,
48 size_t last_row,
49 bool beyond_maximum,
50 Value&& value) noexcept :
51 first_column(first_column),
52 first_row(first_row),
53 last_column(last_column),
54 last_row(last_row),
55 beyond_maximum(beyond_maximum),
56 value(std::forward<Value>(value))
57 {
58 hi_assert(first_column < last_column);
59 hi_assert(first_row < last_row);
60 }
61
62 constexpr void set_constraints(box_constraints const& constraints) noexcept
63 {
64 _constraints = constraints;
65 }
66
67 template<hi::axis Axis>
68 [[nodiscard]] constexpr size_t first() const noexcept
69 {
70 if constexpr (Axis == axis::x) {
71 return first_column;
72 } else if constexpr (Axis == axis::y) {
73 return first_row;
74 } else {
75 hi_static_no_default();
76 }
77 }
78
79 template<hi::axis Axis>
80 [[nodiscard]] constexpr size_t last() const noexcept
81 {
82 if constexpr (Axis == axis::x) {
83 return last_column;
84 } else if constexpr (Axis == axis::y) {
85 return last_row;
86 } else {
87 hi_static_no_default();
88 }
89 }
90
91 template<hi::axis Axis>
92 [[nodiscard]] constexpr size_t span() const noexcept
93 {
94 hi_axiom(first<Axis>() < last<Axis>());
95 return last<Axis>() - first<Axis>();
96 }
97
98 template<hi::axis Axis>
99 [[nodiscard]] constexpr auto alignment() const noexcept
100 {
101 if constexpr (Axis == axis::x) {
102 return _constraints.alignment.horizontal();
103 } else if constexpr (Axis == axis::y) {
104 return _constraints.alignment.vertical();
105 } else {
106 hi_static_no_default();
107 }
108 }
109
110 template<hi::axis Axis>
111 [[nodiscard]] constexpr float minimum() const noexcept
112 {
113 if constexpr (Axis == axis::x) {
114 return _constraints.minimum.width();
115 } else if constexpr (Axis == axis::y) {
116 return _constraints.minimum.height();
117 } else {
118 hi_static_no_default();
119 }
120 }
121
122 template<hi::axis Axis>
123 [[nodiscard]] constexpr float preferred() const noexcept
124 {
125 if constexpr (Axis == axis::x) {
126 return _constraints.preferred.width();
127 } else if constexpr (Axis == axis::y) {
128 return _constraints.preferred.height();
129 } else {
130 hi_static_no_default();
131 }
132 }
133
134 template<hi::axis Axis>
135 [[nodiscard]] constexpr float maximum() const noexcept
136 {
137 if constexpr (Axis == axis::x) {
138 return _constraints.maximum.width();
139 } else if constexpr (Axis == axis::y) {
140 return _constraints.maximum.height();
141 } else {
142 hi_static_no_default();
143 }
144 }
145
146 template<hi::axis Axis>
147 [[nodiscard]] constexpr float margin_before(bool forward) const noexcept
148 {
149 if constexpr (Axis == axis::x) {
150 if (forward) {
151 return _constraints.margins.left();
152 } else {
153 return _constraints.margins.right();
154 }
155 } else if constexpr (Axis == axis::y) {
156 if (forward) {
157 return _constraints.margins.bottom();
158 } else {
159 return _constraints.margins.top();
160 }
161 } else {
162 hi_static_no_default();
163 }
164 }
165
166 template<hi::axis Axis>
167 [[nodiscard]] constexpr float margin_after(bool forward) const noexcept
168 {
169 if constexpr (Axis == axis::x) {
170 if (forward) {
171 return _constraints.margins.right();
172 } else {
173 return _constraints.margins.left();
174 }
175 } else if constexpr (Axis == axis::y) {
176 if (forward) {
177 return _constraints.margins.top();
178 } else {
179 return _constraints.margins.bottom();
180 }
181 } else {
182 hi_static_no_default();
183 }
184 }
185
186private:
187 box_constraints _constraints;
188};
189
190template<hi::axis Axis, typename T>
192public:
193 constexpr static hi::axis axis = Axis;
194
195 using value_type = T;
196 using alignment_type = std::conditional_t<axis == axis::y, vertical_alignment, horizontal_alignment>;
199
203 float minimum = 0.0f;
204
207 float preferred = 0.0f;
208
212
215 float margin_before = 0.0f;
216
219 float margin_after = 0.0f;
220
223 alignment_type alignment = alignment_type::none;
224
227 bool beyond_maximum = false;
228
233 float position = 0.0f;
234
239 float extent = 0.0f;
240
245 std::optional<float> guideline = 0.0f;
246 };
248 using iterator = constraint_vector::iterator;
249 using const_iterator = constraint_vector::const_iterator;
250 using reverse_iterator = constraint_vector::reverse_iterator;
251 using reference = constraint_vector::reference;
252 using const_reference = constraint_vector::const_reference;
253
254 constexpr ~grid_layout_axis_constraints() = default;
255 constexpr grid_layout_axis_constraints() noexcept = default;
256 constexpr grid_layout_axis_constraints(grid_layout_axis_constraints const&) noexcept = default;
257 constexpr grid_layout_axis_constraints(grid_layout_axis_constraints&&) noexcept = default;
258 constexpr grid_layout_axis_constraints& operator=(grid_layout_axis_constraints const&) noexcept = default;
259 constexpr grid_layout_axis_constraints& operator=(grid_layout_axis_constraints&&) noexcept = default;
260 [[nodiscard]] constexpr friend bool
261 operator==(grid_layout_axis_constraints const&, grid_layout_axis_constraints const&) noexcept = default;
262
270 constexpr grid_layout_axis_constraints(cell_vector const& cells, size_t num, bool forward) noexcept :
271 _constraints(num), _forward(forward)
272 {
273 for (auto const& cell : cells) {
274 construct_simple_cell(cell);
275 }
276 construct_fixup();
277
278 for (auto const& cell : cells) {
279 construct_span_cell(cell);
280 }
281 construct_fixup();
282 }
283
284 [[nodiscard]] constexpr float margin_before() const noexcept
285 {
286 return empty() ? 0 : _forward ? front().margin_before : back().margin_before;
287 }
288
289 [[nodiscard]] constexpr float margin_after() const noexcept
290 {
291 return empty() ? 0 : _forward ? back().margin_after : front().margin_after;
292 }
293
294 [[nodiscard]] constexpr float padding_before() const noexcept
295 {
296 return empty() ? 0 : _forward ? front().padding_before : back().padding_before;
297 }
298
299 [[nodiscard]] constexpr float padding_after() const noexcept
300 {
301 return empty() ? 0 : _forward ? back().padding_after : front().padding_after;
302 }
303
304 [[nodiscard]] constexpr std::tuple<float, float, float> update_constraints() const noexcept
305 {
306 return constraints(begin(), end());
307 }
308
316 [[nodiscard]] constexpr std::tuple<float, float, float> constraints(cell_type const& cell) const noexcept
317 {
318 return constraints(cell.template first<axis>(), cell.template last<axis>());
319 }
320
321 [[nodiscard]] constexpr float position(cell_type const& cell) const noexcept
322 {
323 return position(cell.template first<axis>(), cell.template last<axis>());
324 }
325
326 [[nodiscard]] constexpr float extent(cell_type const& cell) const noexcept
327 {
328 return extent(cell.template first<axis>(), cell.template last<axis>());
329 }
330
331 [[nodiscard]] constexpr std::optional<float> guideline(cell_type const& cell) const noexcept
332 {
333 if (cell.template span<axis>() == 1) {
334 return guideline(cell.template first<axis>());
335 } else {
336 return std::nullopt;
337 }
338 }
339
362 constexpr void layout(float new_position, float new_extent, std::optional<float> external_guideline, float guideline_width) noexcept
363 {
364 // Start with the extent of each constraint equal to the preferred extent.
365 for (auto& constraint : _constraints) {
366 constraint.extent = constraint.preferred;
367 }
368
369 // If the total extent is too large, shrink the constraints that allow to be shrunk.
370 auto [total_extent, count] = layout_shrink(begin(), end());
371 while (total_extent > new_extent and count != 0) {
372 // The result may shrink slightly too much, which will be fixed by expanding in the next loop.
373 std::tie(total_extent, count) = layout_shrink(begin(), end(), total_extent - new_extent, count);
374 }
375
376 // If the total extent is too small, expand the constraints that allow to be grown.
377 std::tie(total_extent, count) = layout_expand(begin(), end());
378 while (total_extent < new_extent and count != 0) {
379 // The result may expand slightly too much, we don't care.
380 std::tie(total_extent, count) = layout_expand(begin(), end(), new_extent - total_extent, count);
381 }
382
383 // If the total extent is still too small, expand into the cells that are marked beyond_maximum.
384 if (total_extent < new_extent) {
385 // The result may expand slightly too much, we don't care.
386 count = std::count_if(begin(), end(), [](auto const& item) {
387 return item.beyond_maximum;
388 });
389 if (count) {
390 auto expand = new_extent - total_extent;
391 auto const expand_per = std::ceil(expand / count);
392
393 for (auto& constraint : _constraints) {
394 auto const expand_this = std::min(expand_per, expand);
395 if (constraint.beyond_maximum) {
396 constraint.extent += expand_this;
397 expand -= expand_this;
398 }
399 }
400 }
401 total_extent = extent(cbegin(), cend());
402 }
403
404 // If the total extent is still too small, expand the first constrain above the maximum size.
405 if (total_extent < new_extent and not empty()) {
406 // The result may expand slightly too much, we don't care.
407 front().extent += new_extent - total_extent;
408 }
409
410 if (_forward) {
411 layout_position(begin(), end(), new_position, guideline_width);
412 } else {
413 layout_position(rbegin(), rend(), new_position, guideline_width);
414 }
415
416 if (external_guideline and size() == 1) {
417 // When there is only 1 cell on this axis, the external guideline is used.
418 // XXX If there are more cell, then the external alignment should be taken into account.
419 front().guideline = *external_guideline;
420 }
421 }
422
425 [[nodiscard]] constexpr size_t size() const noexcept
426 {
427 return _constraints.size();
428 }
429
432 [[nodiscard]] constexpr bool empty() const noexcept
433 {
434 return _constraints.empty();
435 }
436
439 [[nodiscard]] constexpr iterator begin() noexcept
440 {
441 return _constraints.begin();
442 }
443
446 [[nodiscard]] constexpr const_iterator begin() const noexcept
447 {
448 return _constraints.begin();
449 }
450
453 [[nodiscard]] constexpr const_iterator cbegin() const noexcept
454 {
455 return _constraints.cbegin();
456 }
457
460 [[nodiscard]] constexpr iterator end() noexcept
461 {
462 return _constraints.end();
463 }
464
467 [[nodiscard]] constexpr const_iterator end() const noexcept
468 {
469 return _constraints.end();
470 }
471
474 [[nodiscard]] constexpr const_iterator cend() const noexcept
475 {
476 return _constraints.cend();
477 }
478
481 [[nodiscard]] constexpr reverse_iterator rbegin() noexcept
482 {
483 return _constraints.rbegin();
484 }
485
488 [[nodiscard]] constexpr reverse_iterator rend() noexcept
489 {
490 return _constraints.rend();
491 }
492
499 [[nodiscard]] constexpr reference operator[](size_t index) noexcept
500 {
501 hi_axiom(index < size());
502 return _constraints[index];
503 }
504
511 [[nodiscard]] constexpr const_reference operator[](size_t index) const noexcept
512 {
513 hi_axiom(index < size());
514 return _constraints[index];
515 }
516
522 [[nodiscard]] constexpr reference front() noexcept
523 {
524 hi_axiom(not empty());
525 return _constraints.front();
526 }
527
533 [[nodiscard]] constexpr const_reference front() const noexcept
534 {
535 hi_axiom(not empty());
536 return _constraints.front();
537 }
538
544 [[nodiscard]] constexpr reference back() noexcept
545 {
546 hi_axiom(not empty());
547 return _constraints.back();
548 }
549
555 [[nodiscard]] constexpr const_reference back() const noexcept
556 {
557 hi_axiom(not empty());
558 return _constraints.back();
559 }
560
561private:
570 constraint_vector _constraints = {};
571
574 bool _forward = true;
575
592 [[nodiscard]] constexpr std::pair<float, size_t>
593 layout_shrink(const_iterator first, const_iterator last, float shrink = 0.0f, size_t count = 1) noexcept
594 {
595 auto const first_ = begin() + std::distance(cbegin(), first);
596 auto const last_ = begin() + std::distance(cbegin(), last);
597
598 hi_axiom(shrink >= 0);
599
600 auto const shrink_per = std::floor(shrink / count);
601
602 auto new_extent = 0.0f;
603 auto new_count = 0_uz;
604 for (auto it = first_; it != last_; ++it) {
605 auto const shrink_this = std::max({shrink_per, shrink, it->extent - it->minimum});
606 it->extent -= shrink_this;
607 shrink -= shrink_this;
608
609 if (it != first_) {
610 new_extent += it->margin_before;
611 }
612 new_extent += it->extent;
613
614 if (it->extent > it->minimum) {
615 ++new_count;
616 }
617 }
618
619 return {new_extent, new_count};
620 }
621
638 [[nodiscard]] constexpr std::pair<float, size_t>
639 layout_expand(const_iterator first, const_iterator last, float expand = 0.0f, size_t count = 1) noexcept
640 {
641 auto const first_ = begin() + std::distance(cbegin(), first);
642 auto const last_ = begin() + std::distance(cbegin(), last);
643
644 hi_axiom(expand >= 0.0f);
645
646 auto const expand_per = std::ceil(expand / count);
647 hi_axiom(expand_per >= 0.0f);
648
649 auto new_extent = 0.0f;
650 auto new_count = 0_uz;
651 for (auto it = first_; it != last_; ++it) {
652 auto const expand_this = std::min({expand_per, expand, it->maximum - it->extent});
653 it->extent += expand_this;
654 expand -= expand_this;
655
656 if (it != first_) {
657 new_extent += it->margin_before;
658 }
659 new_extent += it->extent;
660
661 if (it->extent < it->maximum) {
662 ++new_count;
663 }
664 }
665
666 return {new_extent, new_count};
667 }
668
669 constexpr void layout_position(auto first, auto last, float start_position, float guideline_width) noexcept
670 {
671 auto position = start_position;
672 for (auto it = first; it != last; ++it) {
673 it->position = position;
674 it->guideline = make_guideline(it->alignment, position, position + it->extent, guideline_width);
675
676 position += it->extent;
677 position += it->margin_after;
678 }
679 }
680
688 constexpr void construct_simple_cell(cell_type const& cell) noexcept
689 {
690 inplace_max(_constraints[cell.template first<axis>()].margin_before, cell.template margin_before<axis>(_forward));
691 inplace_max(_constraints[cell.template last<axis>() - 1].margin_after, cell.template margin_after<axis>(_forward));
692
693 for (auto i = cell.template first<axis>(); i != cell.template last<axis>(); ++i) {
694 _constraints[i].beyond_maximum |= cell.beyond_maximum;
695 }
696
697 if (cell.template span<axis>() == 1) {
698 inplace_max(_constraints[cell.template first<axis>()].alignment, cell.template alignment<axis>());
699 inplace_max(_constraints[cell.template first<axis>()].minimum, cell.template minimum<axis>());
700 inplace_max(_constraints[cell.template first<axis>()].preferred, cell.template preferred<axis>());
701 inplace_min(_constraints[cell.template first<axis>()].maximum, cell.template maximum<axis>());
702 }
703 }
704
711 constexpr void construct_span_cell(cell_type const& cell) noexcept
712 {
713 auto num_cells = narrow_cast<float>(cell.template span<axis>());
714
715 if (cell.template span<axis>() > 1) {
716 auto const[span_minimum, span_preferred, span_maximum] = constraints(cell);
717 if (auto const extra = cell.template minimum<axis>() - span_minimum; extra > 0) {
718 auto const extra_per_cell = std::floor(extra / num_cells);
719 for (auto i = cell.template first<axis>(); i != cell.template last<axis>(); ++i) {
720 _constraints[i].minimum += extra_per_cell;
721 }
722 }
723
724 if (auto const extra = cell.template preferred<axis>() - span_preferred; extra > 0) {
725 auto const extra_per_cell = std::floor(extra / num_cells);
726 for (auto i = cell.template first<axis>(); i != cell.template last<axis>(); ++i) {
727 _constraints[i].preferred += extra_per_cell;
728 }
729 }
730
731 if (auto const extra = cell.template maximum<axis>() - span_preferred; extra < 0) {
732 auto const extra_per_cell = std::ceil(extra / num_cells);
733 for (auto i = cell.template first<axis>(); i != cell.template last<axis>(); ++i) {
734 // The maximum could become too low here, fixup() will fix this.
735 _constraints[i].maximum += extra_per_cell;
736 }
737 }
738 }
739 }
740
745 constexpr void construct_fixup() noexcept
746 {
747 for (auto it = begin(); it != end(); ++it) {
748 // Fix the margins so that between two constraints they are equal.
749 if (it + 1 != end()) {
750 it->margin_after = (it + 1)->margin_before = std::max(it->margin_after, (it + 1)->margin_before);
751 }
752
753 // Fix the constraints so that minimum <= preferred <= maximum.
754 inplace_max(it->preferred, it->minimum);
755 inplace_max(it->maximum, it->preferred);
756 }
757 }
758
767 [[nodiscard]] constexpr std::tuple<float, float, float> constraints(const_iterator first, const_iterator last) const noexcept
768 {
769 auto r_minimum = 0.0f;
770 auto r_preferred = 0.0f;
771 auto r_maximum = 0.0f;
772 auto r_margin = 0.0f;
773
774 if (first != last) {
775 r_minimum = first->minimum;
776 r_preferred = first->preferred;
777 r_maximum = first->maximum;
778 for (auto it = first + 1; it != last; ++it) {
779 r_margin += it->margin_before;
780 r_minimum += it->minimum;
781 r_preferred += it->preferred;
782 r_maximum += it->maximum;
783 }
784 }
785 return {r_minimum + r_margin, r_preferred + r_margin, r_maximum + r_margin};
786 }
787
796 [[nodiscard]] constexpr std::tuple<float, float, float> constraints(size_t first, size_t last) const noexcept
797 {
798 hi_axiom(first <= last);
799 hi_axiom(last <= size());
800 return constraints(begin() + first, begin() + last);
801 }
802
810 [[nodiscard]] constexpr float position(const_iterator first, const_iterator last) const noexcept
811 {
812 hi_axiom(first != last);
813 if (_forward) {
814 return first->position;
815 } else {
816 return (last - 1)->position;
817 }
818 }
819
827 [[nodiscard]] constexpr float position(size_t first, size_t last) const noexcept
828 {
829 hi_axiom(first < last);
830 hi_axiom(last <= size());
831 return position(cbegin() + first, cbegin() + last);
832 }
833
841 [[nodiscard]] constexpr float extent(const_iterator first, const_iterator last) const noexcept
842 {
843 auto r = 0.0f;
844 if (first != last) {
845 r = first->extent;
846 for (auto it = first + 1; it != last; ++it) {
847 r += it->margin_before;
848 r += it->extent;
849 }
850 }
851 return r;
852 }
853
861 [[nodiscard]] constexpr float extent(size_t first, size_t last) const noexcept
862 {
863 hi_axiom(first <= last);
864 hi_axiom(last <= size());
865 return extent(cbegin() + first, cbegin() + last);
866 }
867
868 [[nodiscard]] constexpr std::optional<float> guideline(const_iterator it) const noexcept
869 {
870 return it->guideline;
871 }
872
873 [[nodiscard]] constexpr std::optional<float> guideline(size_t i) const noexcept
874 {
875 return guideline(cbegin() + i);
876 }
877};
878
879} // namespace detail
880
886template<typename T>
888public:
889 using value_type = T;
890
891 using cell_type = detail::grid_layout_cell<value_type>;
892 using cell_vector = std::vector<cell_type>;
893 using iterator = cell_vector::iterator;
894 using const_iterator = cell_vector::const_iterator;
895 using reference = cell_vector::reference;
896 using const_reference = cell_vector::const_reference;
897
898 ~grid_layout() = default;
899 constexpr grid_layout() noexcept = default;
900 constexpr grid_layout(grid_layout const&) noexcept = default;
901 constexpr grid_layout(grid_layout&&) noexcept = default;
902 constexpr grid_layout& operator=(grid_layout const&) noexcept = default;
903 constexpr grid_layout& operator=(grid_layout&&) noexcept = default;
904 [[nodiscard]] constexpr friend bool operator==(grid_layout const&, grid_layout const&) noexcept = default;
905
906 [[nodiscard]] constexpr bool empty() const noexcept
907 {
908 return _cells.empty();
909 }
910
911 [[nodiscard]] constexpr size_t size() const noexcept
912 {
913 return _cells.size();
914 }
915
916 [[nodiscard]] constexpr size_t num_columns() const noexcept
917 {
918 return _num_columns;
919 }
920
921 [[nodiscard]] constexpr size_t num_rows() const noexcept
922 {
923 return _num_rows;
924 }
925
926 [[nodiscard]] constexpr iterator begin() noexcept
927 {
928 return _cells.begin();
929 }
930
931 [[nodiscard]] constexpr iterator end() noexcept
932 {
933 return _cells.end();
934 }
935
936 [[nodiscard]] constexpr const_iterator begin() const noexcept
937 {
938 return _cells.begin();
939 }
940
941 [[nodiscard]] constexpr const_iterator end() const noexcept
942 {
943 return _cells.end();
944 }
945
946 [[nodiscard]] constexpr const_iterator cbegin() const noexcept
947 {
948 return _cells.cbegin();
949 }
950
951 [[nodiscard]] constexpr const_iterator cend() const noexcept
952 {
953 return _cells.cend();
954 }
955
956 [[nodiscard]] constexpr const_reference operator[](size_t i) const noexcept
957 {
958 return _cells[i];
959 }
960
961 [[nodiscard]] constexpr reference operator[](size_t i) noexcept
962 {
963 return _cells[i];
964 }
965
974 [[nodiscard]] constexpr bool cell_in_use(size_t first_column, size_t first_row, size_t last_column, size_t last_row) noexcept
975 {
976 // At least one cell must be in the range.
977 hi_axiom(first_column < last_column);
978 hi_axiom(first_row < last_row);
979
980 for (auto const& cell : _cells) {
981 if (first_column >= cell.last_column) {
982 continue;
983 }
984 if (last_column <= cell.first_column) {
985 continue;
986 }
987 if (first_row >= cell.last_row) {
988 continue;
989 }
990 if (last_row <= cell.first_row) {
991 continue;
992 }
993 return true;
994 }
995 return false;
996 }
997
1008 template<forward_of<value_type> Value>
1009 constexpr reference add_cell(
1010 size_t first_column,
1011 size_t first_row,
1012 size_t last_column,
1013 size_t last_row,
1014 Value&& value,
1015 bool beyond_maximum = false) noexcept
1016 {
1017 // At least one cell must be in the range.
1018 hi_assert(first_column < last_column);
1019 hi_assert(first_row < last_row);
1020 hi_assert(not cell_in_use(first_column, first_row, last_column, last_row));
1021 auto& r = _cells.emplace_back(first_column, first_row, last_column, last_row, beyond_maximum, std::forward<Value>(value));
1022 update_after_insert_or_delete();
1023 return r;
1024 }
1025
1034 template<forward_of<value_type> Value>
1035 constexpr reference add_cell(size_t column, size_t row, Value&& value, bool beyond_maximum = false) noexcept
1036 {
1037 return add_cell(column, row, column + 1, row + 1, std::forward<Value>(value), beyond_maximum);
1038 }
1039
1040 constexpr void clear() noexcept
1041 {
1042 _cells.clear();
1043 update_after_insert_or_delete();
1044 }
1045
1046 [[nodiscard]] constexpr box_constraints constraints(bool left_to_right) const noexcept
1047 {
1048 // Rows in the grid are laid out from top to bottom which is reverse from the y-axis up.
1049 _row_constraints = {_cells, num_rows(), false};
1050 _column_constraints = {_cells, num_columns(), left_to_right};
1051
1052 auto r = box_constraints{};
1053 std::tie(r.minimum.width(), r.preferred.width(), r.maximum.width()) = _column_constraints.update_constraints();
1054 r.margins.left() = _column_constraints.margin_before();
1055 r.margins.right() = _column_constraints.margin_after();
1056
1057 std::tie(r.minimum.height(), r.preferred.height(), r.maximum.height()) = _row_constraints.update_constraints();
1058 r.margins.bottom() = _row_constraints.margin_after();
1059 r.margins.top() = _row_constraints.margin_before();
1060
1061 r.alignment = [&] {
1062 if (num_rows() == 1 and num_columns() == 1) {
1063 return hi::alignment{_column_constraints.front().alignment, _row_constraints.front().alignment};
1064 } else if (num_rows() == 1) {
1065 return hi::alignment{_row_constraints.front().alignment};
1066 } else if (num_columns() == 1) {
1067 return hi::alignment{_column_constraints.front().alignment};
1068 } else {
1069 return hi::alignment{};
1070 }
1071 }();
1072
1073 return r;
1074 }
1075
1081 constexpr void set_layout(box_shape const& shape, float baseline_adjustment) noexcept
1082 {
1083 // Rows in the grid are laid out from top to bottom which is reverse from the y-axis up.
1084 _column_constraints.layout(shape.x(), shape.width(), shape.centerline, 0);
1085 _row_constraints.layout(shape.y(), shape.height(), shape.baseline, baseline_adjustment);
1086
1087 // Assign the shape for each cell.
1088 for (auto& cell : _cells) {
1089 cell.shape.rectangle = {
1090 _column_constraints.position(cell),
1091 _row_constraints.position(cell),
1092 _column_constraints.extent(cell),
1093 _row_constraints.extent(cell)};
1094 cell.shape.centerline = _column_constraints.guideline(cell);
1095 cell.shape.baseline = _row_constraints.guideline(cell);
1096 }
1097 }
1098
1099private:
1100 cell_vector _cells = {};
1101 size_t _num_rows = 0;
1102 size_t _num_columns = 0;
1103 mutable detail::grid_layout_axis_constraints<axis::y, value_type> _row_constraints = {};
1104 mutable detail::grid_layout_axis_constraints<axis::x, value_type> _column_constraints = {};
1105
1110 constexpr void sort_cells() noexcept
1111 {
1112 std::sort(_cells.begin(), _cells.end(), [](cell_type const& lhs, cell_type const& rhs) {
1113 if (lhs.first_row != rhs.first_row) {
1114 return lhs.first_row < rhs.first_row;
1115 } else {
1116 return lhs.first_column < rhs.first_column;
1117 }
1118 });
1119 }
1120
1123 constexpr void update_after_insert_or_delete() noexcept
1124 {
1125 sort_cells();
1126
1127 _num_rows = 0;
1128 _num_columns = 0;
1129 for (auto const& cell : _cells) {
1130 inplace_max(_num_rows, cell.last_row);
1131 inplace_max(_num_columns, cell.last_column);
1132 }
1133 }
1134};
1135
1136}} // namespace hi::v1
Utilities for parsing spreadsheet addresses.
constexpr std::optional< float > make_guideline(vertical_alignment alignment, float bottom, float top, float 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:24
The HikoGUI namespace.
Definition array_generic.hpp:20
DOXYGEN BUG.
Definition algorithm_misc.hpp:20
Horizontal/Vertical alignment combination.
Definition alignment.hpp:244
constexpr float & width() noexcept
Access the x-as-width element from the extent.
Definition extent2.hpp:107
constexpr float & height() noexcept
Access the y-as-height element from the extent.
Definition extent2.hpp:118
2D constraints.
Definition box_constraints.hpp:25
Definition box_shape.hpp:18
Definition grid_layout.hpp:26
Definition grid_layout.hpp:191
constexpr reference operator[](size_t index) noexcept
Get element.
Definition grid_layout.hpp:499
constexpr size_t size() const noexcept
Number of cell on this axis.
Definition grid_layout.hpp:425
constexpr const_reference back() const noexcept
Get the last element.
Definition grid_layout.hpp:555
constexpr bool empty() const noexcept
Check if this axis is empty.
Definition grid_layout.hpp:432
constexpr reference front() noexcept
Get the first element.
Definition grid_layout.hpp:522
constexpr reverse_iterator rend() noexcept
Iterator to the first cell on this axis.
Definition grid_layout.hpp:488
constexpr reverse_iterator rbegin() noexcept
Iterator to the first cell on this axis.
Definition grid_layout.hpp:481
constexpr reference back() noexcept
Get the last element.
Definition grid_layout.hpp:544
constexpr const_iterator end() const noexcept
Iterator to beyond the last cell on this axis.
Definition grid_layout.hpp:467
constexpr const_reference operator[](size_t index) const noexcept
Get element.
Definition grid_layout.hpp:511
constexpr const_iterator cbegin() const noexcept
Iterator to the first cell on this axis.
Definition grid_layout.hpp:453
constexpr iterator end() noexcept
Iterator to beyond the last cell on this axis.
Definition grid_layout.hpp:460
constexpr const_iterator begin() const noexcept
Iterator to the first cell on this axis.
Definition grid_layout.hpp:446
constexpr const_iterator cend() const noexcept
Iterator to beyond the last cell on this axis.
Definition grid_layout.hpp:474
constexpr iterator begin() noexcept
Iterator to the first cell on this axis.
Definition grid_layout.hpp:439
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:362
constexpr const_reference front() const noexcept
Get the first element.
Definition grid_layout.hpp:533
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:316
float margin_after
The right/bottom margin of the cells.
Definition grid_layout.hpp:219
float extent
Size of the cell.
Definition grid_layout.hpp:239
float minimum
The minimum width/height of the cells.
Definition grid_layout.hpp:203
bool beyond_maximum
Allow this cell to be resized beyond the maximum constraint.
Definition grid_layout.hpp:227
float maximum
The maximum width/height of the cells.
Definition grid_layout.hpp:211
float margin_before
The left/top margin of the cells.
Definition grid_layout.hpp:215
float preferred
The preferred width/height of the cells.
Definition grid_layout.hpp:207
std::optional< float > guideline
The before-position within this cell where to align to.
Definition grid_layout.hpp:245
float position
The position of the cell.
Definition grid_layout.hpp:233
Grid layout algorithm.
Definition grid_layout.hpp:887
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:974
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:1009
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:1035
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:1081
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)