TachyonUI 0.1.0
High-performance UI for building tools.
Loading...
Searching...
No Matches
gui_layout_helpers.h
Go to the documentation of this file.
1#pragma once
2
5
6#include <algorithm>
7#include <span>
8#include <vector>
9
10namespace tachyon
11{
14
15 struct int2;
16 class ElementStorage;
17 class OSWindowManager;
18 class ITextSystem;
19
21 {
22 static int2 ClampScrollOffsetToContent(int2 childOffset, const ElementRect& parent);
23
27 static int2 CalculateLeafSize(ITextSystem& textSystem, const Element &element,
28 const ElementRect &elementRect, int32_t& minContentWidth);
29
30 static int32_t RecalculateContentHeight(ITextSystem& textSystem,
31 const Element &element, const ElementRect &elementRect);
32
33 static void ApplyHorizontalStyleSize(ElementRect& elementRect, const int32_t contentWidth);
34 static void ApplyVerticalStyleSize(ElementRect& elementRect, const int32_t contentHeight);
35 static void ApplyHorizontalStyleOffset(ElementRect& elementRect);
36 static void ApplyVerticalStyleOffset(ElementRect& elementRect);
37
38 static void ApplyHorizontalPositionConstraints(OSWindowManager& osWindowManager, ElementStorage& storage, ElementRect& elementRect, const ElementRef& elementRef);
39 static void ApplyVerticalPositionConstraints(OSWindowManager& osWindowManager, ElementStorage& storage, ElementRect& elementRect, const ElementRef& elementRef);
40
41 template <int axis> requires(axis == 0 || axis == 1)
42 static inline int32_t CalcLayoutAdd(std::span<const ElementRect> childRects, int32_t& count)
43 {
44 int32_t size = 0;
45 count = 0;
46 for (const ElementRect& childElem : childRects)
47 {
48 if (!childElem.constraints.IsInFlowAxis(axis))
49 continue;
50 size += childElem.rect.size[axis];
51 count++;
52 }
53 return size;
54 }
55
59 template <int axis> requires(axis == 0 || axis == 1)
60 static inline int32_t FlexMinSize(const ElementRect& c)
61 {
62 const SizeConstraintAxis& size = (axis == 0) ? c.constraints.width : c.constraints.height;
63 if (!c.layoutStyle) return std::max<int32_t>(0, size.min);
64 const LayoutStyle& s = *c.layoutStyle;
65 const int32_t margin = (axis == 0) ? s.margin.Horizontal() : s.margin.Vertical();
66 const int32_t chrome = (axis == 0) ? s.GetMinWidth() : s.GetMinHeight();
67 const int32_t content = (axis == 0) ? c.minContentWidth : 0;
68 return std::max({ chrome, int32_t(size.min + margin), content });
69 }
70
73 template <int axis> requires(axis == 0 || axis == 1)
74 static inline int32_t FlexMaxSize(const ElementRect& c)
75 {
76 const SizeConstraintAxis& size = (axis == 0) ? c.constraints.width : c.constraints.height;
78 const int32_t margin = !c.layoutStyle ? 0
79 : (axis == 0) ? c.layoutStyle->margin.Horizontal() : c.layoutStyle->margin.Vertical();
80 return std::max<int32_t>(FlexMinSize<axis>(c), size.max + margin);
81 }
82
85 template <int axis> requires(axis == 0 || axis == 1)
86 static inline void ApplyFlexBasis(std::span<ElementRect> items)
87 {
88 for (ElementRect& c : items)
89 {
90 if (!c.constraints.IsInFlowAxis(axis) || !c.layoutStyle) continue;
91 if (c.constraints.HasFixedAxis(axis)) continue;
92 const int16_t basis = c.layoutStyle->layoutData.flex.flexBasis;
93 if (basis >= 0)
94 c.rect.size[axis] = basis;
95 }
96 }
97
105 template <int axis> requires(axis == 0 || axis == 1)
106 static inline int32_t ResolveFlexibleLengths(std::span<ElementRect> items, int32_t freeSpace)
107 {
108 if (freeSpace == 0) return 0;
109 const bool grow = freeSpace > 0;
110
111 struct Item { uint64_t weight; int32_t base; int32_t target; bool active; };
112 static thread_local std::vector<Item> state;
113 state.assign(items.size(), Item{ 0, 0, 0, false });
114 for (size_t i = 0; i < items.size(); ++i)
115 {
116 const ElementRect& c = items[i];
117 if (!c.constraints.IsInFlowAxis(axis)) continue;
118 const int32_t size = c.rect.size[axis];
119 uint64_t weight = 0;
120 if (grow)
121 weight = c.constraints.GetFlexGrow(c.layoutStyle);
122 else if (!c.constraints.HasFixedAxis(axis))
123 weight = uint64_t(c.constraints.GetFlexShrink(c.layoutStyle)) * uint64_t(std::max<int32_t>(size, 0));
124 if (weight == 0) continue;
125 state[i] = Item{ weight, size, size, true };
126 }
127
128 int64_t remaining = freeSpace;
129 int64_t applied = 0;
130 for (size_t pass = 0; pass <= items.size() && remaining != 0; ++pass)
131 {
132 uint64_t totalWeight = 0;
133 for (const Item& it : state) if (it.active) totalWeight += it.weight;
134 if (totalWeight == 0) break;
135
136 uint64_t cumWeight = 0;
137 int64_t prevBound = 0;
138 int64_t violation = 0; // sum of (clamped - target): > 0 = min violations, < 0 = max
139 for (size_t i = 0; i < items.size(); ++i)
140 {
141 Item& it = state[i];
142 if (!it.active) continue;
143 cumWeight += it.weight;
144 const int64_t bound = remaining * int64_t(cumWeight) / int64_t(totalWeight);
145 it.target = int32_t(it.base + (bound - prevBound));
146 prevBound = bound;
147 violation += std::clamp(it.target, FlexMinSize<axis>(items[i]), FlexMaxSize<axis>(items[i])) - it.target;
148 }
149
150 if (violation == 0)
151 {
152 for (size_t i = 0; i < items.size(); ++i)
153 if (state[i].active) items[i].rect.size[axis] = state[i].target;
154 applied += remaining;
155 break;
156 }
157
158 // Freeze the items on the side of the violation at their limit; the
159 // rest share what is left in the next pass.
160 for (size_t i = 0; i < items.size(); ++i)
161 {
162 Item& it = state[i];
163 if (!it.active) continue;
164 const int32_t clamped = std::clamp(it.target, FlexMinSize<axis>(items[i]), FlexMaxSize<axis>(items[i]));
165 if ((violation > 0 && clamped > it.target) || (violation < 0 && clamped < it.target))
166 {
167 items[i].rect.size[axis] = clamped;
168 remaining -= (clamped - it.base);
169 applied += (clamped - it.base);
170 it.active = false;
171 }
172 }
173 }
174 return int32_t(applied);
175 }
176
177 template <int axis> requires(axis == 0 || axis == 1)
178 static inline int32_t CalcLayoutMax(std::span<const ElementRect> childRects)
179 {
180 int32_t size = 0;
181 for (const ElementRect& childElem : childRects)
182 {
183 if (!childElem.constraints.IsInFlowAxis(axis))
184 continue;
185 size = std::max(size, static_cast<int32_t>(childElem.rect.size[axis]));
186 }
187 return size;
188 }
189
190 template <int axis> requires(axis == 0 || axis == 1)
191 static inline void Place(std::span<ElementRect> childRects, int32_t start, int32_t spacing)
192 {
193 for (ElementRect& childElem : childRects)
194 {
195 if (!childElem.constraints.IsInFlowAxis(axis))
196 continue;
197 childElem.rect.position[axis] = start;
198 start += childElem.rect.size[axis] + spacing;
199 }
200 }
201
205 template <int axis> requires(axis == 0 || axis == 1)
206 static inline int32_t FixedContentSize(const ElementRect& rect)
207 {
208 const LayoutStyle& s = *rect.layoutStyle;
209 if constexpr (axis == 0) return rect.constraints.width.value - s.padding.Horizontal() - s.borderWidths.Horizontal();
210 else return rect.constraints.height.value - s.padding.Vertical() - s.borderWidths.Vertical();
211 }
212
213 template <int axis> requires(axis == 0 || axis == 1)
214 static inline void ReversePlace(std::span<ElementRect> childRects, int32_t start, int32_t spacing)
215 {
216 for (ElementRect& childElem : childRects)
217 {
218 if (!childElem.constraints.IsInFlowAxis(axis))
219 continue;
220 start -= childElem.rect.size[axis];
221 childElem.rect.position[axis] = start;
222 start -= spacing;
223 }
224 }
225
230 template <int axis, bool reverse> requires(axis == 0 || axis == 1)
231 static void JustifyChildren(std::span<ElementRect> childRects,
232 JustifyContent justify, int16_t gap, const IntRect &parentContentRect,
233 int totalContentSize, int totalChildrenInFlow)
234 {
235 const JustifySpacing s = Justify(justify, parentContentRect.size[axis] - totalContentSize, gap, totalChildrenInFlow);
236 if constexpr (reverse)
237 ReversePlace<axis>(childRects, parentContentRect.Max(axis) - s.offset, s.spacing);
238 else
239 Place<axis>(childRects, parentContentRect.position[axis] + s.offset, s.spacing);
240 }
241
244 struct JustifySpacing { int32_t offset; int32_t spacing; };
245 static JustifySpacing Justify(JustifyContent justify, int32_t freeSpace, int16_t gap, int count)
246 {
247 // Without free space the distributing modes fall back as in CSS:
248 // space-between to flex-start, space-around / space-evenly to center.
249 if (freeSpace < 0)
250 {
252 else if (justify == JustifyContent::SpaceAround || justify == JustifyContent::SpaceEvenly) justify = JustifyContent::Center;
253 }
254
255 int32_t offset = 0, spacing = gap;
256 switch (justify)
257 {
258 default:
259 case JustifyContent::FlexStart: break;
260 case JustifyContent::FlexEnd: offset = freeSpace; break;
261 case JustifyContent::Center: offset = freeSpace / 2; break;
263 if (count > 1) spacing += freeSpace / (count - 1);
264 break;
266 if (count > 0) { spacing += freeSpace / count; offset = (spacing - gap) / 2; }
267 break;
269 offset = freeSpace / (count + 1);
270 spacing += offset;
271 break;
272 }
273 return { offset, spacing };
274 }
275
276 template <int axis> requires(axis == 0 || axis == 1)
277 static void Align(AlignItems align, const IntRect &parentContentRect, ElementRect& childRect)
278 {
279 // Per-item align-self overrides the container's align-items
280 if (childRect.layoutStyle && childRect.layoutStyle->alignSelf != AlignItems::Auto)
281 align = childRect.layoutStyle->alignSelf;
282
283 switch (align)
284 {
285 default:
286 case AlignItems::FlexStart: { childRect.rect.position[axis] = parentContentRect.position[axis]; break; }
287 case AlignItems::FlexEnd: { childRect.rect.position[axis] = (parentContentRect.position[axis] + parentContentRect.size[axis]) - childRect.rect.size[axis]; break; }
288 case AlignItems::Center: { childRect.rect.position[axis] = parentContentRect.position[axis] + (parentContentRect.size[axis] - childRect.rect.size[axis]) / 2; break; }
290 {
291 childRect.rect.position[axis] = parentContentRect.position[axis];
292 // Do not override a fixed size constraint on the cross axis.
293 if (!childRect.constraints.HasFixedAxis(axis))
294 {
295 // Never below the child's min, so an overflow:auto parent
296 // still sees content wider than itself.
297 int32_t s = parentContentRect.size[axis];
298 s = (axis == 0) ? childRect.constraints.ClampWidth(s)
299 : childRect.constraints.ClampHeight(s);
300 childRect.rect.size[axis] = s;
301 }
302 break;
303 }
304 }
305 }
306
307 };
308
309} // namespace tachyon
The frame's element tree.
Definition gui_element_storage.h:100
Text and icon registration, measurement, rendering and editing.
Definition gui_text_system.h:178
Keeps the OS windows (through an IWindowBackend) and their window contexts for the GUI's window eleme...
Definition gui_os_window_manager.h:61
IntRect rect
Computed during layout.
Definition gui_element.h:60
JustifyContent
Main-axis distribution of items (CSS: justify-content; grids also justify-items).
Definition gui_style.h:191
AlignItems
Cross-axis alignment of items (CSS: align-items, align-self).
Definition gui_style.h:181
@ SpaceEvenly
Equal space between all items.
Definition gui_style.h:197
@ Center
Pack to center.
Definition gui_style.h:194
@ FlexStart
Pack to start.
Definition gui_style.h:192
@ FlexEnd
Pack to end.
Definition gui_style.h:193
@ SpaceAround
Space around items.
Definition gui_style.h:196
@ SpaceBetween
Space between items.
Definition gui_style.h:195
@ Auto
align-self only: inherit from container's align-items
Definition gui_style.h:182
@ Center
Center on cross axis.
Definition gui_style.h:185
@ FlexStart
Align to start of cross axis.
Definition gui_style.h:183
@ FlexEnd
Align to end of cross axis.
Definition gui_style.h:184
@ Stretch
Stretch to fill container.
Definition gui_style.h:186
Definition gui_logging.h:24
constexpr uint16_t Vertical() const
top + bottom
Definition gui_style.h:81
constexpr uint16_t Horizontal() const
left + right
Definition gui_style.h:80
Definition gui_element.h:57
int16_t minContentWidth
Leaf with visibly overflowing text: the margin box width flex shrink stops at (CSS min-content).
Definition gui_element.h:80
const LayoutStyle * layoutStyle
Definition gui_element.h:69
LayoutOverride constraints
Definition gui_element.h:67
An element's slot in ElementStorage: its depth and index within that depth.
Definition gui_element.h:127
Definition gui_element.h:33
int16_t flexBasis
CSS: flex-basis; kAuto = auto.
Definition gui_style.h:354
Definition gui_base_types.h:407
int32_t Max(int axis) const
Definition gui_base_types.h:417
FlexProperties flex
used when display is Flex (and by flex items)
Definition gui_style.h:443
Where justify-content puts the first of count items, measured from the main start,...
Definition gui_layout_helpers.h:244
int32_t spacing
Definition gui_layout_helpers.h:244
int32_t offset
Definition gui_layout_helpers.h:244
Definition gui_layout_helpers.h:21
static int32_t FlexMinSize(const ElementRect &c)
The smallest margin-box size an item may be flexed to on axis: its CSS min (border box) plus its marg...
Definition gui_layout_helpers.h:60
static int2 ClampScrollOffsetToContent(int2 childOffset, const ElementRect &parent)
static JustifySpacing Justify(JustifyContent justify, int32_t freeSpace, int16_t gap, int count)
Definition gui_layout_helpers.h:245
static void ApplyHorizontalPositionConstraints(OSWindowManager &osWindowManager, ElementStorage &storage, ElementRect &elementRect, const ElementRef &elementRef)
static int32_t FlexMaxSize(const ElementRect &c)
The largest margin-box size an item may be flexed to on axis: its CSS max plus its margin (kUnbounded...
Definition gui_layout_helpers.h:74
static int32_t RecalculateContentHeight(ITextSystem &textSystem, const Element &element, const ElementRect &elementRect)
static void ApplyFlexBasis(std::span< ElementRect > items)
Sets each in-flow item's size on axis to its flex-basis, the starting main size before free space is ...
Definition gui_layout_helpers.h:86
static int32_t FixedContentSize(const ElementRect &rect)
Content size along axis of an element with a fixed size on that axis.
Definition gui_layout_helpers.h:206
static void ApplyVerticalStyleOffset(ElementRect &elementRect)
static int32_t ResolveFlexibleLengths(std::span< ElementRect > items, int32_t freeSpace)
Resolves flexible lengths on axis (CSS "resolve flexible lengths"): positive freeSpace grows the in-f...
Definition gui_layout_helpers.h:106
static void Align(AlignItems align, const IntRect &parentContentRect, ElementRect &childRect)
Definition gui_layout_helpers.h:277
static int2 CalculateLeafSize(ITextSystem &textSystem, const Element &element, const ElementRect &elementRect, int32_t &minContentWidth)
A leaf's MARGIN BOX at its natural (max-content) size.
static void ApplyVerticalStyleSize(ElementRect &elementRect, const int32_t contentHeight)
static void Place(std::span< ElementRect > childRects, int32_t start, int32_t spacing)
Definition gui_layout_helpers.h:191
static int32_t CalcLayoutAdd(std::span< const ElementRect > childRects, int32_t &count)
Definition gui_layout_helpers.h:42
static void ApplyHorizontalStyleOffset(ElementRect &elementRect)
static void ReversePlace(std::span< ElementRect > childRects, int32_t start, int32_t spacing)
Definition gui_layout_helpers.h:214
static void JustifyChildren(std::span< ElementRect > childRects, JustifyContent justify, int16_t gap, const IntRect &parentContentRect, int totalContentSize, int totalChildrenInFlow)
Positions the children on the main axis per justify.
Definition gui_layout_helpers.h:231
static void ApplyVerticalPositionConstraints(OSWindowManager &osWindowManager, ElementStorage &storage, ElementRect &elementRect, const ElementRef &elementRef)
static int32_t CalcLayoutMax(std::span< const ElementRect > childRects)
Definition gui_layout_helpers.h:178
static void ApplyHorizontalStyleSize(ElementRect &elementRect, const int32_t contentWidth)
constexpr bool HasFixedAxis(int axis) const
Definition gui_constraints.h:196
uint32_t GetFlexShrink(const LayoutStyle *ls) const
Definition gui_constraints.h:356
constexpr int32_t ClampHeight(int32_t h) const
Definition gui_constraints.h:202
SizeConstraintAxis height
Size constraints; set with the size builders.
Definition gui_constraints.h:151
SizeConstraintAxis width
Definition gui_constraints.h:151
constexpr bool IsInFlowAxis(int axis) const
Definition gui_constraints.h:216
uint32_t GetFlexGrow(const LayoutStyle *ls) const
Resolvers take the element's layout style (may be null) and return the effective value.
Definition gui_constraints.h:351
constexpr int32_t ClampWidth(int32_t w) const
Clamps a size to the min / max constraints, ignoring the explicit size.
Definition gui_constraints.h:201
The layout part of a registered Style, in pixels; shared by every style with the same values.
Definition gui_style.h:755
BoxEdges padding
pixels
Definition gui_style.h:759
LayoutData layoutData
flex, grid and size properties, in pixels
Definition gui_style.h:762
BoxEdges borderWidths
pixels
Definition gui_style.h:760
AlignItems alignSelf
see Style::alignSelf
Definition gui_style.h:775
constexpr int16_t GetMinWidth() const
Horizontal margin + border + padding: the narrowest the element can be.
Definition gui_style.h:808
constexpr int16_t GetMinHeight() const
Vertical margin + border + padding: the shortest the element can be.
Definition gui_style.h:814
BoxEdges margin
pixels
Definition gui_style.h:758
Per-axis size constraint: an explicit size, or a min / max clamp on the content-driven size.
Definition gui_constraints.h:19
int32_t value
Explicit size (kAuto = content-driven).
Definition gui_constraints.h:23
int32_t min
Lower clamp for the content-driven size.
Definition gui_constraints.h:24
static constexpr int32_t kUnbounded
No maximum.
Definition gui_constraints.h:21
int32_t max
Upper clamp for the content-driven size.
Definition gui_constraints.h:25
Integer 2D vector.
Definition gui_base_types.h:19