/* src/layout.cpp -- A collection of useful layout managers The grid layout was contributed by Christian Schueller. NanoGUI was developed by Wenzel Jakob . 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 #include #include #include #include #include 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(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(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(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(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(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(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 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(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 *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 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(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 &cols, const std::vector &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 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(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 grid[2]; computeLayout(ctx, widget, grid); grid[0].insert(grid[0].begin(), mMargin); const Window *window = dynamic_cast(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; ichildren()) { 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 *_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(widget); if (window && !window->title().empty()) extra[1] += widget->theme()->mWindowHeaderHeight - mMargin/2; containerSize -= extra; for (int axis=0; axis<2; ++axis) { std::vector &grid = _grid[axis]; const std::vector &sizes = axis == 0 ? mCols : mRows; const std::vector &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