1/*
2 src/layout.cpp -- A collection of useful layout managers
3
4 The grid layout was contributed by Christian Schueller.
5
6 NanoGUI was developed by Wenzel Jakob <wenzel.jakob@epfl.ch>.
7 The widget drawing code is based on the NanoVG demo application
8 by Mikko Mononen.
9
10 All rights reserved. Use of this source code is governed by a
11 BSD-style license that can be found in the LICENSE.txt file.
12*/
13
14#include <nanogui/layout.h>
15#include <nanogui/widget.h>
16#include <nanogui/window.h>
17#include <nanogui/theme.h>
18#include <nanogui/label.h>
19#include <numeric>
20
21NAMESPACE_BEGIN(nanogui)
22
23BoxLayout::BoxLayout(Orientation orientation, Alignment alignment,
24 int margin, int spacing)
25 : mOrientation(orientation), mAlignment(alignment), mMargin(margin),
26 mSpacing(spacing) {
27}
28
29Vector2i BoxLayout::preferredSize(NVGcontext *ctx, const Widget *widget) const {
30 Vector2i size = Vector2i::Constant(2*mMargin);
31
32 int yOffset = 0;
33 const Window *window = dynamic_cast<const Window *>(widget);
34 if (window && !window->title().empty()) {
35 if (mOrientation == Orientation::Vertical)
36 size[1] += widget->theme()->mWindowHeaderHeight - mMargin/2;
37 else
38 yOffset = widget->theme()->mWindowHeaderHeight;
39 }
40
41 bool first = true;
42 int axis1 = (int) mOrientation, axis2 = ((int) mOrientation + 1)%2;
43 for (auto w : widget->children()) {
44 if (!w->visible())
45 continue;
46 if (first)
47 first = false;
48 else
49 size[axis1] += mSpacing;
50
51 Vector2i ps = w->preferredSize(ctx), fs = w->fixedSize();
52 Vector2i targetSize(
53 fs[0] ? fs[0] : ps[0],
54 fs[1] ? fs[1] : ps[1]
55 );
56
57 size[axis1] += targetSize[axis1];
58 size[axis2] = std::max(size[axis2], targetSize[axis2] + 2*mMargin);
59 first = false;
60 }
61 return size + Vector2i(0, yOffset);
62}
63
64void BoxLayout::performLayout(NVGcontext *ctx, Widget *widget) const {
65 Vector2i fs_w = widget->fixedSize();
66 Vector2i containerSize(
67 fs_w[0] ? fs_w[0] : widget->width(),
68 fs_w[1] ? fs_w[1] : widget->height()
69 );
70
71 int axis1 = (int) mOrientation, axis2 = ((int) mOrientation + 1)%2;
72 int position = mMargin;
73 int yOffset = 0;
74
75 const Window *window = dynamic_cast<const Window *>(widget);
76 if (window && !window->title().empty()) {
77 if (mOrientation == Orientation::Vertical) {
78 position += widget->theme()->mWindowHeaderHeight - mMargin/2;
79 } else {
80 yOffset = widget->theme()->mWindowHeaderHeight;
81 containerSize[1] -= yOffset;
82 }
83 }
84
85 bool first = true;
86 for (auto w : widget->children()) {
87 if (!w->visible())
88 continue;
89 if (first)
90 first = false;
91 else
92 position += mSpacing;
93
94 Vector2i ps = w->preferredSize(ctx), fs = w->fixedSize();
95 Vector2i targetSize(
96 fs[0] ? fs[0] : ps[0],
97 fs[1] ? fs[1] : ps[1]
98 );
99 Vector2i pos(0, yOffset);
100
101 pos[axis1] = position;
102
103 switch (mAlignment) {
104 case Alignment::Minimum:
105 pos[axis2] += mMargin;
106 break;
107 case Alignment::Middle:
108 pos[axis2] += (containerSize[axis2] - targetSize[axis2]) / 2;
109 break;
110 case Alignment::Maximum:
111 pos[axis2] += containerSize[axis2] - targetSize[axis2] - mMargin * 2;
112 break;
113 case Alignment::Fill:
114 pos[axis2] += mMargin;
115 targetSize[axis2] = fs[axis2] ? fs[axis2] : (containerSize[axis2] - mMargin * 2);
116 break;
117 }
118
119 w->setPosition(pos);
120 w->setSize(targetSize);
121 w->performLayout(ctx);
122 position += targetSize[axis1];
123 }
124}
125
126Vector2i GroupLayout::preferredSize(NVGcontext *ctx, const Widget *widget) const {
127 int height = mMargin, width = 2*mMargin;
128
129 const Window *window = dynamic_cast<const Window *>(widget);
130 if (window && !window->title().empty())
131 height += widget->theme()->mWindowHeaderHeight - mMargin/2;
132
133 bool first = true, indent = false;
134 for (auto c : widget->children()) {
135 if (!c->visible())
136 continue;
137 const Label *label = dynamic_cast<const Label *>(c);
138 if (!first)
139 height += (label == nullptr) ? mSpacing : mGroupSpacing;
140 first = false;
141
142 Vector2i ps = c->preferredSize(ctx), fs = c->fixedSize();
143 Vector2i targetSize(
144 fs[0] ? fs[0] : ps[0],
145 fs[1] ? fs[1] : ps[1]
146 );
147
148 bool indentCur = indent && label == nullptr;
149 height += targetSize.y();
150 width = std::max(width, targetSize.x() + 2*mMargin + (indentCur ? mGroupIndent : 0));
151
152 if (label)
153 indent = !label->caption().empty();
154 }
155 height += mMargin;
156 return Vector2i(width, height);
157}
158
159void GroupLayout::performLayout(NVGcontext *ctx, Widget *widget) const {
160 int height = mMargin, availableWidth =
161 (widget->fixedWidth() ? widget->fixedWidth() : widget->width()) - 2*mMargin;
162
163 const Window *window = dynamic_cast<const Window *>(widget);
164 if (window && !window->title().empty())
165 height += widget->theme()->mWindowHeaderHeight - mMargin/2;
166
167 bool first = true, indent = false;
168 for (auto c : widget->children()) {
169 if (!c->visible())
170 continue;
171 const Label *label = dynamic_cast<const Label *>(c);
172 if (!first)
173 height += (label == nullptr) ? mSpacing : mGroupSpacing;
174 first = false;
175
176 bool indentCur = indent && label == nullptr;
177 Vector2i ps = Vector2i(availableWidth - (indentCur ? mGroupIndent : 0),
178 c->preferredSize(ctx).y());
179 Vector2i fs = c->fixedSize();
180
181 Vector2i targetSize(
182 fs[0] ? fs[0] : ps[0],
183 fs[1] ? fs[1] : ps[1]
184 );
185
186 c->setPosition(Vector2i(mMargin + (indentCur ? mGroupIndent : 0), height));
187 c->setSize(targetSize);
188 c->performLayout(ctx);
189
190 height += targetSize.y();
191
192 if (label)
193 indent = !label->caption().empty();
194 }
195}
196
197Vector2i GridLayout::preferredSize(NVGcontext *ctx,
198 const Widget *widget) const {
199 /* Compute minimum row / column sizes */
200 std::vector<int> grid[2];
201 computeLayout(ctx, widget, grid);
202
203 Vector2i size(
204 2*mMargin + std::accumulate(grid[0].begin(), grid[0].end(), 0)
205 + std::max((int) grid[0].size() - 1, 0) * mSpacing[0],
206 2*mMargin + std::accumulate(grid[1].begin(), grid[1].end(), 0)
207 + std::max((int) grid[1].size() - 1, 0) * mSpacing[1]
208 );
209
210 const Window *window = dynamic_cast<const Window *>(widget);
211 if (window && !window->title().empty())
212 size[1] += widget->theme()->mWindowHeaderHeight - mMargin/2;
213
214 return size;
215}
216
217void GridLayout::computeLayout(NVGcontext *ctx, const Widget *widget, std::vector<int> *grid) const {
218 int axis1 = (int) mOrientation, axis2 = (axis1 + 1) % 2;
219 size_t numChildren = widget->children().size(), visibleChildren = 0;
220 for (auto w : widget->children())
221 visibleChildren += w->visible() ? 1 : 0;
222
223 Vector2i dim;
224 dim[axis1] = mResolution;
225 dim[axis2] = (int) ((visibleChildren + mResolution - 1) / mResolution);
226
227 grid[axis1].clear(); grid[axis1].resize(dim[axis1], 0);
228 grid[axis2].clear(); grid[axis2].resize(dim[axis2], 0);
229
230 size_t child = 0;
231 for (int i2 = 0; i2 < dim[axis2]; i2++) {
232 for (int i1 = 0; i1 < dim[axis1]; i1++) {
233 Widget *w = nullptr;
234 do {
235 if (child >= numChildren)
236 return;
237 w = widget->children()[child++];
238 } while (!w->visible());
239
240 Vector2i ps = w->preferredSize(ctx);
241 Vector2i fs = w->fixedSize();
242 Vector2i targetSize(
243 fs[0] ? fs[0] : ps[0],
244 fs[1] ? fs[1] : ps[1]
245 );
246
247 grid[axis1][i1] = std::max(grid[axis1][i1], targetSize[axis1]);
248 grid[axis2][i2] = std::max(grid[axis2][i2], targetSize[axis2]);
249 }
250 }
251}
252
253void GridLayout::performLayout(NVGcontext *ctx, Widget *widget) const {
254 Vector2i fs_w = widget->fixedSize();
255 Vector2i containerSize(
256 fs_w[0] ? fs_w[0] : widget->width(),
257 fs_w[1] ? fs_w[1] : widget->height()
258 );
259
260 /* Compute minimum row / column sizes */
261 std::vector<int> grid[2];
262 computeLayout(ctx, widget, grid);
263 int dim[2] = { (int) grid[0].size(), (int) grid[1].size() };
264
265 Vector2i extra = Vector2i::Zero();
266 const Window *window = dynamic_cast<const Window *>(widget);
267 if (window && !window->title().empty())
268 extra[1] += widget->theme()->mWindowHeaderHeight - mMargin / 2;
269
270 /* Strech to size provided by \c widget */
271 for (int i = 0; i < 2; i++) {
272 int gridSize = 2 * mMargin + extra[i];
273 for (int s : grid[i]) {
274 gridSize += s;
275 if (i+1 < dim[i])
276 gridSize += mSpacing[i];
277 }
278
279 if (gridSize < containerSize[i]) {
280 /* Re-distribute remaining space evenly */
281 int gap = containerSize[i] - gridSize;
282 int g = gap / dim[i];
283 int rest = gap - g * dim[i];
284 for (int j = 0; j < dim[i]; ++j)
285 grid[i][j] += g;
286 for (int j = 0; rest > 0 && j < dim[i]; --rest, ++j)
287 grid[i][j] += 1;
288 }
289 }
290
291 int axis1 = (int) mOrientation, axis2 = (axis1 + 1) % 2;
292 Vector2i start = Vector2i::Constant(mMargin) + extra;
293
294 size_t numChildren = widget->children().size();
295 size_t child = 0;
296
297 Vector2i pos = start;
298 for (int i2 = 0; i2 < dim[axis2]; i2++) {
299 pos[axis1] = start[axis1];
300 for (int i1 = 0; i1 < dim[axis1]; i1++) {
301 Widget *w = nullptr;
302 do {
303 if (child >= numChildren)
304 return;
305 w = widget->children()[child++];
306 } while (!w->visible());
307
308 Vector2i ps = w->preferredSize(ctx);
309 Vector2i fs = w->fixedSize();
310 Vector2i targetSize(
311 fs[0] ? fs[0] : ps[0],
312 fs[1] ? fs[1] : ps[1]
313 );
314
315 Vector2i itemPos(pos);
316 for (int j = 0; j < 2; j++) {
317 int axis = (axis1 + j) % 2;
318 int item = j == 0 ? i1 : i2;
319 Alignment align = alignment(axis, item);
320
321 switch (align) {
322 case Alignment::Minimum:
323 break;
324 case Alignment::Middle:
325 itemPos[axis] += (grid[axis][item] - targetSize[axis]) / 2;
326 break;
327 case Alignment::Maximum:
328 itemPos[axis] += grid[axis][item] - targetSize[axis];
329 break;
330 case Alignment::Fill:
331 targetSize[axis] = fs[axis] ? fs[axis] : grid[axis][item];
332 break;
333 }
334 }
335 w->setPosition(itemPos);
336 w->setSize(targetSize);
337 w->performLayout(ctx);
338 pos[axis1] += grid[axis1][i1] + mSpacing[axis1];
339 }
340 pos[axis2] += grid[axis2][i2] + mSpacing[axis2];
341 }
342}
343
344AdvancedGridLayout::AdvancedGridLayout(const std::vector<int> &cols, const std::vector<int> &rows, int margin)
345 : mCols(cols), mRows(rows), mMargin(margin) {
346 mColStretch.resize(mCols.size(), 0);
347 mRowStretch.resize(mRows.size(), 0);
348}
349
350Vector2i AdvancedGridLayout::preferredSize(NVGcontext *ctx, const Widget *widget) const {
351 /* Compute minimum row / column sizes */
352 std::vector<int> grid[2];
353 computeLayout(ctx, widget, grid);
354
355 Vector2i size(
356 std::accumulate(grid[0].begin(), grid[0].end(), 0),
357 std::accumulate(grid[1].begin(), grid[1].end(), 0));
358
359 Vector2i extra = Vector2i::Constant(2 * mMargin);
360 const Window *window = dynamic_cast<const Window *>(widget);
361 if (window && !window->title().empty())
362 extra[1] += widget->theme()->mWindowHeaderHeight - mMargin/2;
363
364 return size+extra;
365}
366
367void AdvancedGridLayout::performLayout(NVGcontext *ctx, Widget *widget) const {
368 std::vector<int> grid[2];
369 computeLayout(ctx, widget, grid);
370
371 grid[0].insert(grid[0].begin(), mMargin);
372 const Window *window = dynamic_cast<const Window *>(widget);
373 if (window && !window->title().empty())
374 grid[1].insert(grid[1].begin(), widget->theme()->mWindowHeaderHeight + mMargin/2);
375 else
376 grid[1].insert(grid[1].begin(), mMargin);
377
378 for (int axis=0; axis<2; ++axis) {
379 for (size_t i=1; i<grid[axis].size(); ++i)
380 grid[axis][i] += grid[axis][i-1];
381
382 for (Widget *w : widget->children()) {
383 if (!w->visible())
384 continue;
385 Anchor anchor = this->anchor(w);
386
387 int itemPos = grid[axis][anchor.pos[axis]];
388 int cellSize = grid[axis][anchor.pos[axis] + anchor.size[axis]] - itemPos;
389 int ps = w->preferredSize(ctx)[axis], fs = w->fixedSize()[axis];
390 int targetSize = fs ? fs : ps;
391
392 switch (anchor.align[axis]) {
393 case Alignment::Minimum:
394 break;
395 case Alignment::Middle:
396 itemPos += (cellSize - targetSize) / 2;
397 break;
398 case Alignment::Maximum:
399 itemPos += cellSize - targetSize;
400 break;
401 case Alignment::Fill:
402 targetSize = fs ? fs : cellSize;
403 break;
404 }
405
406 Vector2i pos = w->position(), size = w->size();
407 pos[axis] = itemPos;
408 size[axis] = targetSize;
409 w->setPosition(pos);
410 w->setSize(size);
411 w->performLayout(ctx);
412 }
413 }
414}
415
416void AdvancedGridLayout::computeLayout(NVGcontext *ctx, const Widget *widget,
417 std::vector<int> *_grid) const {
418 Vector2i fs_w = widget->fixedSize();
419 Vector2i containerSize(
420 fs_w[0] ? fs_w[0] : widget->width(),
421 fs_w[1] ? fs_w[1] : widget->height()
422 );
423
424 Vector2i extra = Vector2i::Constant(2 * mMargin);
425 const Window *window = dynamic_cast<const Window *>(widget);
426 if (window && !window->title().empty())
427 extra[1] += widget->theme()->mWindowHeaderHeight - mMargin/2;
428
429 containerSize -= extra;
430
431 for (int axis=0; axis<2; ++axis) {
432 std::vector<int> &grid = _grid[axis];
433 const std::vector<int> &sizes = axis == 0 ? mCols : mRows;
434 const std::vector<float> &stretch = axis == 0 ? mColStretch : mRowStretch;
435 grid = sizes;
436
437 for (int phase = 0; phase < 2; ++phase) {
438 for (auto pair : mAnchor) {
439 const Widget *w = pair.first;
440 if (!w->visible())
441 continue;
442 const Anchor &anchor = pair.second;
443 if ((anchor.size[axis] == 1) != (phase == 0))
444 continue;
445 int ps = w->preferredSize(ctx)[axis], fs = w->fixedSize()[axis];
446 int targetSize = fs ? fs : ps;
447
448 if (anchor.pos[axis] + anchor.size[axis] > (int) grid.size())
449 throw std::runtime_error(
450 "Advanced grid layout: widget is out of bounds: " +
451 (std::string) anchor);
452
453 int currentSize = 0;
454 float totalStretch = 0;
455 for (int i = anchor.pos[axis];
456 i < anchor.pos[axis] + anchor.size[axis]; ++i) {
457 if (sizes[i] == 0 && anchor.size[axis] == 1)
458 grid[i] = std::max(grid[i], targetSize);
459 currentSize += grid[i];
460 totalStretch += stretch[i];
461 }
462 if (targetSize <= currentSize)
463 continue;
464 if (totalStretch == 0)
465 throw std::runtime_error(
466 "Advanced grid layout: no space to place widget: " +
467 (std::string) anchor);
468 float amt = (targetSize - currentSize) / totalStretch;
469 for (int i = anchor.pos[axis];
470 i < anchor.pos[axis] + anchor.size[axis]; ++i) {
471 grid[i] += (int) std::round(amt * stretch[i]);
472 }
473 }
474 }
475
476 int currentSize = std::accumulate(grid.begin(), grid.end(), 0);
477 float totalStretch = std::accumulate(stretch.begin(), stretch.end(), 0.0f);
478 if (currentSize >= containerSize[axis] || totalStretch == 0)
479 continue;
480 float amt = (containerSize[axis] - currentSize) / totalStretch;
481 for (size_t i = 0; i<grid.size(); ++i)
482 grid[i] += (int) std::round(amt * stretch[i]);
483 }
484}
485
486NAMESPACE_END(nanogui)
487