# zAddonFramework — Developer Guide

A low-level UI building-block library for World of Warcraft addons (Retail 12.0.x+). No schema, no data binding, no panel management — just clean primitives, widgets, and chrome utilities that any addon can call directly.

---

## Table of Contents

1. [Quick Start](#quick-start)
2. [Installation](#installation)
3. [Architecture Overview](#architecture-overview)
4. [Registration & Contexts](#registration--contexts)
5. [Build Context: Push / Pop](#build-context-push--pop)
6. [Color Handling](#color-handling)
7. [Utilities](#utilities)
8. [Primitives Reference](#primitives-reference)
9. [Chrome Reference](#chrome-reference)
10. [Widget Reference](#widget-reference)
11. [Modal Dialogs](#modal-dialogs)
12. [Style Constants](#style-constants)
13. [Easing Functions](#easing-functions)
14. [Complete Example](#complete-example)

---

## Quick Start

```lua
-- 1. Register your addon — creates per-addon font objects and normalises theme colors.
local zafCtx = zAddonFramework.Register({
    addonName   = "MyAddon",
    themeColors = {
        primary   = ZAF.CLASS_WARRIOR,
        secondary = ZAF.CLASS_MAGE,
    },
})

-- 2. Build widgets inside a Push/Pop block so they pick up your fonts and colors.
local function BuildMyUI(parent)
    zafCtx:Push()

    local header = zAF_BuildSectionHeader(parent, "My Section", 10)

    local toggle = zAF_MakePillToggle(parent)
    toggle:SetPoint("TOPLEFT", header, "BOTTOMLEFT", 0, -8)

    zafCtx:Pop()
end
```

That's it. Every widget factory call inside a `Push`/`Pop` block automatically uses your registered fonts and colors when you pass `nil` for those parameters.

---

## Installation

1. Copy the `zAddonFramework-1.0` folder into your addon's `Libs/` directory.
2. Call `zAF_SetAssetBase` if the library is nested deeper (see [Utilities](#utilities)).
3. Reference the XML loader in your `.toc` file **before** your addon files:

```toc
## Interface: 120001

Libs\zAddonFramework-1.0\zAddonFramework-1.0.xml

MyAddon.lua
```

The XML loads files in the correct order:

```
zAF_Init.lua        → LibStub registration, asset path, color helpers, Register()
zAF_Style.lua       → ZAF.* color constants, class colors, easing functions
zAF_Primitives.lua  → Frame, text, texture, layout, scroll, tween engine
zAF_Chrome.lua      → Tooltip system, gradient border, section header
zAF_Widgets.lua     → Pill toggle, slider, dropdown, color picker, modals, etc.
```

**Bundled assets** (in `Assets/`): `1px.png` (border texture), `zAF_Circle.tga` (slider thumb / toggle knob), `zAF_Pill.tga` (toggle track mask), `close-button.png` (close icon), and **OpenSans-CondBold.ttf** (default font for `enUS` clients; other locales fall back to `STANDARD_TEXT_FONT` unless overridden).

---

## Architecture Overview

zAF has no opinion about panels or data. It is purely a widget factory. You create frames, call a widget constructor on them, and wire up your own callbacks.

```
┌──────────────┐     ┌─────────────────────────┐
│  Your Addon  │────▶│  zAddonFramework (zAF)   │
│              │     │  Widget factories        │
└──────────────┘     │  Layout / chrome helpers │
                     │  Tween engine            │
                     └─────────────────────────┘
```

When zSF is involved the call chain is deeper, but zAF is unaware of it — zSF is just another caller:

```
┌──────────────┐     ┌────────────────┐     ┌──────────────┐
│  Your Addon  │────▶│ zSettingsFrame │────▶│    zAF       │
│  (adapter)   │     │  (panel mgmt)  │     │  (widgets)   │
└──────────────┘     └────────────────┘     └──────────────┘
```

Both paths are valid. You can call zAF directly without zSF.

---

## Registration & Contexts

`zAddonFramework.Register(adapter)` creates a per-addon context that holds virtual font objects and normalised theme colors. Call it once at file-load time.

```lua
local zafCtx = zAddonFramework.Register(adapter)
```

### Adapter Fields

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `addonName` | `string` | yes | Unique name identifying this addon. Used to name font objects globally. |
| `fontStyles` | `table<string, string>` | no | Locale-keyed font paths: `{ enUS = "Interface\\...\\MyFont.ttf", koKR = "..." }`. Absent locales fall back to `STANDARD_TEXT_FONT`. The built-in default maps `enUS` to OpenSans-CondBold — supply `enUS` to override it. |
| `themeColors` | `{ primary, secondary }` | no | Theme accent colors. Each value is a `{ r, g, b }` array, a `ZAF.CLASS_*` constant, or a `ColorMixin`. Defaults to `ZAF.CLASS_EVOKER` / `ZAF.CLASS_PALADIN` if omitted. |

### Returned Context (`zAF_AddonContext`)

| Field / Method | Description |
|----------------|-------------|
| `ctx.addonName` | The registered addon name. |
| `ctx.FONTOBJ_NORMAL` | Virtual font object at 12pt. |
| `ctx.FONTOBJ_SMALL` | Virtual font object at 10pt. |
| `ctx.FONTOBJ_LARGE` | Virtual font object at 16pt. |
| `ctx.FONTOBJ_HEADING` | Virtual font object at 18pt. |
| `ctx._themeColors` | `{ primary = {r,g,b,a}, secondary = {r,g,b,a} }` after normalisation. `nil` if no `themeColors` were provided. |
| `ctx:Push()` | Activate this context for the current build block. Saves the previously active context. |
| `ctx:Pop()` | Deactivate this context, restoring the previously active one. |

Multiple addons can each call `Register()` independently — each receives an isolated context with its own font objects.

---

## Build Context: Push / Pop

The **build context** is a thread-local-style slot (`ZAF._buildCtx`) that widget constructors read when their color or font parameters are `nil`. Activate yours by calling `Push()` before building widgets and `Pop()` afterwards.

```lua
zafCtx:Push()
-- everything inside here uses zafCtx's fonts and colors as defaults
local slider = zAF_BuildSlider(cell, 0, 100, 50, 1)
zafCtx:Pop()
```

`Push` and `Pop` can be nested. Each `Push` saves the previous context; each `Pop` restores it. This means two Push/Pop blocks can be interleaved correctly if one is a callback inside another's scope.

If no context is active, widget functions fall back to `ZAF.DEFAULT_PRIMARY` / `ZAF.DEFAULT_SECONDARY` for colors and `GameFontNormal` / `GameFontHighlightSmall` / `GameFontNormalLarge` for font strings.

### Color resolution order

For every widget parameter that accepts a color, the resolution chain is:

1. The explicit value you pass.
2. The active build context's `_themeColors.primary` or `_themeColors.secondary`.
3. `ZAF.DEFAULT_PRIMARY` / `ZAF.DEFAULT_SECONDARY`.

This means passing `nil` is always safe — you'll get a sensible color regardless of whether a context is active.

---

## Color Handling

### `zAF_NormalizeColor(c)`

Converts any color input to a four-element numeric array `{ r, g, b, a }`. Widget functions call this internally; you rarely need to call it yourself.

- `{ r, g, b [, a] }` arrays: returned with `a` defaulting to `1`.
- `ColorMixin` / field-keyed tables (`{ r = ..., g = ..., b = ... }`): converted to arrays.
- Single-element tables wrapping another table: unwrapped recursively.
- Non-table values (`nil`, strings): returned unchanged.

```lua
local c = zAF_NormalizeColor(RAID_CLASS_COLORS["WARRIOR"])
-- c = { 0.78, 0.61, 0.43, 1 }

local d = zAF_NormalizeColor({ 1, 0.5, 0 })
-- d = { 1, 0.5, 0, 1 }
```

All color parameters across every zAF function accept any of these formats — arrays, mixins, or `ZAF.CLASS_*` constants. You do not need to normalize before passing.

---

## Utilities

### `zAF_SetAssetBase(base)`

Override the asset base path. zAF auto-detects the host addon folder on load, so you only need this when the library is relocated (e.g. double-nested inside another library).

```lua
zAF_SetAssetBase("Interface\\AddOns\\MyAddon\\Libs\\zAddonFramework-1.0\\Assets\\")
```

Call this before any widget is built. Once any widget has created a texture the path is already baked in.

### `zAF_AssetPath(file)`

Returns the full WoW interface path for a file in the library's `Assets/` folder.

```lua
local path = zAF_AssetPath("1px.png")
-- "Interface\\AddOns\\MyAddon\\Libs\\zAddonFramework-1.0\\Assets\\1px.png"
```

### `zAF_SetBuildCtx(ctx)`

Low-level primitive: sets `ZAF._buildCtx` directly. Prefer `ctx:Push()` / `ctx:Pop()` — they handle nesting correctly. Use this only when you need to set a context without a matching pop (e.g. from an outside orchestrator).

---

## Primitives Reference

All functions in `zAF_Primitives.lua`. These are stateless construction helpers — they create WoW objects and return them. The caller owns the result.

---

### `zAF_Frame(name, parent)`

Create a plain `Frame`.

```lua
local f = zAF_Frame(nil, UIParent)
f:SetSize(200, 100)
```

| Parameter | Type | Description |
|-----------|------|-------------|
| `name` | `string\|nil` | Global frame name. Pass `nil` for anonymous frames. |
| `parent` | `Frame\|nil` | Parent frame. |

**Returns** `Frame`.

---

### `zAF_Text(parent, fontObject, justifyH)`

Create a `FontString` attached to `parent`. When called inside a `Push`/`Pop` block, blizzard font object names (`"GameFontNormal"`, `"GameFontHighlightSmall"`, `"GameFontNormalLarge"`) are automatically swapped for the active context's virtual font objects.

```lua
local fs = zAF_Text(myFrame, "GameFontNormal", "LEFT")
fs:SetText("Hello")
fs:SetPoint("TOPLEFT", myFrame, "TOPLEFT", 8, -8)
```

| Parameter | Type | Description |
|-----------|------|-------------|
| `parent` | `Region` | Owning frame or region. |
| `fontObject` | `string\|Font\|nil` | Font object or template name. `nil` for no font assignment. |
| `justifyH` | `string\|nil` | `"LEFT"`, `"CENTER"`, `"RIGHT"`. Defaults to `"LEFT"`. |

**Returns** `FontString`. Call `:SetText()` afterwards.

---

### `zAF_Texture(parent, layer, r, g, b, a)`

Create a `Texture` attached to `parent`. Pass `r/g/b` to fill with a solid color.

```lua
local t = zAF_Texture(myFrame, "BACKGROUND", 0, 0, 0, 0.6)
t:SetAllPoints(myFrame)
```

| Parameter | Type | Description |
|-----------|------|-------------|
| `parent` | `Region` | |
| `layer` | `string\|nil` | Draw layer. Defaults to `"BACKGROUND"`. |
| `r/g/b` | `number\|nil` | Solid color fill (0–1). Omit all three to skip coloring. |
| `a` | `number\|nil` | Alpha. Defaults to `1` when `r/g/b` are provided. |

**Returns** `Texture`.

---

### `zAF_FillBG(frame, r, g, b, a)`

Shortcut: create an `ALLPOINTS` `BACKGROUND` texture filled with the given solid color. Returns it so you can recolor it later.

```lua
local bg = zAF_FillBG(myFrame, 0.05, 0.05, 0.05, 0.95)
```

**Returns** `Texture`.

---

### `zAF_DrawBorder(frame, r, g, b, a)`

Draw a 1-pixel solid border around `frame` using four edge textures sized via `PixelUtil`. Safe to call repeatedly — on subsequent calls only the color is updated, no new textures are created.

```lua
zAF_DrawBorder(myFrame)                           -- ZAF.COLOR_BORDER defaults
zAF_DrawBorder(myFrame, 0.8, 0.2, 0.2, 1)        -- red
```

| Parameter | Type | Default |
|-----------|------|---------|
| `r/g/b/a` | `number\|nil` | `ZAF.COLOR_BORDER[1..4]` |

---

### `zAF_DisablePixelSnap(t)`

Disable WoW's built-in texel snapping on a `Texture` or `StatusBar`. Call this **after** `SetColorTexture`, `SetTexture`, or `SetGradient` — WoW re-enables snapping whenever the texture or color changes.

```lua
local t = myFrame:CreateTexture(nil, "BACKGROUND")
t:SetColorTexture(1, 0, 0, 1)
zAF_DisablePixelSnap(t)
```

---

### `zAF_LayoutVertical(parent, children, gapY, padTop)`

Stack child frames top-to-bottom inside `parent`. Returns the total pixel height consumed.

```lua
local totalH = zAF_LayoutVertical(container, { row1, row2, row3 }, 4, 8)
container:SetHeight(totalH)
```

| Parameter | Type | Default |
|-----------|------|---------|
| `gapY` | `number\|nil` | `0` |
| `padTop` | `number\|nil` | `0` |

**Returns** `number` — sum of all child heights, gaps, and padTop.

---

### `zAF_LayoutHorizontal(parent, children, gapX, padLeft)`

Anchor child frames left-to-right inside `parent`.

```lua
zAF_LayoutHorizontal(row, { label, control }, 8, 10)
```

| Parameter | Type | Default |
|-----------|------|---------|
| `gapX` | `number\|nil` | `0` |
| `padLeft` | `number\|nil` | `0` |

---

### `zAF_ScrollFrame(parent, scrollSpeed)`

Create a `ScrollFrame` with a managed content child. Mousewheel scrolling is wired automatically. The content child stretches to the scroll frame's full width via two anchor points. Its height must be set by the caller after widgets are laid out.

```lua
local sf, content = zAF_ScrollFrame(container, 20)
sf:SetAllPoints(container)

-- parent all content to 'content', then:
content:SetHeight(totalContentHeight)
```

| Parameter | Type | Default |
|-----------|------|---------|
| `scrollSpeed` | `number\|nil` | `20` |

**Returns** `ScrollFrame`, `Frame` (content child).

---

### `zAF_Tween(from, to, duration, ease, apply, onFinish)`

Start a numeric tween. Calls `apply(value)` each frame until `to` is reached, then calls `onFinish()`. Returns a handle with a `:Cancel()` method.

```lua
local tw = zAF_Tween(0, 1, 0.3, ZAF.EASE_QUAD_OUT, function(v)
    myFrame:SetAlpha(v)
end)

-- later, if needed:
tw:Cancel()
```

| Parameter | Type | Description |
|-----------|------|-------------|
| `from` | `number` | Start value. |
| `to` | `number` | End value. |
| `duration` | `number` | Seconds. |
| `ease` | `function` | Easing function (see [Easing Functions](#easing-functions)). |
| `apply` | `function(v)` | Called each frame with the interpolated value. |
| `onFinish` | `function\|nil` | Called once on completion. |

**Returns** `table` handle with `:Cancel()`.

---

## Chrome Reference

Stateless display helpers in `zAF_Chrome.lua`.

---

### `zAF_ShowTooltip(anchor, titleText, bodyText, secondary)`

Show the custom tooltip anchored below-left of `anchor`. The title is colored with `secondary`; the body is white.

```lua
zAF_ShowTooltip(myButton, "My Title", "Optional longer description here.", { 0.8, 0.6, 1 })
```

| Parameter | Type | Description |
|-----------|------|-------------|
| `anchor` | `Region` | The tooltip positions itself relative to this. |
| `titleText` | `string` | Bold title line. |
| `bodyText` | `string\|nil` | Secondary body line. Omit or pass `nil` for title-only. |
| `secondary` | `table\|nil` | Color for the title text. Defaults to ctx secondary → `ZAF.DEFAULT_SECONDARY`. |

---

### `zAF_HideTooltip()`

Hide the custom tooltip.

```lua
myButton:SetScript("OnLeave", zAF_HideTooltip)
```

---

### `zAF_AddTooltip(region, titleText, bodyText, secondary)`

Wire `OnEnter` / `OnLeave` scripts on `region` to show/hide the tooltip automatically.

```lua
zAF_AddTooltip(myButton, "Reset", "Resets all values to their defaults.")
```

Same parameters as `zAF_ShowTooltip`. `secondary` follows the same fallback chain.

---

### `zAF_DrawGradientBorder(frame, cTL, cTR, cBL, cBR)`

Draw a four-corner gradient 1-pixel border. Textures are created once and reused on subsequent calls — only the gradient colors update.

```lua
zAF_DrawGradientBorder(myFrame,
    ZAF.CLASS_WARRIOR, ZAF.CLASS_MAGE,    -- top-left, top-right
    ZAF.CLASS_MAGE,    ZAF.CLASS_WARRIOR  -- bottom-left, bottom-right
)
```

Each color accepts any format that `zAF_NormalizeColor` handles: `{ r, g, b }` array, `ZAF.CLASS_*`, or `ColorMixin`.

---

### `zAF_BuildSectionHeader(parent, text, padX, primary, secondary)`

Render a section title in `primary` color with a `secondary`-colored trailing rule line that extends to the right edge for the `parent`'s width. Uses the active context's heading font when called inside a `Push`/`Pop` block.

```lua
zAF_BuildSectionHeader(contentFrame, "Appearance", 12)
```

| Parameter | Type | Default |
|-----------|------|---------|
| `padX` | `number` | Horizontal padding for text anchor and rule end. |
| `primary` | `table\|nil` | Text color. Falls back to ctx → `ZAF.DEFAULT_PRIMARY`. |
| `secondary` | `table\|nil` | Rule line color. Falls back to ctx → `ZAF.DEFAULT_SECONDARY`. |

---

## Widget Reference

All widget constructors are in `zAF_Widgets.lua`. Every color parameter follows the three-step fallback: explicit value → active context `_themeColors` → `ZAF.DEFAULT_*`.

---

### `zAF_MakePillToggle(parent, secondary, indicatorColor)`

Pill-shaped on/off toggle with a circular knob. Animates the knob and cross-fades the track color. Returns a `Button` with `:SetChecked(bool)` and `:GetChecked()`.

```lua
local toggle = zAF_MakePillToggle(parent)
toggle:SetPoint("TOPLEFT", parent, "TOPLEFT", 10, -10)
toggle:SetChecked(true)

toggle:SetScript("OnClick", function(self)
    local isOn = self:GetChecked()
    MyAddonDB.enabled = isOn
end)
```

| Parameter | Type | Description |
|-----------|------|-------------|
| `secondary` | `table\|nil` | Track color in the ON state. |
| `indicatorColor` | `table\|nil` | Knob tint in the ON state. Defaults to white. |

**Size**: 36 × 16 px (fixed).

---

### `zAF_MakeStepperArrow(parent, flipH, primary, secondary)`

A left/right arrow button using WoW's Renown arrow atlas, desaturated and tinted. Handles `OnEnter` / `OnLeave` / `OnMouseDown` / `OnMouseUp` color states internally. No click callback is set — attach your own `OnClick` or `OnMouseDown`.

```lua
local leftArrow  = zAF_MakeStepperArrow(parent, false)  -- left-facing
local rightArrow = zAF_MakeStepperArrow(parent, true)   -- right-facing

rightArrow:SetScript("OnClick", function() StepValue(1) end)
```

| Parameter | Type | Description |
|-----------|------|-------------|
| `flipH` | `boolean` | `true` = right-facing arrow. |
| `primary` | `table\|nil` | Normal color. |
| `secondary` | `table\|nil` | Hover color. |

**Size**: 14 × 20 px (fixed).

---

### `zAF_MakeVerticalArrow(parent, pointDown, primary, secondary)`

Same as `zAF_MakeStepperArrow` but rotated 90° for up/down use.

```lua
local downArrow = zAF_MakeVerticalArrow(parent, true)
local upArrow   = zAF_MakeVerticalArrow(parent, false)
```

**Size**: 14 × 14 px (fixed).

---

### `zAF_BuildSlider(cell, minVal, maxVal, curVal, step, primary, secondary, inline)`

Fill-bar drag slider with a numeric input box and stepper arrows. The `cell` frame's width must be set before calling. Returns a result table.

```lua
local cell = CreateFrame("Frame", nil, parent)
cell:SetPoint("TOPLEFT", parent, "TOPLEFT", 10, -40)
cell:SetWidth(200)

local slider = zAF_BuildSlider(cell, 0, 100, 50, 1)
slider.SetOnChanged(function(v)
    MyAddonDB.opacity = v
end)
```

| Parameter | Type | Description |
|-----------|------|-------------|
| `minVal` | `number` | |
| `maxVal` | `number` | |
| `curVal` | `number` | Initial value (clamped to min/max). |
| `step` | `number` | Step increment. Controls decimal precision in the display. |
| `primary` | `table\|nil` | Thumb color. |
| `secondary` | `table\|nil` | Fill track color. |
| `inline` | `boolean\|nil` | `true` → input box and arrows sit to the right of the track on one line instead of below it. |

**Returned table:**

| Key | Type | Description |
|-----|------|-------------|
| `trackFrame` | `Frame` | The track container. |
| `inputText` | `EditBox` | The value edit box. |
| `inputFrame` | `Frame` | The frame wrapping the edit box. |
| `GetValue()` | `→ number` | Current value. |
| `SetValue(v)` | | Set value and update the visual. |
| `SetOnChanged(fn)` | | Register `fn(v)` called on every change. |
| `UpdateVisual()` | | Force a visual refresh (use after resizing the cell). |

---

### `zAF_MakeDropButton(parent, w, primary)`

Create a dropdown button shell: dark background, border, text label, and a down-arrow icon. Returns the button, its label `FontString`, and the arrow `Texture`. Wire your own popup logic.

```lua
local btn, lbl, arrow = zAF_MakeDropButton(parent, 160)
btn:SetPoint("TOPLEFT", parent, "TOPLEFT", 10, -10)
lbl:SetText("Choose one...")
btn:SetScript("OnClick", function() OpenMyPopup() end)
```

**Returns** `Button`, `FontString`, `Texture`.

---

### `zAF_BuildDropdown(cell, selText, primary)`

Full dropdown selector with a shared popup menu, scrollbar, and keyboard-dismiss catcher. The `cell` frame's width must be set by the caller.

```lua
local cell = CreateFrame("Frame", nil, parent)
cell:SetWidth(180)
cell:SetPoint("TOPLEFT", parent, "TOPLEFT", 10, -10)

local dd = zAF_BuildDropdown(cell, "Select font…")

dd.SetMenuAccessors(
    function() return MyAddonDB.font end,          -- getValue()
    function() return { arial = "Arial", mono = "Monospace" }, { "arial", "mono" } end, -- getValues()
    function(key, display)                          -- onPick(key, display)
        MyAddonDB.font = key
        dd.text:SetText(display)
    end
)
```

**Returned table:**

| Key | Description |
|-----|-------------|
| `frame` | The button `Frame`. |
| `text` | The selected-value `FontString`. |
| `arrow` | The down-arrow `Texture`. |
| `SetMenuAccessors(getValue, getValues, onPick)` | Wire value resolution. `getValue() → string\|nil`; `getValues() → table, table\|nil` (values map, optional order array); `onPick(key, display)`. |
| `SetItemDecorator(fn)` | `fn(item, key)` called per item button after it is created — use to add icons or extra labels. |
| `OpenMenu()` | Open the popup programmatically. |
| `CloseMenu()` | Close the popup. |

---

### `zAF_BuildColorSwatch(cell, r, g, b, a, primary)`

A solid-color swatch with an optional alpha percentage label. Clicking is **not** wired — attach a `SetScript("OnMouseDown")` to open `zAF_ShowColorPicker`.

```lua
local cell = CreateFrame("Frame", nil, parent)
cell:SetWidth(120)

local swatch = zAF_BuildColorSwatch(cell, 1, 0.5, 0, 0.8)
swatch.frame:SetScript("OnMouseDown", function()
    zAF_ShowColorPicker({
        r = 1, g = 0.5, b = 0, a = 0.8,
        hasAlpha  = true,
        onAccept  = function(r, g, b, a)
            MyAddonDB.color = { r, g, b, a }
            swatch.bg:SetColorTexture(r, g, b, 1)
            swatch.alphaText:SetText(string.format("%.0f%%", a * 100))
        end,
    })
end)
```

| Parameter | Type | Description |
|-----------|------|-------------|
| `r/g/b` | `number` | Initial swatch color (0–1). |
| `a` | `number\|nil` | When provided, an alpha percentage label is rendered inside the swatch. |
| `primary` | `table\|nil` | Border color. |

**Returned table:**

| Key | Type | Description |
|-----|------|-------------|
| `frame` | `Frame` | The swatch frame. |
| `bg` | `Texture` | The fill texture — call `SetColorTexture` to update the displayed color. |
| `alphaText` | `FontString\|nil` | The alpha label. `nil` when `a` was not passed. |

---

### `zAF_BuildActionButton(parent, labelText, iconAtlas, width, secondary, iconColor, borderColor, rotateIcon, flipIcon)`

A flat action button with optional atlas icon and hover/press overlay.

```lua
local btn = zAF_BuildActionButton(parent, "Reset All", "common-icon-redo", 130)
btn:SetPoint("TOPLEFT", parent, "TOPLEFT", 10, -10)
btn:SetScript("OnClick", function() ResetAll() end)
```

| Parameter | Type | Default | Description |
|-----------|------|---------|-------------|
| `labelText` | `string` | | Button label. |
| `iconAtlas` | `string\|nil` | | WoW atlas name. |
| `width` | `number\|nil` | `140` | Pixel width. |
| `secondary` | `table\|nil` | ctx/default | Hover and press overlay color. |
| `iconColor` | `table\|false\|nil` | | Tint the icon (desaturated + tinted). `false` or `nil` = show raw atlas. |
| `borderColor` | `table\|nil` | `ZAF.COLOR_BORDER` | |
| `rotateIcon` | `number\|nil` | | Clockwise degrees. |
| `flipIcon` | `"horizontal"\|"vertical"\|nil` | | Mirror axis. |

**Returns** `Button`. The button has a `.label` field pointing to its `FontString`.

---

### `zAF_BuildCloseButton(parent, secondary)`

The standard title-bar close button (20 × 20 px). Tinted with `secondary`; turns red on hover.

```lua
local close = zAF_BuildCloseButton(titleBar)
close:SetPoint("TOPRIGHT", titleBar, "TOPRIGHT", -6, -6)
close:SetScript("OnClick", function() myFrame:Hide() end)
```

**Returns** `Button`.

---

### `zAF_ShowColorPicker(config)`

Open the custom floating color picker dialog.

```lua
zAF_ShowColorPicker({
    r          = 1,
    g          = 0.5,
    b          = 0,
    a          = 1,
    hasAlpha   = true,
    title      = "Choose Color",
    onChange   = function(r, g, b, a) LivePreview(r, g, b, a) end,
    onAccept   = function(r, g, b, a) SaveColor(r, g, b, a) end,
    onCancel   = function(r, g, b, a) RevertColor(r, g, b, a) end,
    resetFunction = function() ResetColor() end,
})
```

| Config Key | Type | Description |
|------------|------|-------------|
| `r/g/b/a` | `number` | Initial RGBA values (0–1). |
| `hasAlpha` | `boolean\|nil` | Show the alpha bar. |
| `title` | `string\|nil` | Dialog title. |
| `onChange` | `function(r,g,b,a)\|nil` | Called on every live change while the picker is open. |
| `onAccept` | `function(r,g,b,a)\|nil` | Called when the user confirms. |
| `onCancel` | `function(r,g,b,a)\|nil` | Called when the user cancels. |
| `resetFunction` | `function()\|nil` | Called when the user clicks Reset. |

---

## Modal Dialogs

---

### `zAF_ShowConfirm(text, onAccept, onRevert)`

Simple centered confirmation popup with OK / Cancel buttons.

```lua
zAF_ShowConfirm(
    "Are you sure you want to reset all settings?",
    function() ResetAll() end,     -- onAccept
    function() end                 -- onRevert (called on Cancel or close)
)
```

| Parameter | Type | Description |
|-----------|------|-------------|
| `text` | `string` | Message shown in the dialog. |
| `onAccept` | `function()` | Called when the user confirms. |
| `onRevert` | `function()\|nil` | Called if the user cancels or closes without confirming. |

---

### `zAF_ShowInput(config)`

Floating dialog with a title bar, body text, and a multi-line (or single-line) edit box.

```lua
zAF_ShowInput({
    title      = "Import Profile",
    body       = "Paste your export string below.",
    acceptText = "Import",
    mode       = nil,               -- nil = multi-line, "single" = single-line
    onAccept   = function(text) ParseImport(text) end,
    onCancel   = function() end,
})
```

| Config Key | Type | Description |
|------------|------|-------------|
| `title` | `string\|nil` | Dialog title bar text. |
| `body` | `string\|nil` | Instruction text above the edit box. |
| `acceptText` | `string\|nil` | Confirm button label. Defaults to the WoW global `OKAY`. |
| `mode` | `"single"\|nil` | `"single"` = single-line input. Omit for multi-line. |
| `onAccept` | `function(text)\|nil` | Receives the contents of the edit box. |
| `onCancel` | `function()\|nil` | Called on cancel or close. |

---

### `zAF_ShowOutput(config)`

Read-only floating dialog for displaying long text (export strings, debug dumps, etc.).

```lua
zAF_ShowOutput({
    title = "Export Profile",
    body  = "Copy the string below.",
    text  = MyAddon:ExportProfile(),
})
```

| Config Key | Type | Description |
|------------|------|-------------|
| `title` | `string\|nil` | Dialog title bar text. |
| `body` | `string\|nil` | Instruction text above the read-only box. |
| `text` | `string\|nil` | The text pre-filled in the read-only box. |
| `onClose` | `function()\|nil` | Called when the dialog closes. |

---

## Style Constants

Defined in `zAF_Style.lua` as fields on the global `ZAF` table. Available immediately after the XML loads.

### Colors

| Constant | Default | Description |
|----------|---------|-------------|
| `ZAF.COLOR_PANEL_BG` | `{ 0, 0, 0, 0.10 }` | Suggested content area background. |
| `ZAF.COLOR_SIDEBAR_BG` | `{ 0, 0, 0, 0.45 }` | Suggested sidebar / title bar overlay. |
| `ZAF.COLOR_ROW_HOVER` | `{ 1.0, 0.90, 0.65, 0.12 }` | Row hover highlight tint. |
| `ZAF.COLOR_BORDER` | `{ 0.35, 0.35, 0.35, 0.65 }` | Default border and widget edges. |
| `ZAF.COLOR_LABEL` | `{ 0.90, 0.90, 0.90, 1.0 }` | Primary label text. |
| `ZAF.COLOR_DESC` | `{ 0.58, 0.58, 0.58, 1.0 }` | Description / sub-label text. |
| `ZAF.COLOR_HEADER_TEXT` | `{ 0.85, 0.75, 0.30, 1.0 }` | Section header text (warm gold). |
| `ZAF.COLOR_DISABLED` | `{ 0.38, 0.38, 0.38, 1.0 }` | Disabled control text / icons. |
| `ZAF.COLOR_TAB_ACTIVE` | `{ 0, 0, 0, 0.35 }` | Active tab / nav highlight. |
| `ZAF.COLOR_SEPARATOR` | `{ 0.22, 0.22, 0.22, 1.0 }` | Horizontal separator lines. |
| `ZAF.COLOR_ROW_STRIPE_ODD` | `{ 0.1, 0.1, 0.1, 0.25 }` | Odd row stripe background. |
| `ZAF.COLOR_ROW_STRIPE_EVEN` | `{ 0.01, 0.01, 0.01, 0.5 }` | Even row stripe background. |
| `ZAF.COLOR_COL_DIVIDER` | `{ 1, 1, 1, 0.04 }` | Column divider in multi-column layouts. |
| `ZAF.COLOR_SECTION_BORDER` | `{ 1, 1, 1, 0.04 }` | Per-section block border. |
| `ZAF.DISABLED_ALPHA` | `0.40` | Alpha for disabled widgets (scalar, not a color table). |

### Theme Defaults

| Constant | Value | Description |
|----------|-------|-------------|
| `ZAF.DEFAULT_PRIMARY` | `ZAF.CLASS_EVOKER` | Fallback primary accent when no context or caller value is present. |
| `ZAF.DEFAULT_SECONDARY` | `ZAF.CLASS_PALADIN` | Fallback secondary accent. |

### Class Color Constants

Pre-defined class color tables (no alpha component — normalisation adds `a = 1` automatically):

| Constant | Class | Value |
|----------|-------|-------|
| `ZAF.CLASS_DEATHKNIGHT` | Death Knight | `{ 0.77, 0.12, 0.23 }` |
| `ZAF.CLASS_DEMONHUNTER` | Demon Hunter | `{ 0.64, 0.19, 0.79 }` |
| `ZAF.CLASS_DRUID` | Druid | `{ 1.0, 0.49, 0.04 }` |
| `ZAF.CLASS_EVOKER` | Evoker | `{ 0.20, 0.58, 0.50 }` |
| `ZAF.CLASS_HUNTER` | Hunter | `{ 0.67, 0.83, 0.45 }` |
| `ZAF.CLASS_MAGE` | Mage | `{ 0.25, 0.78, 0.92 }` |
| `ZAF.CLASS_MONK` | Monk | `{ 0.0, 1.0, 0.60 }` |
| `ZAF.CLASS_PALADIN` | Paladin | `{ 0.96, 0.55, 0.73 }` |
| `ZAF.CLASS_PRIEST` | Priest | `{ 1.0, 1.0, 1.0 }` |
| `ZAF.CLASS_ROGUE` | Rogue | `{ 1.0, 0.96, 0.41 }` |
| `ZAF.CLASS_SHAMAN` | Shaman | `{ 0.0, 0.44, 0.87 }` |
| `ZAF.CLASS_WARLOCK` | Warlock | `{ 0.53, 0.53, 0.93 }` |
| `ZAF.CLASS_WARRIOR` | Warrior | `{ 0.78, 0.61, 0.43 }` |

All three forms are accepted wherever colors are expected:

```lua
-- All equivalent:
primary = ZAF.CLASS_WARRIOR
primary = RAID_CLASS_COLORS["WARRIOR"]
primary = { 0.78, 0.61, 0.43 }
```

---

## Easing Functions

`ZAF.EASE_*` functions take `t ∈ [0, 1]` and return an eased value in `[0, 1]`. Pass them to `zAF_Tween`.

| Constant | Description |
|----------|-------------|
| `ZAF.EASE_LINEAR` | No easing. |
| `ZAF.EASE_QUAD_IN` | Accelerating (quadratic). |
| `ZAF.EASE_QUAD_OUT` | Decelerating (quadratic). |
| `ZAF.EASE_CUBIC_IN` | Accelerating (cubic). |
| `ZAF.EASE_CUBIC_OUT` | Decelerating (cubic). |
| `ZAF.EASE_SINE_OUT` | Smooth deceleration (sine). Good for UI reveals. |
| `ZAF.EASE_SINE_IN` | Smooth acceleration (sine). Good for UI hides. |

```lua
zAF_Tween(0, 1, 0.25, ZAF.EASE_SINE_OUT, function(v) myFrame:SetAlpha(v) end)
```

---

## Complete Example

A standalone addon that uses zAF directly — no zSF, no schema.

### `MyAddon.toc`

```toc
## Interface: 120001
## Title: MyAddon
## SavedVariables: MyAddonDB

Libs\zAddonFramework-1.0\zAddonFramework-1.0.xml

MyAddon.lua
```

### `MyAddon.lua`

```lua
-- Initialize saved variables.
local function OnLoaded()
    MyAddonDB = MyAddonDB or { volume = 50, enabled = true, color = { 1, 0.8, 0 } }
end

-- Register with zAF.
local ctx = zAddonFramework.Register({
    addonName   = "MyAddon",
    themeColors = {
        primary   = ZAF.CLASS_WARRIOR,
        secondary = ZAF.CLASS_ROGUE,
    },
})

-- Build the UI.
local function BuildUI()
    local f = zAF_Frame("MyAddonFrame", UIParent)
    f:SetSize(300, 220)
    f:SetPoint("CENTER")
    f:SetMovable(true)
    f:EnableMouse(true)
    f:RegisterForDrag("LeftButton")
    f:SetScript("OnDragStart", function(self) self:StartMoving() end)
    f:SetScript("OnDragStop",  function(self) self:StopMovingOrSizing() end)
    f:SetFrameStrata("DIALOG")

    zAF_FillBG(f, 0.05, 0.05, 0.05, 0.96)
    zAF_DrawBorder(f)

    -- Push context so all widget calls below inherit our fonts and colors.
    ctx:Push()

    -- Section header.
    zAF_BuildSectionHeader(f, "Settings", 12)

    -- Pill toggle.
    local toggleCell = CreateFrame("Frame", nil, f)
    toggleCell:SetPoint("TOPLEFT", f, "TOPLEFT", 12, -40)
    toggleCell:SetSize(280, 20)

    local lbl = zAF_Text(toggleCell, "GameFontNormal", "LEFT")
    lbl:SetText("Enable feature")
    lbl:SetTextColor(unpack(ZAF.COLOR_LABEL))
    lbl:SetPoint("LEFT", toggleCell, "LEFT", 0, 0)

    local toggle = zAF_MakePillToggle(toggleCell)
    toggle:SetPoint("RIGHT", toggleCell, "RIGHT", 0, 0)
    toggle:SetChecked(MyAddonDB.enabled)
    toggle:SetScript("OnClick", function(self) MyAddonDB.enabled = self:GetChecked() end)

    -- Slider.
    local sliderCell = CreateFrame("Frame", nil, f)
    sliderCell:SetPoint("TOPLEFT", toggleCell, "BOTTOMLEFT", 0, -16)
    sliderCell:SetWidth(200)

    local sliderLbl = zAF_Text(f, "GameFontNormal", "LEFT")
    sliderLbl:SetText("Volume")
    sliderLbl:SetTextColor(unpack(ZAF.COLOR_LABEL))
    sliderLbl:SetPoint("BOTTOMLEFT", sliderCell, "TOPLEFT", 0, 2)

    local slider = zAF_BuildSlider(sliderCell, 0, 100, MyAddonDB.volume, 1)
    slider.SetOnChanged(function(v) MyAddonDB.volume = v end)

    -- Color swatch.
    local swatchCell = CreateFrame("Frame", nil, f)
    swatchCell:SetPoint("TOPLEFT", sliderCell, "BOTTOMLEFT", 0, -24)
    swatchCell:SetWidth(100)

    local c = MyAddonDB.color
    local swatch = zAF_BuildColorSwatch(swatchCell, c[1], c[2], c[3])
    swatch.frame:SetScript("OnMouseDown", function()
        local cur = MyAddonDB.color
        zAF_ShowColorPicker({
            r = cur[1], g = cur[2], b = cur[3],
            onAccept = function(r, g, b)
                MyAddonDB.color = { r, g, b }
                swatch.bg:SetColorTexture(r, g, b, 1)
            end,
        })
    end)
    zAF_AddTooltip(swatch.frame, "Accent Color", "Click to change.")

    -- Close button.
    local closeBtn = zAF_BuildCloseButton(f)
    closeBtn:SetPoint("TOPRIGHT", f, "TOPRIGHT", -6, -6)
    closeBtn:SetScript("OnClick", function() f:Hide() end)

    ctx:Pop()

    return f
end

local _frame = nil

local loader = CreateFrame("Frame")
loader:RegisterEvent("ADDON_LOADED")
loader:SetScript("OnEvent", function(_, _, addonName)
    if addonName == "MyAddon" then
        OnLoaded()
        _frame = BuildUI()
    end
end)

SLASH_MYADDON1 = "/myaddon"
SlashCmdList["MYADDON"] = function()
    if _frame then
        _frame:SetShown(not _frame:IsShown())
    end
end
```
