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 | |
21 | NAMESPACE_BEGIN(nanogui) |
22 | |
23 | BoxLayout::BoxLayout(Orientation orientation, Alignment alignment, |
24 | int margin, int spacing) |
25 | : mOrientation(orientation), mAlignment(alignment), mMargin(margin), |
26 | mSpacing(spacing) { |
27 | } |
28 | |
29 | Vector2i 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 | |
64 | void 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 | |
126 | Vector2i 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 | |
159 | void 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 | |
197 | Vector2i 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 | |
217 | void 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 | |
253 | void 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 = 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 | |
344 | AdvancedGridLayout::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 | |
350 | Vector2i 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 = 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 | |
367 | void 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 | |
416 | void 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 = 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 | |
486 | NAMESPACE_END(nanogui) |
487 | |