/*************************************************************************
* ModernUO *
* Copyright 2019-2026 - ModernUO Development Team *
* Email: hi@modernuo.com *
* File: PropsLayoutExtensions.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;
using static Server.Gumps.PropsConfig;
namespace Server.Gumps;
///
/// Shared helper for advancing to the next row in PropsConfig-style layouts.
/// Resets x to content origin and advances y by one row height.
///
public static class PropsLayout
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void NextRow(ref int x, ref int y, int entryHeight = EntryHeight)
{
x = BorderSize + OffsetSize;
y += entryHeight + OffsetSize;
}
}
///
/// Extension methods for legacy Gump that provide PropsConfig-style layout building blocks.
/// Each method encapsulates a common row pattern used across staff gumps.
///
public static class PropsLayoutExtensions
{
extension(Gump gump)
{
///
/// Adds the standard PropsConfig frame: outer background + inner offset region.
/// Outputs the content origin (x, y) for the first row.
/// Does NOT call AddPage — callers handle paging strategy.
///
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void AddPropsFrame(
int totalWidth,
int rowCount,
out int x,
out int y,
int entryHeight = EntryHeight
)
{
var totalHeight = OffsetSize + (entryHeight + OffsetSize) * rowCount;
gump.AddBackground(0, 0, BorderSize + totalWidth + BorderSize, BorderSize + totalHeight + BorderSize, BackGumpID);
gump.AddImageTiled(BorderSize, BorderSize, totalWidth, totalHeight, OffsetGumpID);
x = BorderSize + OffsetSize;
y = BorderSize + OffsetSize;
}
///
/// Adds a 3-column navigation header: [Prev] [Title] [Next].
/// All three columns use HeaderGumpID backgrounds.
///
public void AddPropsHeader(
int totalWidth,
ref int x,
ref int y,
string title,
bool hasPrev,
int prevButtonId,
bool hasNext,
int nextButtonId,
GumpButtonType prevType = GumpButtonType.Reply,
int prevParam = 0,
GumpButtonType nextType = GumpButtonType.Reply,
int nextParam = 0,
int entryHeight = EntryHeight
)
{
var emptyWidth = totalWidth - PrevWidth - NextWidth - OffsetSize * 4;
gump.AddImageTiled(x, y, PrevWidth, entryHeight, HeaderGumpID);
if (hasPrev)
{
gump.AddButton(x + PrevOffsetX, y + PrevOffsetY, PrevButtonID1, PrevButtonID2, prevButtonId, prevType, prevParam);
}
x += PrevWidth + OffsetSize;
gump.AddImageTiled(x, y, emptyWidth, entryHeight, HeaderGumpID);
if (title != null)
{
gump.AddHtml(x, y, emptyWidth, entryHeight, title);
}
x += emptyWidth + OffsetSize;
gump.AddImageTiled(x, y, NextWidth, entryHeight, HeaderGumpID);
if (hasNext)
{
gump.AddButton(x + NextOffsetX, y + NextOffsetY, NextButtonID1, NextButtonID2, nextButtonId, nextType, nextParam);
}
}
///
/// Adds a 4-column navigation header: [Back] [Title] [Prev] [Next].
/// Back/Prev/Next use HeaderGumpID, Title uses EntryGumpID.
///
public void AddPropsHeaderWithBack(
int totalWidth,
ref int x,
ref int y,
string title,
bool hasBack,
int backButtonId,
bool hasPrev,
int prevButtonId,
bool hasNext,
int nextButtonId,
GumpButtonType nextType = GumpButtonType.Reply,
int nextParam = 0,
int entryHeight = EntryHeight
)
{
var emptyWidth = totalWidth - PrevWidth * 2 - NextWidth - OffsetSize * 5;
gump.AddImageTiled(x, y, PrevWidth, entryHeight, HeaderGumpID);
if (hasBack)
{
gump.AddButton(x + PrevOffsetX, y + PrevOffsetY, PrevButtonID1, PrevButtonID2, backButtonId);
}
x += PrevWidth + OffsetSize;
gump.AddImageTiled(x, y, emptyWidth, entryHeight, EntryGumpID);
if (title != null)
{
gump.AddHtml(
x + TextOffsetX,
y + (entryHeight - EntryHeight) / 2,
emptyWidth - TextOffsetX,
entryHeight,
$"
{title}"
);
}
x += emptyWidth + OffsetSize;
gump.AddImageTiled(x, y, PrevWidth, entryHeight, HeaderGumpID);
if (hasPrev)
{
gump.AddButton(x + PrevOffsetX, y + PrevOffsetY, PrevButtonID1, PrevButtonID2, prevButtonId);
}
x += PrevWidth + OffsetSize;
gump.AddImageTiled(x, y, NextWidth, entryHeight, HeaderGumpID);
if (hasNext)
{
gump.AddButton(x + NextOffsetX, y + NextOffsetY, NextButtonID1, NextButtonID2, nextButtonId, nextType, nextParam);
}
}
///
/// Adds an entry row with a cropped label and optional action button.
/// Layout: [Label:EntryGumpID] [Button:SetGumpID]
///
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void AddPropsEntryButton(
ref int x,
ref int y,
int entryWidth,
string label,
bool hasButton,
int buttonId,
int textHue = TextHue,
int entryHeight = EntryHeight
)
{
gump.AddImageTiled(x, y, entryWidth, entryHeight, EntryGumpID);
gump.AddLabelCropped(x + TextOffsetX, y, entryWidth - TextOffsetX, entryHeight, textHue, label);
x += entryWidth + OffsetSize;
if (SetGumpID != 0)
{
gump.AddImageTiled(x, y, SetWidth, entryHeight, SetGumpID);
}
if (hasButton)
{
gump.AddButton(x + SetOffsetX, y + SetOffsetY, SetButtonID1, SetButtonID2, buttonId);
}
}
///
/// Adds a two-column entry row with name, value, and optional action button.
/// Layout: [Name:EntryGumpID] [Value:EntryGumpID] [Button:SetGumpID]
///
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void AddPropsEntryNameValue(
ref int x,
ref int y,
int nameWidth,
int valueWidth,
string name,
string value,
bool hasButton,
int buttonId,
int entryHeight = EntryHeight
)
{
gump.AddImageTiled(x, y, nameWidth, entryHeight, EntryGumpID);
gump.AddLabelCropped(x + TextOffsetX, y, nameWidth - TextOffsetX, entryHeight, TextHue, name);
x += nameWidth + OffsetSize;
gump.AddImageTiled(x, y, valueWidth, entryHeight, EntryGumpID);
gump.AddLabelCropped(x + TextOffsetX, y, valueWidth - TextOffsetX, entryHeight, TextHue, value);
x += valueWidth + OffsetSize;
if (SetGumpID != 0)
{
gump.AddImageTiled(x, y, SetWidth, entryHeight, SetGumpID);
}
if (hasButton)
{
gump.AddButton(x + SetOffsetX, y + SetOffsetY, SetButtonID1, SetButtonID2, buttonId);
}
}
///
/// Adds an entry row with a text input field and optional action button.
/// Layout: [TextEntry:EntryGumpID] [Button:SetGumpID]
///
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void AddPropsEntryTextInput(
ref int x,
ref int y,
int entryWidth,
int entryId,
string initialText,
bool hasButton,
int buttonId,
int entryHeight = EntryHeight
)
{
gump.AddImageTiled(x, y, entryWidth, entryHeight, EntryGumpID);
gump.AddTextEntry(x + TextOffsetX, y, entryWidth - TextOffsetX, entryHeight, TextHue, entryId, initialText);
x += entryWidth + OffsetSize;
if (SetGumpID != 0)
{
gump.AddImageTiled(x, y, SetWidth, entryHeight, SetGumpID);
}
if (hasButton)
{
gump.AddButton(x + SetOffsetX, y + SetOffsetY, SetButtonID1, SetButtonID2, buttonId);
}
}
///
/// Adds an entry row with a cropped label and no action button.
/// Layout: [Label:EntryGumpID] [Empty:SetGumpID]
///
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void AddPropsEntryLabel(
ref int x,
ref int y,
int entryWidth,
string label,
int entryHeight = EntryHeight
)
{
gump.AddImageTiled(x, y, entryWidth, entryHeight, EntryGumpID);
gump.AddLabelCropped(x + TextOffsetX, y, entryWidth - TextOffsetX, entryHeight, TextHue, label);
x += entryWidth + OffsetSize;
if (SetGumpID != 0)
{
gump.AddImageTiled(x, y, SetWidth, entryHeight, SetGumpID);
}
}
///
/// Adds a full-width type label row (used for PropsGump type group headers).
/// Layout: [TypeName:EntryGumpID spanning typeWidth] [Empty:SetGumpID]
///
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void AddPropsEntryType(
ref int x,
ref int y,
int typeWidth,
string typeName,
int entryHeight = EntryHeight
)
{
gump.AddImageTiled(x, y, typeWidth, entryHeight, EntryGumpID);
gump.AddLabelCropped(x + TextOffsetX, y, typeWidth - TextOffsetX, entryHeight, TextHue, typeName);
x += typeWidth + OffsetSize;
if (SetGumpID != 0)
{
gump.AddImageTiled(x, y, SetWidth, entryHeight, SetGumpID);
}
}
///
/// Adds a blank separator row spanning the full width.
///
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void AddPropsEntryBlank(
int x,
int y,
int totalWidth,
int entryHeight = EntryHeight
)
{
gump.AddImageTiled(x - OffsetSize, y, totalWidth, entryHeight, BackGumpID + 4);
}
}
}
///
/// Extension methods for DynamicGumpBuilder that provide PropsConfig-style layout building blocks.
/// Mirrors PropsLayoutExtensions but uses ReadOnlySpan<char> for text parameters.
///
public static class PropsLayoutBuilderExtensions
{
extension(ref DynamicGumpBuilder builder)
{
///
/// Adds the standard PropsConfig frame: outer background + inner offset region.
/// Outputs the content origin (x, y) for the first row.
/// Does NOT call AddPage — callers handle paging strategy.
///
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void AddPropsFrame(
int totalWidth,
int rowCount,
out int x,
out int y,
int entryHeight = EntryHeight
)
{
var totalHeight = OffsetSize + (entryHeight + OffsetSize) * rowCount;
builder.AddBackground(
0,
0,
BorderSize + totalWidth + BorderSize,
BorderSize + totalHeight + BorderSize,
BackGumpID
);
builder.AddImageTiled(BorderSize, BorderSize, totalWidth, totalHeight, OffsetGumpID);
x = BorderSize + OffsetSize;
y = BorderSize + OffsetSize;
}
///
/// Adds a 3-column navigation header: [Prev] [Title] [Next].
/// All three columns use HeaderGumpID backgrounds.
///
public void AddPropsHeader(
int totalWidth,
ref int x,
ref int y,
ReadOnlySpan title,
bool hasPrev,
int prevButtonId,
bool hasNext,
int nextButtonId,
GumpButtonType prevType = GumpButtonType.Reply,
int prevParam = 0,
GumpButtonType nextType = GumpButtonType.Reply,
int nextParam = 0,
int entryHeight = EntryHeight
)
{
var emptyWidth = totalWidth - PrevWidth - NextWidth - OffsetSize * 4;
builder.AddImageTiled(x, y, PrevWidth, entryHeight, HeaderGumpID);
if (hasPrev)
{
builder.AddButton(
x + PrevOffsetX,
y + PrevOffsetY,
PrevButtonID1,
PrevButtonID2,
prevButtonId,
prevType,
prevParam
);
}
x += PrevWidth + OffsetSize;
builder.AddImageTiled(x, y, emptyWidth, entryHeight, HeaderGumpID);
if (title.Length > 0)
{
builder.AddHtml(
x + TextOffsetX,
y,
emptyWidth - TextOffsetX,
entryHeight,
title,
align: TextAlignment.Center
);
}
x += emptyWidth + OffsetSize;
builder.AddImageTiled(x, y, NextWidth, entryHeight, HeaderGumpID);
if (hasNext)
{
builder.AddButton(
x + NextOffsetX,
y + NextOffsetY,
NextButtonID1,
NextButtonID2,
nextButtonId,
nextType,
nextParam
);
}
}
///
/// Adds a 4-column navigation header: [Back] [Title] [Prev] [Next].
/// Back/Prev/Next use HeaderGumpID, Title uses EntryGumpID.
///
public void AddPropsHeaderWithBack(
int totalWidth,
ref int x,
ref int y,
ReadOnlySpan title,
bool hasBack,
int backButtonId,
bool hasPrev,
int prevButtonId,
bool hasNext,
int nextButtonId,
GumpButtonType nextType = GumpButtonType.Reply,
int nextParam = 0,
int entryHeight = EntryHeight
)
{
var emptyWidth = totalWidth - PrevWidth * 2 - NextWidth - OffsetSize * 5;
builder.AddImageTiled(x, y, PrevWidth, entryHeight, HeaderGumpID);
if (hasBack)
{
builder.AddButton(x + PrevOffsetX, y + PrevOffsetY, PrevButtonID1, PrevButtonID2, backButtonId);
}
x += PrevWidth + OffsetSize;
builder.AddImageTiled(x, y, emptyWidth, entryHeight, EntryGumpID);
if (title.Length > 0)
{
builder.AddHtml(
x + TextOffsetX,
y + (entryHeight - EntryHeight) / 2,
emptyWidth - TextOffsetX,
entryHeight,
title,
align: TextAlignment.Center
);
}
x += emptyWidth + OffsetSize;
builder.AddImageTiled(x, y, PrevWidth, entryHeight, HeaderGumpID);
if (hasPrev)
{
builder.AddButton(x + PrevOffsetX, y + PrevOffsetY, PrevButtonID1, PrevButtonID2, prevButtonId);
}
x += PrevWidth + OffsetSize;
builder.AddImageTiled(x, y, NextWidth, entryHeight, HeaderGumpID);
if (hasNext)
{
builder.AddButton(
x + NextOffsetX,
y + NextOffsetY,
NextButtonID1,
NextButtonID2,
nextButtonId,
nextType,
nextParam
);
}
}
///
/// Adds an entry row with a cropped label and optional action button.
/// Layout: [Label:EntryGumpID] [Button:SetGumpID]
///
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void AddPropsEntryButton(
ref int x,
ref int y,
int entryWidth,
ReadOnlySpan label,
bool hasButton,
int buttonId,
int textHue = TextHue,
int entryHeight = EntryHeight
)
{
builder.AddImageTiled(x, y, entryWidth, entryHeight, EntryGumpID);
builder.AddLabelCropped(x + TextOffsetX, y, entryWidth - TextOffsetX, entryHeight, textHue, label);
x += entryWidth + OffsetSize;
if (SetGumpID != 0)
{
builder.AddImageTiled(x, y, SetWidth, entryHeight, SetGumpID);
}
if (hasButton)
{
builder.AddButton(x + SetOffsetX, y + SetOffsetY, SetButtonID1, SetButtonID2, buttonId);
}
}
///
/// Adds a two-column entry row with name, value, and optional action button.
/// Layout: [Name:EntryGumpID] [Value:EntryGumpID] [Button:SetGumpID]
///
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void AddPropsEntryNameValue(
ref int x,
ref int y,
int nameWidth,
int valueWidth,
ReadOnlySpan name,
ReadOnlySpan value,
bool hasButton,
int buttonId,
int entryHeight = EntryHeight
)
{
builder.AddImageTiled(x, y, nameWidth, entryHeight, EntryGumpID);
builder.AddLabelCropped(x + TextOffsetX, y, nameWidth - TextOffsetX, entryHeight, TextHue, name);
x += nameWidth + OffsetSize;
builder.AddImageTiled(x, y, valueWidth, entryHeight, EntryGumpID);
builder.AddLabelCropped(x + TextOffsetX, y, valueWidth - TextOffsetX, entryHeight, TextHue, value);
x += valueWidth + OffsetSize;
if (SetGumpID != 0)
{
builder.AddImageTiled(x, y, SetWidth, entryHeight, SetGumpID);
}
if (hasButton)
{
builder.AddButton(x + SetOffsetX, y + SetOffsetY, SetButtonID1, SetButtonID2, buttonId);
}
}
///
/// Adds an entry row with a text input field and optional action button.
/// Layout: [TextEntry:EntryGumpID] [Button:SetGumpID]
///
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void AddPropsEntryTextInput(
ref int x,
ref int y,
int entryWidth,
int entryId,
ReadOnlySpan initialText,
bool hasButton,
int buttonId,
int entryHeight = EntryHeight
)
{
builder.AddImageTiled(x, y, entryWidth, entryHeight, EntryGumpID);
builder.AddTextEntry(x + TextOffsetX, y, entryWidth - TextOffsetX, entryHeight, TextHue, entryId, initialText);
x += entryWidth + OffsetSize;
if (SetGumpID != 0)
{
builder.AddImageTiled(x, y, SetWidth, entryHeight, SetGumpID);
}
if (hasButton)
{
builder.AddButton(x + SetOffsetX, y + SetOffsetY, SetButtonID1, SetButtonID2, buttonId);
}
}
///
/// Adds an entry row with a cropped label and no action button.
/// Layout: [Label:EntryGumpID] [Empty:SetGumpID]
///
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void AddPropsEntryLabel(
ref int x,
ref int y,
int entryWidth,
ReadOnlySpan label,
int entryHeight = EntryHeight
)
{
builder.AddImageTiled(x, y, entryWidth, entryHeight, EntryGumpID);
builder.AddLabelCropped(x + TextOffsetX, y, entryWidth - TextOffsetX, entryHeight, TextHue, label);
x += entryWidth + OffsetSize;
if (SetGumpID != 0)
{
builder.AddImageTiled(x, y, SetWidth, entryHeight, SetGumpID);
}
}
///
/// Adds a full-width type label row (used for PropsGump type group headers).
/// Layout: [TypeName:EntryGumpID spanning typeWidth] [Empty:SetGumpID]
///
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void AddPropsEntryType(
ref int x,
ref int y,
int typeWidth,
ReadOnlySpan typeName,
int entryHeight = EntryHeight
)
{
builder.AddImageTiled(x, y, typeWidth, entryHeight, EntryGumpID);
builder.AddLabelCropped(x + TextOffsetX, y, typeWidth - TextOffsetX, entryHeight, TextHue, typeName);
x += typeWidth + OffsetSize;
if (SetGumpID != 0)
{
builder.AddImageTiled(x, y, SetWidth, entryHeight, SetGumpID);
}
}
///
/// Adds a blank separator row spanning the full width.
///
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void AddPropsEntryBlank(
int x,
int y,
int totalWidth,
int entryHeight = EntryHeight
)
{
builder.AddImageTiled(x - OffsetSize, y, totalWidth, entryHeight, BackGumpID + 4);
}
}
}