libgui 2.01
libgui is a GUI library for the GameCube, Wii, and Wii U
 
Loading...
Searching...
No Matches
GuiLayout.cpp
1/****************************************************************************
2 * libgui
3 * Daryl Borth 2026
4 * GuiLayout.cpp
5 ***************************************************************************/
6
7#include "Gui.h"
8#include <cmath>
9
10static int roundi(float v)
11{
12 return (int)floorf(v + 0.5f);
13}
14
15GuiLayout::GuiLayout() :
16 spacingX(0.0f), spacingY(0.0f), justify(LAYOUT_JUSTIFY::START), alignItems(LAYOUT_ALIGN::START),
17 userFixedW(0), userFixedH(0), assignedW(0), assignedH(0), naturalW(0), naturalH(0), dirty(true), settled(false)
18{
19}
20
21GuiLayout& GuiLayout::add(GuiElement* e, const GuiItem& item)
22{
23 if(!e)
24 return *this;
25
26 this->append(e); // calls remove(e) first, which drops any earlier entry for it
27 Entry en;
28 en.e = e;
29 en.item = item;
30 entries.push_back(en);
31 dirty = true;
32 return *this;
33}
34
35GuiLayout& GuiLayout::addGap(float size)
36{
37 Entry en;
38 en.gapSize = size < 0.0f ? 0.0f : size;
39 entries.push_back(en);
40 dirty = true;
41 return *this;
42}
43
44GuiLayout& GuiLayout::addSpace(float weight)
45{
46 Entry en;
47 en.item.grow(weight);
48 entries.push_back(en);
49 dirty = true;
50 return *this;
51}
52
53GuiLayout& GuiLayout::setPadding(float all)
54{
55 return setPadding(all, all, all, all);
56}
57
58GuiLayout& GuiLayout::setPadding(float horizontal, float vertical)
59{
60 return setPadding(horizontal, vertical, horizontal, vertical);
61}
62
63GuiLayout& GuiLayout::setPadding(float left, float top, float right, float bottom)
64{
65 padding = GuiInsets(left, top, right, bottom);
66 dirty = true;
67 return *this;
68}
69
70GuiLayout& GuiLayout::setSpacing(float s)
71{
72 return setSpacing(s, s);
73}
74
75GuiLayout& GuiLayout::setSpacing(float x, float y)
76{
77 spacingX = x;
78 spacingY = y;
79 dirty = true;
80 return *this;
81}
82
84{
85 justify = j;
86 dirty = true;
87 return *this;
88}
89
91{
92 alignItems = a;
93 dirty = true;
94 return *this;
95}
96
97GuiLayout& GuiLayout::setFixedSize(int w, int h)
98{
99 userFixedW = w < 0 ? 0 : w;
100 userFixedH = h < 0 ? 0 : h;
101 dirty = true;
102 return *this;
103}
104
106{
107 dirty = true;
108}
109
111{
112 this->removeAll();
113 entries.clear();
114 dirty = true;
115}
116
118{
120 for(size_t i = 0; i < entries.size(); i++)
121 {
122 if(entries[i].e == e)
123 {
124 entries.erase(entries.begin() + i);
125 break;
126 }
127 }
128 dirty = true;
129}
130
131//Entries follow the window's children: pick up children added with append() directly,
132//and drop entries whose child was removed some other way
133void GuiLayout::syncEntries()
134{
135 // Nearly always the entries are exactly the children, in order (gaps aside): confirm that in one
136 // pass and skip the searches below, which compare every entry with every child
137 size_t next = 0;
138 bool inSync = true;
139 for(const Entry& en : entries)
140 {
141 if(!en.e)
142 continue;
143 if(next >= _elements.size() || _elements[next] != en.e)
144 {
145 inSync = false;
146 break;
147 }
148 next++;
149 }
150 if(inSync && next == _elements.size())
151 return;
152
153 for(size_t i = 0; i < entries.size();)
154 {
155 if(entries[i].e && !this->find(entries[i].e))
156 entries.erase(entries.begin() + i), dirty = true;
157 else
158 i++;
159 }
160 for(GuiElement* child : _elements)
161 {
162 bool known = false;
163 for(const Entry& en : entries)
164 if(en.e == child) { known = true; break; }
165 if(!known)
166 {
167 Entry en;
168 en.e = child;
169 entries.push_back(en);
170 dirty = true;
171 }
172 }
173}
174
176{
177 settled = true;
178
179 // nested layouts settle first, so their sizes are right when measured below
180 for(Entry& en : entries)
181 if(en.e)
182 en.e->layout();
183 this->syncEntries();
184
185 std::vector<Entry*>& visible = visibleScratch;
186 visible.clear();
187 bool changed = dirty;
188 for(Entry& en : entries)
189 {
190 const bool shown = !en.e || en.e->isVisible();
191 if(shown != en.lastVisible)
192 {
193 changed = true;
194 en.lastVisible = shown;
195 }
196 if(!shown)
197 continue;
198
199 if(en.e)
200 {
201 int w, h;
202 en.e->getNaturalSize(w, h);
203 if(en.haveAssigned && w == en.assignedW && h == en.assignedH)
204 {
205 // still the size layout gave it: its natural size is what it was before
206 w = en.baseW;
207 h = en.baseH;
208 }
209 else
210 {
211 en.baseW = w;
212 en.baseH = h;
213 en.haveAssigned = false;
214 }
215 if(w != en.lastW || h != en.lastH)
216 {
217 changed = true;
218 en.lastW = w;
219 en.lastH = h;
220 }
221 en.natW = w;
222 en.natH = h;
223 }
224 visible.push_back(&en);
225 }
226 if(!changed)
227 return;
228 dirty = false;
229
230 GuiLayoutResult fit = this->solve(visible, (float)userFixedW, (float)userFixedH);
231 naturalW = roundi(fit.width);
232 naturalH = roundi(fit.height);
233
234 GuiLayoutResult res = fit;
235 if(assignedW > 0 || assignedH > 0)
236 res = this->solve(visible, (float)(assignedW > 0 ? assignedW : userFixedW), (float)(assignedH > 0 ? assignedH : userFixedH));
237 width = roundi(res.width);
238 height = roundi(res.height);
239
240 for(size_t i = 0; i < visible.size() && i < res.rects.size(); i++)
241 {
242 Entry& en = *visible[i];
243 if(!en.e)
244 continue;
245
246 const GuiRect& r = res.rects[i];
247 const int w = roundi(r.w), h = roundi(r.h);
248 if(w != en.natW || h != en.natH)
249 {
250 en.e->setLayoutSize(w, h);
251 en.assignedW = w;
252 en.assignedH = h;
253 en.haveAssigned = true;
254 en.e->layout(); // a nested layout re-flows to its new size right away
255 }
256 else if(en.haveAssigned)
257 {
258 en.e->setLayoutSize(en.baseW, en.baseH);
259 en.haveAssigned = false;
260 en.e->layout();
261 }
262 en.e->setLayoutPosition(roundi(r.x), roundi(r.y));
263 }
264}
265
266// A layout inside another layout is settled by its parent's layout() on the way down, which comes
267// just before the parent updates or draws it. Repeating that here would re-measure everything below
268// it once more for every level of nesting.
270{
271 if(!settled)
272 this->layout();
274 settled = false;
275}
276
278{
279 if(!settled)
280 this->layout();
282 settled = false;
283}
284
285void GuiLayout::getNaturalSize(int& w, int& h)
286{
287 w = naturalW;
288 h = naturalH;
289}
290
291void GuiLayout::setLayoutSize(int w, int h)
292{
293 assignedW = w < 0 ? 0 : w;
294 assignedH = h < 0 ? 0 : h;
295 width = assignedW;
296 height = assignedH;
297 dirty = true;
298}
299
300void GuiLayout::collectNavigable(std::vector<GuiElement*>& out)
301{
302 for(GuiElement* child : _elements)
303 child->collectNavigable(out);
304}
305
306GuiRow::GuiRow(int w, int h)
307{
308 setFixedSize(w, h);
309}
310
311GuiLayoutResult GuiRow::solve(const std::vector<Entry*>& visible, float fixedW, float fixedH)
312{
313 GuiStackSpec spec;
314 spec.vertical = false;
315 spec.padding = padding;
316 spec.spacing = spacingX;
317 spec.fixedW = fixedW;
318 spec.fixedH = fixedH;
319 spec.justify = justify;
320 spec.alignItems = alignItems;
321
322 std::vector<GuiStackItem> items(visible.size());
323 for(size_t i = 0; i < visible.size(); i++)
324 {
325 const Entry& en = *visible[i];
326 if(en.e) { items[i].w = (float)en.natW; items[i].h = (float)en.natH; }
327 else items[i].w = en.gapSize;
328 items[i].grow = en.item.growWeight;
329 items[i].useContainerAlign = !en.item.hasAlignV;
330 items[i].align = en.item.alignV;
331 }
332 return GuiSolveStack(spec, items);
333}
334
336{
337 setFixedSize(w, h);
338}
339
340GuiLayoutResult GuiColumn::solve(const std::vector<Entry*>& visible, float fixedW, float fixedH)
341{
342 GuiStackSpec spec;
343 spec.vertical = true;
344 spec.padding = padding;
345 spec.spacing = spacingY;
346 spec.fixedW = fixedW;
347 spec.fixedH = fixedH;
348 spec.justify = justify;
349 spec.alignItems = alignItems;
350
351 std::vector<GuiStackItem> items(visible.size());
352 for(size_t i = 0; i < visible.size(); i++)
353 {
354 const Entry& en = *visible[i];
355 if(en.e) { items[i].w = (float)en.natW; items[i].h = (float)en.natH; }
356 else items[i].h = en.gapSize;
357 items[i].grow = en.item.growWeight;
358 items[i].useContainerAlign = !en.item.hasAlignH;
359 items[i].align = en.item.alignH;
360 }
361 return GuiSolveStack(spec, items);
362}
363
364GuiGrid::GuiGrid(int cols, int w, int h) : columns(cols < 1 ? 1 : cols)
365{
366 setFixedSize(w, h);
367}
368
369GuiLayoutResult GuiGrid::solve(const std::vector<Entry*>& visible, float fixedW, float fixedH)
370{
371 GuiGridSpec spec;
372 spec.columns = columns;
373 spec.padding = padding;
374 spec.spacingX = spacingX;
375 spec.spacingY = spacingY;
376 spec.fixedW = fixedW;
377 spec.fixedH = fixedH;
378
379 std::vector<GuiGridItem> items(visible.size());
380 for(size_t i = 0; i < visible.size(); i++)
381 {
382 const Entry& en = *visible[i];
383 if(en.e) { items[i].w = (float)en.natW; items[i].h = (float)en.natH; }
384 else items[i].w = items[i].h = en.gapSize; // a gap holds a cell open
385 if(en.item.hasAlignH) items[i].alignH = en.item.alignH;
386 if(en.item.hasAlignV) items[i].alignV = en.item.alignV;
387 }
388 return GuiSolveGrid(spec, items);
389}
390
392{
393 setFixedSize(w, h);
394}
395
396GuiLayoutResult GuiOverlay::solve(const std::vector<Entry*>& visible, float fixedW, float fixedH)
397{
398 GuiOverlaySpec spec;
399 spec.padding = padding;
400 spec.fixedW = fixedW;
401 spec.fixedH = fixedH;
402
403 std::vector<GuiOverlayItem> items;
404 for(size_t i = 0; i < visible.size(); i++)
405 {
406 const Entry& en = *visible[i];
408 it.w = (float)en.natW;
409 it.h = (float)en.natH;
410 it.background = en.item.isBackground;
411 if(en.item.hasAlignH) it.alignH = en.item.alignH;
412 if(en.item.hasAlignV) it.alignV = en.item.alignV;
413 items.push_back(it);
414 }
415 return GuiSolveOverlay(spec, items);
416}
Umbrella header for the platform-agnostic libgui core.
GuiColumn(int w=0, int h=0)
GuiLayoutResult solve(const std::vector< Entry * > &visible, float fixedW, float fixedH) override
Works out the container size and each visible entry's rectangle (what a subclass does)
int width
Element width.
Definition GuiElement.h:268
int height
Element height.
Definition GuiElement.h:269
GuiElement()
Constructor.
Definition GuiElement.cpp:9
bool visible
Visibility of the element.
Definition GuiElement.h:296
virtual void layout()
Brings the element's own layout up to date.
virtual void setLayoutPosition(int x, int y)
Places the element so its top-left corner is at (x, y) relative to its parent's top-left (default: LE...
virtual void setLayoutSize(int w, int h)
Gives the element a size chosen by a container (default: sets width and height)
GuiLayoutResult solve(const std::vector< Entry * > &visible, float fixedW, float fixedH) override
Works out the container size and each visible entry's rectangle (what a subclass does)
GuiGrid(int columns, int w=0, int h=0)
Base of the layout containers. Not used directly; see GuiRow, GuiColumn, GuiGrid, GuiOverlay.
Definition GuiLayout.h:55
void clear()
Removes every child.
void remove(GuiElement *e) override
Removes the specified child.
GuiLayout & add(GuiElement *e, const GuiItem &item=GuiItem())
Adds a child. Children are laid out in the order they are added.
Definition GuiLayout.cpp:21
void collectNavigable(std::vector< GuiElement * > &out) override
Appends the elements that selection can move between (default: this element; layout containers add th...
GuiLayout & size(int w, int h)
Short names for the setters above, for reading a layout top to bottom: ui.row().size(568,...
Definition GuiLayout.h:84
void layout() override
Brings the element's own layout up to date.
GuiLayout & addSpace(float weight=1.0f)
Adds flexible empty space that takes a share of what is left over, for example to push one child to e...
Definition GuiLayout.cpp:44
GuiLayout & setFixedSize(int w, int h)
Fixes the container's size (0 on an axis = fit the content)
Definition GuiLayout.cpp:97
GuiLayout & setSpacing(float s)
Sets the gap between neighbouring children (a grid uses both numbers)
Definition GuiLayout.cpp:70
void getNaturalSize(int &w, int &h) override
Layout protocol: how a layout container (GuiRow, GuiColumn, GuiGrid, GuiOverlay) asks about and arran...
GuiLayout & setAlignItems(LAYOUT_ALIGN a)
Sets the default alignment of children across the main axis.
Definition GuiLayout.cpp:90
void update(InputController *c) override
Called constantly to allow the element to respond to the current input data.
void draw() override
Called constantly to redraw the element.
void invalidate()
Forces layout to run again.
GuiLayout & addGap(float size)
Adds a fixed gap along a row's width or a column's height.
Definition GuiLayout.cpp:35
GuiLayout & setPadding(float all)
Sets the space between the container's edge and its children.
Definition GuiLayout.cpp:53
virtual GuiLayoutResult solve(const std::vector< Entry * > &visible, float fixedW, float fixedH)=0
Works out the container size and each visible entry's rectangle (what a subclass does)
GuiLayout & setJustify(LAYOUT_JUSTIFY j)
Sets how leftover space along the main axis is spent when no child grows.
Definition GuiLayout.cpp:83
void setLayoutSize(int w, int h) override
Gives the element a size chosen by a container (default: sets width and height)
GuiOverlay(int w=0, int h=0)
GuiLayoutResult solve(const std::vector< Entry * > &visible, float fixedW, float fixedH) override
Works out the container size and each visible entry's rectangle (what a subclass does)
GuiLayoutResult solve(const std::vector< Entry * > &visible, float fixedW, float fixedH) override
Works out the container size and each visible entry's rectangle (what a subclass does)
GuiRow(int w=0, int h=0)
void append(GuiElement *e)
Appends a GuiElement to the GuiWindow.
Definition GuiWindow.cpp:31
bool find(GuiElement *e)
Looks for the specified GuiElement.
Definition GuiWindow.cpp:85
void removeAll()
Removes all GuiElements.
Definition GuiWindow.cpp:75
void draw()
Draws all the elements in this GuiWindow.
void update(InputController *c)
Updates the window and all elements contains within Allows the GuiWindow and all elements to respond ...
void remove(GuiElement *e)
Removes the specified GuiElement from the GuiWindow.
Definition GuiWindow.cpp:58
std::vector< GuiElement * > _elements
Contains all elements within the GuiWindow.
Definition GuiWindow.h:93
Logical controller for one input channel.
LAYOUT_ALIGN
How an item sits across the main direction of a stack, or inside a grid cell.
GuiLayoutResult GuiSolveGrid(const GuiGridSpec &spec, const std::vector< GuiGridItem > &items)
Lays items out in rows.
GuiLayoutResult GuiSolveStack(const GuiStackSpec &spec, const std::vector< GuiStackItem > &items)
Lays items out along one axis.
GuiLayoutResult GuiSolveOverlay(const GuiOverlaySpec &spec, const std::vector< GuiOverlayItem > &items)
Stacks items on top of each other in one box.
LAYOUT_JUSTIFY
How leftover space along a stack's main direction is spent (when no item grows).
@ START
Top or left.
Rules for a grid, filled row by row.
float fixedH
Container size; 0 = fit.
The solver's answer: the container's size and each item's rectangle.
A child (or gap) and what layout knows about it.
Definition GuiLayout.h:107
int natH
Natural size, as measured this pass.
Definition GuiLayout.h:111
int baseH
Natural size before layout resized it.
Definition GuiLayout.h:112
int assignedH
Size layout last gave it.
Definition GuiLayout.h:113
float gapSize
Fixed size of a gap.
Definition GuiLayout.h:110
GuiElement * e
The child; null for a gap or flexible space.
Definition GuiLayout.h:108
One child of an overlay.
bool background
Fills the whole box, ignores padding, and does not count toward the fit size.
Rules for an overlay: children share one box.
float fixedH
Container size; 0 = fit.
An axis-aligned rectangle (top-left origin) in design-canvas pixels.
Definition GuiGeometry.h:30
float x
Left edge.
Definition GuiGeometry.h:31
float w
Width.
Definition GuiGeometry.h:33
float h
Height.
Definition GuiGeometry.h:34
float y
Top edge.
Definition GuiGeometry.h:32
Rules for a row or column.
float spacing
Gap between neighbouring items.
bool vertical
true = column, false = row.
float fixedH
Container size; 0 = fit.