reactphysics3d/testbed/nanogui/src/layout.cpp

487 lines
17 KiB
C++

/*
src/layout.cpp -- A collection of useful layout managers
The grid layout was contributed by Christian Schueller.
NanoGUI was developed by Wenzel Jakob <wenzel.jakob@epfl.ch>.
The widget drawing code is based on the NanoVG demo application
by Mikko Mononen.
All rights reserved. Use of this source code is governed by a
BSD-style license that can be found in the LICENSE.txt file.
*/
#include <nanogui/layout.h>
#include <nanogui/widget.h>
#include <nanogui/window.h>
#include <nanogui/theme.h>
#include <nanogui/label.h>
#include <numeric>
NAMESPACE_BEGIN(nanogui)
BoxLayout::BoxLayout(Orientation orientation, Alignment alignment,
int margin, int spacing)
: mOrientation(orientation), mAlignment(alignment), mMargin(margin),
mSpacing(spacing) {
}
Vector2i BoxLayout::preferredSize(NVGcontext *ctx, const Widget *widget) const {
Vector2i size = Vector2i::Constant(2*mMargin);
int yOffset = 0;
const Window *window = dynamic_cast<const Window *>(widget);
if (window && !window->title().empty()) {
if (mOrientation == Orientation::Vertical)
size[1] += widget->theme()->mWindowHeaderHeight - mMargin/2;
else
yOffset = widget->theme()->mWindowHeaderHeight;
}
bool first = true;
int axis1 = (int) mOrientation, axis2 = ((int) mOrientation + 1)%2;
for (auto w : widget->children()) {
if (!w->visible())
continue;
if (first)
first = false;
else
size[axis1] += mSpacing;
Vector2i ps = w->preferredSize(ctx), fs = w->fixedSize();
Vector2i targetSize(
fs[0] ? fs[0] : ps[0],
fs[1] ? fs[1] : ps[1]
);
size[axis1] += targetSize[axis1];
size[axis2] = std::max(size[axis2], targetSize[axis2] + 2*mMargin);
first = false;
}
return size + Vector2i(0, yOffset);
}
void BoxLayout::performLayout(NVGcontext *ctx, Widget *widget) const {
Vector2i fs_w = widget->fixedSize();
Vector2i containerSize(
fs_w[0] ? fs_w[0] : widget->width(),
fs_w[1] ? fs_w[1] : widget->height()
);
int axis1 = (int) mOrientation, axis2 = ((int) mOrientation + 1)%2;
int position = mMargin;
int yOffset = 0;
const Window *window = dynamic_cast<const Window *>(widget);
if (window && !window->title().empty()) {
if (mOrientation == Orientation::Vertical) {
position += widget->theme()->mWindowHeaderHeight - mMargin/2;
} else {
yOffset = widget->theme()->mWindowHeaderHeight;
containerSize[1] -= yOffset;
}
}
bool first = true;
for (auto w : widget->children()) {
if (!w->visible())
continue;
if (first)
first = false;
else
position += mSpacing;
Vector2i ps = w->preferredSize(ctx), fs = w->fixedSize();
Vector2i targetSize(
fs[0] ? fs[0] : ps[0],
fs[1] ? fs[1] : ps[1]
);
Vector2i pos(0, yOffset);
pos[axis1] = position;
switch (mAlignment) {
case Alignment::Minimum:
pos[axis2] += mMargin;
break;
case Alignment::Middle:
pos[axis2] += (containerSize[axis2] - targetSize[axis2]) / 2;
break;
case Alignment::Maximum:
pos[axis2] += containerSize[axis2] - targetSize[axis2] - mMargin * 2;
break;
case Alignment::Fill:
pos[axis2] += mMargin;
targetSize[axis2] = fs[axis2] ? fs[axis2] : (containerSize[axis2] - mMargin * 2);
break;
}
w->setPosition(pos);
w->setSize(targetSize);
w->performLayout(ctx);
position += targetSize[axis1];
}
}
Vector2i GroupLayout::preferredSize(NVGcontext *ctx, const Widget *widget) const {
int height = mMargin, width = 2*mMargin;
const Window *window = dynamic_cast<const Window *>(widget);
if (window && !window->title().empty())
height += widget->theme()->mWindowHeaderHeight - mMargin/2;
bool first = true, indent = false;
for (auto c : widget->children()) {
if (!c->visible())
continue;
const Label *label = dynamic_cast<const Label *>(c);
if (!first)
height += (label == nullptr) ? mSpacing : mGroupSpacing;
first = false;
Vector2i ps = c->preferredSize(ctx), fs = c->fixedSize();
Vector2i targetSize(
fs[0] ? fs[0] : ps[0],
fs[1] ? fs[1] : ps[1]
);
bool indentCur = indent && label == nullptr;
height += targetSize.y();
width = std::max(width, targetSize.x() + 2*mMargin + (indentCur ? mGroupIndent : 0));
if (label)
indent = !label->caption().empty();
}
height += mMargin;
return Vector2i(width, height);
}
void GroupLayout::performLayout(NVGcontext *ctx, Widget *widget) const {
int height = mMargin, availableWidth =
(widget->fixedWidth() ? widget->fixedWidth() : widget->width()) - 2*mMargin;
const Window *window = dynamic_cast<const Window *>(widget);
if (window && !window->title().empty())
height += widget->theme()->mWindowHeaderHeight - mMargin/2;
bool first = true, indent = false;
for (auto c : widget->children()) {
if (!c->visible())
continue;
const Label *label = dynamic_cast<const Label *>(c);
if (!first)
height += (label == nullptr) ? mSpacing : mGroupSpacing;
first = false;
bool indentCur = indent && label == nullptr;
Vector2i ps = Vector2i(availableWidth - (indentCur ? mGroupIndent : 0),
c->preferredSize(ctx).y());
Vector2i fs = c->fixedSize();
Vector2i targetSize(
fs[0] ? fs[0] : ps[0],
fs[1] ? fs[1] : ps[1]
);
c->setPosition(Vector2i(mMargin + (indentCur ? mGroupIndent : 0), height));
c->setSize(targetSize);
c->performLayout(ctx);
height += targetSize.y();
if (label)
indent = !label->caption().empty();
}
}
Vector2i GridLayout::preferredSize(NVGcontext *ctx,
const Widget *widget) const {
/* Compute minimum row / column sizes */
std::vector<int> grid[2];
computeLayout(ctx, widget, grid);
Vector2i size(
2*mMargin + std::accumulate(grid[0].begin(), grid[0].end(), 0)
+ std::max((int) grid[0].size() - 1, 0) * mSpacing[0],
2*mMargin + std::accumulate(grid[1].begin(), grid[1].end(), 0)
+ std::max((int) grid[1].size() - 1, 0) * mSpacing[1]
);
const Window *window = dynamic_cast<const Window *>(widget);
if (window && !window->title().empty())
size[1] += widget->theme()->mWindowHeaderHeight - mMargin/2;
return size;
}
void GridLayout::computeLayout(NVGcontext *ctx, const Widget *widget, std::vector<int> *grid) const {
int axis1 = (int) mOrientation, axis2 = (axis1 + 1) % 2;
size_t numChildren = widget->children().size(), visibleChildren = 0;
for (auto w : widget->children())
visibleChildren += w->visible() ? 1 : 0;
Vector2i dim;
dim[axis1] = mResolution;
dim[axis2] = (int) ((visibleChildren + mResolution - 1) / mResolution);
grid[axis1].clear(); grid[axis1].resize(dim[axis1], 0);
grid[axis2].clear(); grid[axis2].resize(dim[axis2], 0);
size_t child = 0;
for (int i2 = 0; i2 < dim[axis2]; i2++) {
for (int i1 = 0; i1 < dim[axis1]; i1++) {
Widget *w = nullptr;
do {
if (child >= numChildren)
return;
w = widget->children()[child++];
} while (!w->visible());
Vector2i ps = w->preferredSize(ctx);
Vector2i fs = w->fixedSize();
Vector2i targetSize(
fs[0] ? fs[0] : ps[0],
fs[1] ? fs[1] : ps[1]
);
grid[axis1][i1] = std::max(grid[axis1][i1], targetSize[axis1]);
grid[axis2][i2] = std::max(grid[axis2][i2], targetSize[axis2]);
}
}
}
void GridLayout::performLayout(NVGcontext *ctx, Widget *widget) const {
Vector2i fs_w = widget->fixedSize();
Vector2i containerSize(
fs_w[0] ? fs_w[0] : widget->width(),
fs_w[1] ? fs_w[1] : widget->height()
);
/* Compute minimum row / column sizes */
std::vector<int> grid[2];
computeLayout(ctx, widget, grid);
int dim[2] = { (int) grid[0].size(), (int) grid[1].size() };
Vector2i extra = Vector2i::Zero();
const Window *window = dynamic_cast<const Window *>(widget);
if (window && !window->title().empty())
extra[1] += widget->theme()->mWindowHeaderHeight - mMargin / 2;
/* Strech to size provided by \c widget */
for (int i = 0; i < 2; i++) {
int gridSize = 2 * mMargin + extra[i];
for (int s : grid[i]) {
gridSize += s;
if (i+1 < dim[i])
gridSize += mSpacing[i];
}
if (gridSize < containerSize[i]) {
/* Re-distribute remaining space evenly */
int gap = containerSize[i] - gridSize;
int g = gap / dim[i];
int rest = gap - g * dim[i];
for (int j = 0; j < dim[i]; ++j)
grid[i][j] += g;
for (int j = 0; rest > 0 && j < dim[i]; --rest, ++j)
grid[i][j] += 1;
}
}
int axis1 = (int) mOrientation, axis2 = (axis1 + 1) % 2;
Vector2i start = Vector2i::Constant(mMargin) + extra;
size_t numChildren = widget->children().size();
size_t child = 0;
Vector2i pos = start;
for (int i2 = 0; i2 < dim[axis2]; i2++) {
pos[axis1] = start[axis1];
for (int i1 = 0; i1 < dim[axis1]; i1++) {
Widget *w = nullptr;
do {
if (child >= numChildren)
return;
w = widget->children()[child++];
} while (!w->visible());
Vector2i ps = w->preferredSize(ctx);
Vector2i fs = w->fixedSize();
Vector2i targetSize(
fs[0] ? fs[0] : ps[0],
fs[1] ? fs[1] : ps[1]
);
Vector2i itemPos(pos);
for (int j = 0; j < 2; j++) {
int axis = (axis1 + j) % 2;
int item = j == 0 ? i1 : i2;
Alignment align = alignment(axis, item);
switch (align) {
case Alignment::Minimum:
break;
case Alignment::Middle:
itemPos[axis] += (grid[axis][item] - targetSize[axis]) / 2;
break;
case Alignment::Maximum:
itemPos[axis] += grid[axis][item] - targetSize[axis];
break;
case Alignment::Fill:
targetSize[axis] = fs[axis] ? fs[axis] : grid[axis][item];
break;
}
}
w->setPosition(itemPos);
w->setSize(targetSize);
w->performLayout(ctx);
pos[axis1] += grid[axis1][i1] + mSpacing[axis1];
}
pos[axis2] += grid[axis2][i2] + mSpacing[axis2];
}
}
AdvancedGridLayout::AdvancedGridLayout(const std::vector<int> &cols, const std::vector<int> &rows, int margin)
: mCols(cols), mRows(rows), mMargin(margin) {
mColStretch.resize(mCols.size(), 0);
mRowStretch.resize(mRows.size(), 0);
}
Vector2i AdvancedGridLayout::preferredSize(NVGcontext *ctx, const Widget *widget) const {
/* Compute minimum row / column sizes */
std::vector<int> grid[2];
computeLayout(ctx, widget, grid);
Vector2i size(
std::accumulate(grid[0].begin(), grid[0].end(), 0),
std::accumulate(grid[1].begin(), grid[1].end(), 0));
Vector2i extra = Vector2i::Constant(2 * mMargin);
const Window *window = dynamic_cast<const Window *>(widget);
if (window && !window->title().empty())
extra[1] += widget->theme()->mWindowHeaderHeight - mMargin/2;
return size+extra;
}
void AdvancedGridLayout::performLayout(NVGcontext *ctx, Widget *widget) const {
std::vector<int> grid[2];
computeLayout(ctx, widget, grid);
grid[0].insert(grid[0].begin(), mMargin);
const Window *window = dynamic_cast<const Window *>(widget);
if (window && !window->title().empty())
grid[1].insert(grid[1].begin(), widget->theme()->mWindowHeaderHeight + mMargin/2);
else
grid[1].insert(grid[1].begin(), mMargin);
for (int axis=0; axis<2; ++axis) {
for (size_t i=1; i<grid[axis].size(); ++i)
grid[axis][i] += grid[axis][i-1];
for (Widget *w : widget->children()) {
if (!w->visible())
continue;
Anchor anchor = this->anchor(w);
int itemPos = grid[axis][anchor.pos[axis]];
int cellSize = grid[axis][anchor.pos[axis] + anchor.size[axis]] - itemPos;
int ps = w->preferredSize(ctx)[axis], fs = w->fixedSize()[axis];
int targetSize = fs ? fs : ps;
switch (anchor.align[axis]) {
case Alignment::Minimum:
break;
case Alignment::Middle:
itemPos += (cellSize - targetSize) / 2;
break;
case Alignment::Maximum:
itemPos += cellSize - targetSize;
break;
case Alignment::Fill:
targetSize = fs ? fs : cellSize;
break;
}
Vector2i pos = w->position(), size = w->size();
pos[axis] = itemPos;
size[axis] = targetSize;
w->setPosition(pos);
w->setSize(size);
w->performLayout(ctx);
}
}
}
void AdvancedGridLayout::computeLayout(NVGcontext *ctx, const Widget *widget,
std::vector<int> *_grid) const {
Vector2i fs_w = widget->fixedSize();
Vector2i containerSize(
fs_w[0] ? fs_w[0] : widget->width(),
fs_w[1] ? fs_w[1] : widget->height()
);
Vector2i extra = Vector2i::Constant(2 * mMargin);
const Window *window = dynamic_cast<const Window *>(widget);
if (window && !window->title().empty())
extra[1] += widget->theme()->mWindowHeaderHeight - mMargin/2;
containerSize -= extra;
for (int axis=0; axis<2; ++axis) {
std::vector<int> &grid = _grid[axis];
const std::vector<int> &sizes = axis == 0 ? mCols : mRows;
const std::vector<float> &stretch = axis == 0 ? mColStretch : mRowStretch;
grid = sizes;
for (int phase = 0; phase < 2; ++phase) {
for (auto pair : mAnchor) {
const Widget *w = pair.first;
if (!w->visible())
continue;
const Anchor &anchor = pair.second;
if ((anchor.size[axis] == 1) != (phase == 0))
continue;
int ps = w->preferredSize(ctx)[axis], fs = w->fixedSize()[axis];
int targetSize = fs ? fs : ps;
if (anchor.pos[axis] + anchor.size[axis] > (int) grid.size())
throw std::runtime_error(
"Advanced grid layout: widget is out of bounds: " +
(std::string) anchor);
int currentSize = 0;
float totalStretch = 0;
for (int i = anchor.pos[axis];
i < anchor.pos[axis] + anchor.size[axis]; ++i) {
if (sizes[i] == 0 && anchor.size[axis] == 1)
grid[i] = std::max(grid[i], targetSize);
currentSize += grid[i];
totalStretch += stretch[i];
}
if (targetSize <= currentSize)
continue;
if (totalStretch == 0)
throw std::runtime_error(
"Advanced grid layout: no space to place widget: " +
(std::string) anchor);
float amt = (targetSize - currentSize) / totalStretch;
for (int i = anchor.pos[axis];
i < anchor.pos[axis] + anchor.size[axis]; ++i) {
grid[i] += (int) std::round(amt * stretch[i]);
}
}
}
int currentSize = std::accumulate(grid.begin(), grid.end(), 0);
float totalStretch = std::accumulate(stretch.begin(), stretch.end(), 0.0f);
if (currentSize >= containerSize[axis] || totalStretch == 0)
continue;
float amt = (containerSize[axis] - currentSize) / totalStretch;
for (size_t i = 0; i<grid.size(); ++i)
grid[i] += (int) std::round(amt * stretch[i]);
}
}
NAMESPACE_END(nanogui)