using System;
using System.Diagnostics.CodeAnalysis;
using Robust.Shared.Maths;
namespace Robust.Client.UserInterface.Controls
{
///
/// A container that lays out its children in a grid.
///
public class GridContainer : Container
{
private int _columns = 1;
///
/// The amount of columns to organize the children into.
///
///
/// Thrown if the value assigned is less than or equal to 0.
///
public int Columns
{
get => _columns;
set
{
if (value <= 0)
{
throw new ArgumentOutOfRangeException(nameof(value), value, "Value must be greater than zero.");
}
_columns = value;
MinimumSizeChanged();
UpdateLayout();
}
}
///
/// The amount of rows being used for the current amount of children.
///
public int Rows
{
get
{
if (ChildCount == 0)
{
return 1;
}
var div = ChildCount / Columns;
if (ChildCount % Columns != 0)
{
div += 1;
}
return div;
}
}
private int? _vSeparationOverride;
[SuppressMessage("ReSharper", "StringLiteralTypo")]
public int? VSeparationOverride
{
get => _vSeparationOverride;
set => _vSeparationOverride = value;
}
private int? _hSeparationOverride;
[SuppressMessage("ReSharper", "StringLiteralTypo")]
public int? HSeparationOverride
{
get => _hSeparationOverride;
set => _hSeparationOverride = value;
}
private Vector2i Separations => (_hSeparationOverride ?? 4, _vSeparationOverride ?? 4);
protected override Vector2 CalculateMinimumSize()
{
var (wSep, hSep) = (Vector2i) (Separations * UIScale);
var rows = Rows;
// Minimum width of the columns.
Span columnSizes = stackalloc int[_columns];
// Minimum height of the rows.
Span rowSizes = stackalloc int[rows];
var index = 0;
foreach (var child in Children)
{
if (!child.Visible)
{
index--;
continue;
}
var row = index / _columns;
var column = index % _columns;
var (minSizeX, minSizeY) = child.CombinedPixelMinimumSize;
columnSizes[column] = Math.Max(minSizeX, columnSizes[column]);
rowSizes[row] = Math.Max(minSizeY, rowSizes[row]);
index += 1;
}
var minWidth = AccumSizes(columnSizes, wSep);
var minHeight = AccumSizes(rowSizes, hSep);
return new Vector2(minWidth, minHeight) / UIScale;
}
private static int AccumSizes(Span sizes, int separator)
{
var totalSize = 0;
var firstColumn = true;
foreach (var size in sizes)
{
totalSize += size;
if (firstColumn)
{
firstColumn = false;
}
else
{
totalSize += separator;
}
}
return totalSize;
}
protected override void LayoutUpdateOverride()
{
var rows = Rows;
// Minimum width of the columns.
Span columnSizes = stackalloc int[_columns];
// Minimum height of the rows.
Span rowSizes = stackalloc int[rows];
// Columns that are set to expand horizontally.
Span columnExpand = stackalloc bool[_columns];
// Columns that are set to expand vertically.
Span rowExpand = stackalloc bool[rows];
// Get minSize and size flag expand of each column and row.
// All we need to apply the same logic BoxContainer does.
var index = 0;
foreach (var child in Children)
{
if (!child.Visible)
{
continue;
}
var row = index / _columns;
var column = index % _columns;
var (minSizeX, minSizeY) = child.CombinedPixelMinimumSize;
columnSizes[column] = Math.Max(minSizeX, columnSizes[column]);
rowSizes[row] = Math.Max(minSizeY, rowSizes[row]);
columnExpand[column] = columnExpand[column] || (child.SizeFlagsHorizontal & SizeFlags.Expand) != 0;
rowExpand[row] = rowExpand[row] || (child.SizeFlagsVertical & SizeFlags.Expand) != 0;
index += 1;
}
// Basically now we just apply BoxContainer logic on rows and columns.
var (vSep, hSep) = (Vector2i) (Separations * UIScale);
var stretchMinX = 0;
var stretchMinY = 0;
// We do not use stretch ratios because Godot doesn't,
// which makes sense since what happens if two things on the same column have a different stretch ratio?
// Maybe there's an answer for that but I'm too lazy to think of a concrete solution
// and it would make this code more complex so...
// pass.
var stretchCountX = 0;
var stretchCountY = 0;
for (var i = 0; i < columnSizes.Length; i++)
{
if (!columnExpand[i])
{
stretchMinX += columnSizes[i];
}
else
{
stretchCountX++;
}
}
for (var i = 0; i < rowSizes.Length; i++)
{
if (!rowExpand[i])
{
stretchMinY += rowSizes[i];
}
else
{
stretchCountY++;
}
}
var stretchMaxX = Width - hSep * (_columns - 1);
var stretchMaxY = Height - vSep * (rows - 1);
var stretchAvailX = Math.Max(0, stretchMaxX - stretchMinX);
var stretchAvailY = Math.Max(0, stretchMaxY - stretchMinY);
for (var i = 0; i < columnSizes.Length; i++)
{
if (!columnExpand[i])
{
continue;
}
columnSizes[i] = (int) (stretchAvailX / stretchCountX);
}
for (var i = 0; i < rowSizes.Length; i++)
{
if (!rowExpand[i])
{
continue;
}
rowSizes[i] = (int) (stretchAvailY / stretchCountY);
}
// Actually lay them out.
var vOffset = 0;
var hOffset = 0;
index = 0;
for (var i = 0; i < ChildCount; i++, index++)
{
var child = GetChild(i);
if (!child.Visible)
{
index--;
continue;
}
var row = index / _columns;
var column = index % _columns;
if (column == 0)
{
// Just started a new row.
hOffset = 0;
if (row != 0)
{
vOffset += vSep + rowSizes[row - 1];
}
}
var box = UIBox2i.FromDimensions(hOffset, vOffset, columnSizes[column], rowSizes[row]);
FitChildInPixelBox(child, box);
hOffset += columnSizes[column] + hSep;
}
}
}
}