ModernUO/Projects/UOContent/Gumps/Base/GridLayout/ListViewLayout.cs
2026-03-05 19:36:54 -08:00

239 lines
8.5 KiB
C#

/*************************************************************************
* ModernUO *
* Copyright 2019-2026 - ModernUO Development Team *
* Email: hi@modernuo.com *
* File: ListViewLayout.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 <http://www.gnu.org/licenses/>. *
*************************************************************************/
using System;
using System.Runtime.CompilerServices;
namespace Server.Gumps;
/// <summary>
/// Calculates pagination and provides helper methods for a paginated list view.
/// This struct does NOT hold column data - column positions/widths are passed
/// separately via spans to avoid ref struct limitations.
/// </summary>
public ref struct ListViewLayout
{
private readonly int _originY;
private readonly int _headerHeight;
private readonly int _rowHeight;
/// <summary>
/// The total number of items in the list.
/// </summary>
public readonly int TotalItems;
/// <summary>
/// The number of items that can be displayed per page.
/// </summary>
public readonly int ItemsPerPage;
/// <summary>
/// The current page index (0-based).
/// </summary>
public readonly int CurrentPage;
/// <summary>
/// The total number of pages.
/// </summary>
public readonly int TotalPages;
/// <summary>
/// The index of the first item on the current page.
/// </summary>
public readonly int StartIndex;
/// <summary>
/// The number of items visible on the current page.
/// </summary>
public readonly int VisibleCount;
/// <summary>
/// Whether there is a previous page.
/// </summary>
public readonly bool CanGoBack;
/// <summary>
/// Whether there is a next page.
/// </summary>
public readonly bool CanGoNext;
/// <summary>
/// The number of columns in the list.
/// </summary>
public readonly int ColumnCount;
/// <summary>
/// The height of each data row.
/// </summary>
public int RowHeight
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => _rowHeight;
}
/// <summary>
/// The height of the header row.
/// </summary>
public int HeaderHeight
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => _headerHeight;
}
/// <summary>
/// Creates a new ListViewLayout and computes column positions into the provided spans.
/// </summary>
/// <param name="originX">The X origin of the list area.</param>
/// <param name="originY">The Y origin of the list area.</param>
/// <param name="width">The total width of the list area.</param>
/// <param name="height">The total height of the list area.</param>
/// <param name="totalItems">The total number of items in the data source.</param>
/// <param name="currentPage">The current page index (0-based).</param>
/// <param name="rowHeight">The height of each data row.</param>
/// <param name="headerHeight">The height of the header row (0 for no header).</param>
/// <param name="columnSpec">Space-separated column size specification.</param>
/// <param name="columnPositions">Span to receive calculated column X positions.</param>
/// <param name="columnWidths">Span to receive calculated column widths.</param>
/// <returns>The configured ListViewLayout.</returns>
public static ListViewLayout Create(
int originX,
int originY,
int width,
int height,
int totalItems,
int currentPage,
int rowHeight,
int headerHeight,
ReadOnlySpan<char> columnSpec,
Span<int> columnPositions,
Span<int> columnWidths
)
{
// Parse and compute column widths
var columnCount = GridCalculator.ComputeFromSpec(columnSpec, width, originX, columnPositions, columnWidths);
return new ListViewLayout(
originY,
headerHeight, rowHeight,
totalItems, currentPage,
height,
columnCount
);
}
private ListViewLayout(
int originY,
int headerHeight,
int rowHeight,
int totalItems,
int currentPage,
int totalHeight,
int columnCount
)
{
_originY = originY;
_headerHeight = headerHeight;
_rowHeight = rowHeight;
ColumnCount = columnCount;
TotalItems = totalItems;
// Calculate items per page based on available height
var contentHeight = totalHeight - headerHeight;
ItemsPerPage = contentHeight / rowHeight;
// Calculate pagination
if (totalItems > 0 && ItemsPerPage > 0)
{
TotalPages = (totalItems + ItemsPerPage - 1) / ItemsPerPage;
CurrentPage = Math.Clamp(currentPage, 0, TotalPages - 1);
StartIndex = CurrentPage * ItemsPerPage;
VisibleCount = Math.Min(ItemsPerPage, totalItems - StartIndex);
CanGoBack = CurrentPage > 0;
CanGoNext = CurrentPage < TotalPages - 1;
}
else
{
TotalPages = 1;
CurrentPage = 0;
StartIndex = 0;
VisibleCount = 0;
CanGoBack = false;
CanGoNext = false;
}
}
/// <summary>
/// Gets the header cell for the specified column using pre-computed column data.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public GridCell GetHeaderCell(int column, ReadOnlySpan<int> columnPositions, ReadOnlySpan<int> columnWidths) =>
new(columnPositions[column], _originY, columnWidths[column], _headerHeight);
/// <summary>
/// Gets the data cell for the specified visible row and column.
/// </summary>
/// <param name="visibleRowIndex">The 0-based index within visible rows (not data index).</param>
/// <param name="column">The column index.</param>
/// <param name="columnPositions">Pre-computed column X positions.</param>
/// <param name="columnWidths">Pre-computed column widths.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public GridCell GetRowCell(
int visibleRowIndex, int column, ReadOnlySpan<int> columnPositions, ReadOnlySpan<int> columnWidths
) => new(
columnPositions[column],
_originY + _headerHeight + visibleRowIndex * _rowHeight,
columnWidths[column],
_rowHeight
);
/// <summary>
/// Gets the data index for a visible row.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public int GetDataIndex(int visibleRowIndex) => StartIndex + visibleRowIndex;
/// <summary>
/// Gets the Y position for a visible row.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public int GetRowY(int visibleRowIndex) => _originY + _headerHeight + visibleRowIndex * _rowHeight;
/// <summary>
/// Gets the X position of a column.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static int GetColumnX(ReadOnlySpan<int> columnPositions, int column) => columnPositions[column];
/// <summary>
/// Gets the width of a column.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static int GetColumnWidth(ReadOnlySpan<int> columnWidths, int column) => columnWidths[column];
/// <summary>
/// Gets the horizontal center of a column.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static int GetColumnCenterX(ReadOnlySpan<int> columnPositions, ReadOnlySpan<int> columnWidths, int column) =>
columnPositions[column] + columnWidths[column] / 2;
/// <summary>
/// Gets the right edge of a column.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static int GetColumnRightX(ReadOnlySpan<int> columnPositions, ReadOnlySpan<int> columnWidths, int column) =>
columnPositions[column] + columnWidths[column];
}