# OneWoW_GUI - Quick Reference

- **Library:** `LibStub("OneWoW_GUI-1.0")`
- **Location:** `/OneWoW_GUI/`
- **Loaded by:** All addons (via RequiredDeps)

---

## How To Get It

```lua
local OneWoW_GUI = LibStub("OneWoW_GUI-1.0", true)
if not OneWoW_GUI then return end
```

---

## Centralized Settings (Settings.lua)

GUI owns the shared settings database (`OneWoW_GUI_DB` SavedVariables).
All ecosystem addons read/write through GUI. No more duplicate theme/language/minimap storage.

### Settings stored
- `theme` - color theme key (default: "green")
- `language` - locale key (default: GetLocale())
- `font` - font key (default: "default")
- `minimap.hide` - minimap button visibility (default: false)
- `minimap.theme` - faction icon: "horde", "alliance", or "neutral" (default: "horde")

### Get a setting
```lua
local theme = OneWoW_GUI:GetSetting("theme")         -- "green", "blue", etc.
local lang  = OneWoW_GUI:GetSetting("language")       -- "enUS", "koKR", etc.
local font  = OneWoW_GUI:GetSetting("font")           -- "default", "expressway", etc.
local hide  = OneWoW_GUI:GetSetting("minimap.hide")   -- true/false
local icon  = OneWoW_GUI:GetSetting("minimap.theme")  -- "horde"/"alliance"/"neutral"
```

### Set a setting (fires callbacks to all registered addons)
```lua
OneWoW_GUI:SetSetting("theme", "blue")
OneWoW_GUI:SetSetting("language", "koKR")
OneWoW_GUI:SetSetting("font", "expressway")
OneWoW_GUI:SetSetting("minimap.hide", true)
OneWoW_GUI:SetSetting("minimap.theme", "alliance")
```

### Register for settings change callbacks
```lua
OneWoW_GUI:RegisterSettingsCallback("OnThemeChanged", myAddon, function(self, newThemeKey)
    OneWoW_GUI:ApplyTheme(self)
    -- rebuild your UI here
end)

OneWoW_GUI:RegisterSettingsCallback("OnLanguageChanged", myAddon, function(self, newLangKey)
    -- re-apply your locale tables here, rebuild UI
end)

OneWoW_GUI:RegisterSettingsCallback("OnMinimapChanged", myAddon, function(self, isHidden)
    -- show/hide your minimap button
end)

OneWoW_GUI:RegisterSettingsCallback("OnIconThemeChanged", myAddon, function(self, newIconTheme)
    -- update your minimap icon
end)

OneWoW_GUI:RegisterSettingsCallback("OnFontChanged", myAddon, function(self, newFontKey)
    -- refresh your UI text with the new font
end)
```

### Get the current font file path
```lua
local fontPath = OneWoW_GUI:GetFont()
-- Returns the font file path string, or nil if set to "WoW Default"
-- Example: "Interface\AddOns\OneWoW_GUI\Media\Fonts\Expressway.ttf"
if fontPath then
    myFontString:SetFont(fontPath, 12)
end
```

### Available font keys

Use `OneWoW_GUI:GetFontList()` for the complete list. Sample:

| Key | Label |
|-----|-------|
| default | WoW Default |
| actionman | Action Man |
| adventure | Adventure |
| bazooka | Bazooka |
| blackchancery | Black Chancery |
| celestia | Celestia Medium Redux |
| continuum | Continuum Medium |
| dejavusans | DejaVu Sans |
| dejavuserif | DejaVu Serif |
| diedidie | DieDieDie |
| dorispp | DorisPP |
| enigmatic | Enigmatic |
| expressway | Expressway |
| fitzgerald | Fitzgerald |
| gentiumplus | Gentium Plus |
| hack | Hack |
| homespun | Homespun |
| hookedup | All Hooked Up |
| liberationmono | Liberation Mono |
| liberationsans | Liberation Sans |
| liberationserif | Liberation Serif |
| ptsansnarrow | PT Sans Narrow |
| sfatarian | SF Atarian System |
| sfcovington | SF Covington |
| sfmovieposter | SF Movie Poster |
| sfwondercomic | SF Wonder Comic |
| swfit | SWF!T |
| texgyreadventor | TeX Gyre Adventor |
| texgyreadventorbold | TeX Gyre Adventor Bold |
| wenquanyi | WenQuanYi Zen Hei |
| yellowjacket | Yellowjacket |

### Font API
```lua
local fontList = OneWoW_GUI:GetFontList()           -- full list of { key, label, file }
local path = OneWoW_GUI:GetFontByKey("expressway") -- path or nil for default
OneWoW_GUI:SafeSetFont(fontString, path, 12, "")  -- applies font with fallback to GameFontNormal
local key = OneWoW_GUI:MigrateLSMFontName("Expressway")  -- maps LibSharedMedia names to GUI keys
```

### Stamp out the standard 4-part settings panel
```lua
local yOffset = OneWoW_GUI:CreateSettingsPanel(parentFrame, {
    yOffset = -10,  -- optional, default -10
})
-- yOffset is now updated; continue adding addon-specific content below
```
This creates three themed containers:
1. Language Selection (left) | Color Theme (right) - with dropdowns
2. Font (full width) - dropdown with live preview text
3. Minimap Button checkbox (left) | Icon Theme dropdown (right)

All dropdowns read/write directly to `OneWoW_GUI_DB` and fire callbacks.
The panel consumes ~480px of vertical space.

### Migrate existing settings (call once at addon init)
```lua
OneWoW_GUI:MigrateSettings(addon.db.global)
```
On first run, copies theme/language/minimap from the addon's old DB into GUI DB.
Only runs once (sets `_migrated` flag). Safe to call every load.

### Window Position Persistence

Use `SaveWindowPosition` and `RestoreWindowPosition` for movable main windows. Standard DB key: `mainFramePosition` (shape: `{ point, relativePoint, x, y, width?, height? }`). Save on `OnHide` so position persists on close, FullReset, and theme change.

```lua
-- In addon DB defaults: mainFramePosition = {}

-- After creating the main frame:
local storage = addon.db.global.mainFramePosition or {}
if not OneWoW_GUI:RestoreWindowPosition(mainFrame, storage) then
    mainFrame:SetPoint("CENTER")
end

mainFrame:SetScript("OnHide", function()
    local db = addon.db.global
    db.mainFramePosition = db.mainFramePosition or {}
    OneWoW_GUI:SaveWindowPosition(mainFrame, db.mainFramePosition)
end)
```

### Adding GUI settings to a new addon (full pattern)
```lua
function addon:OnInitialize()
    self:InitializeDatabase()
    OneWoW_GUI:MigrateSettings(self.db.global)
    OneWoW_GUI:ApplyTheme(self)

    OneWoW_GUI:RegisterSettingsCallback("OnThemeChanged", self, function(self2)
        OneWoW_GUI:ApplyTheme(self2)
        -- rebuild UI
    end)
    OneWoW_GUI:RegisterSettingsCallback("OnLanguageChanged", self, function(self2)
        -- re-apply locale, rebuild UI
    end)
    OneWoW_GUI:RegisterSettingsCallback("OnMinimapChanged", self, function(self2, hidden)
        -- show/hide minimap button
    end)
    OneWoW_GUI:RegisterSettingsCallback("OnIconThemeChanged", self, function(self2)
        -- update minimap icon
    end)
    OneWoW_GUI:RegisterSettingsCallback("OnFontChanged", self, function(self2, newFontKey)
        -- refresh UI text with OneWoW_GUI:GetFont()
    end)
end

-- In your settings tab builder:
function CreateMySettingsTab(parent)
    local yOffset = OneWoW_GUI:CreateSettingsPanel(parent, { yOffset = -10 })
    -- add addon-specific settings below using yOffset
end
```

---

## Theme System

### Apply a theme (call once at addon startup)
```lua
OneWoW_GUI:ApplyTheme(addon)
```
Checks GUI settings DB first, then OneWoW hub, then addon.db.global.theme, falls back to green.

### Get a theme color
```lua
local r, g, b, a = OneWoW_GUI:GetThemeColor("ACCENT_PRIMARY")
frame:SetBackdropColor(OneWoW_GUI:GetThemeColor("BG_PRIMARY"))
```

### Available color keys
BG_PRIMARY, BG_SECONDARY, BG_TERTIARY, BG_HOVER, BG_ACTIVE,
ACCENT_PRIMARY, ACCENT_SECONDARY, ACCENT_HIGHLIGHT, ACCENT_MUTED,
TEXT_PRIMARY, TEXT_SECONDARY, TEXT_MUTED, TEXT_ACCENT,
BORDER_DEFAULT, BORDER_SUBTLE, BORDER_FOCUS, BORDER_ACCENT,
TITLEBAR_BG, TITLEBAR_BORDER,
BTN_NORMAL, BTN_HOVER, BTN_PRESSED, BTN_BORDER, BTN_BORDER_HOVER,
TEXT_FEATURES_ENABLED, TEXT_FEATURES_DISABLED,
DOT_FEATURES_ENABLED, DOT_FEATURES_DISABLED,
TEXT_WARNING,
BTN_DANGER_NORMAL, BTN_DANGER_HOVER, BTN_DANGER_BORDER, BTN_DANGER_BORDER_HOVER

### Get spacing value
```lua
local px = OneWoW_GUI:GetSpacing("MD")
```
XS=4, SM=8, MD=12, LG=16, XL=24

### Available themes (24 total)
green, blue, purple, red, orange, teal, gold, pink, dark, amber, cyan, slate,
voidblack, charcoal, forestnight, obsidian, monochrome, twilight, neon,
glassmorphic, lightmode, retro, fantasy, nightfae

Order stored in `Constants.THEMES_ORDER`.

### Get faction brand icon
```lua
local texture = OneWoW_GUI:GetBrandIcon("horde")  -- or "alliance" or "neutral"
```

### Register GUI constants with fallback

Addons can override or add GUI constants (especially window sizes). Missing keys fall back to `Constants.GUI`, then to `0`. The returned table is read-only.

**Signature:** `OneWoW_GUI:RegisterGUIConstants(guiConstants)` — takes a table, returns a table with metatable.

**Typical usage** — store as `addon.Constants.GUI` in Core/Constants.lua:

```lua
OneWoW_MyAddon.Constants = {
    GUI = OneWoW_GUI:RegisterGUIConstants({
        WINDOW_WIDTH  = 820,
        WINDOW_HEIGHT = 580,
        MIN_WIDTH     = 820,
        MIN_HEIGHT    = 500,
        LEFT_PANEL_WIDTH = 300,
        SEARCH_HEIGHT = 28,
        ROW_HEIGHT    = 38,  -- addon-specific; falls back to 0 if unused
    }),
}
```

**Base GUI keys** (override any; add custom keys as needed):
WINDOW_WIDTH, WINDOW_HEIGHT, MIN_WIDTH, MIN_HEIGHT, MAX_WIDTH, MAX_HEIGHT,
PADDING, BUTTON_HEIGHT, BUTTON_WIDTH, SEARCH_HEIGHT, SEARCH_WIDTH,
CHECKBOX_SIZE, ROW1_HEIGHT, ROW2_HEIGHT, LEFT_PANEL_WIDTH, PANEL_GAP, TAB_BUTTON_HEIGHT

**Common overrides:** WINDOW_WIDTH, WINDOW_HEIGHT, MIN_WIDTH, MIN_HEIGHT, LEFT_PANEL_WIDTH, SIDEBAR_WIDTH, SEARCH_HEIGHT, ROW_HEIGHT, SUBTAB_BUTTON_HEIGHT. Use WINDOW_WIDTH and WINDOW_HEIGHT for main window dimensions.

---

## Frames & Layout

### Component API Conventions

All component creation functions use the **`(parent, options)`** pattern: parent first (when applicable), all other parameters in an options table. This improves discoverability and extensibility.

```lua
local C = OneWoW_GUI.Constants
local frame = OneWoW_GUI:CreateFrame(parent, {
    name = "MyFrame",
    width = 400,
    height = 300,
    backdrop = C.BACKDROP_SOFT,  -- required; use Constants.BACKDROP_SOFT, BACKDROP_INNER_NO_INSETS, etc.
})
```

### Basic themed frame
```lua
local C = OneWoW_GUI.Constants
local frame = OneWoW_GUI:CreateFrame(parent, {
    name = "MyFrame",
    width = 400,
    height = 300,
    backdrop = C.BACKDROP_SOFT,
})
```
Returns a BackdropTemplate frame with theme BG_PRIMARY + BORDER_DEFAULT.
`backdrop` is required; use `OneWoW_GUI.Constants.BACKDROP_SOFT`, `BACKDROP_INNER_NO_INSETS`, etc.

### Dialog
```lua
local result = OneWoW_GUI:CreateDialog({
    name = "MyDialog",              -- frame name (nil = anonymous, but needed for ESC close)
    title = "Export Profile",       -- title bar text
    width = 620,                    -- required
    height = 500,                   -- required
    strata = "DIALOG",             -- optional, default "DIALOG"
    movable = true,                -- optional, default true
    escClose = true,               -- optional, default true (adds to UISpecialFrames)
    showBrand = false,             -- optional, OneWoW brand icon in title bar
    titleIcon = nil,               -- optional, texture path for icon left of title
    titleHeight = 28,              -- optional, default 28
    onClose = function() end,      -- optional, called when X button or ESC closes
    showScrollFrame = false,       -- optional, creates scroll frame in content area
    buttons = {                    -- optional footer button row
        { text = "Import", onClick = function(dialog) end },
        { text = "Cancel", onClick = function(dialog) dialog:Hide() end, color = {0.6, 0.2, 0.2} },
    },
})
```
Returns a table:
- `result.frame` - main frame (call `:Show()` / `:Hide()`)
- `result.titleBar` - title bar frame from CreateTitleBar
- `result.contentFrame` - area between title bar and button row (add your content here)
- `result.scrollFrame` / `result.scrollContent` - if `showScrollFrame = true`
- `result.buttons` - indexed table of button frames matching `buttons` order

Button `color` option: `{r, g, b}` overrides the button background (useful for green confirm, red destructive).
Buttons are right-aligned in footer. A 1px divider separates content from buttons.
Frame starts hidden - call `result.frame:Show()` when ready.

### Confirm dialog (simple yes/no)
```lua
local result = OneWoW_GUI:CreateConfirmDialog({
    name = "MyConfirm",             -- optional frame name
    title = "Confirm Restore",      -- accent header text
    message = "Are you sure?",      -- body text below title
    width = 420,                    -- optional, default 420
    buttons = {
        { text = "Confirm", color = {0.2, 0.6, 0.2}, onClick = function(dialog) end },
        { text = "Cancel", onClick = function(dialog) dialog:Hide() end },
    },
})
```
Convenience wrapper around `CreateDialog` with `movable = false` and auto-calculated height.
Returns the same table as `CreateDialog` plus:
- `result.titleLabel` - FontString for the title text
- `result.messageLabel` - FontString for the message text

Not movable, centered on screen, ESC closes. Title displayed as large accent text (no title bar).

### Filter bar (horizontal control container)
```lua
local filterBar = OneWoW_GUI:CreateFilterBar(parent, {
    height = 40,              -- optional, default 40
    anchorBelow = someFrame,  -- optional, anchor below this frame instead of parent top
    offset = -5,              -- optional, vertical gap from anchor
})
```
Creates a themed container bar (BG_SECONDARY + BORDER_DEFAULT) anchored across the top of parent.
Add your own controls inside (dropdowns, search boxes, buttons) using existing library functions.

### Title bar
```lua
local titleBar = OneWoW_GUI:CreateTitleBar(parent, {
    title = "My Title",
    height = 20,           -- optional, default 20
    onClose = function() parent:Hide() end,  -- optional close button
    showBrand = true,      -- optional OneWoW brand icon + text
    factionTheme = "horde" -- optional, auto-reads from GUI settings if omitted
})
```
Access title text via `titleBar._titleText`.
Access close button via `titleBar._closeBtn` (nil if no `onClose` provided).
When `showBrand = true` and `factionTheme` is omitted, the icon is auto-read from
`OneWoW_GUI:GetSetting("minimap.theme")` (horde/alliance/neutral). This means all
title bars automatically update when the user changes their faction icon setting.

---

## Buttons & Controls

### Button (base - fixed size)
```lua
local btn = OneWoW_GUI:CreateButton(parent, {
    name = "CloseBtn",
    text = "X",
    width = 20,
    height = 20,
})
```
Fixed-size button. Use only for icon buttons (e.g. "X" close). For text buttons, use FitText or FitFrame.

### Fit Text Button (auto-sizes to text)
```lua
local btn = OneWoW_GUI:CreateFitTextButton(parent, {
    text = "Click Me",
    height = 28,      -- optional, default BUTTON_HEIGHT
    minWidth = 40,    -- optional, default 40
    paddingX = 24,    -- optional, default 24 (12 each side)
})
```
Auto-sizes width to fit text content. Handles localization where translated text may be longer.
Call `btn:SetFitText("New Text")` to update text and auto-resize.
Access label via `btn.text`.

### Fit Frame Buttons (fill container width)
```lua
local buttons, finalY = OneWoW_GUI:CreateFitFrameButtons(parent, {
    yOffset = 0,
    items = {
        { text = "Option A", value = "a", isActive = true },
        { text = "Option B", value = "b" },
        { text = "Option C", value = "c" },
    },
    height = 26,      -- optional, default 26
    gap = 4,          -- optional, default 4
    marginX = 12,     -- optional, default 12
    width = 400,      -- optional, defaults to parent:GetWidth()
    onSelect = function(value, text, btn)
        -- handle selection
    end,
})
```
Creates N equal-width buttons that fill the available width. Auto-wraps to next row if needed.
Active button: BG_ACTIVE + BORDER_ACCENT + TEXT_ACCENT. Inactive: BTN_NORMAL + TEXT_MUTED.
Clicking a button auto-toggles active state across all buttons.
Use `buttons.SetActiveByValue(value)` to update selection externally.
Returns buttons table and finalY offset for layout continuation.

### On/Off toggle pair
```lua
local onBtn, offBtn, refresh, statusPfx, statusVal = OneWoW_GUI:CreateOnOffToggleButtons(parent, {
    yOffset = 0,
    onLabel = "On",
    offLabel = "Off",
    width = 50,
    height = 22,
    isEnabled = true,
    value = true,
    onValueChange = function(newValue)
        -- handle value change
    end,
})
-- Update state later:
refresh(isEnabled, newValue)
```
Layout: `Status: On [On] [Off]` - statusPfx anchors TOPLEFT at x=12, buttons follow.
Active button: BG_ACTIVE + BORDER_ACCENT + TEXT_ACCENT. Inactive: BTN_NORMAL + TEXT_MUTED.
Status text: TEXT_FEATURES_ENABLED (green) when on, TEXT_FEATURES_DISABLED (red) when off.
When disabled (isEnabled=false): all elements muted, buttons non-interactive.
To right-align, clear points on offBtn/onBtn/statusVal/statusPfx and re-anchor from TOPRIGHT.
To reposition the cluster after a label, call `statusPfx:ClearAllPoints()` + `statusPfx:SetPoint(...)`.

### Toggle row (label + description/custom + On/Off)
```lua
local newYOffset, refresh, refs = OneWoW_GUI:CreateToggleRow(parent, {
    yOffset = 0,
    label = "Show Lockouts Panel",
    description = "Show the lockouts panel when the Group Finder opens.",  -- optional
    value = true,
    isEnabled = true,
    onValueChange = function(newVal) SaveSetting("show_panel", newVal) end,
    onLabel = "On",   -- optional
    offLabel = "Off", -- optional
})
-- Update state later:
refresh(isEnabled, newValue)
-- refs.label, refs.contentArea (nil if description used)
```
Layout: Row 1: [Label] ... [Status: On] [On] [Off] (right-aligned by default). Row 2: [Description] or custom content.
Use `align = "left"` for module-level Enable: [Label] [Status: On] [On] [Off] all left-aligned.
Use `createContent` instead of `description` for custom widgets (e.g. mount picker). Must return `(widget, height)`:
```lua
local newYOffset, refresh, refs = OneWoW_GUI:CreateToggleRow(parent, {
    yOffset = 0,
    label = "Ground Mount",
    createContent = function(container)
        local btn = CreateFrame("Button", nil, container, "BackdropTemplate")
        btn:SetSize(220, 30)
        btn:SetPoint("TOPLEFT", container, "TOPLEFT", 0, 0)
        -- ... setup btn ...
        return btn, 30  -- widget, height (required)
    end,
    value = true,
    isEnabled = true,
    onValueChange = function(newVal) ... end,
})
```

### Checkbox
```lua
local cb = OneWoW_GUI:CreateCheckbox(parent, {
    name = "MyCheckbox",
    label = "Label text",
})
```
Uses UICheckButtonTemplate. Access label via `cb.label`.

### Edit box
```lua
local box = OneWoW_GUI:CreateEditBox(parent, {
    name = "MyEditBox",
    width = 200,           -- optional, omit for anchor-based width (flexible)
    height = 22,           -- optional, default SEARCH_HEIGHT
    placeholderText = "Search...",  -- optional
    maxLetters = 50,       -- optional
    onTextChanged = function(text)  -- optional, text has placeholder filtered out
        FilterMyList(text)
    end,
})
```
Themed with focus border highlight and placeholder text behavior.
When `width` is omitted, only height is set - use anchor points for flexible width.
Use `box:GetSearchText()` to get current text with placeholder filtered out.

Use `CreateEditBox` with `placeholderText` for search boxes. The deprecated `CreateSearchBox` wrapper has been removed.

### Status dot
```lua
local dot = OneWoW_GUI:CreateStatusDot(parent, {
    size = 8,          -- optional, default 8
    enabled = true,    -- optional, sets initial color (true=green, false=red)
})
dot:SetPoint("RIGHT", row, "RIGHT", -8, 0)
dot:SetStatus(true)   -- update: true=DOT_FEATURES_ENABLED, false=DOT_FEATURES_DISABLED
```

### List row (basic)
```lua
local row = OneWoW_GUI:CreateListRowBasic(parent, {
    height = 30,                -- optional, default 30
    label = "Item Name",        -- optional, default ""
    showDot = true,             -- optional, adds status dot on right
    dotEnabled = true,          -- optional, initial dot state
    showValueText = false,      -- optional, adds right-aligned value text
    valueText = "1.50",         -- optional, initial value text
    onClick = function(self)    -- optional
        previousRow:SetActive(false)
        self:SetActive(true)
    end,
})
row:SetPoint("TOPLEFT", parent, "TOPLEFT", 4, yOffset)
row:SetPoint("TOPRIGHT", parent, "TOPRIGHT", -4, yOffset)
```
Returns a Button with themed hover/active states. Properties:
- `row.label` - FontString (GameFontNormal)
- `row.dot` - StatusDot texture (if showDot=true), has `:SetStatus(bool)`
- `row.valueText` - FontString (if showValueText=true)
- `row:SetActive(bool)` - toggle active/selected styling
- `row.isActive` - current active state

Future variants: `CreateListRowExtended` (expandable content section on click).

---

## Text & Dividers

### Header (large accent text)
```lua
local header = OneWoW_GUI:CreateHeader(parent, {
    text = "Section Title",
    yOffset = -12,
})
```

### Divider (1px horizontal line)
```lua
local divider = OneWoW_GUI:CreateDivider(parent, {
    yOffset = 0,
})
```

### Section (header + divider combo)
```lua
local newYOffset = OneWoW_GUI:CreateSection(parent, {
    title = "Section Title",
    yOffset = 0,
})
-- Returns updated yOffset to continue laying out below
```

---

## Section Headers

### Themed section header bar
```lua
local section = OneWoW_GUI:CreateSectionHeader(parent, {
    title = "Section Title",
    yOffset = 0,
})
-- section.bottomY = yOffset below the header for continued layout
```
Creates a themed bar with background, border, and accent-colored title text.

---

## Scroll Frames

### Standalone scroll frame
```lua
local scrollFrame, content = OneWoW_GUI:CreateScrollFrame(parent, {
    name = "MyScroll",  -- optional, nil for anonymous
    width = 400,        -- optional; omit for auto-sync on resize
})
```
Uses UIPanelScrollFrameTemplate (Lesson 3 compliant).
ScrollBar anchored to parent container.
- Without width: content width auto-syncs on resize.
- With width: content width set to (width - 32).

### Scrollable multiline edit box
```lua
local scrollFrame, editBox = OneWoW_GUI:CreateScrollEditBox(parent, {
    name = "MyEditBox",        -- optional; scrollFrame gets name.."Scroll"
    font = fontPath,           -- optional; falls back to user's chosen GUI font, then ChatFontNormal
    fontSize = 12,             -- optional, default 12 (used when font is set)
    fontFlags = "",            -- optional, default ""
    maxLetters = 0,            -- optional, default 0 (unlimited)
    textInsets = { 4, 4, 4, 4 },  -- optional, {left, right, top, bottom}, default 4px all sides
    textColor = { r, g, b },  -- optional; defaults to TEXT_PRIMARY theme color
    onTextChanged = function(self, userInput)  -- optional
        -- fires on every keystroke
    end,
    onEscapePressed = function(self)  -- optional
        -- fires after ClearFocus() is already called
    end,
})
```
Correct pattern for multiline text entry areas. Fixes the focus dead-zone bug inherent to
`SetHeight(1)` scroll children: clicking anywhere in the visible area always focuses the edit box.

- ScrollFrame uses `UIPanelScrollFrameTemplate` with styled scrollbar.
- EditBox is the scroll child, starts at height 1 and auto-expands with content.
- Width auto-syncs to scrollFrame on resize.
- `scrollFrame:HookScript("OnMouseDown")` calls `editBox:SetFocus()` so clicks anywhere in the
  visible area work, not just the first pixel row.
- Font defaults to the user's active GUI font setting, then `ChatFontNormal`.
- Default anchor: TOPLEFT +8,-8 / BOTTOMRIGHT -8,8 relative to parent. Override after creation if needed.

Use this instead of manually creating `ScrollFrame + EditBox` pairs. Migrate existing scroll+editbox
combos to this function to get the focus fix for free.

### Style an existing scroll bar
```lua
OneWoW_GUI:StyleScrollBar(scrollFrame, {
    container = parentFrame,  -- optional, anchors scrollbar to this
    offset = -2,              -- optional, right offset
})
```

---

## Split Panel (List + Detail Layout)

```lua
local panels = OneWoW_GUI:CreateSplitPanel(parent, {
    showSearch = true,              -- optional search box in list panel
    searchPlaceholder = "Search...",-- optional placeholder text for search box
})
```

Returns a table with:
- `panels.listPanel` - left panel frame
- `panels.listTitle` - left title font string
- `panels.listScrollFrame` / `panels.listScrollChild` - left scroll area
- `panels.detailPanel` - right panel frame
- `panels.detailTitle` - right title font string
- `panels.detailScrollFrame` / `panels.detailScrollChild` - right scroll area
- `panels.searchBox` - search edit box (if showSearch=true)
- `panels.leftStatusBar` / `panels.leftStatusText` - left status bar
- `panels.rightStatusBar` / `panels.rightStatusText` - right status bar

Left panel width: 320px. Gap between panels: 10px.

---

## Dropdowns

### Simple dropdown (no search)
```lua
local dropdown, text = OneWoW_GUI:CreateDropdown(parent, {
    width = 200,     -- optional, default 200
    height = 26,     -- optional, default 26
    text = "All",    -- optional, default display text
})
dropdown:SetPoint("LEFT", someFrame, "RIGHT", 8, 0)

OneWoW_GUI:AttachFilterMenu(dropdown, {
    searchable = false,  -- default is true
    buildItems = function()
        return {
            { value = nil, text = "All Characters" },
            { value = "char1", text = "Arthas" },
            { value = "char2", text = "Thrall" },
        }
    end,
    onSelect = function(value, displayText)
        text:SetText(displayText)
        -- do something with value
    end,
    getActiveValue = function() return currentSelection end,
})
```

### Searchable dropdown (with filter box)
```lua
local dropdown, text = OneWoW_GUI:CreateDropdown(parent, {
    width = 200,
    text = "All Zones",
})
dropdown:SetPoint(...)

OneWoW_GUI:AttachFilterMenu(dropdown, {
    searchable = true,  -- default; adds search box at top of menu
    buildItems = function()
        local items = {}
        table.insert(items, { value = nil, text = "All Zones" })
        for _, zone in ipairs(GetZoneList()) do
            table.insert(items, { value = zone, text = zone })
        end
        return items
    end,
    onSelect = function(value, displayText)
        text:SetText(displayText)
    end,
    getActiveValue = function() return currentZone end,
    maxVisible = 20,     -- optional, default 20 (unlimited when searching)
    menuHeight = 314,    -- optional, default 314
})
```

### Dropdown behavior
- Click to open, click again to close
- Active item highlighted with ACCENT_PRIMARY
- Hover: BG_HOVER + TEXT_ACCENT
- Auto-closes after 0.5s when mouse leaves both menu and trigger button
- ESC: clears search text first, closes menu on second press (searchable only)
- Menu opens at FULLSCREEN_DIALOG strata
- buildItems() called fresh each click (supports dynamic lists)
- Scroll uses UIPanelScrollFrameTemplate (Lesson 3 compliant)

### Reset dropdown text externally
```lua
text:SetText("All Zones")
dropdown._activeValue = nil
```

---

## Additional Components

These components exist in the library but are not fully documented here. See source for option keys.

- **CreateSlider(parent, options)** — minVal, maxVal, step, currentVal, onChange, width, fmt
- **CreateProgressBar(parent, options)** — progress bar with theme colors
- **CreateDataTable(parent, options)** — table with `ClearDataRows`, `LayoutDataRows`, `CreateDataRow`
- **CreateOverviewPanel(parent, options)** — overview layout
- **CreateStatusBar(parent, anchorFrame, options)** — status bar
- **CreateRosterPanel(parent, anchorFrame)** — roster layout
- **CreateItemIcon(parent, options)** — item icon frame
- **CreateFactionIcon(parent, options)** — faction icon
- **CreateMailIcon(parent, options)** — mail icon
- **CreateExpandedPanelGrid(ef, options)** — expanded panel grid

**Utility:**
- `GetAddonVersion(addonName)` — returns addon version via C_AddOns
- `GetProgressColor(current, max)` — returns color from PROGRESS_COLORS (NONE/LOW/MID/FULL)

---

## Utility

### Clear all children from a frame
```lua
OneWoW_GUI:ClearFrame(frame)
```
Hides and orphans all child frames and regions.

---

## Available Backdrop Templates

```lua
Constants.BACKDROP_SIMPLE        -- just bgFile (white8x8)
Constants.BACKDROP_SOFT          -- tooltip bg + tooltip border, with insets
Constants.BACKDROP_INNER         -- white8x8 bg + 1px edge, with 1px insets
Constants.BACKDROP_INNER_NO_INSETS  -- white8x8 bg + 1px edge, no insets
```

---

## GUI Dimension Defaults

```
WINDOW_WIDTH = 1075     MIN_WIDTH = 1075      MAX_WIDTH = 2000
WINDOW_HEIGHT = 900     MIN_HEIGHT = 700      MAX_HEIGHT = 1200
PADDING = 12            BUTTON_HEIGHT = 28    BUTTON_WIDTH = 100
SEARCH_HEIGHT = 22      SEARCH_WIDTH = 200    CHECKBOX_SIZE = 24
ROW1_HEIGHT = 35        ROW2_HEIGHT = 30
LEFT_PANEL_WIDTH = 320  PANEL_GAP = 10        TAB_BUTTON_HEIGHT = 30
```

### Adding OneWoW_GUI to a new addon

**Standard (recommended):** Add `## RequiredDeps: OneWoW_GUI` to your TOC. No need to add `OneWoW_GUI_DB` — OneWoW_GUI declares it. All suite addons use this approach.

**Embedding (legacy):** Only if embedding the library files into your addon. Add `OneWoW_GUI_DB` to SavedVariables and the 4 library files to load order (after LibStub, before any UI code). Embedding is uncommon in this suite.

```
## SavedVariables: MyAddon_DB, OneWoW_GUI_DB

Libs\LibStub\LibStub.lua
Libs\OneWoW_GUI\Core.lua
Libs\OneWoW_GUI\Constants.lua
Libs\OneWoW_GUI\OneWoW_GUI.lua
Libs\OneWoW_GUI\Settings.lua
```
