using System; using System.Collections.Generic; using System.Numerics; namespace HSUI.Helpers { public struct LayoutInfo { public readonly uint TotalRowCount; public readonly uint TotalColCount; public readonly uint RealRowCount; public readonly uint RealColCount; public readonly Vector2 ContentSize; public LayoutInfo(uint totalRowCount, uint totalColCount, uint realRowCount, uint realColCount, Vector2 contentSize) { TotalRowCount = totalRowCount; TotalColCount = totalColCount; RealRowCount = realRowCount; RealColCount = realColCount; ContentSize = contentSize; } } public static class LayoutHelper { // Calculates rows and columns. Used for status effect lists and party frames. public static LayoutInfo CalculateLayout( Vector2 maxSize, Vector2 itemSize, uint count, Vector2 padding, bool fillRowsFirst ) { uint rowCount = 1; uint colCount = 1; uint realRowCount = 1; uint realColCount = 1; if (maxSize.X < itemSize.X) { colCount = count; realColCount = count; } else if (maxSize.Y < itemSize.Y) { rowCount = count; realRowCount = count; } else { if (fillRowsFirst) { float remainingWidth = maxSize.X; colCount = 0; while (remainingWidth > 0) { remainingWidth -= (itemSize.X + padding.X); colCount++; } if (itemSize.X * colCount + padding.X * (colCount - 1) > maxSize.X) { colCount = Math.Max(1, colCount - 1); } rowCount = (uint)Math.Ceiling((double)count / colCount); int remaining = (int)(count - colCount); while (remaining > 0) { realRowCount++; remaining -= (int)colCount; } realColCount = Math.Min(count, colCount); } else { float remainingHeight = maxSize.Y; rowCount = 0; while (remainingHeight > 0) { remainingHeight -= (itemSize.Y + padding.Y); rowCount++; } if (itemSize.Y * rowCount + padding.Y * (rowCount - 1) > maxSize.Y) { rowCount = Math.Max(1, rowCount - 1); } colCount = (uint)Math.Ceiling((double)count / rowCount); int remaining = (int)(count - rowCount); while (remaining > 0) { realColCount++; remaining -= (int)rowCount; } realRowCount = Math.Min(count, rowCount); } } Vector2 contentSize = new Vector2( realColCount * itemSize.X + (realColCount - 1) * padding.X, realRowCount * itemSize.Y + (realRowCount - 1) * padding.Y ); return new LayoutInfo(rowCount, colCount, realRowCount, realColCount, contentSize); } private static List DirectionOptionsValues = new List() { GrowthDirections.Right | GrowthDirections.Down, GrowthDirections.Right | GrowthDirections.Up, GrowthDirections.Left | GrowthDirections.Down, GrowthDirections.Left | GrowthDirections.Up, GrowthDirections.Centered | GrowthDirections.Up, GrowthDirections.Centered | GrowthDirections.Down, GrowthDirections.Centered | GrowthDirections.Left, GrowthDirections.Centered | GrowthDirections.Right }; public static GrowthDirections GrowthDirectionsFromIndex(int index) { if (index > 0 && index < DirectionOptionsValues.Count) { return DirectionOptionsValues[index]; } return DirectionOptionsValues[0]; } public static int IndexFromGrowthDirections(GrowthDirections directions) { int index = DirectionOptionsValues.FindIndex(d => d == directions); return index > 0 ? index : 0; } public static bool GetFillsRowsFirst(bool fallback, GrowthDirections directions) { if ((directions & GrowthDirections.Centered) != 0) { if ((directions & GrowthDirections.Up) != 0 || (directions & GrowthDirections.Down) != 0) { return true; } else if ((directions & GrowthDirections.Left) != 0 || (directions & GrowthDirections.Right) != 0) { return false; } } return fallback; } public static void CalculateAxisDirections( GrowthDirections growthDirections, int row, int col, uint elementCount, Vector2 size, Vector2 iconSize, Vector2 iconPadding, out Vector2 direction, out Vector2 offset) { if ((growthDirections & GrowthDirections.Centered) != 0) { if ((growthDirections & GrowthDirections.Up) != 0 || (growthDirections & GrowthDirections.Down) != 0) { int elementsPerRow = (int)(size.X / (iconSize.X + iconPadding.X)); long elementsInRow = Math.Min(elementsPerRow, elementCount - (elementsPerRow * row)); direction.X = 1; direction.Y = (growthDirections & GrowthDirections.Down) != 0 ? 1 : -1; offset.X = -(iconSize.X + iconPadding.X) * elementsInRow / 2f; offset.Y = direction.Y == 1 ? 0 : -iconSize.Y; } else// if ((growthDirections & GrowthDirections.Left) != 0 || (growthDirections & GrowthDirections.Right) != 0) { int elementsPerCol = (int)(size.Y / (iconSize.Y + iconPadding.Y)); long elementsInCol = Math.Min(elementsPerCol, elementCount - (elementsPerCol * col)); direction.X = (growthDirections & GrowthDirections.Left) != 0 ? -1 : 1; direction.Y = 1; offset.X = direction.X == 1 ? 0 : -iconSize.X; offset.Y = -(iconSize.Y + iconPadding.Y) * elementsInCol / 2f; } } else { direction.X = (growthDirections & GrowthDirections.Right) != 0 ? 1 : -1; direction.Y = (growthDirections & GrowthDirections.Down) != 0 ? 1 : -1; offset.X = direction.X == 1 ? 0 : -iconSize.X; offset.Y = direction.Y == 1 ? 0 : -iconSize.Y; } } public static Vector2 CalculateStartPosition(Vector2 position, Vector2 size, GrowthDirections growthDirections) { Vector2 area = size; if ((growthDirections & GrowthDirections.Left) != 0) { area.X = -area.X; } if ((growthDirections & GrowthDirections.Up) != 0) { area.Y = -area.Y; } Vector2 startPos = position; if ((growthDirections & GrowthDirections.Centered) != 0) { if ((growthDirections & GrowthDirections.Up) != 0 || (growthDirections & GrowthDirections.Down) != 0) { startPos.X = position.X - size.X / 2f; } else if ((growthDirections & GrowthDirections.Left) != 0 || (growthDirections & GrowthDirections.Right) != 0) { startPos.Y = position.Y - size.Y / 2f; } } Vector2 endPos = position + area; if (endPos.X < position.X) { startPos.X = endPos.X; } if (endPos.Y < position.Y) { startPos.Y = endPos.Y; } return startPos; } public static (List, Vector2, Vector2) CalculateIconPositions( GrowthDirections directions, uint count, Vector2 position, Vector2 size, Vector2 iconSize, Vector2 iconPadding, bool fillRowsFirst, LayoutInfo layoutInfo) { List list = new List(); Vector2 minPos = new Vector2(float.MaxValue, float.MaxValue); Vector2 maxPos = Vector2.Zero; int row = 0; int col = 0; for (int i = 0; i < count; i++) { CalculateAxisDirections( directions, row, col, count, size, iconSize, iconPadding, out Vector2 direction, out Vector2 offset ); Vector2 pos = new Vector2( position.X + offset.X + iconSize.X * col * direction.X + iconPadding.X * col * direction.X, position.Y + offset.Y + iconSize.Y * row * direction.Y + iconPadding.Y * row * direction.Y ); minPos.X = Math.Min(pos.X, minPos.X); minPos.Y = Math.Min(pos.Y, minPos.Y); maxPos.X = Math.Max(pos.X + iconSize.X, maxPos.X); maxPos.Y = Math.Max(pos.Y + iconSize.Y, maxPos.Y); list.Add(pos); // rows / columns if (fillRowsFirst) { col += 1; if (col >= layoutInfo.TotalColCount) { col = 0; row += 1; } } else { row += 1; if (row >= layoutInfo.TotalRowCount) { row = 0; col += 1; } } } return (list, minPos, maxPos); } } [Flags] public enum GrowthDirections : short { Up = 1, Down = 2, Left = 4, Right = 8, Centered = 16, } }