Initial release
This commit is contained in:
+106
@@ -0,0 +1,106 @@
|
||||
// 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 "autosizeviewlayouter.h"
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
namespace VSTGUI {
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
SharedPointer<IViewLayouter> AutoSizeViewLayouter::get () noexcept
|
||||
{
|
||||
static AutoSizeViewLayouter instance;
|
||||
return {&instance};
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
std::optional<ViewLayout> AutoSizeViewLayouter::calculateLayout (const CViewContainer& container,
|
||||
const Children& children,
|
||||
const CRect& newSize)
|
||||
{
|
||||
if (container.getAutosizingEnabled () == false)
|
||||
return {ViewLayout {newSize, LayoutData {}}};
|
||||
|
||||
auto oldSize = container.getViewSize ();
|
||||
auto widthDelta = newSize.getWidth () - oldSize.getWidth ();
|
||||
auto heightDelta = newSize.getHeight () - oldSize.getHeight ();
|
||||
container.getTransform ().inverse ().transform (widthDelta, heightDelta);
|
||||
if (widthDelta == 0 && heightDelta == 0)
|
||||
return {ViewLayout {newSize, LayoutData {}}};
|
||||
|
||||
LayoutData layoutData;
|
||||
|
||||
auto numSubviews = children.size ();
|
||||
auto counter = 0u;
|
||||
auto treatAsColumn = (container.getAutosizeFlags () & kAutosizeColumn) != 0;
|
||||
auto treatAsRow = (container.getAutosizeFlags () & kAutosizeRow) != 0;
|
||||
|
||||
layoutData.reserve (numSubviews);
|
||||
|
||||
for (const auto& child : children)
|
||||
{
|
||||
int32_t autosize = child->getAutosizeFlags ();
|
||||
CRect viewSize (child->getViewSize ());
|
||||
CRect mouseSize (child->getMouseableArea ());
|
||||
if (treatAsColumn)
|
||||
{
|
||||
if (counter)
|
||||
{
|
||||
viewSize.offset (counter * (widthDelta / (numSubviews)), 0);
|
||||
mouseSize.offset (counter * (widthDelta / (numSubviews)), 0);
|
||||
}
|
||||
viewSize.setWidth (viewSize.getWidth () + (widthDelta / (numSubviews)));
|
||||
mouseSize.setWidth (mouseSize.getWidth () + (widthDelta / (numSubviews)));
|
||||
}
|
||||
else if (widthDelta != 0 && autosize & kAutosizeRight)
|
||||
{
|
||||
viewSize.right += widthDelta;
|
||||
mouseSize.right += widthDelta;
|
||||
if (!(autosize & kAutosizeLeft))
|
||||
{
|
||||
viewSize.left += widthDelta;
|
||||
mouseSize.left += widthDelta;
|
||||
}
|
||||
}
|
||||
if (treatAsRow)
|
||||
{
|
||||
if (counter)
|
||||
{
|
||||
viewSize.offset (0, counter * (heightDelta / (numSubviews)));
|
||||
mouseSize.offset (0, counter * (heightDelta / (numSubviews)));
|
||||
}
|
||||
viewSize.setHeight (viewSize.getHeight () + (heightDelta / (numSubviews)));
|
||||
mouseSize.setHeight (mouseSize.getHeight () + (heightDelta / (numSubviews)));
|
||||
}
|
||||
else if (heightDelta != 0 && autosize & kAutosizeBottom)
|
||||
{
|
||||
viewSize.bottom += heightDelta;
|
||||
mouseSize.bottom += heightDelta;
|
||||
if (!(autosize & kAutosizeTop))
|
||||
{
|
||||
viewSize.top += heightDelta;
|
||||
mouseSize.top += heightDelta;
|
||||
}
|
||||
}
|
||||
if (viewSize != child->getViewSize ())
|
||||
{
|
||||
std::optional<ViewLayout> childLayout;
|
||||
if (auto childContainer = child->asViewContainer ())
|
||||
childLayout = childContainer->calculateViewLayout (viewSize);
|
||||
layoutData.push_back (
|
||||
{child->getRuntimeID (), viewSize, mouseSize, std::move (childLayout)});
|
||||
}
|
||||
counter++;
|
||||
}
|
||||
|
||||
return std::make_optional (ViewLayout {newSize, std::move (layoutData)});
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
// there's only one stateless static instance of this object so disable reference counting:
|
||||
void AutoSizeViewLayouter::forget () {}
|
||||
void AutoSizeViewLayouter::remember () {};
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
} // VSTGUI
|
||||
@@ -0,0 +1,40 @@
|
||||
// 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
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "baseviewlayouter.h"
|
||||
#include <tuple>
|
||||
#include <vector>
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
namespace VSTGUI {
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
/** autosize view layouter
|
||||
*
|
||||
* a view layouter that uses the autosize flags of the views to determine the size and position
|
||||
* of the child views in a view container.
|
||||
*
|
||||
* @ingroup new_in_4_15
|
||||
*/
|
||||
struct AutoSizeViewLayouter final : BaseViewLayouter
|
||||
{
|
||||
private:
|
||||
AutoSizeViewLayouter () = default;
|
||||
|
||||
std::optional<ViewLayout> calculateLayout (const CViewContainer& container,
|
||||
const Children& children,
|
||||
const CRect& newSize) override;
|
||||
|
||||
void forget () final;
|
||||
void remember () final;
|
||||
|
||||
public:
|
||||
/** get the shared instance of the AutoSizeViewLayouter */
|
||||
static SharedPointer<IViewLayouter> get () noexcept;
|
||||
};
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
} // VSTGUI
|
||||
@@ -0,0 +1,80 @@
|
||||
// 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
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "../iviewlayouter.h"
|
||||
#include <vector>
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
namespace VSTGUI {
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
struct BaseViewLayouterEntry
|
||||
{
|
||||
uint64_t viewId {};
|
||||
CRect viewSize {};
|
||||
CRect mouseSize {};
|
||||
std::optional<ViewLayout> childLayout {};
|
||||
};
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
/** a base class for view layouters
|
||||
*
|
||||
* @ingroup new_in_4_15
|
||||
*/
|
||||
class BaseViewLayouter : public IViewLayouter
|
||||
{
|
||||
public:
|
||||
using LayoutData = std::vector<BaseViewLayouterEntry>;
|
||||
|
||||
bool applyLayout (CViewContainer& container, const Children& children,
|
||||
const ViewLayout& layout) override;
|
||||
};
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
inline bool BaseViewLayouter::applyLayout (CViewContainer& container, const Children& children,
|
||||
const ViewLayout& layout)
|
||||
{
|
||||
auto layoutData = std::any_cast<const LayoutData> (&layout.data);
|
||||
if (layoutData == nullptr)
|
||||
{
|
||||
vstgui_assert (false, "BaseViewLayouter: Unexpected layout data");
|
||||
return false;
|
||||
}
|
||||
auto childIt = children.begin ();
|
||||
auto childEnd = children.end ();
|
||||
for (const auto& entry : *layoutData)
|
||||
{
|
||||
while (childIt != childEnd && (*childIt)->getRuntimeID () != entry.viewId)
|
||||
++childIt;
|
||||
|
||||
if (childIt == childEnd)
|
||||
continue;
|
||||
|
||||
auto doSetViewSize = true;
|
||||
auto& child = *childIt;
|
||||
if (auto childContainer = child->asViewContainer ())
|
||||
{
|
||||
if (entry.childLayout)
|
||||
{
|
||||
if (!childContainer->applyViewLayout (*entry.childLayout))
|
||||
return false;
|
||||
doSetViewSize = false;
|
||||
}
|
||||
}
|
||||
if (doSetViewSize)
|
||||
{
|
||||
child->setViewSize (entry.viewSize, true);
|
||||
child->setMouseableArea (entry.mouseSize == entry.viewSize ? child->getViewSize ()
|
||||
: entry.mouseSize);
|
||||
}
|
||||
}
|
||||
container.setViewSize (layout.size);
|
||||
container.setMouseableArea (layout.size);
|
||||
return true;
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
} // VSTGUI
|
||||
@@ -0,0 +1,540 @@
|
||||
// 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
|
||||
@@ -0,0 +1,146 @@
|
||||
// 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
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "../iviewlayouter.h"
|
||||
#include <vector>
|
||||
#include <variant>
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
namespace VSTGUI {
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
/** Properties for configuring a grid layout, compatible with CSS Grid Layout.
|
||||
*
|
||||
* The GridProperties struct allows you to define a grid layout similar to CSS Grid.
|
||||
*
|
||||
* - rows: Number of grid rows (default: 1).
|
||||
* - columns: Number of grid columns (default: 1).
|
||||
* - autoRows: Defines the height of each row, similar to 'grid-template-rows' in CSS.
|
||||
* - autoColumns: Defines the width of each column, similar to 'grid-template-columns' in CSS.
|
||||
* - rowGap: Space between rows, equivalent to 'row-gap' in CSS.
|
||||
* - columnGap: Space between columns, equivalent to 'column-gap' in CSS.
|
||||
* - alignItems: Controls alignment of items within their grid area (CSS: 'align-items').
|
||||
* Options: Start, Center, End, Stretch.
|
||||
* - justifyItems: Controls horizontal alignment of items within their grid area (CSS:
|
||||
* 'justify-items'). Options: Start, Center, End, Stretch.
|
||||
* - alignContent: Controls vertical alignment of the grid as a whole (CSS: 'align-content').
|
||||
* Options: Start, Center, End, Stretch, SpaceBetween, SpaceAround.
|
||||
* - justifyContent: Controls horizontal alignment of the grid as a whole (CSS: 'justify-content').
|
||||
* Options: Start, Center, End, Stretch, SpaceBetween, SpaceAround.
|
||||
* - gridAreas: Optional. Defines the grid area (row, column, rowSpan, colSpan) for each
|
||||
* child/view. If provided, each child can span multiple rows/columns, similar to
|
||||
* CSS grid-area. If not provided, each child is placed in a single cell by
|
||||
* default.
|
||||
*
|
||||
* Note: justify-content is ignored when grid areas are used and the grid tracks exactly fit the
|
||||
* content (no extra free space). If grid areas are used and the grid is smaller than the
|
||||
* container, justify-content can affect the grid's position within the container.
|
||||
*
|
||||
* This struct is designed to be compatible with the CSS Grid Layout specification:
|
||||
* https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_grid_layout
|
||||
*
|
||||
* @ingroup new_in_4_15
|
||||
*/
|
||||
struct GridLayoutProperties
|
||||
{
|
||||
enum class AlignItems
|
||||
{
|
||||
Start,
|
||||
Center,
|
||||
End,
|
||||
Stretch
|
||||
};
|
||||
enum class JustifyItems
|
||||
{
|
||||
Start,
|
||||
Center,
|
||||
End,
|
||||
Stretch
|
||||
};
|
||||
enum class AlignContent
|
||||
{
|
||||
Start,
|
||||
Center,
|
||||
End,
|
||||
Stretch,
|
||||
SpaceBetween,
|
||||
SpaceAround
|
||||
};
|
||||
enum class JustifyContent
|
||||
{
|
||||
Start,
|
||||
Center,
|
||||
End,
|
||||
Stretch,
|
||||
SpaceBetween,
|
||||
SpaceAround
|
||||
};
|
||||
struct GridArea
|
||||
{
|
||||
// 0-based indices
|
||||
size_t row {0};
|
||||
size_t column {0};
|
||||
size_t rowSpan {1};
|
||||
size_t colSpan {1};
|
||||
};
|
||||
struct Percentage
|
||||
{
|
||||
double value {0.0}; // 0.0 - 100.0
|
||||
};
|
||||
struct Auto
|
||||
{
|
||||
};
|
||||
using SizeSpec = std::variant<Auto, CCoord, Percentage>;
|
||||
|
||||
size_t rows {1};
|
||||
size_t columns {1};
|
||||
std::vector<SizeSpec> autoRows;
|
||||
std::vector<SizeSpec> autoColumns;
|
||||
CCoord rowGap {0.0};
|
||||
CCoord columnGap {0.0};
|
||||
AlignItems alignItems {AlignItems::Stretch};
|
||||
JustifyItems justifyItems {JustifyItems::Stretch};
|
||||
AlignContent alignContent {AlignContent::Stretch};
|
||||
JustifyContent justifyContent {JustifyContent::Stretch};
|
||||
|
||||
std::vector<GridArea> gridAreas; // Optional: one per child, defines grid area (span)
|
||||
};
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
/** grid view layouter
|
||||
*
|
||||
* @ingroup new_in_4_15
|
||||
*/
|
||||
class GridLayouter : public IViewLayouter,
|
||||
public NonAtomicReferenceCounted
|
||||
{
|
||||
public:
|
||||
using LayoutData = std::vector<std::pair<CRect, std::optional<ViewLayout>>>;
|
||||
|
||||
GridLayouter (const GridLayoutProperties& props = {});
|
||||
|
||||
void setProperties (const GridLayoutProperties& props);
|
||||
const GridLayoutProperties& getProperties () const;
|
||||
GridLayoutProperties& getProperties ();
|
||||
|
||||
bool applyLayout (CViewContainer& container, const Children& children,
|
||||
const ViewLayout& layout) override;
|
||||
std::optional<ViewLayout> calculateLayout (const CViewContainer& view, const Children& children,
|
||||
const CRect& newSize) override;
|
||||
|
||||
private:
|
||||
static constexpr auto kAutoSize = std::numeric_limits<CCoord>::min ();
|
||||
|
||||
std::vector<CCoord> calculateAutoRows (CCoord& usedRowHeight, CCoord availableHeight,
|
||||
size_t& outNumAutoRows) const;
|
||||
std::vector<CCoord> calculateAutoColumns (CCoord& usedColWidth, CCoord availableWidth,
|
||||
size_t& outNumAutoCols) const;
|
||||
|
||||
GridLayoutProperties gridProps;
|
||||
};
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
} // namespace VSTGUI
|
||||
@@ -0,0 +1,31 @@
|
||||
// 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
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "baseviewlayouter.h"
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
namespace VSTGUI {
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
/** A view layouter that does not layout any views.
|
||||
*
|
||||
* @ingroup new_in_4_15
|
||||
*/
|
||||
struct NoViewLayouter final : BaseViewLayouter,
|
||||
NonAtomicReferenceCounted
|
||||
{
|
||||
NoViewLayouter () = default;
|
||||
|
||||
std::optional<ViewLayout> calculateLayout (const CViewContainer& container,
|
||||
const Children& children,
|
||||
const CRect& newSize) override
|
||||
{
|
||||
return {ViewLayout {newSize, LayoutData {}}};
|
||||
}
|
||||
};
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
} // VSTGUI
|
||||
Reference in New Issue
Block a user