HikoGUI
A low latency retained GUI
Loading...
Searching...
No Matches
grid.hpp
Go to the documentation of this file.
1
6#pragma once
7
9#include "grid_axis.hpp"
10#include "grid_cell.hpp"
11#include "grid_state.hpp"
12#include <cstddef>
13#include <cstdint>
14#include <limits>
15
16namespace hi { inline namespace v1 {
17
24class grid {
25public:
26 grid(grid const&) = delete;
27 grid(grid&&) = delete;
28 grid& operator=(grid const&) = delete;
29 grid& operator=(grid&&) = delete;
30
31 constexpr grid() noexcept = default;
32
51 constexpr void update() noexcept
52 {
53 if (state != grid_state::done) {
54 [[unlikely]] _update();
55 state = 0;
56 }
57 }
58
59 constexpr void remove_cell(size_t id) noexcept
60 {
61 _cells[id].make_free(std::exchange(_first_free, id));
62 }
63
64 [[nodiscard]] constexpr size_t add_cell() noexcept
65 {
66 hilet id = [&] {
67 if (_first_free != std::numeric_limits<size_t>::max()) {
68 hilet next_free = _cells[_first_free].parent;
69 return std::exchange(_first_free, next_free);
70
71 } else {
72 hilet id = _cells.size();
73 _cells.emplace_back();
74 return id;
75 }
76 }();
77
78 _cells[id].parent = std::numeric_limits<size_t>::max();
79 return id;
80 }
81
82 [[nodiscard]] constexpr detail::grid_cell_data& operator[](size_t id) noexcept
83 {
84 hi_axiom_bounds(id, _cells);
85 return _cells[id];
86 }
87
88 [[nodiscard]] constexpr detail::grid_cell_data const& operator[](size_t id) const noexcept
89 {
90 hi_axiom_bounds(id, _cells);
91 return _cells[id];
92 }
93
94private:
98
101 int32_t _first_free = -1;
102
108 std::vector<int32_t> _indices = {};
109
112 decltype(_indices)::iterator _grid_begin = {};
113
116 decltype(_indices)::iterator _root_begin = {};
117
120 grid_axis _rows = {};
121
124 grid_axis _columns = {};
125
128 grid_state _state = grid_state::need_constrain;
129
130 constexpr auto row_begin(detail::grid_cell_data const& cell) noexcept
131 {
132 return _rows.begin() + cell.row_offset;
133 }
134
135 constexpr auto col_end(detail::grid_cell_data const& cell) noexcept
136 {
137 return _cols.begin() + cell.col_offset;
138 }
139
140 constexpr auto row_pair(detail::grid_cell_data const& parent, detail::grid_cell_data const& child) noexcept
141 {
142 return std::make_tuple(row_begin(parent) + cell.row_begin, row_begin(parent) + cell.row_end);
143 }
144
145 constexpr auto col_pair(detail::grid_cell_data const& parent, detail::grid_cell_data const& child) noexcept
146 {
147 return std::make_tuple(col_begin(parent) + cell.col_begin, col_begin(parent) + cell.col_end);
148 }
149
150 constexpr void update_indices_visit(int32_t i, detail::grid_cell_data& n) noexcept
151 {
152 hi_axiom(n.in_use);
153
154 if (n.permanent_mark) {
155 return;
156 }
157 if (n.temporary_mark) {
158 hi_no_default("Loop found in super-grid.");
159 }
160
161 n.temporary_mark = 1;
162 if (n.parent != -1) {
163 auto& p = _cells[n.parent];
164 p.leaf = 0;
165 make_tree_visit(n.parent, p);
166 }
167
168 n.temporary_mark = 0;
169 n.permanent_mark = 1;
170 _indices.push_back(i);
171 }
172
175 constexpr void update_indices() noexcept
176 {
177 // Calculate how many children each node has and if the node is a leaf.
178 // This also works on entries that are on the free-list.
179 for (auto& cell : _cells) {
180 cell.leaf = 1;
181 cell.perminant_mark = 0;
182 cell.temporary_mark = 0;
183 }
184
185 // depth-first topological sort.
186 _indices.clear();
187 for (int32_t i = 0; i != std::ssize(_cells); ++i) {
188 auto& n = _cells[i];
189 if (n.in_use) {
190 update_indices_visit(i, n);
191 }
192 }
193
194 // The ordering is parents first, children last, reverse this.
195 std::reverse(_indices.begin(), _indices.end());
196
197 // Put all the leaves at the start
198 _grid_begin = std::stable_partition(_indices.begin(), _indices.end(), [](hilet a) {
199 return _cells[a].leaf;
200 });
201
202 // Put all the root entries at the end.
203 _root_begin = std::stable_partition(_grid_begin, _indices.end(), [](hilet a) {
204 return _cells[a].parent != -1;
205 }
206 }
207
208 constexpr void calculate_row_col_count_and_margins() noexcept
209 {
210 // This also works on entries that are on the free-list.
211 for (auto& cell : _cells) {
212 cell.num_cols = 0;
213 cell.num_rows = 0;
214 cell.row_before_margin = cell.top_margin;
215 cell.col_before_margin = _left_to_right ? cell.left_margin : cell.right_margin;
216 cell.row_after_margin = cell.bottom_margin;
217 cell.col_after_margin = _left_to_right ? cell.right_margin : cell.left_margin;
218 }
219
220 // This also works on entries that are on the free-list.
221 for (hilet& cell : _cells) {
222 if (cell.parent == -1) {
223 continue;
224 }
225
226 inplace_max(_cells[cell.parent].num_cols, cell.col_end);
227 inplace_max(_cells[cell.parent].num_rows, cell.row_end);
228 }
229
230 // Calculate the total margin of each grid.
231 // This is done in topological order, so that grids inside grids get
232 // the correct margins.
233 for (hilet i : _indices) {
234 hilet& cell : _cells[i];
235
236 auto& parent = _cells[cell.parent];
237 if (cell.col_begin == 0) {
238 inplace_max(parent.col_before_margin, cell.col_before_margin);
239 }
240 if (cell.row_begin == 0) {
241 inplace_max(parent.row_before_margin, cell.row_before_margin);
242 }
243 if (cell.col_end == parent.num_cols) {
244 inplace_max(parent.col_after_margin, cell.col_after_margin);
245 }
246 if (cell.row_end == parent.num_row) {
247 inplace_max(parent.row_after_margin, cell.row_after_margin);
248 }
249 }
250 }
251
252 constexpr void setup_row_col_tables() noexcept
253 {
254 auto num_rows = 0;
255 auto num_cols = 0;
256 for (auto it = _indices_split; it != _indices.end(); ++it) {
257 auto& cell = _cells[*it];
258 hi_axiom(cell.num_cols != 0);
259 hi_axiom(cell.num_rows != 0);
260
261 cell.col_offset = num_cols;
262 cell.row_offset = num_rows;
263 num_cols += cell.num_cols;
264 num_rows += cell.num_rows;
265 }
266 _cols.clear(num_cols);
267 _rows.clear(num_rows);
268 }
269
270 constexpr void populate_row_col_tables() noexcept
271 {
272 // First step is filling in the row and column tables based on
273 // data from each cell. We are only filling in the minimums and maximums
274 // of single-span cells, as it allows multi-span to more properly scale
275 // the rows and columns.
276 for (hilet& cell : _cells) {
277 if (cell.parent != -1 and cell.in_use) {
278 hilet& parent = _cells[cell.parent];
279
280 hilet[first_row, last_row] = row_pair(parent, cell);
281 set_priority(first_row, last_row, cell.row_priority);
282 set_margins(first_row, last_row, cell.row_before_margin, cell.row_after_margin);
283
284 if (std::distance(first_row, last_row) == 1) {
285 set_minimum(first_row, last_row, cell.minimum_height);
286 set_preferred(first_row, last_row, cell.minimum_height);
287 set_maximum(first_row, last_row, cell.maximum_height);
288 }
289
290 hilet[first_col, last_col] = col_pair(parent, cell);
291 set_priority(first_col, last_col, cell.row_priority);
292 set_margins(first_col, last_col, cell.col_before_margin, cell.col_after_margin);
293
294 if (std::distance(first_col, last_col) == 1) {
295 // The minimum width is determined after knowing all row heights.
296 set_preferred(first_col, last_col, cell.minimum_width);
297 set_maximum(first_col, last_col, cell.maximum_width);
298 }
299 }
300 }
301
302 // Now that we know the proper minimum and maximum sizes of the rows
303 // and columns. We can scale them to fit multi-span cells.
304 for (hilet& cell : _cells) {
305 if (cell.parent != -1 and cell.in_use) {
306 hilet& parent = _cells[cell.parent];
307
308 hilet[first_row, last_row] = row_pair(parent, cell);
309 if (std::distance(first_row, last_row) > 1) {
310 set_minimum(first_row, last_row, cell.minimum_height);
311 set_preferred(first_row, last_row, cell.minimum_height);
312 set_maximum(first_row, last_row, cell.maximum_height);
313 }
314
315 hilet[first_col, last_col] = col_pair(parent, cell);
316 if (std::distance(first_col, last_col) > 1) {
317 // The minimum width is determined after knowing all row heights.
318 set_preferred(first_col, last_col, cell.minimum_width);
319 set_maximum(first_col, last_col, cell.maximum_width);
320 }
321 }
322 }
323
324 // Now that we know the minimum-height of each row, we can see if it is
325 // possible to wrap cells to become less wide while keeping inside the
326 // height requirements.
327 for (hilet& cell : _cells) {
328 if (cell.parent != -1 and cell.in_use) {
329 hilet[first_row, last_row] = row_pair(parent, cell);
330 hilet minimum_height = get_minimum(first_row, last_row);
331
332 hilet[first_col, last_col] = col_pair(parent, cell);
333 hilet minimum_width = cell.wrapped_height <= minimum_height ? cell.wrapped_width : cell.minimum_width);
334 set_minimum(first_col, last_col, minimum_width);
335 }
336 }
337 }
338
339 constexpr void constrain() noexcept
340 {
341 update_indices();
342 calculate_row_col_count_and_margins();
343 setup_row_col_tables();
344 populate_row_col_tables();
345 }
346
347 constexpr void layout() noexcept
348 {
349 // By iterating in reverse we start with the root grids, for which
350 // the width and height are known.
351 for (auto it = _indices.rbegin(); it != _indices.rend(); ++it) {
352 auto& cell = _cells[*it];
353
354 if (cell.parent != -1) {
355 // If this cell has a parent, determine the width and height of
356 // this cell.
357 hilet &parent = _cells[cell.parent];
358 hilet row_first = row_begin(parent) + cell.row_begin;
359 hilet row_last = row_begin(parent) + cell.row_end;
360 hilet col_first = col_begin(parent) + cell.col_begin;
361 hilet col_last = col_begin(parent) + cell.col_end;
362 cell.height = get_size(row_first, row_last);
363 cell.width = get_size(col_first, col_last);
364 }
365
366 if (not cell.leaf) {
367 // For each grid calculate the sizes and positions for rows
368 // and columns
369 hilet [row_first, row_last] = row_pair(grid);
370 update_size(row_first, row_last, grid.height);
371 update_position(row_first, row_last);
372
373 hilet[col_first, col_last] = col_pair(grid);
374 update_size(col_first, col_last, grid.width);
375 update_position(col_first, col_last);
376 }
377 }
378 }
379
380 hi_no_inline constexpr void _update() noexcept
381 {
382 if (to_bool(state & grid_state::need_constrain)) {
383 constraints();
384 }
385 layout();
386 }
387};
388
389}} // namespace hi::v1
Utilities for parsing spreadsheet addresses.
#define hi_axiom_bounds(x,...)
Specify an axiom that the value is within bounds.
Definition assert.hpp:264
#define hi_no_default(...)
This part of the code should not be reachable, unless a programming bug.
Definition assert.hpp:279
#define hi_axiom(expression,...)
Specify an axiom; an expression that is true.
Definition assert.hpp:253
#define hilet
Invariant should be the default for variables.
Definition utility.hpp:23
DOXYGEN BUG.
Definition algorithm.hpp:13
geometry/margins.hpp
Definition cache.hpp:11
The layout-algorithm:
Definition grid.hpp:24
constexpr void update() noexcept
Calculate the constraints for the grid.
Definition grid.hpp:51
T begin(T... args)
T clear(T... args)
T distance(T... args)
T end(T... args)
T make_tuple(T... args)
T max(T... args)
T push_back(T... args)
T rbegin(T... args)
T rend(T... args)
T reverse(T... args)
T stable_partition(T... args)