Arcanum GUI Toolkit
What is it for?
ArcanumLib.Gui is a small toolkit for building Vintage Story GUIs with less boilerplate and a consistent look. It provides a shared colour palette, ready-to-use controls, layout helpers, and a fluent dialog builder.
When to use it
- You want a consistent, themeable look for mod dialogs without hand-tweaking every colour and bound.
- You need vertical or horizontal layout helpers to avoid repeated
ElementBounds.Fixedcalculations. - You want reusable, pre-styled controls such as icons, cards, buttons, scrollbars, and lists.
- You are implementing a dialog and want a fluent
ArcanumComposerbuilder. - You need a self-contained scrollable list with selection, hover, and zebra-row rendering.
Quick example
using ArcanumLib.Gui.Controls;
using ArcanumLib.Gui.Layout;
using ArcanumLib.Gui.Theme;
SingleComposer = ArcanumComposer
.Create(capi, "my-dialog")
.WithTitleBar(Lang.Get("mydomain:my-dialog-title"), TryClose)
.BeginVertical(padding: 20, gap: 12)
.AddText("Dialog header", ArcanumFont.Title)
.AddCard(card =>
{
card.BeginHorizontal(gap: 12)
.AddIcon("mydomain:textures/icons/star.webp", 48,
color: ArcanumGuiTheme.TextPrimary,
fit: IconFit.Circle)
.BeginVertical(gap: 4)
.AddText("Primary label", ArcanumFont.Body)
.AddText("Secondary caption", ArcanumFont.Caption)
.EndVertical()
.EndHorizontal();
})
.AddList(
_data.Items,
item => item.DisplayName,
rowHeight: 40,
onSelected: OnItemSelected,
height: 240)
.AddButtonRow("Cancel", OnCancel, "Confirm", OnConfirm)
.EndVertical()
.Compose();
API overview
The toolkit is organised into the following namespaces. The snippets below show common tasks.
Namespaces
| Namespace | Purpose |
|---|---|
ArcanumLib.Gui.Theme | The ArcanumGuiTheme colour palette, RGBA, ArcanumFont, and Cairo drawing helpers. |
ArcanumLib.Gui.Icons | ImageIconCache for loading and drawing PNG/JPEG/GIF/BMP/ICO/WebP/HEIF icons and other SKCodec formats. Also contains CustomIconRegistry and ICustomIconRenderer for custom Cairo-drawn icons, and CustomTabIconRenderer for decorative tab icons. |
ArcanumLib.Gui.Controls | Ready-to-use elements: ArcanumIcon, ArcanumCard, ArcanumButton, ArcanumScrollbar, ArcanumDialogBackground, ArcanumList<T>, ItemListElement, GuiElementCustomTabContent, GuiDateTimePicker. |
ArcanumLib.Gui.Layout | ArcanumLayout helpers for vertical/horizontal stacks and the ArcanumComposer fluent builder. |
ArcanumLib.Gui.Dialogs | ArcanumGuiDialog base class. |
ArcanumLib.Gui.RadialMenu | RadialMenuGui, RadialMenuItem, IRadialMenuStyle, and RadialMenuStyleRegistry. See the Radial Menu page for details. |
ArcanumLib.Gui | Extension methods for GuiComposer such as AddItemSlot. |
Theme
using ArcanumLib.Gui.Theme;
var fill = ArcanumGuiTheme.SurfaceCard;
var border = ArcanumGuiTheme.BorderDefault;
var textColor = ArcanumGuiTheme.TextPrimary;
Fonts
var title = ArcanumFont.Title;
var body = ArcanumFont.Body;
var caption = ArcanumFont.Caption;
Layout
Instead of manually computing ElementBounds.Fixed coordinates:
var rows = ArcanumLayout.VerticalFill(bgBounds, gap: 12, padding: 20,
headerH: 64,
bodyH: 120,
footerH: 36);
var headerBounds = rows[0];
var bodyBounds = rows[1];
var footerBounds = rows[2];
Icon
var iconBounds = ElementBounds.Fixed(0, 0, 48, 48);
composer.AddArcanumIcon(
"mydomain:textures/icons/gear.webp",
iconBounds,
color: ArcanumGuiTheme.TextPrimary,
fit: IconFit.Circle);
Card
composer.AddArcanumCard(
bodyBounds,
fill: ArcanumGuiTheme.SurfaceCard.WithAlpha(0.45),
border: ArcanumGuiTheme.BorderShadow.WithAlpha(0.55),
accent: ArcanumGuiTheme.StatusActive);
Dialog base
public class MyDialog : ArcanumGuiDialog
{
protected override void BuildComposer()
{
var bgBounds = ArcanumGuiTheme.ArcanumConfigBackgroundBounds();
var bounds = ArcanumGuiTheme.ArcanumConfigDialogBounds();
SingleComposer = capi.Gui.CreateCompo("MyDialog", bounds)
.AddArcanumDialogBackground(bgBounds)
.AddDialogTitleBar("My Title", TryClose)
.BeginChildElements(bgBounds)
.AddArcanumCard(bodyBounds)
.EndChildElements();
}
}
ArcanumComposer
ArcanumComposer is a thin, fluent wrapper around GuiComposer. It keeps a stack of vertical and horizontal containers and a current cursor, so common dialogs can be built without manually stacking ElementBounds.Fixed calls. See the Quick example for a complete dialog.
| Method | Purpose |
|---|---|
BeginVertical / EndVertical | Stack children top-to-bottom. Auto-sizes height unless a height is given. |
BeginHorizontal / EndHorizontal | Stack children left-to-right. Fits width when not explicitly set. |
AddText | Single- or multi-line static text with automatic height. |
AddButton / AddButtonRow | Themed Arcanum buttons. |
AddIcon | Draw a WebP/PNG icon with optional clip/tint. |
AddCard | Rounded themed card background with nested child content. |
AddList<T> | Add an ArcanumList<T> with selection and scrolling. |
AddTextInput / AddNumberInput / AddDropdown | Standard Vintage Story inputs. |
AddScrollbar | A standalone ArcanumScrollbar. |
ArcanumComposer.Compose() finalises the stack and returns the underlying GuiComposer.
ArcanumList
ArcanumList<T> is a self-contained scrollable list of selectable text rows.
var listBounds = ElementBounds.Fixed(0, 0, 400, 240);
SingleComposer = capi.Gui.CreateCompo("my-list-dialog", dialogBounds)
.AddArcanumDialogBackground(bgBounds)
.AddArcanumList(
myItems,
listBounds,
item => item.Name,
rowHeight: 36,
onSelected: (item, index) => api.Logger.Notification($"Selected {item.Name} at {index}"),
font: ArcanumFont.Body,
key: "myList")
.Compose();
The list is added as an interactive element. You can retrieve it later through the composer:
var list = SingleComposer.GetArcanumList<MyItem>("myList");
list.SetItems(newItems);
list.ScrollTo(120f);
list.Select(3);
- Built-in selection, hover, and zebra-row rendering.
- Mouse wheel and draggable scrollbar.
SetItems,ScrollTo, andSelectmethods for dynamic content.- Optional selection callback with
(T item, int index).
Virtualization
For large item sets, opt into viewport virtualization. The renderer then keeps the card and scrollbar in separate cached textures and bakes only the visible rows plus an overscan band into a scrollable buffer — scrolling inside the band costs no texture rebake at all:
list.WithVirtualization(); // enabled, overscan = 2 rows
// or fine-tune:
list.WithVirtualization(overscanRows: 4, maxRenderedRows: 100);
// Equivalent property setters:
list.Virtualized = true;
list.OverscanRows = 2;
list.MaxRenderedRows = 0; // 0 = buffer auto-sized to viewport + overscan
MaxRenderedRows never goes below what the viewport needs, so the cap can’t leave blank space under normal use.
Tooltips
GuiElementTooltip is an invisible, non-interactive hover-tracker element. Wrap it around another control’s bounds; after the cursor rests inside for 500 ms it draws a themed tooltip card (SurfaceCard fill, brass border, TextPrimary text) near the cursor, clamped to the window. Text supports \n and word-wraps at ~40 chars/line.
composer.AddTooltip(buttonBounds.FlatCopy(), "Harvests moss.\nBest used on rainy days.");
// or target an existing element:
composer.AddTooltip(myElement, "Tooltip text", key: "myTip");
// Update later by key:
ArcanumTooltip.Set(SingleComposer, "myTip", "New text");
Notes
ArcanumComposeris a convenience wrapper, not a full layout engine. Complex absolute-positioned or overlapping dialogs may still need directGuiComposercalls.ArcanumList<T>draws simple text rows. For rich per-row layouts (icons, multiple columns, etc.), build a customGuiElementor useArcanumComposerinside a scrollableGuiElementContainer.ArcanumList<T>does not support multi-select in this first version.