libgui 3.00
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();
281 // A hidden layout is not drawn, so it must not take input either: its buttons would still answer the pointer
282 // (and any press) from where they sit under whatever is showing in their place.
283 if(this->isVisible())
285 settled = false;
286}
287
288void GuiLayout::getNaturalSize(int& w, int& h)
289{
290 w = naturalW;
291 h = naturalH;
292}
293
294void GuiLayout::setLayoutSize(int w, int h)
295{
296 assignedW = w < 0 ? 0 : w;
297 assignedH = h < 0 ? 0 : h;
298 width = assignedW;
299 height = assignedH;
300 dirty = true;
301}
302
303void GuiLayout::collectNavigable(std::vector<GuiElement*>& out)
304{
305 if(!this->isVisible())
306 return; // nothing in a hidden layout can take the D-pad's selection
307 for(GuiElement* child : _elements)
308 child->collectNavigable(out);
309}
310
311GuiRow::GuiRow(int w, int h)
312{
313 setFixedSize(w, h);
314}
315
316GuiLayoutResult GuiRow::solve(const std::vector<Entry*>& visible, float fixedW, float fixedH)
317{
318 GuiStackSpec spec;
319 spec.vertical = false;
320 spec.padding = padding;
321 spec.spacing = spacingX;
322 spec.fixedW = fixedW;
323 spec.fixedH = fixedH;
324 spec.justify = justify;
325 spec.alignItems = alignItems;
326
327 std::vector<GuiStackItem> items(visible.size());
328 for(size_t i = 0; i < visible.size(); i++)
329 {
330 const Entry& en = *visible[i];
331 if(en.e) { items[i].w = (float)en.natW; items[i].h = (float)en.natH; }
332 else items[i].w = en.gapSize;
333 items[i].grow = en.item.growWeight;
334 items[i].useContainerAlign = !en.item.hasAlignV;
335 items[i].align = en.item.alignV;
336 }
337 return GuiSolveStack(spec, items);
338}
339
341{
342 setFixedSize(w, h);
343}
344
345GuiLayoutResult GuiColumn::solve(const std::vector<Entry*>& visible, float fixedW, float fixedH)
346{
347 GuiStackSpec spec;
348 spec.vertical = true;
349 spec.padding = padding;
350 spec.spacing = spacingY;
351 spec.fixedW = fixedW;
352 spec.fixedH = fixedH;
353 spec.justify = justify;
354 spec.alignItems = alignItems;
355
356 std::vector<GuiStackItem> items(visible.size());
357 for(size_t i = 0; i < visible.size(); i++)
358 {
359 const Entry& en = *visible[i];
360 if(en.e) { items[i].w = (float)en.natW; items[i].h = (float)en.natH; }
361 else items[i].h = en.gapSize;
362 items[i].grow = en.item.growWeight;
363 items[i].useContainerAlign = !en.item.hasAlignH;
364 items[i].align = en.item.alignH;
365 }
366 return GuiSolveStack(spec, items);
367}
368
369GuiGrid::GuiGrid(int cols, int w, int h) : columns(cols < 1 ? 1 : cols)
370{
371 setFixedSize(w, h);
372}
373
374GuiLayoutResult GuiGrid::solve(const std::vector<Entry*>& visible, float fixedW, float fixedH)
375{
376 GuiGridSpec spec;
377 spec.columns = columns;
378 spec.padding = padding;
379 spec.spacingX = spacingX;
380 spec.spacingY = spacingY;
381 spec.fixedW = fixedW;
382 spec.fixedH = fixedH;
383
384 std::vector<GuiGridItem> items(visible.size());
385 for(size_t i = 0; i < visible.size(); i++)
386 {
387 const Entry& en = *visible[i];
388 if(en.e) { items[i].w = (float)en.natW; items[i].h = (float)en.natH; }
389 else items[i].w = items[i].h = en.gapSize; // a gap holds a cell open
390 if(en.item.hasAlignH) items[i].alignH = en.item.alignH;
391 if(en.item.hasAlignV) items[i].alignV = en.item.alignV;
392 }
393 return GuiSolveGrid(spec, items);
394}
395
397{
398 setFixedSize(w, h);
399}
400
401GuiLayoutResult GuiOverlay::solve(const std::vector<Entry*>& visible, float fixedW, float fixedH)
402{
403 GuiOverlaySpec spec;
404 spec.padding = padding;
405 spec.fixedW = fixedW;
406 spec.fixedH = fixedH;
407
408 std::vector<GuiOverlayItem> items;
409 for(size_t i = 0; i < visible.size(); i++)
410 {
411 const Entry& en = *visible[i];
413 it.w = (float)en.natW;
414 it.h = (float)en.natH;
415 it.background = en.item.isBackground;
416 if(en.item.hasAlignH) it.alignH = en.item.alignH;
417 if(en.item.hasAlignV) it.alignV = en.item.alignV;
418 items.push_back(it);
419 }
420 return GuiSolveOverlay(spec, items);
421}
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)
bool isVisible()
Checks whether or not the element is visible.
int width
Element width.
Definition GuiElement.h:273
int height
Element height.
Definition GuiElement.h:274
GuiElement()
Constructor.
Definition GuiElement.cpp:9
bool visible
Visibility of the element.
Definition GuiElement.h:301
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:96
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:33
float x
Left edge.
Definition GuiGeometry.h:34
float w
Width.
Definition GuiGeometry.h:36
float h
Height.
Definition GuiGeometry.h:37
float y
Top edge.
Definition GuiGeometry.h:35
Rules for a row or column.
float spacing
Gap between neighbouring items.
bool vertical
true = column, false = row.
float fixedH
Container size; 0 = fit.