/*************************************************************************
* ModernUO *
* Copyright 2019-2026 - ModernUO Development Team *
* Email: hi@modernuo.com *
* File: GridCalculator.cs *
* *
* This program is free software: you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation, either version 3 of the License, or *
* (at your option) any later version. *
* *
* You should have received a copy of the GNU General Public License *
* along with this program. If not, see . *
*************************************************************************/
using System;
using System.Runtime.CompilerServices;
namespace Server.Gumps;
///
/// Static helper for calculating grid cell positions without heap allocations.
/// All results are written to caller-provided spans using stackalloc.
///
public static class GridCalculator
{
///
/// Maximum supported columns or rows per grid.
///
public const int MaxTracks = 32;
///
/// Computes track sizes from size specifications.
/// Writes positions and sizes to the provided spans.
///
/// The size specifications for each track.
/// The total available size (width or height).
/// The starting position (x or y offset).
/// Output: position of each track.
/// Output: size of each track.
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void ComputeTrackSizes(
ReadOnlySpan specs,
int totalSize,
int origin,
Span positions,
Span sizes
)
{
var trackCount = specs.Length;
// First pass: calculate absolute/percent sizes and count star tracks
var remainingSize = totalSize;
var starCount = 0;
for (var i = 0; i < trackCount; i++)
{
var spec = specs[i];
switch (spec.Type)
{
case GridSizeType.Absolute:
{
sizes[i] = spec.Value;
remainingSize -= spec.Value;
break;
}
case GridSizeType.Percent:
{
sizes[i] = totalSize * spec.Value / 100;
remainingSize -= sizes[i];
break;
}
case GridSizeType.Star:
{
starCount++;
sizes[i] = -1; // Mark as star for second pass
break;
}
}
}
// Second pass: distribute remaining space to star tracks
if (starCount > 0 && remainingSize > 0)
{
var starSize = remainingSize / starCount;
for (var i = 0; i < trackCount; i++)
{
if (sizes[i] == -1)
{
sizes[i] = starSize;
}
}
}
else
{
// No star tracks or no remaining space - set any marked stars to 0
for (var i = 0; i < trackCount; i++)
{
if (sizes[i] < 0)
{
sizes[i] = 0;
}
}
}
// Third pass: calculate positions
var pos = origin;
for (var i = 0; i < trackCount; i++)
{
positions[i] = pos;
pos += sizes[i];
}
}
///
/// Computes uniform track sizes (equal-sized cells).
///
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void ComputeUniformTrackSizes(
int trackCount,
int totalSize,
int origin,
Span positions,
Span sizes
)
{
var trackSize = totalSize / trackCount;
var pos = origin;
for (var i = 0; i < trackCount; i++)
{
positions[i] = pos;
sizes[i] = trackSize;
pos += trackSize;
}
}
///
/// Parses a size specification string and computes track sizes.
///
/// Space-separated size specification (e.g., "10* * 100").
/// The total available size.
/// The starting position.
/// Output: position of each track.
/// Output: size of each track.
/// The number of tracks computed.
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static int ComputeFromSpec(
ReadOnlySpan sizeSpec,
int totalSize,
int origin,
Span positions,
Span sizes
) => ComputeFromSpec(sizeSpec, totalSize, origin, 0, positions, sizes);
///
/// Parses a size specification string and computes track sizes with gaps between tracks.
///
/// Space-separated size specification (e.g., "10* * 100").
/// The total available size.
/// The starting position.
/// The gap size between tracks.
/// Output: position of each track.
/// Output: size of each track.
/// The number of tracks computed.
public static int ComputeFromSpec(
ReadOnlySpan sizeSpec,
int totalSize,
int origin,
int gap,
Span positions,
Span sizes)
{
Span specs = stackalloc GridSizeSpec[MaxTracks];
var trackCount = GridSizeSpec.ParseAll(sizeSpec, specs);
if (trackCount > 0)
{
// Subtract total gap space from available size before computing track sizes
var totalGapSpace = gap * (trackCount - 1);
var availableSize = totalSize - totalGapSpace;
ComputeTrackSizes(specs[..trackCount], availableSize, origin, positions, sizes);
// Adjust positions to account for gaps
if (gap > 0)
{
for (var i = 1; i < trackCount; i++)
{
positions[i] += gap * i;
}
}
}
return trackCount;
}
///
/// Gets a cell at the specified column and row from pre-computed grid arrays.
///
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static GridCell GetCell(
ReadOnlySpan columnPositions,
ReadOnlySpan columnWidths,
ReadOnlySpan rowPositions,
ReadOnlySpan rowHeights,
int column,
int row
) => new(columnPositions[column], rowPositions[row], columnWidths[column], rowHeights[row]);
///
/// Gets a cell spanning multiple columns and/or rows from pre-computed grid arrays.
///
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static GridCell GetCell(
ReadOnlySpan columnPositions,
ReadOnlySpan columnWidths,
ReadOnlySpan rowPositions,
ReadOnlySpan rowHeights,
int column,
int row,
int columnSpan,
int rowSpan
)
{
var width = 0;
var endCol = Math.Min(column + columnSpan, columnWidths.Length);
for (var c = column; c < endCol; c++)
{
width += columnWidths[c];
}
var height = 0;
var endRow = Math.Min(row + rowSpan, rowHeights.Length);
for (var r = row; r < endRow; r++)
{
height += rowHeights[r];
}
return new GridCell(columnPositions[column], rowPositions[row], width, height);
}
}