541 lines
15 KiB
C++
541 lines
15 KiB
C++
// This file is part of VSTGUI. It is subject to the license terms
|
|
// in the LICENSE file found in the top-level directory of this
|
|
// distribution and at http://github.com/steinbergmedia/vstgui/LICENSE
|
|
|
|
#include "gridlayouter.h"
|
|
#include "../cviewcontainer.h"
|
|
|
|
//------------------------------------------------------------------------
|
|
namespace VSTGUI {
|
|
|
|
//------------------------------------------------------------------------
|
|
GridLayouter::GridLayouter (const GridLayoutProperties& props) : gridProps (props) {}
|
|
|
|
//------------------------------------------------------------------------
|
|
void GridLayouter::setProperties (const GridLayoutProperties& props) { gridProps = props; }
|
|
|
|
//------------------------------------------------------------------------
|
|
const GridLayoutProperties& GridLayouter::getProperties () const { return gridProps; }
|
|
|
|
//------------------------------------------------------------------------
|
|
GridLayoutProperties& GridLayouter::getProperties () { return gridProps; }
|
|
|
|
//------------------------------------------------------------------------
|
|
std::vector<CCoord> GridLayouter::calculateAutoRows (CCoord& usedRowHeight, CCoord availableHeight,
|
|
size_t& outNumAutoRows) const
|
|
{
|
|
std::vector<CCoord> autoRows;
|
|
if (!gridProps.autoRows.empty ())
|
|
{
|
|
for (const auto& el : gridProps.autoRows)
|
|
{
|
|
if (std::holds_alternative<CCoord> (el))
|
|
{
|
|
autoRows.push_back (std::get<CCoord> (el));
|
|
usedRowHeight += autoRows.back ();
|
|
}
|
|
else if (std::holds_alternative<GridLayoutProperties::Percentage> (el))
|
|
{
|
|
autoRows.push_back (
|
|
availableHeight *
|
|
(std::get<GridLayoutProperties::Percentage> (el).value / 100.0));
|
|
usedRowHeight += autoRows.back ();
|
|
}
|
|
else if (std::holds_alternative<GridLayoutProperties::Auto> (el))
|
|
{
|
|
autoRows.push_back (kAutoSize);
|
|
++outNumAutoRows;
|
|
}
|
|
}
|
|
}
|
|
return autoRows;
|
|
}
|
|
|
|
//------------------------------------------------------------------------
|
|
std::vector<CCoord> GridLayouter::calculateAutoColumns (CCoord& usedColWidth, CCoord availableWidth,
|
|
size_t& outNumAutoCols) const
|
|
{
|
|
std::vector<CCoord> autoColumns;
|
|
if (!gridProps.autoColumns.empty ())
|
|
{
|
|
for (const auto& el : gridProps.autoColumns)
|
|
{
|
|
if (std::holds_alternative<CCoord> (el))
|
|
{
|
|
autoColumns.push_back (std::get<CCoord> (el));
|
|
usedColWidth += autoColumns.back ();
|
|
}
|
|
else if (std::holds_alternative<GridLayoutProperties::Percentage> (el))
|
|
{
|
|
autoColumns.push_back (
|
|
availableWidth *
|
|
(std::get<GridLayoutProperties::Percentage> (el).value / 100.0));
|
|
usedColWidth += autoColumns.back ();
|
|
}
|
|
else if (std::holds_alternative<GridLayoutProperties::Auto> (el))
|
|
{
|
|
autoColumns.push_back (kAutoSize);
|
|
++outNumAutoCols;
|
|
}
|
|
}
|
|
}
|
|
return autoColumns;
|
|
}
|
|
|
|
//------------------------------------------------------------------------
|
|
std::optional<ViewLayout> GridLayouter::calculateLayout (const CViewContainer& /*view*/,
|
|
const Children& children,
|
|
const CRect& _newSize)
|
|
{
|
|
if (children.empty ())
|
|
return {{_newSize, LayoutData {}}};
|
|
|
|
size_t rows = gridProps.rows;
|
|
size_t cols = gridProps.columns;
|
|
CCoord rowGap = gridProps.rowGap;
|
|
CCoord colGap = gridProps.columnGap;
|
|
if (rows == 0 || cols == 0)
|
|
{
|
|
return std::nullopt;
|
|
}
|
|
|
|
auto newSize = _newSize;
|
|
newSize.originize ();
|
|
|
|
std::vector<CCoord> rowHeights (rows, 0.0);
|
|
std::vector<CCoord> colWidths (cols, 0.0);
|
|
CCoord totalRowGap = (rows - 1) * rowGap;
|
|
CCoord totalColGap = (cols - 1) * colGap;
|
|
CCoord availableHeight = newSize.getHeight () - totalRowGap;
|
|
CCoord availableWidth = newSize.getWidth () - totalColGap;
|
|
CCoord usedRowHeight = 0.0;
|
|
size_t numAutoRows = 0u;
|
|
CCoord usedColWidth = 0.0;
|
|
size_t numAutoCols = 0u;
|
|
|
|
std::vector<CCoord> autoRows = calculateAutoRows (usedRowHeight, availableHeight, numAutoRows);
|
|
std::vector<CCoord> autoColumns =
|
|
calculateAutoColumns (usedColWidth, availableWidth, numAutoCols);
|
|
|
|
CCoord sumAutoRows = 0.0;
|
|
for (size_t r = 0; r < rows; ++r)
|
|
{
|
|
if (r < autoRows.size ())
|
|
{
|
|
if (autoRows[r] == kAutoSize)
|
|
autoRows[r] = (availableHeight - usedRowHeight) / numAutoRows;
|
|
rowHeights[r] = autoRows[r];
|
|
sumAutoRows += rowHeights[r];
|
|
}
|
|
}
|
|
CCoord defaultRowHeight = (rows > autoRows.size ())
|
|
? (availableHeight - sumAutoRows) / (rows - autoRows.size ())
|
|
: 0.0;
|
|
for (size_t r = 0; r < rows; ++r)
|
|
{
|
|
if (r >= autoRows.size ())
|
|
rowHeights[r] = defaultRowHeight;
|
|
}
|
|
|
|
CCoord sumAutoCols = 0.0;
|
|
for (size_t c = 0; c < cols; ++c)
|
|
{
|
|
if (c < autoColumns.size ())
|
|
{
|
|
if (autoColumns[c] == kAutoSize)
|
|
autoColumns[c] = (availableWidth - usedColWidth) / numAutoCols;
|
|
colWidths[c] = autoColumns[c];
|
|
sumAutoCols += colWidths[c];
|
|
}
|
|
}
|
|
CCoord defaultColWidth = (cols > autoColumns.size ())
|
|
? (availableWidth - sumAutoCols) / (cols - autoColumns.size ())
|
|
: 0.0;
|
|
for (size_t c = 0; c < cols; ++c)
|
|
{
|
|
if (c >= autoColumns.size ())
|
|
colWidths[c] = defaultColWidth;
|
|
}
|
|
|
|
CCoord gridHeight = sumAutoRows + defaultRowHeight * (rows - autoRows.size ()) + totalRowGap;
|
|
CCoord gridWidth = sumAutoCols + defaultColWidth * (cols - autoColumns.size ()) + totalColGap;
|
|
CCoord offsetY = 0.0;
|
|
CCoord offsetX = 0.0;
|
|
CCoord extraSpaceY = newSize.getHeight () - gridHeight;
|
|
CCoord extraSpaceX = newSize.getWidth () - gridWidth;
|
|
CCoord spaceBetweenY = 0.0;
|
|
CCoord spaceAroundY = 0.0;
|
|
CCoord spaceBetweenX = 0.0;
|
|
CCoord spaceAroundX = 0.0;
|
|
switch (gridProps.alignContent)
|
|
{
|
|
case GridLayoutProperties::AlignContent::Center:
|
|
{
|
|
offsetY = extraSpaceY / 2.0;
|
|
break;
|
|
}
|
|
case GridLayoutProperties::AlignContent::End:
|
|
{
|
|
offsetY = extraSpaceY;
|
|
break;
|
|
}
|
|
case GridLayoutProperties::AlignContent::Stretch:
|
|
{
|
|
if (rows > 0 && extraSpaceY > 0.0)
|
|
{
|
|
CCoord add = extraSpaceY / rows;
|
|
for (size_t r = 0; r < rows; ++r)
|
|
rowHeights[r] += add;
|
|
}
|
|
offsetY = 0.0;
|
|
break;
|
|
}
|
|
case GridLayoutProperties::AlignContent::SpaceBetween:
|
|
{
|
|
spaceBetweenY = (rows > 1) ? (extraSpaceY / (rows - 1)) : 0.0;
|
|
offsetY = 0.0;
|
|
break;
|
|
}
|
|
case GridLayoutProperties::AlignContent::SpaceAround:
|
|
{
|
|
spaceAroundY = (rows > 0) ? (extraSpaceY / (rows + 1)) : 0.0;
|
|
offsetY = spaceAroundY;
|
|
break;
|
|
}
|
|
default:
|
|
{
|
|
offsetY = 0.0;
|
|
break;
|
|
}
|
|
}
|
|
switch (gridProps.justifyContent)
|
|
{
|
|
case GridLayoutProperties::JustifyContent::Center:
|
|
{
|
|
offsetX = (newSize.getWidth () - gridWidth) / 2.0;
|
|
break;
|
|
}
|
|
case GridLayoutProperties::JustifyContent::End:
|
|
{
|
|
offsetX = newSize.getWidth () - gridWidth;
|
|
break;
|
|
}
|
|
case GridLayoutProperties::JustifyContent::Stretch:
|
|
{
|
|
if (cols > 0 && extraSpaceX > 0.0)
|
|
{
|
|
CCoord add = extraSpaceX / cols;
|
|
for (size_t c = 0; c < cols; ++c)
|
|
colWidths[c] += add;
|
|
}
|
|
offsetX = 0.0;
|
|
break;
|
|
}
|
|
case GridLayoutProperties::JustifyContent::SpaceBetween:
|
|
{
|
|
spaceBetweenX = (cols > 1) ? (extraSpaceX / (cols - 1)) : 0.0;
|
|
offsetX = 0.0;
|
|
break;
|
|
}
|
|
case GridLayoutProperties::JustifyContent::SpaceAround:
|
|
{
|
|
spaceAroundX = (cols > 0) ? (extraSpaceX / (cols + 1)) : 0.0;
|
|
offsetX = spaceAroundX;
|
|
break;
|
|
}
|
|
default:
|
|
{
|
|
offsetX = 0.0;
|
|
break;
|
|
}
|
|
}
|
|
|
|
LayoutData layout;
|
|
layout.reserve (children.size ());
|
|
auto child = children.begin ();
|
|
|
|
if (!gridProps.gridAreas.empty ())
|
|
{
|
|
// Build an occupancy grid of explicit tracks
|
|
auto idx2D = [cols] (size_t r, size_t c) {
|
|
return r * cols + c;
|
|
};
|
|
std::vector<bool> occupied (rows * cols, false);
|
|
|
|
// Helper to compute the raw cell rectangle for an area [r0,r1) x [c0,c1)
|
|
auto computeCellRect = [&] (size_t r0, size_t c0, size_t r1, size_t c1) {
|
|
CCoord y = newSize.top + offsetY;
|
|
for (size_t r = 0; r < r0; ++r)
|
|
{
|
|
y += rowHeights[r] + rowGap;
|
|
if (r < rows - 1)
|
|
y += spaceBetweenY + spaceAroundY;
|
|
}
|
|
CCoord x = newSize.left + offsetX;
|
|
for (size_t c = 0; c < c0; ++c)
|
|
{
|
|
x += colWidths[c] + colGap;
|
|
if (c < cols - 1)
|
|
x += spaceBetweenX + spaceAroundX;
|
|
}
|
|
CCoord h = 0.0;
|
|
for (size_t r = r0; r < r1; ++r)
|
|
h += rowHeights[r];
|
|
if (r1 > r0)
|
|
{
|
|
auto count = r1 - r0 - 1;
|
|
h += spaceBetweenY * count + (rowGap + spaceAroundY) * count;
|
|
}
|
|
CCoord w = 0.0;
|
|
for (size_t c = c0; c < c1; ++c)
|
|
w += colWidths[c];
|
|
if (c1 > c0)
|
|
{
|
|
auto count = c1 - c0 - 1;
|
|
w += spaceBetweenX * count + (colGap + spaceAroundX) * count;
|
|
}
|
|
return CRect (x, y, x + w, y + h);
|
|
};
|
|
|
|
// Helper to place a single child for the given area with justify/align items handling
|
|
auto placeChildForArea = [&] (CView* child, size_t r0, size_t c0, size_t r1, size_t c1) {
|
|
CRect areaRect = computeCellRect (r0, c0, r1, c1);
|
|
CCoord x = areaRect.left;
|
|
CCoord y = areaRect.top;
|
|
CCoord w = areaRect.getWidth ();
|
|
CCoord h = areaRect.getHeight ();
|
|
|
|
CCoord itemX = x;
|
|
CCoord itemY = y;
|
|
CCoord itemWidth = w;
|
|
CCoord itemHeight = h;
|
|
|
|
// Determine intrinsic size when not stretching
|
|
CCoord intrinsicW = w;
|
|
CCoord intrinsicH = h;
|
|
CRect vs = child->getViewSize ();
|
|
intrinsicW = std::max<CCoord> (0.0, std::min (vs.getWidth (), w));
|
|
intrinsicH = std::max<CCoord> (0.0, std::min (vs.getHeight (), h));
|
|
|
|
// Horizontal sizing and positioning
|
|
switch (gridProps.justifyItems)
|
|
{
|
|
case GridLayoutProperties::JustifyItems::Stretch:
|
|
{
|
|
itemWidth = w;
|
|
itemX = x;
|
|
break;
|
|
}
|
|
case GridLayoutProperties::JustifyItems::Center:
|
|
{
|
|
itemWidth = intrinsicW;
|
|
itemX = x + (w - itemWidth) / 2.0;
|
|
break;
|
|
}
|
|
case GridLayoutProperties::JustifyItems::End:
|
|
{
|
|
itemWidth = intrinsicW;
|
|
itemX = x + (w - itemWidth);
|
|
break;
|
|
}
|
|
case GridLayoutProperties::JustifyItems::Start:
|
|
default:
|
|
{
|
|
itemWidth = intrinsicW;
|
|
itemX = x;
|
|
break;
|
|
}
|
|
}
|
|
// Vertical sizing and positioning
|
|
switch (gridProps.alignItems)
|
|
{
|
|
case GridLayoutProperties::AlignItems::Stretch:
|
|
{
|
|
itemHeight = h;
|
|
itemY = y;
|
|
break;
|
|
}
|
|
case GridLayoutProperties::AlignItems::Center:
|
|
{
|
|
itemHeight = intrinsicH;
|
|
itemY = y + (h - itemHeight) / 2.0;
|
|
break;
|
|
}
|
|
case GridLayoutProperties::AlignItems::End:
|
|
{
|
|
itemHeight = intrinsicH;
|
|
itemY = y + (h - itemHeight);
|
|
break;
|
|
}
|
|
case GridLayoutProperties::AlignItems::Start:
|
|
default:
|
|
{
|
|
itemHeight = intrinsicH;
|
|
itemY = y;
|
|
break;
|
|
}
|
|
}
|
|
|
|
CRect childRect (itemX, itemY, itemX + itemWidth, itemY + itemHeight);
|
|
std::optional<ViewLayout> childLayout;
|
|
if (auto childViewContainer = child->asViewContainer ())
|
|
childLayout = {childViewContainer->calculateViewLayout (childRect)};
|
|
layout.emplace_back (childRect, childLayout);
|
|
};
|
|
|
|
// Mark occupied cells for the areas that correspond to existing children
|
|
const size_t numAreaChildren = std::min (children.size (), gridProps.gridAreas.size ());
|
|
for (size_t childIdx = 0; childIdx < numAreaChildren; ++childIdx)
|
|
{
|
|
const auto& area = gridProps.gridAreas[childIdx];
|
|
size_t r0 = std::min (area.row, rows - 1);
|
|
size_t c0 = std::min (area.column, cols - 1);
|
|
size_t r1 = std::min (r0 + area.rowSpan, rows);
|
|
size_t c1 = std::min (c0 + area.colSpan, cols);
|
|
for (size_t r = r0; r < r1; ++r)
|
|
{
|
|
for (size_t c = c0; c < c1; ++c)
|
|
{
|
|
occupied[idx2D (r, c)] = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Now place children in order: explicit areas first, then auto-place remaining
|
|
for (size_t childIdx = 0; childIdx < children.size (); ++childIdx, ++child)
|
|
{
|
|
if (childIdx < gridProps.gridAreas.size ())
|
|
{
|
|
const auto& area = gridProps.gridAreas[childIdx];
|
|
size_t r0 = std::min (area.row, rows - 1);
|
|
size_t c0 = std::min (area.column, cols - 1);
|
|
size_t r1 = std::min (r0 + area.rowSpan, rows);
|
|
size_t c1 = std::min (c0 + area.colSpan, cols);
|
|
placeChildForArea (*child, r0, c0, r1, c1);
|
|
}
|
|
else
|
|
{
|
|
// Auto-place into the next free 1x1 cell (row-major)
|
|
bool placed = false;
|
|
for (size_t r = 0; r < rows && !placed; ++r)
|
|
{
|
|
for (size_t c = 0; c < cols && !placed; ++c)
|
|
{
|
|
if (!occupied[idx2D (r, c)])
|
|
{
|
|
occupied[idx2D (r, c)] = true;
|
|
placeChildForArea (*child, r, c, r + 1, c + 1);
|
|
placed = true;
|
|
}
|
|
}
|
|
}
|
|
if (!placed)
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Default: one cell per child
|
|
CCoord y = newSize.top + offsetY;
|
|
for (size_t r = 0; r < rows; ++r)
|
|
{
|
|
CCoord x = newSize.left + offsetX;
|
|
for (size_t c = 0; c < cols; ++c)
|
|
{
|
|
if (child == children.end ())
|
|
break;
|
|
CCoord cellWidth = colWidths[c];
|
|
CCoord cellHeight = rowHeights[r];
|
|
// Use autoColumns/autoRows if provided
|
|
CCoord itemWidth = autoColumns.size () > c ? autoColumns[c] : cellWidth;
|
|
CCoord itemHeight = autoRows.size () > r ? autoRows[r] : cellHeight;
|
|
CCoord itemX = x;
|
|
CCoord itemY = y;
|
|
if (gridProps.justifyItems == GridLayoutProperties::JustifyItems::Start)
|
|
itemX = x;
|
|
else if (gridProps.justifyItems == GridLayoutProperties::JustifyItems::Center)
|
|
itemX = x + (cellWidth - itemWidth) / 2.0;
|
|
else if (gridProps.justifyItems == GridLayoutProperties::JustifyItems::End)
|
|
itemX = x + (cellWidth - itemWidth);
|
|
else if (gridProps.justifyItems == GridLayoutProperties::JustifyItems::Stretch)
|
|
{
|
|
itemX = x;
|
|
itemWidth = cellWidth;
|
|
}
|
|
if (gridProps.alignItems == GridLayoutProperties::AlignItems::Start)
|
|
itemY = y;
|
|
else if (gridProps.alignItems == GridLayoutProperties::AlignItems::Center)
|
|
itemY = y + (cellHeight - itemHeight) / 2.0;
|
|
else if (gridProps.alignItems == GridLayoutProperties::AlignItems::End)
|
|
itemY = y + cellHeight - itemHeight;
|
|
else if (gridProps.alignItems == GridLayoutProperties::AlignItems::Stretch)
|
|
{
|
|
itemY = y;
|
|
itemHeight = cellHeight;
|
|
}
|
|
CRect childRect (itemX, itemY, itemX + itemWidth, itemY + itemHeight);
|
|
std::optional<ViewLayout> childLayout;
|
|
if (auto childViewContainer = (*child)->asViewContainer ())
|
|
childLayout = {childViewContainer->calculateViewLayout (childRect)};
|
|
layout.emplace_back (childRect, childLayout);
|
|
|
|
x += cellWidth + colGap;
|
|
if (spaceBetweenX > 0.0 && c < cols - 1)
|
|
x += spaceBetweenX;
|
|
else if (spaceAroundX > 0.0)
|
|
x += spaceAroundX;
|
|
++child;
|
|
}
|
|
y += rowHeights[r] + rowGap;
|
|
// Add extra space for SpaceBetween/SpaceAround
|
|
if (gridProps.alignContent == GridLayoutProperties::AlignContent::SpaceBetween &&
|
|
r < rows - 1)
|
|
y += spaceBetweenY;
|
|
else if (gridProps.alignContent == GridLayoutProperties::AlignContent::SpaceAround &&
|
|
r < rows - 1)
|
|
y += spaceAroundY;
|
|
}
|
|
}
|
|
return {{_newSize, std::move (layout)}};
|
|
}
|
|
|
|
//------------------------------------------------------------------------
|
|
bool GridLayouter::applyLayout (CViewContainer& container, const Children& children,
|
|
const ViewLayout& layout)
|
|
{
|
|
if (gridProps.rows == 0 || gridProps.columns == 0)
|
|
return false;
|
|
|
|
const auto layoutData = std::any_cast<LayoutData> (&layout.data);
|
|
if (!layoutData || layoutData->size () > children.size ())
|
|
return false;
|
|
|
|
size_t childIdx = 0;
|
|
for (auto& child : children)
|
|
{
|
|
if (childIdx >= layoutData->size ())
|
|
break;
|
|
auto rect = (*layoutData)[childIdx].first;
|
|
auto& childLayout = (*layoutData)[childIdx].second;
|
|
if (auto childContainer = child->asViewContainer (); childContainer && childLayout)
|
|
{
|
|
childContainer->applyViewLayout (*childLayout);
|
|
}
|
|
else
|
|
{
|
|
child->setViewSize (rect, true);
|
|
child->setMouseableArea (rect);
|
|
}
|
|
++childIdx;
|
|
}
|
|
container.setViewSize (layout.size);
|
|
container.setMouseableArea (layout.size);
|
|
return true;
|
|
}
|
|
|
|
//------------------------------------------------------------------------
|
|
} // namespace VSTGUI
|