Sparkle 0.0.1
Loading...
Searching...
No Matches
spk_grid_layout.hpp
1#pragma once
2
3#include <algorithm>
4#include <cstddef>
5#include <limits>
6#include <numeric>
7#include <stdexcept>
8#include <vector>
9
10#include "structure/graphics/spk_extend_2d.hpp"
11#include "structure/math/spk_vector2.hpp"
12#include "structure/widget/spk_layout.hpp"
13
14namespace spk
15{
26 class GridLayout : public Layout
27 {
28 private:
30 using Layout::clear;
31
32 spk::Vector2UInt _nbCells = {0, 0};
33
34 static const Vector2UInt &_positionOf(const Element &p_e)
35 {
36 const auto *ptr = std::any_cast<Vector2UInt>(&p_e.extraData);
37 if (ptr == nullptr)
38 {
39 throw std::logic_error("GridLayout element extraData is not a Vector2UInt");
40 }
41 return *ptr;
42 }
43
44 void _setupSizeHint()
45 {
47 return (_computeMinimalSize());
48 });
50 return (_computeMinimalSize());
51 });
52 }
53
54 spk::Vector2UInt _baseElementSize(const Element &p_element) const
55 {
56 const spk::Vector2UInt &desired = p_element.element->sizeHint().desired();
57 const spk::Vector2UInt &minimal = p_element.element->sizeHint().minimal();
58
59 return (spk::Vector2UInt{
60 (p_element.sizePolicy.horizontal == SizePolicy::Desired ? desired.x : minimal.x),
61 (p_element.sizePolicy.vertical == SizePolicy::Desired ? desired.y : minimal.y)});
62 }
63
64 using AxisSizeContainer = std::vector<spk::Vector2UInt::value_type>;
65 struct GridSizeContainer
66 {
67 std::vector<spk::Vector2UInt::value_type> xAxisSizes;
68 std::vector<spk::Vector2UInt::value_type> yAxisSizes;
69
70 GridSizeContainer() = default;
71 GridSizeContainer(const spk::Vector2UInt &p_gridSize) :
72 xAxisSizes(p_gridSize.x, 0),
73 yAxisSizes(p_gridSize.y, 0)
74 {
75 }
76 };
77
78 struct AxisState
79 {
80 AxisSizeContainer::value_type size = 0;
81 AxisSizeContainer::value_type maximal = std::numeric_limits<AxisSizeContainer::value_type>::max();
82 Layout::SizePolicy::Value policy = Layout::SizePolicy::Minimum;
83 bool hasElement = false;
84 bool hasDesired = false;
85 };
86
87 static Layout::SizePolicy::Value _strongerPolicy(Layout::SizePolicy::Value p_current, Layout::SizePolicy::Value p_candidate)
88 {
89 const auto rank = [](Layout::SizePolicy::Value p_value) {
90 switch (p_value)
91 {
92 case Layout::SizePolicy::Extend:
93 return 3;
94 case Layout::SizePolicy::Standard:
95 return 2;
96 case Layout::SizePolicy::Minimum:
97 return 1;
98 default:
99 return 0;
100 }
101 };
102 return (rank(p_candidate) > rank(p_current) ? p_candidate : p_current);
103 }
104
105 std::vector<size_t> _collectAxisCandidates(const std::vector<AxisState> &p_states, Layout::SizePolicy::Value p_policy) const
106 {
107 const AxisSizeContainer::value_type axisUnlimited = std::numeric_limits<AxisSizeContainer::value_type>::max();
108 std::vector<size_t> candidateIndices;
109 candidateIndices.reserve(p_states.size());
110
111 for (size_t i = 0; i < p_states.size(); ++i)
112 {
113 const AxisState &state = p_states[i];
114 const bool canGrow = (state.maximal == axisUnlimited || state.size < state.maximal);
115
116 if (state.hasElement == true &&
117 state.hasDesired == false &&
118 state.policy == p_policy &&
119 canGrow == true)
120 {
121 candidateIndices.push_back(i);
122 }
123 }
124
125 return candidateIndices;
126 }
127
128 AxisSizeContainer::value_type _consumeAxisShare(AxisSizeContainer::value_type &p_spaceToAssign, const std::vector<size_t> &p_candidateIndices, std::vector<AxisState> &p_states) const
129 {
130 const AxisSizeContainer::value_type axisUnlimited = std::numeric_limits<AxisSizeContainer::value_type>::max();
131 AxisSizeContainer::value_type share = p_spaceToAssign / p_candidateIndices.size();
132 if (share == 0)
133 {
134 share = 1;
135 }
136
137 AxisSizeContainer::value_type consumed = 0;
138 for (size_t index : p_candidateIndices)
139 {
140 if (p_spaceToAssign == 0)
141 {
142 break;
143 }
144
145 AxisState &state = p_states[index];
146 AxisSizeContainer::value_type available = axisUnlimited;
147
148 if (state.maximal != axisUnlimited)
149 {
150 if (state.size >= state.maximal)
151 {
152 continue;
153 }
154 available = state.maximal - state.size;
155 }
156
157 if (available == 0)
158 {
159 continue;
160 }
161
162 AxisSizeContainer::value_type increment = std::min(share, available);
163 increment = std::min(increment, p_spaceToAssign);
164
165 state.size += increment;
166 p_spaceToAssign -= increment;
167 consumed += increment;
168 }
169
170 return consumed;
171 }
172
173 void _updateAxisState(AxisState &p_state, AxisSizeContainer::value_type p_baseSize, AxisSizeContainer::value_type p_maximalSize, Layout::SizePolicy::Value p_value)
174 {
175 p_state.hasElement = true;
176 p_state.size = std::max(p_state.size, p_baseSize);
177 p_state.maximal = std::min(p_state.maximal, p_maximalSize);
178
179 if (p_value == Layout::SizePolicy::Desired)
180 {
181 p_state.hasDesired = true;
182 p_state.maximal = std::min(p_state.maximal, p_baseSize);
183 return;
184 }
185
186 p_state.policy = _strongerPolicy(p_state.policy, p_value);
187 }
188
189 void _assignSpaceToAxis(AxisSizeContainer::value_type &p_spaceToAssign, std::vector<AxisState> &p_states, Layout::SizePolicy::Value p_policy) const
190 {
191 if (p_spaceToAssign == 0)
192 {
193 return;
194 }
195
196 while (p_spaceToAssign > 0)
197 {
198 std::vector<size_t> candidateIndices = _collectAxisCandidates(p_states, p_policy);
199 if (candidateIndices.empty())
200 {
201 break;
202 }
203
204 AxisSizeContainer::value_type consumed = _consumeAxisShare(p_spaceToAssign, candidateIndices, p_states);
205 if (consumed == 0)
206 {
207 break;
208 }
209 }
210 }
211
212 GridSizeContainer _computeGridBaseSize() const
213 {
214 GridSizeContainer result(_nbCells);
215
216 for (size_t i = 0; i < _elements().size(); i++)
217 {
218 const Element &element = _elements()[i];
219 const spk::Vector2UInt &elementPosition = _positionOf(element);
220 spk::Vector2UInt elementSize = _baseElementSize(element);
221
222 result.xAxisSizes[elementPosition.x] = std::max(result.xAxisSizes[elementPosition.x], elementSize.x);
223 result.yAxisSizes[elementPosition.y] = std::max(result.yAxisSizes[elementPosition.y], elementSize.y);
224 }
225
226 return (result);
227 }
228
229 spk::Vector2UInt _computeTotalGridSize(const GridSizeContainer &p_rawGridSize) const
230 {
231 using AxisValue = AxisSizeContainer::value_type;
232
233 const auto accumulateAxis = [&](const AxisSizeContainer &p_axisSizes, const AxisValue p_padding) {
234 const AxisValue totalSize = std::accumulate(p_axisSizes.begin(), p_axisSizes.end(), AxisValue{0});
235 if (p_axisSizes.empty() == true)
236 {
237 return totalSize;
238 }
239
240 const AxisValue paddingCount = static_cast<AxisValue>(static_cast<size_t>(p_axisSizes.size() - 1) * static_cast<size_t>(p_padding));
241 return (totalSize + paddingCount);
242 };
243
244 const AxisValue xSize = accumulateAxis(p_rawGridSize.xAxisSizes, padding().x);
245 const AxisValue ySize = accumulateAxis(p_rawGridSize.yAxisSizes, padding().y);
246
247 return (spk::Vector2UInt{xSize, ySize});
248 }
249
250 spk::Vector2UInt _computeMinimalSize() const
251 {
252 return (_computeTotalGridSize(_computeGridBaseSize()));
253 }
254
255 public:
256 GridLayout()
257 {
258 _setupSizeHint();
259 }
260
267 void addElement(const Vector2UInt &p_position, ResizableElement *p_element, const SizePolicy &p_sizePolicy)
268 {
269 _nbCells = spk::Vector2UInt::max(_nbCells, p_position + 1);
270 Layout::addElement(p_element, p_sizePolicy, p_position);
271 }
272
276 void clear()
277 {
278 _nbCells = {0, 0};
280 }
281
286 void setGeometry(const Extend2D &p_extend) override
287 {
288 const GridSizeContainer baseGridSize = _computeGridBaseSize();
289 spk::Vector2UInt spaceUsed = _computeTotalGridSize(baseGridSize);
290
291 spk::Vector2UInt spaceLeft{
292 p_extend.size.x <= spaceUsed.x ? 0 : (p_extend.size.x - spaceUsed.x),
293 p_extend.size.y <= spaceUsed.y ? 0 : (p_extend.size.y - spaceUsed.y)};
294
295 std::vector<AxisState> xAxisStates(_nbCells.x);
296 std::vector<AxisState> yAxisStates(_nbCells.y);
297
298 for (size_t i = 0; i < xAxisStates.size(); ++i)
299 {
300 xAxisStates[i].size = baseGridSize.xAxisSizes[i];
301 }
302 for (size_t i = 0; i < yAxisStates.size(); ++i)
303 {
304 yAxisStates[i].size = baseGridSize.yAxisSizes[i];
305 }
306
307 for (const Element &element : _elements())
308 {
309 const spk::Vector2UInt position = _positionOf(element);
310 const spk::Vector2UInt elementBase = _baseElementSize(element);
311 const spk::Vector2UInt elementMaximal = element.element->sizeHint().maximal();
312
313 if (position.x < xAxisStates.size())
314 {
315 _updateAxisState(xAxisStates[position.x], elementBase.x, elementMaximal.x, element.sizePolicy.horizontal);
316 }
317 if (position.y < yAxisStates.size())
318 {
319 _updateAxisState(yAxisStates[position.y], elementBase.y, elementMaximal.y, element.sizePolicy.vertical);
320 }
321 }
322
323 _assignSpaceToAxis(spaceLeft.x, xAxisStates, SizePolicy::Extend);
324 _assignSpaceToAxis(spaceLeft.x, xAxisStates, SizePolicy::Standard);
325
326 _assignSpaceToAxis(spaceLeft.y, yAxisStates, SizePolicy::Extend);
327 _assignSpaceToAxis(spaceLeft.y, yAxisStates, SizePolicy::Standard);
328
329 std::vector<spk::Vector2Int::value_type> xAnchors(_nbCells.x);
330 std::vector<spk::Vector2Int::value_type> yAnchors(_nbCells.y);
331
332 spk::Vector2Int::value_type currentX = p_extend.anchor.x;
333 for (size_t i = 0; i < _nbCells.x; ++i)
334 {
335 xAnchors[i] = currentX;
336 currentX += static_cast<spk::Vector2Int::value_type>(xAxisStates[i].size);
337 if (i + 1 < _nbCells.x)
338 {
339 currentX += static_cast<spk::Vector2Int::value_type>(padding().x);
340 }
341 }
342
343 spk::Vector2Int::value_type currentY = p_extend.anchor.y;
344 for (size_t i = 0; i < _nbCells.y; ++i)
345 {
346 yAnchors[i] = currentY;
347 currentY += static_cast<spk::Vector2Int::value_type>(yAxisStates[i].size);
348 if (i + 1 < _nbCells.y)
349 {
350 currentY += static_cast<spk::Vector2Int::value_type>(padding().y);
351 }
352 }
353
354 for (const Element &element : _elements())
355 {
356 const spk::Vector2UInt position = _positionOf(element);
357 if (position.x >= xAxisStates.size() || position.y >= yAxisStates.size())
358 {
359 continue;
360 }
361
362 spk::Vector2UInt elementSize{
363 xAxisStates[position.x].size,
364 yAxisStates[position.y].size};
365
366 const spk::Vector2UInt elementBase = _baseElementSize(element);
367 if (element.sizePolicy.horizontal == SizePolicy::Minimum)
368 {
369 elementSize.x = elementBase.x;
370 }
371
372 if (element.sizePolicy.vertical == SizePolicy::Minimum)
373 {
374 elementSize.y = elementBase.y;
375 }
376
377 element.element->setGeometry({{xAnchors[position.x], yAnchors[position.y]}, elementSize});
378 }
379 }
380 };
381}
void addElement(const Vector2UInt &p_position, ResizableElement *p_element, const SizePolicy &p_sizePolicy)
Adds an element at a grid position.
Definition spk_grid_layout.hpp:267
void clear()
Removes all grid elements.
Definition spk_grid_layout.hpp:276
void setGeometry(const Extend2D &p_extend) override
Sets the layout geometry and positions elements.
Definition spk_grid_layout.hpp:286
void addElement(ResizableElement *p_element, const SizePolicy &p_sizePolicy, TExtra &&p_extra)
Adds an element with extra layout data.
Definition spk_layout.hpp:121
const ElementContainer & _elements() const
Returns the stored elements.
Definition spk_layout.cpp:33
const spk::Vector2UInt & padding() const
Returns layout padding.
Definition spk_layout.cpp:11
void clear()
Removes all elements from the layout.
Definition spk_layout.cpp:16
const Vector2UInt & minimal() const
Returns the minimal size.
Definition spk_resizable_element.cpp:73
const Vector2UInt & desired() const
Returns the desired size.
Definition spk_resizable_element.cpp:77
void configureDesiredGenerator(Generator p_generator)
Configures the desired size generator.
Definition spk_resizable_element.cpp:26
void configureMinimalGenerator(Generator p_generator)
Configures the minimal size generator.
Definition spk_resizable_element.cpp:21
Base interface for elements that can be sized by layouts.
Definition spk_resizable_element.hpp:17
SizeHint & sizeHint()
Returns mutable size hint data.
Definition spk_resizable_element.cpp:86
Axis-aligned rectangle defined by an anchor point and size.
Definition spk_extend_2d.hpp:24
static IVector2 max(const IVector2 &p_a, const IVector2 &p_b)
Definition spk_vector2.hpp:635
TType x
X component.
Definition spk_vector2.hpp:44
int32_t value_type
Definition spk_vector2.hpp:39
TType y
Y component.
Definition spk_vector2.hpp:48
Layout element entry with sizing policy and extra data.
Definition spk_layout.hpp:63
SizePolicy sizePolicy
Size policy for the element.
Definition spk_layout.hpp:71
ResizableElement * element
Pointer to the resizable element.
Definition spk_layout.hpp:67
std::any extraData
Extra data used by derived layouts.
Definition spk_layout.hpp:75
Defines sizing policies for layout elements.
Definition spk_layout.hpp:31
Value
Policy value controlling sizing behavior.
Definition spk_layout.hpp:37
Value horizontal
Horizontal size policy.
Definition spk_layout.hpp:47
Value vertical
Vertical size policy.
Definition spk_layout.hpp:51