HikoGUI
A low latency retained GUI
Loading...
Searching...
No Matches
flow_layout.hpp
1// Copyright Take Vos 2020.
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 "required.hpp"
8#include "interval.hpp"
9#include "alignment.hpp"
10#include <vector>
11#include <optional>
12#include <numeric>
13
14namespace tt {
15
17 constexpr flow_layout_item() noexcept :
18 minimum_size(0), preferred_size(0), maximum_size(0), offset(0), size(0)
19 {
20 }
21
22 constexpr flow_layout_item(flow_layout_item const &rhs) noexcept = default;
23 constexpr flow_layout_item(flow_layout_item &&rhs) noexcept = default;
24 constexpr flow_layout_item &operator=(flow_layout_item const &rhs) noexcept = default;
25 constexpr flow_layout_item &operator=(flow_layout_item &&rhs) noexcept = default;
26
27 constexpr void update(float min_size, float pref_size, float max_size) noexcept
28 {
29 minimum_size = std::max(minimum_size, narrow_cast<int>(std::ceil(min_size)));
30 preferred_size = std::max(preferred_size, narrow_cast<int>(std::round(pref_size)));
31 maximum_size = std::max(maximum_size, narrow_cast<int>(std::floor(max_size)));
32
33 // The maximum size must be larger than the minimum size.
34 maximum_size = std::max(maximum_size, minimum_size);
35
36 // Try to keep the preferred size below the maximum.
37 preferred_size = std::clamp(preferred_size, minimum_size, maximum_size);
38
39 tt_axiom(minimum_size <= preferred_size);
40 tt_axiom(preferred_size <= maximum_size);
41 }
42
43 int minimum_size;
44 int preferred_size;
45 int maximum_size;
46 int offset;
47 int size;
48};
49
53public:
54 flow_layout() noexcept : margins(), items()
55 {
56 clear();
57 }
58
59 void clear() noexcept
60 {
61 margins.clear();
62 margins.push_back(0);
63 items.clear();
64 }
65
66 [[nodiscard]] size_t nr_items() const noexcept
67 {
68 return items.size();
69 }
70
71 void update(ssize_t index, float minimum_size, float preferred_size, float maximum_size, float margin) noexcept
72 {
73 tt_axiom(index >= 0);
74 tt_axiom(index < std::ssize(items));
75 tt_axiom(index + 1 < std::ssize(margins));
76
77 items[index].update(minimum_size, preferred_size, maximum_size);
78
79 ttlet margin_ = narrow_cast<int>(std::ceil(margin));
80 margins[index] = std::max(margins[index], margin_);
81 margins[index + 1] = std::max(margins[index + 1], margin_);
82 }
83
84 [[nodiscard]] int total_margin_size() const noexcept
85 {
86 return std::accumulate(margins.cbegin(), margins.cend(), 0);
87 }
88
89 [[nodiscard]] float minimum_size() const noexcept
90 {
91 auto a = total_margin_size();
92 for (ttlet &item : items) {
93 a += item.minimum_size;
94 }
95 return narrow_cast<float>(a);
96 }
97
98 [[nodiscard]] float preferred_size() const noexcept
99 {
100 auto a = total_margin_size();
101 for (ttlet &item : items) {
102 a += item.preferred_size;
103 }
104 return narrow_cast<float>(a);
105 }
106
107 [[nodiscard]] float maximum_size() const noexcept
108 {
109 auto a = total_margin_size();
110 for (ttlet &item : items) {
111 a += item.maximum_size;
112 }
113 return narrow_cast<float>(a);
114 }
115
118 void set_size(float total_size) noexcept
119 {
120 ttlet total_size_ = narrow_cast<int>(std::round(total_size));
121 // It is possible that total_size crosses the maximum_size.
122 tt_axiom(total_size_ >= minimum_size());
123
124 set_items_to_preferred_size();
125
126 auto grow_by = total_size_ - size();
127 while (grow_by != 0) {
128 int num = num_items_can_resize(grow_by);
129
130 auto resize_beyond_maximum = num == 0;
131 if (resize_beyond_maximum) {
132 num = narrow_cast<int>(std::size(items));
133 }
134
135 resize_items(num, grow_by, resize_beyond_maximum);
136
137 grow_by = total_size_ - size();
138 };
139 }
140
141 [[nodiscard]] std::pair<float, float> get_offset_and_size(size_t index) const noexcept
142 {
143 tt_axiom(index < std::size(items));
144 auto offset = narrow_cast<float>(items[index].offset);
145 auto size = narrow_cast<float>(items[index].size);
146 return {offset, size};
147 }
148
151 void reserve(ssize_t new_size) noexcept
152 {
153 while (std::ssize(items) < new_size) {
154 items.emplace_back();
155 }
156
157 while (std::ssize(margins) < new_size + 1) {
158 margins.push_back(0);
159 }
160
161 tt_axiom(margins.size() == items.size() + 1);
162 }
163
164private:
165 /* The margin between the items, margin[0] is the margin
166 * before the first item. margin[items.size()] is the margin
167 * after the last item. margins.size() == items.size() + 1.
168 */
169 std::vector<int> margins;
171
172 void set_items_to_preferred_size() noexcept
173 {
174 for (auto &&item : items) {
175 item.size = item.preferred_size;
176 }
177 calculate_offset_and_size();
178 }
179
180 [[nodiscard]] int num_items_can_resize(int grow_by) noexcept
181 {
182 if (grow_by > 0) {
183 return narrow_cast<int>(std::ranges::count_if(items, [](ttlet &item) {
184 return item.size < item.maximum_size;
185 }));
186 } else if (grow_by < 0) {
187 return narrow_cast<int>(std::ranges::count_if(items, [](ttlet &item) {
188 return item.size > item.minimum_size;
189 }));
190
191 } else {
192 return 0;
193 }
194 }
195
196 [[nodiscard]] void resize_items(int nr_items, int grow_by, bool resize_beyond_maximum) noexcept
197 {
198 tt_axiom(grow_by != 0);
199 tt_axiom(nr_items > 0);
200
201 auto per_item_grow_by = grow_by / nr_items;
202 if (per_item_grow_by == 0) {
203 per_item_grow_by = grow_by > 0 ? 1 : -1;
204 }
205
206 for (auto &&item : items) {
207 auto new_item_size = item.size + per_item_grow_by;
208 if (!resize_beyond_maximum) {
209 new_item_size = std::clamp(new_item_size, item.minimum_size, item.maximum_size);
210 }
211
212 ttlet this_item_grown_by = new_item_size - item.size;
213 item.size = new_item_size;
214
215 tt_axiom(item.size >= item.minimum_size);
216 if (!resize_beyond_maximum) {
217 tt_axiom(item.size <= item.maximum_size);
218 }
219
220 if ((grow_by -= this_item_grown_by) == 0) {
221 // All the growth has been spread to the widgets.
222 break;
223 }
224 }
225
226 calculate_offset_and_size();
227 }
228
229 void calculate_offset_and_size() noexcept
230 {
231 auto offset = 0;
232 for (ssize_t i = 0; i != std::ssize(items); ++i) {
233 offset += margins[i];
234 items[i].offset = offset;
235 offset += items[i].size;
236 }
237 }
238
242 [[nodiscard]] int size() const noexcept
243 {
244 if (items.empty()) {
245 return margins.back();
246 } else {
247 tt_axiom(items.back().offset >= 0);
248 return items.back().offset + items.back().size + margins.back();
249 }
250 }
251};
252
253} // namespace tt
Definition flow_layout.hpp:16
Layout algorithm.
Definition flow_layout.hpp:52
void reserve(ssize_t new_size) noexcept
Grow layout to include upto new_size of items.
Definition flow_layout.hpp:151
void set_size(float total_size) noexcept
Update the layout of all items based on the total size.
Definition flow_layout.hpp:118
T accumulate(T... args)
T back(T... args)
T cbegin(T... args)
T ceil(T... args)
T clear(T... args)
T empty(T... args)
T cend(T... args)
T floor(T... args)
T max(T... args)
T push_back(T... args)
T round(T... args)
T size(T... args)