Sparkle 0.0.1
Loading...
Searching...
No Matches
spk_linear_layout.hpp
1#pragma once
2
3#include <algorithm>
4#include <cstdint>
5#include <limits>
6#include <set>
7#include <type_traits>
8#include <vector>
9
10#include "structure/widget/spk_layout.hpp"
11#include "type/spk_orientation.hpp"
12#include "type/spk_text_aligmnent.hpp"
13
14namespace spk
15{
22 template <Orientation TOrientation>
23 class LinearLayout : public Layout
24 {
25 private:
26 spk::HorizontalAlignment _horizontalAlignment = spk::HorizontalAlignment::Centered;
27 spk::VerticalAlignment _verticalAlignment = spk::VerticalAlignment::Centered;
28
29 void _setupSizeHint()
30 {
32 return (_computeMinimalSize());
33 });
35 return (_computeMinimalSize());
36 });
37 }
38
39 template <typename THorizontal, typename TVertical, typename... TArgs>
40 auto _executeOrientationDefined(THorizontal &&p_horizontalFunction, TVertical &&p_verticalFunction, TArgs &&...p_args) const
41 -> decltype(auto)
42 {
43 if constexpr (TOrientation == Orientation::Horizontal)
44 {
45 return std::forward<THorizontal>(p_horizontalFunction)(
46 std::forward<TArgs>(p_args)...);
47 }
48 else
49 {
50 return std::forward<TVertical>(p_verticalFunction)(
51 std::forward<TArgs>(p_args)...);
52 }
53 }
54
55 spk::Vector2UInt _baseElementSize(const Element &p_element) const
56 {
57 const spk::Vector2UInt &desired = p_element.element->sizeHint().desired();
58 const spk::Vector2UInt &minimal = p_element.element->sizeHint().minimal();
59
60 return (spk::Vector2UInt{
61 (p_element.sizePolicy.horizontal == SizePolicy::Desired ? desired.x : minimal.x),
62 (p_element.sizePolicy.vertical == SizePolicy::Desired ? desired.y : minimal.y)});
63 }
64
65 spk::Vector2UInt _computeMinimalSize() const
66 {
67 spk::Vector2UInt result = _elements().empty() ? spk::Vector2UInt{0, 0} : padding() * (_elements().size() - 1);
68
69 for (size_t i = 0; i < _elements().size(); i++)
70 {
71 const spk::Vector2UInt &tmpSize = _baseElementSize(_elements()[i]);
72
73 switch (TOrientation)
74 {
75 case Orientation::Vertical:
76 result.x = std::max(result.x, tmpSize.x);
77 result.y += tmpSize.y;
78 break;
79 case Orientation::Horizontal:
80 result.y = std::max(result.y, tmpSize.y);
81 result.x += tmpSize.x;
82 break;
83 }
84 }
85
86 return result;
87 }
88
89 static constexpr bool _isVertical()
90 {
91 return TOrientation == Orientation::Vertical;
92 }
93
94 std::int64_t _crossAxisOffset(const std::int64_t &p_difference) const
95 {
96 return _executeOrientationDefined(
97 [&]() -> std::int64_t {
98 switch (_verticalAlignment)
99 {
100 case spk::VerticalAlignment::Top:
101 return 0;
102 case spk::VerticalAlignment::Down:
103 return p_difference;
104 case spk::VerticalAlignment::Centered:
105 default:
106 return p_difference / 2;
107 }
108 },
109 [&]() -> std::int64_t {
110 switch (_horizontalAlignment)
111 {
112 case spk::HorizontalAlignment::Left:
113 return 0;
114 case spk::HorizontalAlignment::Right:
115 return p_difference;
116 case spk::HorizontalAlignment::Centered:
117 default:
118 return p_difference / 2;
119 }
120 });
121 }
122
123 static SizePolicy::Value _secondaryAxisPolicy(const SizePolicy::Value &p_mainPolicy)
124 {
125 switch (p_mainPolicy)
126 {
127 case SizePolicy::Desired:
128 case SizePolicy::Minimum:
129 return (p_mainPolicy);
130 default:
131 return (SizePolicy::Extend);
132 }
133 }
134
135 using Layout::addElement;
136 using Layout::setPadding;
137
138 std::vector<Element> _pullElementOnPriority(const SizePolicy::Value &p_value)
139 {
140 std::vector<Element> result;
141
142 for (const auto &element : _elements())
143 {
144 if (_executeOrientationDefined(
145 [&](const Element &p_e) -> bool {
146 return (p_e.sizePolicy.horizontal == p_value);
147 },
148 [&](const Element &p_e) -> bool {
149 return (p_e.sizePolicy.vertical == p_value);
150 },
151 element) == true)
152 {
153 result.push_back(element);
154 }
155 }
156
157 return (result);
158 }
159
160 spk::Vector2UInt _getSpaceToAssign(const spk::Extend2D &p_extend)
161 {
162 spk::Vector2UInt result = p_extend.size - sizeHint().minimal();
163
164 _executeOrientationDefined(
165 [&]() {
166 result.y = 0;
167 },
168 [&]() {
169 result.x = 0;
170 });
171
172 return (result);
173 }
174
175 using MainAxisViewRegionSize = std::vector<size_t>;
176 using SecondaryAxisViewRegionSize = std::vector<size_t>;
177
178 size_t _computeSizeToAssign(const spk::Extend2D &p_extend, MainAxisViewRegionSize &p_mainAxisViewRegionSize)
179 {
180 size_t result = _executeOrientationDefined(
181 [&]() {
182 return (p_extend.size.x);
183 },
184 [&]() {
185 return (p_extend.size.y);
186 });
187
188 if (p_mainAxisViewRegionSize.empty() == false)
189 {
190 result -= (p_mainAxisViewRegionSize.size() - 1) * _executeOrientationDefined(
191 [&]() {
192 return (padding().x);
193 },
194 [&]() {
195 return (padding().y);
196 });
197 }
198
199 for (const auto &size : p_mainAxisViewRegionSize)
200 {
201 if (result <= size)
202 {
203 return (0);
204 }
205 else
206 {
207 result -= size;
208 }
209 }
210
211 return (result);
212 }
213
214 using ElementIndexVector = std::vector<size_t>;
215
216 ElementIndexVector _getElementWithSizePolicy(const SizePolicy::Value &p_value)
217 {
218 ElementIndexVector result;
219
220 for (size_t i = 0; i < _elements().size(); i++)
221 {
222 if (_executeOrientationDefined(
223 [&](const Element &p_e) {
224 return p_e.sizePolicy.horizontal == p_value;
225 },
226 [&](const Element &p_e) {
227 return p_e.sizePolicy.vertical == p_value;
228 },
229 _elements()[i]) == true)
230 {
231 result.push_back(i);
232 }
233 }
234
235 return (result);
236 }
237
238 size_t _updateViewRegionSize(const size_t &p_spaceToAssign, MainAxisViewRegionSize &p_mainAxisViewRegionSize, const SizePolicy::Value &p_value)
239 {
240 size_t sizeLeft = p_spaceToAssign;
241 ElementIndexVector targetElements = _getElementWithSizePolicy(p_value);
242
243 if (targetElements.empty())
244 {
245 return (sizeLeft);
246 }
247
248 for (size_t i = 0; i < targetElements.size(); i++)
249 {
250 size_t index = targetElements[i];
251 size_t currentSize = p_mainAxisViewRegionSize[index];
252 }
253
254 size_t sizeToAdd = sizeLeft / targetElements.size();
255
256 for (size_t i = 0; i < targetElements.size();)
257 {
258 size_t index = targetElements[i];
259 size_t currentSize = p_mainAxisViewRegionSize[index];
260 const Element &tmpElement = _elements()[index];
261 size_t elementMaxSize = _executeOrientationDefined(
262 [&](const Element &p_e) -> size_t {
263 return (p_e.element->sizeHint().maximal().x);
264 },
265 [&](const Element &p_e) -> size_t {
266 return (p_e.element->sizeHint().maximal().y);
267 },
268 tmpElement);
269
270 if (elementMaxSize <= currentSize + sizeToAdd)
271 {
272 p_mainAxisViewRegionSize[targetElements[i]] = elementMaxSize;
273 targetElements.erase(targetElements.begin() + i);
274 i = 0;
275 if (sizeLeft < elementMaxSize)
276 {
277 return 0;
278 }
279 sizeLeft -= (elementMaxSize - currentSize);
280 if (targetElements.empty())
281 {
282 return sizeLeft;
283 }
284 sizeToAdd = sizeLeft / targetElements.size();
285 }
286 else
287 {
288 i++;
289 }
290 }
291
292 for (size_t i = 0; i < targetElements.size(); i++)
293 {
294 size_t index = targetElements[i];
295 size_t currentSize = p_mainAxisViewRegionSize[index];
296
297 p_mainAxisViewRegionSize[targetElements[i]] = currentSize + sizeToAdd;
298 sizeLeft -= sizeToAdd;
299 }
300
301 return (sizeLeft);
302 }
303
304 public:
305 LinearLayout()
306 {
307 _setupSizeHint();
308 }
309 ~LinearLayout() override = default;
310
315 template <Orientation TLocalOrientation = TOrientation, typename std::enable_if_t<TLocalOrientation == Orientation::Horizontal, int> = 0>
316 void setAlignment(spk::VerticalAlignment p_alignment)
317 {
318 _verticalAlignment = p_alignment;
319 }
320
325 template <Orientation TLocalOrientation = TOrientation, typename std::enable_if_t<TLocalOrientation == Orientation::Vertical, int> = 0>
326 void setAlignment(spk::HorizontalAlignment p_alignment)
327 {
328 _horizontalAlignment = p_alignment;
329 }
330
335 template <Orientation TLocalOrientation = TOrientation, typename std::enable_if_t<TLocalOrientation == Orientation::Horizontal, int> = 0>
336 spk::VerticalAlignment alignment() const
337 {
338 return (_verticalAlignment);
339 }
340
345 template <Orientation TLocalOrientation = TOrientation, typename std::enable_if_t<TLocalOrientation == Orientation::Vertical, int> = 0>
346 spk::HorizontalAlignment alignment() const
347 {
348 return (_horizontalAlignment);
349 }
350
355 void setGeometry(const spk::Extend2D &p_geometry) override
356 {
357 MainAxisViewRegionSize mainAxisViewRegionSize;
358 SecondaryAxisViewRegionSize secondaryAxisViewRegionSize;
359
360 mainAxisViewRegionSize.resize(_elements().size());
361 secondaryAxisViewRegionSize.resize(_elements().size());
362
363 for (size_t i = 0; i < _elements().size(); i++)
364 {
365 spk::Vector2UInt elementMinSize = _baseElementSize(_elements()[i]);
366 spk::Vector2UInt elementMaxSize = _elements()[i].element->sizeHint().maximal();
367
368 _executeOrientationDefined(
369 [&]() {
370 mainAxisViewRegionSize[i] = elementMinSize.x;
371 const Layout::SizePolicy::Value crossPolicy = _elements()[i].sizePolicy.vertical;
372 secondaryAxisViewRegionSize[i] = std::min(elementMaxSize.y, (crossPolicy == SizePolicy::Desired || crossPolicy == SizePolicy::Minimum ? elementMinSize.y : p_geometry.height));
373 },
374 [&]() {
375 mainAxisViewRegionSize[i] = elementMinSize.y;
376 const Layout::SizePolicy::Value crossPolicy = _elements()[i].sizePolicy.horizontal;
377 secondaryAxisViewRegionSize[i] = std::min(elementMaxSize.x, (crossPolicy == SizePolicy::Desired || crossPolicy == SizePolicy::Minimum ? elementMinSize.x : p_geometry.width));
378 });
379 }
380
381 size_t sizeToAssign = _computeSizeToAssign(p_geometry, mainAxisViewRegionSize);
382
383 sizeToAssign = _updateViewRegionSize(sizeToAssign, mainAxisViewRegionSize, SizePolicy::Extend);
384 sizeToAssign = _updateViewRegionSize(sizeToAssign, mainAxisViewRegionSize, SizePolicy::Standard);
385
386 const std::int64_t geometryMainAxisSize = _executeOrientationDefined(
387 [&]() -> std::int64_t {
388 return (static_cast<std::int64_t>(p_geometry.size.x));
389 },
390 [&]() -> std::int64_t {
391 return (static_cast<std::int64_t>(p_geometry.size.y));
392 });
393
394 const std::int64_t geometryCrossAxisSize = _executeOrientationDefined(
395 [&]() -> std::int64_t {
396 return (static_cast<std::int64_t>(p_geometry.size.y));
397 },
398 [&]() -> std::int64_t {
399 return (static_cast<std::int64_t>(p_geometry.size.x));
400 });
401
402 const std::int64_t clampedCenterOffset = std::clamp<std::int64_t>(
403 static_cast<std::int64_t>(0),
404 static_cast<std::int64_t>(std::numeric_limits<spk::Vector2Int::value_type>::min()),
405 static_cast<std::int64_t>(std::numeric_limits<spk::Vector2Int::value_type>::max()));
406
407 spk::Vector2Int anchor = p_geometry.anchor;
408 _executeOrientationDefined(
409 [&]() {
410 anchor.x += static_cast<spk::Vector2Int::value_type>(clampedCenterOffset);
411 },
412 [&]() {
413 anchor.y += static_cast<spk::Vector2Int::value_type>(clampedCenterOffset);
414 });
415 for (size_t i = 0; i < _elements().size(); i++)
416 {
417 spk::Vector2UInt elementSize = _executeOrientationDefined(
418 [&]() {
419 return (spk::Vector2UInt(mainAxisViewRegionSize[i], secondaryAxisViewRegionSize[i]));
420 },
421 [&]() {
422 return (spk::Vector2UInt(secondaryAxisViewRegionSize[i], mainAxisViewRegionSize[i]));
423 });
424
425 spk::Vector2Int elementAnchor = anchor;
426 const std::int64_t crossAxisSize = _executeOrientationDefined(
427 [&]() -> std::int64_t {
428 return (static_cast<std::int64_t>(elementSize.y));
429 },
430 [&]() -> std::int64_t {
431 return (static_cast<std::int64_t>(elementSize.x));
432 });
433
434 const std::int64_t crossDifference = geometryCrossAxisSize - crossAxisSize;
435 const std::int64_t crossAxisOffset = _crossAxisOffset(crossDifference);
436 const std::int64_t clampedCrossOffset = std::clamp<std::int64_t>(
437 crossAxisOffset,
438 static_cast<std::int64_t>(std::numeric_limits<spk::Vector2Int::value_type>::min()),
439 static_cast<std::int64_t>(std::numeric_limits<spk::Vector2Int::value_type>::max()));
440
441 _executeOrientationDefined(
442 [&]() {
443 elementAnchor.y += static_cast<spk::Vector2Int::value_type>(clampedCrossOffset);
444 },
445 [&]() {
446 elementAnchor.x += static_cast<spk::Vector2Int::value_type>(clampedCrossOffset);
447 });
448
449 _elements()[i].element->setGeometry({elementAnchor, elementSize});
450
451 _executeOrientationDefined(
452 [&]() {
453 anchor.x += elementSize.x + padding().x;
454 },
455 [&]() {
456 anchor.y += elementSize.y + padding().y;
457 });
458 }
459 }
460
466 void addElement(ResizableElement *p_object, const SizePolicy::Value &p_sizePolicy)
467 {
468 const SizePolicy::Value secondaryPolicy = _secondaryAxisPolicy(p_sizePolicy);
469 Layout::addElement(p_object, _executeOrientationDefined([&]() -> SizePolicy {
470 return (SizePolicy{p_sizePolicy, secondaryPolicy});
471 },
472 [&]() -> SizePolicy {
473 return (SizePolicy{secondaryPolicy, p_sizePolicy});
474 }));
475 }
476
482 void addElement(ResizableElement *p_object, const SizePolicy &p_sizePolicy)
483 {
484 Layout::addElement(p_object, p_sizePolicy);
485 }
486
491 void setPadding(const uint32_t &p_padding)
492 {
493 _executeOrientationDefined(
494 [&]() {
495 Layout::setPadding({p_padding, 0});
496 },
497 [&]() {
498 Layout::setPadding({0, p_padding});
499 });
500 }
501 };
502
503 using VerticalLayout = LinearLayout<Orientation::Vertical>;
504 using HorizontalLayout = LinearLayout<Orientation::Horizontal>;
505}
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
void setPadding(const spk::Vector2UInt &p_padding)
Sets layout padding between elements.
Definition spk_layout.cpp:5
const spk::Vector2UInt & padding() const
Returns layout padding.
Definition spk_layout.cpp:11
spk::HorizontalAlignment alignment() const
Returns alignment along the cross axis (vertical layout).
Definition spk_linear_layout.hpp:346
void addElement(ResizableElement *p_object, const SizePolicy &p_sizePolicy)
Adds an element with an explicit size policy.
Definition spk_linear_layout.hpp:482
void setPadding(const uint32_t &p_padding)
Sets padding along the main axis.
Definition spk_linear_layout.hpp:491
spk::VerticalAlignment alignment() const
Returns alignment along the cross axis (horizontal layout).
Definition spk_linear_layout.hpp:336
void addElement(ResizableElement *p_object, const SizePolicy::Value &p_sizePolicy)
Adds an element with a main-axis size policy.
Definition spk_linear_layout.hpp:466
void setGeometry(const spk::Extend2D &p_geometry) override
Sets the layout geometry and positions elements.
Definition spk_linear_layout.hpp:355
void setAlignment(spk::HorizontalAlignment p_alignment)
Sets alignment along the cross axis (vertical layout).
Definition spk_linear_layout.hpp:326
void setAlignment(spk::VerticalAlignment p_alignment)
Sets alignment along the cross axis (horizontal layout).
Definition spk_linear_layout.hpp:316
const Vector2UInt & minimal() const
Returns the minimal size.
Definition spk_resizable_element.cpp:73
const Vector2UInt & maximal() const
Returns the maximal size.
Definition spk_resizable_element.cpp:81
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
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
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