Skip to content
APHONIC edited this page Sep 15, 2026 · 4 revisions

Window

Window.lua: the status window shell, and an auto-sizing row list built on top of it.

LibAPH.CreateStatusWindow(opts)

Signature: returns window, label.

opts (all optional):

Field Default Meaning
name None Control name
parent GuiRoot Parent control
width, height 150, 40 Initial dimensions
movable true Whether the window can be dragged
bgColor {0,0,0,0.6} {r,g,b,a} backdrop fill
borderColor {0.6,0.6,0.6,0.8} {r,g,b,a} border strip color
borderThickness 2 Border strip thickness in pixels
isGamepad, fontPC, fontGamepad false, "ZoFontGameSmall", "ZoFontGamepad22" Font selection
initialText "Loading..." Starting label text
centerAlign true Center the label text
onMoveStop None function(left, top) on drag release

Usage:

local win, text_lbl = LibAPH.CreateStatusWindow({
    name = "MyAddonWindow",
    movable = not MyAddon.settings.is_ui_locked,
    isGamepad = is_pad,
    onMoveStop = function(left, top)
        MyAddon.settings.ui_x = left
        MyAddon.settings.ui_y = top
    end,
})

LibAPH.CreateRowList(parent, opts)

Signature: returns a list object with :SetRows(texts) and :Clear(). parent must already exist (typically CreateStatusWindow's own returned window): this function doesn't build a container, it lays out rows inside one you already made. Measures every row's real rendered text via label:GetTextDimensions() and resizes the container to fit.

opts:

Field Default Meaning
orientation "vertical" "vertical" (top-to-bottom) or "horizontal" (left-to-right)
spacing 4 Pixels between rows
maxRows 20 Cap on row count, so a runaway list can't grow the window unboundedly
padding 6 Inset from the container's edges
minWidth, minHeight 40, 20 Floor dimensions when the list is empty
isGamepad, fontPC, fontGamepad, color None Same convention as CreateStatusWindow

:SetRows(texts) takes an ordered array of strings and replaces the entire row set on every call. :Clear() is :SetRows({}).

local rowlist = LibAPH.CreateRowList(window, { orientation = "vertical", padding = 6, spacing = 2 })

-- every refresh:
rowlist:SetRows({
    "Sundered  3.2s  (Daedroth)",
    "Chilled  5.9s  (Bandit)",
})
-- or, when nothing's active:
rowlist:Clear()

LibAPH.CreateScrollListWindow(opts)

Movable window with a scrollbar. Uses ZO_ScrollList, ZO_SelectableLabel, ZO_DefaultBackdrop, ZO_CheckButton, ZO_CloseButton.

Signature: returns an object with :SetTitle(text), :SetRows(rows), :SetAllChecked(bool), :Show(), :Hide(), plus the raw window, list, footer controls for anchoring your own buttons into the footer.

rows (for :SetRows) is { {text=, color={r,g,b,a}}, ... }. A row's data table also takes:

  • tooltip (string): hovering shows it via ItemTooltip, keeping the row's own text short.
  • is_header (bool): a permanent background band instead of the hover highlight, for section headers.
  • checkable ({checked=bool, onToggle=function(isChecked) end}): renders a ZO_CheckButton. Clicking the checkbox or anywhere else on the row toggles it and mutates the same row-data table in place, so read rows[i].checked afterward rather than re-fetching.

opts:

Field Default Meaning
name None Control name
titleText None Window title
footerHeight 44 Footer band height in pixels
rowHeight 26 Row height in pixels
isGamepad, fontPC, fontGamepad false Font selection
onClose None Called when the window closes
widthPct, heightPct 0.34, 0.55 Fraction of screen resolution, sizing the window responsively
minWidth, maxWidth, minHeight, maxHeight 480, 900, 420, 820 Clamp for the responsive sizing above
width, height None Pass explicit values to opt out of responsive sizing entirely

:Show()/:Hide() also call SCENE_MANAGER:SetInUIMode(...), unlocking/relocking the mouse cursor.

local window = LibAPH.CreateScrollListWindow({
    name = "MyAddon_LibraryWindow",
    widthPct = 0.36, heightPct = 0.6,
    minWidth = 520, maxWidth = 950,
    footerHeight = 58,
    titleText = "Library Manager",
})
window:SetTitle("Optional Libraries")
window:SetRows({
    { text = "LibAddonMenu-2.0 (v41)", color = { 0.4, 1, 0.4, 1 }, checkable = { checked = true, onToggle = function(checked) d(checked) end } },
})
window:Show()

LibAPH.CreateCopyTextBox(opts)

Movable window with a read/copy multi-line text view - for a "copy last error" or "copy bug report" style button. Fixed size regardless of how much text you show it. The text area itself uses ZO_MultiLineEditBackdrop_Keyboard, the same real edit-box texture as the search box below it, instead of a flat color fill. Has mouse-wheel scrolling with a position indicator on the right edge, a search box (Enter jumps to the next match and wraps at the end; Shift+Enter jumps to the previous match and wraps at the start), a close button (ZO_CloseButton), a "Select All" button (selects the whole box and gives it keyboard focus, so Ctrl+C copies it), an optional second button for your own testing, and an optional footer row for a "dismiss this capture" / "wipe everything" pair of buttons.

Signature: returns an object with :Show(plain_text) and :Hide().

opts:

Field Default Meaning
name None Control name
titleText None Window title
copyText "Select All" Label for the select-all button
searchLabel "Search:" Label next to the search box
noMatchesText "No matches" Search status text shown when nothing matches
maxInputChars 4000 Max characters the box accepts
stripColors LibAPH.StripColors Function run on the text before display
devButton None { text = "...", onClick = function() end } - shows a second button, e.g. gated behind your own is_dev check
dismissBug None { text = "...", onClick = function() end } - bottom-left footer button; the window grows to make room for it
wipeAllBugs None { text = "...", onClick = function() end } - bottom-right footer button, same reserved footer as dismissBug
onClose None Called when the close button is clicked

Neither footer button assumes you're keeping a real multi-error log: what "dismiss" and "wipe" actually do is entirely up to your own onClick. If your addon only ever tracks one last-captured error (the common case with LibAPH.HookErrorCapture), a reasonable split is dismiss clears that one error and re-shows the box with its now-empty state, while wipe clears it and closes the box outright via :Hide().

local is_dev = (GetDisplayName() == "@YourAccountName")

local error_box = LibAPH.CreateCopyTextBox({
    name = "MyAddonErrorBox",
    titleText = "Last Error",
    devButton = is_dev and {
        text = "Simulate Error",
        onClick = function()
            zo_callLater(function() error(MyAddon.name .. ": test error") end, 1)
        end,
    } or nil,
    dismissBug = {
        text = "Dismiss Bug",
        onClick = function()
            MyAddon.last_own_error = nil
            MyAddon.show_bug_report_box()
        end,
    },
    wipeAllBugs = {
        text = "Wipe All Bugs",
        onClick = function()
            MyAddon.last_own_error = nil
            error_box:Hide()
        end,
    },
})
error_box:Show(MyAddon.last_own_error or "No error captured yet.")

LibAPH.CreateKeybindLabelButton(parent, opts)

A keybind button (ZO_KeybindButton) - a child of your own window, no KEYBIND_STRIP/scene integration required. Its key icon auto-updates to whatever the player has bound. Use a generic action layer for your own buttons (UI_SHORTCUT_SECONDARY, UI_SHORTCUT_TERTIARY, UI_SHORTCUT_QUATERNARY, UI_SHORTCUT_QUINARY), or a gameplay keybind like GAME_CAMERA_INTERACT (Interact) when it fits.

Signature: returns the button control. Assign button.libaph_click_action = function() ... end to change its action later.

opts:

Field Default Meaning
keybind None A keybind action-layer name, e.g. "UI_SHORTCUT_SECONDARY" or "GAME_CAMERA_INTERACT"
gamepadPreferredKeybind None Optional gamepad-specific keybind name (e.g. "GAMEPAD_JUMP_OR_INTERACT" alongside GAME_CAMERA_INTERACT)
name "" Label text
callback None function() run when the bound key (or a click) fires
local apply_btn = LibAPH.CreateKeybindLabelButton(window.footer, {
    keybind = "GAME_CAMERA_INTERACT",
    gamepadPreferredKeybind = "GAMEPAD_JUMP_OR_INTERACT",
    name = "Apply Changes",
    callback = function() MyAddon.ApplyChanges() end,
})
apply_btn:SetAnchor(TOPRIGHT, window.footer, TOPRIGHT, 0, 24)

LibAPH.AddButtonHoverEffects(control, baseColor)

Hover (brightens), press (darkens), and click-sound feedback for a plain clickable text label. Wires OnMouseEnter/OnMouseExit/OnMouseDown. Assign control.libaph_click_action = function() ... end for the click action.

local btn = WINDOW_MANAGER:CreateControl(nil, parent, CT_LABEL)
btn:SetText("Apply Changes")
btn:SetColor(0.4, 1, 0.4, 1)
btn:SetMouseEnabled(true)
LibAPH.AddButtonHoverEffects(btn, { 0.4, 1, 0.4, 1 })
btn.libaph_click_action = function() d("Applied.") end

LibAPH.SetWindowActive(window, label, isActive, opts)

Shows or hides a status window by fading SetAlpha and toggling SetMouseEnabled, rather than SetHidden - keeps the window's own move/anchor state intact while it's inactive. Returns true/false for the new active state.

opts.emptyText sets the label when inactive; opts.onResize runs after going inactive.

local is_active = LibAPH.SetWindowActive(MyAddon.window, MyAddon.label, has_target, { emptyText = "" })

LibAPH.AddFragmentToScenes(fragment, sceneNames) / LibAPH.RemoveFragmentFromScenes(fragment, sceneNames)

Attaches or detaches a ZO_HUDFadeSceneFragment (or any scene fragment) across a list of named scenes in one call.

LibAPH.AddFragmentToScenes(MyAddon.fragment, { "hud", "hudui" })

Home · Getting Started

Clone this wiki locally