/************************************************************************* * 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); } }