# LibMatic

Shared widget library for Infinite Loop Alchemist addons.

## Modules

### LibMatic-IconPicker-1.0

A modal icon-browser widget any consumer addon can open. Backed by `IconDataProviderMixin` on modern clients with a `GetMacroIcons` / `GetMacroItemIcons` fallback on older flavors.

```lua
local picker = LibStub("LibMatic-IconPicker-1.0")
picker:Open(currentFileID, function(chosen)
    if chosen then
        -- user picked an icon (fileDataID)
    else
        -- user cancelled
    end
end)
```

Public API:

- `lib:Open(currentIcon, callback)` - opens the modal. `currentIcon` is the fileDataID to preview as already-selected (may be `nil`). `callback` is required; called exactly once with the chosen fileDataID or `nil` if cancelled.
- `lib:Close()` - hides the modal without invoking the callback.
- `lib:IsOpen()` - boolean.

### LibMatic-ChangeLog-1.0

An in-game changelog popup any consumer addon can show. Renders a scrolling AceGUI window from a structured spec, version-gates auto-show, and builds the AceConfig styling panel.

```lua
local lib = LibStub("LibMatic-ChangeLog-1.0")
lib:Show({
    title = "MyAddon - Change Log",
    version = "1.2.0",
    footer = "For the full version history see CurseForge.",
    entries = {
        { version = "1.2.0", date = "05/21/2026", sections = {
            { label = "Added", body = "A new thing." },
        } },
    },
    style = MyAddonDB.profile.changeLogPopUp,
})
```

Public API:

- `lib:Show(spec)` - open or re-focus the popup.
- `lib:ShowIfNew(spec, lastSeenVersion)` - show only when `spec.version` is newer than `lastSeenVersion` and `spec.freshInstall` is not set. Returns `wasNewer`.
- `lib:Close()` - hide the popup.
- `lib:BuildOptionsTable(opts)` - return an AceConfig group for changelog styling. `opts.profile` is the live style table; `opts.defaults`, `opts.order`, `opts.name`, `opts.locale` are optional.

When AceGUI is unavailable the library degrades to a `StaticPopup`.

### LibMatic-CombatTextBounce-1.0

Bouncing combat-text animations. Attaches floating, bouncing text to a frame and plays one-off messages.

Public API:

- `lib:AttachBounce(target, opts)` - attach a managed bounce emitter to a frame.
- `lib:Show(text, opts)` - play a single bounce message.
- `lib:SetDefaultFont(path, size, outline, critOutline)`, `lib:SetBounceStyle(style)` / `lib:GetBounceStyle()`, `lib:SetAlwaysBounce(enabled, scale)` - configure behavior.
- `lib:CancelAll()` - stop every active animation. `lib:Demo()` - preview.

### LibMatic-FitText-1.0

Text-fitting mixins. Sizes a button to its label, or scales a font string down to fit a width.

Public API:

- `lib:Mixin(target, padding, canDecrease)` - mix fit-to-text behavior into a button.
- `lib:MixinFontString(fs, maxWidth)` - mix scale-to-fit behavior into a font string.

### LibMatic-RosterFrame-1.0

A resizable, scrollable roster window widget.

```lua
local lib = LibStub("LibMatic-RosterFrame-1.0")
local window = lib:New(config)
window:Show()
```

Public API:

- `lib:New(config)` - create a roster window, returns a window object.
- Window methods: `Show()`, `Hide()`, `Toggle()`, `IsShown()`, `GetFrame()`, `Refresh()`, `ScheduleRefresh()`, `SetHScroll(enabled)`, `SetZebra(enabled)`.

### LibMatic-Startup-1.0

The chat-frame load banner and slash-command help listing. Renders text only; the consumer keeps its own slash-command registration.

```lua
local Startup = LibStub("LibMatic-Startup-1.0")
Startup:Register({
    name = "MyAddon",
    useFactionColors = true,
    optionsCommand = "/myaddon",
    helpCommand = "/myaddonhelp",
    commands = {
        { command = "/myaddon", desc = "Toggle the main window." },
        { command = "/myaddonhelp", desc = "Show this list." },
    },
})
Startup:PrintBanner("MyAddon")
```

Public API:

- `lib:Register(config)` - store a startup profile, returns a handle. Idempotent per `config.name`.
- `lib:Get(name)` - return a stored handle, or `nil`.
- `lib:PrintBanner(handleOrName)` - print the one-line load banner.
- `lib:GetBannerText(handleOrName)` - return the banner string without printing.
- `lib:PrintHelp(handleOrName)` - print the slash-command listing.
- `lib:GetHelpLines(handleOrName)` - return the help listing as a string array.
- `lib:GetAddOnVersion(addonName)` - read the addon `Version` from metadata, `"0.0.0"` on failure.
- `lib:GetBrandColor(handleOrName)` - return the resolved brand color escape.

`useFactionColors` themes the banner Alliance blue or Horde red by `UnitFactionGroup`.

### LibMatic-Settings-1.0

Ace3 page builders for the shared settings UI. Each builder returns a `type="group"` table; the consumer composes the outer options table and keeps its own `RegisterOptionsTable` / `AddToBlizOptions` calls.

```lua
local Settings = LibStub("LibMatic-Settings-1.0")
local args = {
    general   = Settings:BuildGeneralPage({ currentAddon = "MyAddon", nav = { factionIcons = true } }),
    alert     = Settings:BuildAlertPage({ notificationTypes = { { key = "hit", title = "Hit", defaultFormat = "New %s Hit: %d!" } } }),
    sound     = Settings:BuildSoundPage({ slots = { { key = "hit", label = "Hit Sound" } } }),
    social    = Settings:BuildSocialPage({ bind = { table = function() return MyAddon.db.profile.social end } }),
    changeLog = Settings:BuildChangeLogPage({ profile = function() return MyAddon.db.profile.changeLog end }),
    profiles  = Settings:BuildProfilesPage({ db = MyAddon.db }),
}
```

Public API:

- `lib:BuildGeneralPage(opts)` - General page with the cross-addon nav block. `opts.currentAddon` is excluded from the block; `opts.nav.factionIcons` swaps link icons by faction.
- `lib:BuildAlertPage(opts)` - tabbed Alert page. `opts.notificationTypes` generates one editable format-string per type; `opts.bounce` adds the bounce-style picker; `opts.position` adds a "Position Alert" button to the General tab that opens `LibMatic-AlertPositioner-1.0` (carrying `get`/`set`/`default`/`reopen` callbacks plus a `close` callback that hides the settings panel).
- `lib:BuildSoundPage(opts)` - Sound page. `opts.slots` generates one LibSharedMedia sound dropdown per slot.
- `lib:BuildSocialPage(opts)` - Social page channel toggles and optional broadcast formats.
- `lib:BuildChangeLogPage(opts)` - passthrough to `LibMatic-ChangeLog-1.0:BuildOptionsTable`.
- `lib:BuildProfilesPage(opts)` - wraps `AceDBOptions-3.0`. `opts.db` required.
- `lib:ResolveShow(value)` - normalize a `show` flag (nil/boolean/function) to a boolean.
- `lib:GetRoster()` - the canonical ILA addon roster.

Every builder and every setting accepts a developer `show` flag (boolean or function). A page whose `show` resolves false returns `nil`; a setting whose `show` resolves false is hidden.

### LibMatic-AlertPositioner-1.0

A drag-to-place positioner for on-screen alerts. Opens a movable overlay with a looping mock crit so the player can see and place the alert, then commits or discards the new position.

```lua
local positioner = LibStub("LibMatic-AlertPositioner-1.0")
positioner:Open({
    get = function() return MyAddon.db.profile.alert.xPct, MyAddon.db.profile.alert.yPct end,
    set = function(xPct, yPct)
        MyAddon.db.profile.alert.xPct = xPct
        MyAddon.db.profile.alert.yPct = yPct
    end,
    default = function() return 50, 50 end,
    reopen = function() MyAddon:OpenSettings() end,
})
```

Public API:

- `lib:Open(opts)` - open the positioner overlay. `opts.get` returns the current `xPct, yPct` (center-relative percentages, 0-100); `opts.set(xPct, yPct)` is called on Accept; `opts.default` returns the default `xPct, yPct` for the Reset button; `opts.reopen` is called after the overlay closes (Accept or Cancel).

Accept commits the dragged position, Cancel discards it, Reset snaps the overlay to the default position. Escape behaves as Cancel. `LibMatic-CombatTextBounce-1.0` is a soft dependency for the mock crit bounce, with a local fallback when absent.

### LibMatic-Broadcast-1.0

Broadcasts a group chat message with optional single-announcer election. With election on, only one group member who runs the addon sends the message; receiver-side dedupe collapses simultaneous triggers. Renders text only.

```lua
local Broadcast = LibStub("LibMatic-Broadcast-1.0")
Broadcast:Register({ name = "MyAddon", commPrefix = "MYADDONBC", electionEnabled = true })
Broadcast:Broadcast("MyAddon", { text = "MyAddon: <name> needs a buff", marker = 8 })
```

Public API:

- `lib:Register(config)` - store a broadcast profile, register the comm prefix, start the peer registry. `config`: `name`, `commPrefix`, `electionEnabled` (default false), `dedupeSeconds` (default 30).
- `lib:Get(name)` - return a stored handle, or `nil`.
- `lib:Broadcast(handleOrName, opts)` - broadcast one message. `opts`: `text` (required), `marker` (required, raid-target icon 1-8), `dedupeKey`, `channel`, `electionEnabled`.
- `lib:GetPeers(handleOrName)` - group members running the addon.
- `lib:SetElectionEnabled(handleOrName, enabled)` - flip the dev flag at runtime.

`electionEnabled` is a code-only developer flag, off by default. `marker` is emitted as the locale-safe `{rt1}`..`{rt8}` token. AceComm-3.0 is a soft dependency; absent, every broadcast is a direct send.

## Embedding

LibMatic is the single source of truth. Consumer addons do not copy the modules in; they reference this repo two ways.

Local development: symlink the consumer's `Libs/LibMatic` at this repo, so the running addon always uses the live library.

```
ln -s /path/to/libmatic <consumer>/Libs/LibMatic
```

The symlink is listed in the consumer's `.gitignore`; it is a local-machine artifact, not committed.

Packaging: the consumer's `.pkgmeta` declares LibMatic as an external, so the BigWigs packager fetches this repo into `Libs/LibMatic` when it builds the release zip.

```yaml
externals:
  Libs/LibMatic:
    url: git@github.com:InfiniteLoopAlchemist/LibMatic.git
    branch: main
```

This repo's own `.pkgmeta` ignore list keeps `docs` and dev files out of consumer packages. Either way the consumer's `embeds.xml` references the modules at the same paths, after LibStub:

```xml
<Script file="Libs\LibMatic\LibStub\LibStub.lua"/>
<Include file="Libs\LibMatic\LibMatic-IconPicker-1.0\LibMatic-IconPicker-1.0.xml"/>
```

LibStub's version arbitration means the highest-minor copy across all loaded addons wins.

## Standalone test

Install the LibMatic folder as a standalone addon, log in, type `/libmatictest` (alias `/lmt`). Subcommands: `changelog`, `startup`, `settings`, `broadcast`. With no subcommand it opens the icon picker.

## License

All Rights Reserved. See LICENSE. LibStub keeps its own original license.
