DopingControl v0.6.1
This commit is contained in:
+3072
File diff suppressed because it is too large
Load Diff
+1845
File diff suppressed because it is too large
Load Diff
+420
@@ -0,0 +1,420 @@
|
||||
-- DopingControl ui/report.lua
|
||||
-- Report preview overlay + raid-chat send queue.
|
||||
--
|
||||
-- Pure part (offline-testable):
|
||||
-- DC_Report.SendDisabled(source) -> bool (true iff source == "sim")
|
||||
-- DC_Report.QueueCreate(lines) -> queue (independent copy of lines)
|
||||
-- DC_Report.QueueTick(q, now, src) -> line-to-send-now | nil
|
||||
-- DC_Report.QueueLabel(q) -> "sending k/n" button label
|
||||
-- DC_Report.BuildLines(store, db, tab) -> lines, vm (via DC_Model)
|
||||
--
|
||||
-- Queue semantics:
|
||||
-- * First line goes out immediately on Send; every further line waits
|
||||
-- >= SEND_INTERVAL (0.6 s) measured from the PREVIOUS ACTUAL SEND
|
||||
-- (the client chat throttle is ~10 msgs/10 s --
|
||||
-- 0.6 s spacing stays safely under it even with other addons chatting).
|
||||
-- * ABORT: if store.source flips to "sim" mid-drain, the very next tick
|
||||
-- aborts the queue without sending (synthetic names must
|
||||
-- never reach real chat). An aborted queue never resumes.
|
||||
-- * In sim mode the Send button never activates in the first place
|
||||
-- (SendDisabled predicate), with tooltip "disabled in test mode".
|
||||
--
|
||||
-- WoW wiring (behind `if CreateFrame then`): overlay frame
|
||||
-- DopingControlReportFrame, DIALOG strata, anchored over the matrix frame,
|
||||
-- scrollable preview FontString showing DC_Model.reportLines output EXACTLY
|
||||
-- as it will be sent, buttons "Send to raid chat" + "Close".
|
||||
-- SendChatMessage(line, "RAID") only ever fires from the queue drainer here
|
||||
-- (single-sender rule).
|
||||
--
|
||||
-- Deliberate resolutions:
|
||||
-- * DC_Report.Open() is exposed with OPTIONAL (vm, tab) arguments: the
|
||||
-- matrix "Report..." button may pass its already-built vm; with no
|
||||
-- arguments the report builds its own vm from DC.store via
|
||||
-- DC_Model/DC_Roles and the current tab (DC_Matrix.GetTab()/.tab if
|
||||
-- the matrix exposes it, else DC.currentTab, else "CONSUMABLES").
|
||||
-- * The matrix frame global name is not fixed; anchoring
|
||||
-- tries DC_Matrix.frame, then DopingControlFrame /
|
||||
-- DopingControlMatrixFrame, else UIParent.
|
||||
-- * Closing the overlay (Close / ESC) while draining cancels the rest of
|
||||
-- the queue -- hidden frames get no OnUpdate on 1.12, so a hidden
|
||||
-- "paused" queue would silently resume minutes later; cancel is the
|
||||
-- predictable behavior.
|
||||
|
||||
DopingControl = DopingControl or {}
|
||||
local DC = DopingControl
|
||||
|
||||
DC_Report = DC_Report or {}
|
||||
local R = DC_Report
|
||||
|
||||
-- ==================================================================
|
||||
-- Pure queue core
|
||||
-- ==================================================================
|
||||
|
||||
R.SEND_INTERVAL = 0.6 -- seconds between two sent lines
|
||||
R.CHAT_TYPE = "RAID"
|
||||
|
||||
-- Send is dead exactly in test mode. nil source (no scan
|
||||
-- yet) is NOT sim: sending a "coverage 0/0" report is pointless but safe.
|
||||
function R.SendDisabled(source)
|
||||
return source == "sim"
|
||||
end
|
||||
|
||||
-- Create a drain queue from an array of lines. Takes an independent copy
|
||||
-- (the preview may be rebuilt while an old queue is still draining).
|
||||
function R.QueueCreate(lines)
|
||||
local q = {
|
||||
lines = {},
|
||||
n = 0,
|
||||
sent = 0,
|
||||
nextAt = 0, -- absolute time the next send is allowed
|
||||
done = false,
|
||||
aborted = false,
|
||||
}
|
||||
local n = table.getn(lines)
|
||||
for i = 1, n do
|
||||
table.insert(q.lines, lines[i])
|
||||
end
|
||||
q.n = n
|
||||
if n == 0 then
|
||||
q.done = true
|
||||
end
|
||||
return q
|
||||
end
|
||||
|
||||
-- One drain step. now = current time (GetTime() in-game), source = current
|
||||
-- store source ("scan"/"sim"/nil). Returns the line to send NOW, or nil.
|
||||
-- Sets q.done when the last line went out, q.aborted on a sim flip.
|
||||
function R.QueueTick(q, now, source)
|
||||
if not q or q.done or q.aborted then
|
||||
return nil
|
||||
end
|
||||
if source == "sim" then
|
||||
q.aborted = true -- never resumes
|
||||
return nil
|
||||
end
|
||||
if now < q.nextAt then
|
||||
return nil
|
||||
end
|
||||
q.sent = q.sent + 1
|
||||
local line = q.lines[q.sent]
|
||||
q.nextAt = now + R.SEND_INTERVAL
|
||||
if q.sent >= q.n then
|
||||
q.done = true
|
||||
end
|
||||
return line
|
||||
end
|
||||
|
||||
-- Button label while a queue exists.
|
||||
function R.QueueLabel(q)
|
||||
if not q then
|
||||
return nil
|
||||
end
|
||||
if q.aborted then
|
||||
return "aborted"
|
||||
end
|
||||
return string.format("sending %d/%d", q.sent, q.n)
|
||||
end
|
||||
|
||||
-- ==================================================================
|
||||
-- Line building (pure given a store; uses DC_Model + DC_Roles)
|
||||
-- ==================================================================
|
||||
|
||||
-- "14:04"-style timestamp: WoW 1.12 exposes a global date(); plain
|
||||
-- Lua 5.0 has os.date.
|
||||
function R.TimeStr()
|
||||
if type(date) == "function" then
|
||||
return date("%H:%M")
|
||||
end
|
||||
if os and type(os.date) == "function" then
|
||||
return os.date("%H:%M")
|
||||
end
|
||||
return "--:--"
|
||||
end
|
||||
|
||||
-- Build the report lines for a store + db + tab. Returns lines, vm.
|
||||
function R.BuildLines(store, dbt, tab)
|
||||
store = store or DC.store or { players = {} }
|
||||
tab = tab or "CONSUMABLES"
|
||||
local expect = (dbt and dbt.expectations) or DC.DEFAULT_EXPECT or {}
|
||||
local function roleOf(p)
|
||||
return DC_Roles.resolve(dbt, store.source, p)
|
||||
end
|
||||
local vm = DC_Model.build(store, roleOf, expect, tab)
|
||||
local label = (DC.TAB_LABEL and DC.TAB_LABEL[tab]) or tab
|
||||
return DC_Model.reportLines(vm, label, R.TimeStr()), vm
|
||||
end
|
||||
|
||||
-- ==================================================================
|
||||
-- WoW wiring
|
||||
-- ==================================================================
|
||||
if CreateFrame then
|
||||
|
||||
local frame = nil -- DopingControlReportFrame (lazy)
|
||||
local previewText = nil -- the preview FontString
|
||||
local scrollChild = nil
|
||||
local infoText = nil
|
||||
local sendBtn = nil
|
||||
local sendBlocker = nil -- invisible tooltip overlay while disabled
|
||||
local lines = nil -- current preview lines (array)
|
||||
local queue = nil -- active drain queue or nil
|
||||
|
||||
local function db()
|
||||
return DopingControlDB
|
||||
end
|
||||
|
||||
local function currentSource()
|
||||
return DC.store and DC.store.source
|
||||
end
|
||||
|
||||
local function currentTab()
|
||||
if DC_Matrix then
|
||||
if DC_Matrix.GetTab then
|
||||
return DC_Matrix.GetTab()
|
||||
end
|
||||
if DC_Matrix.tab then
|
||||
return DC_Matrix.tab
|
||||
end
|
||||
end
|
||||
return DC.currentTab or "CONSUMABLES"
|
||||
end
|
||||
|
||||
-- The matrix frame global name is not guaranteed; try the likely
|
||||
-- handles, fall back safely.
|
||||
local function anchorFrame()
|
||||
if DC_Matrix and DC_Matrix.frame then
|
||||
return DC_Matrix.frame
|
||||
end
|
||||
local m = getglobal("DopingControlFrame")
|
||||
or getglobal("DopingControlMatrixFrame")
|
||||
return m or UIParent
|
||||
end
|
||||
|
||||
local function panelBackdrop()
|
||||
if DC_Widgets and DC_Widgets.PANEL_BACKDROP then
|
||||
return DC_Widgets.PANEL_BACKDROP
|
||||
end
|
||||
if DC.PANEL_BACKDROP then
|
||||
return DC.PANEL_BACKDROP
|
||||
end
|
||||
return {
|
||||
bgFile = "Interface\\AddOns\\DopingControl\\textures\\panel_bg",
|
||||
edgeFile = "Interface\\AddOns\\DopingControl\\textures\\panel_border",
|
||||
tile = true, tileSize = 128, edgeSize = 16,
|
||||
insets = { left = 5, right = 5, top = 5, bottom = 5 },
|
||||
}
|
||||
end
|
||||
|
||||
-- Send button + blocker state machine: draining -> disabled "sending
|
||||
-- k/n"; sim -> disabled with tooltip blocker; else enabled.
|
||||
local function UpdateSendState()
|
||||
if not sendBtn then
|
||||
return
|
||||
end
|
||||
if queue and not queue.done and not queue.aborted then
|
||||
sendBtn:Disable()
|
||||
sendBtn:SetText(R.QueueLabel(queue))
|
||||
sendBlocker:Hide()
|
||||
elseif R.SendDisabled(currentSource()) then
|
||||
sendBtn:Disable()
|
||||
sendBtn:SetText("Send to raid chat")
|
||||
sendBlocker:Show()
|
||||
else
|
||||
sendBtn:Enable()
|
||||
sendBtn:SetText("Send to raid chat")
|
||||
sendBlocker:Hide()
|
||||
end
|
||||
end
|
||||
|
||||
-- OnUpdate drainer (installed on the frame only while a queue exists;
|
||||
-- no allocation on the idle path).
|
||||
local function OnUpdateDrain()
|
||||
if not queue then
|
||||
this:SetScript("OnUpdate", nil)
|
||||
return
|
||||
end
|
||||
local line = R.QueueTick(queue, GetTime(), currentSource())
|
||||
if line then
|
||||
SendChatMessage(line, R.CHAT_TYPE)
|
||||
UpdateSendState()
|
||||
end
|
||||
if queue.done or queue.aborted then
|
||||
queue = nil
|
||||
this:SetScript("OnUpdate", nil)
|
||||
UpdateSendState()
|
||||
end
|
||||
end
|
||||
|
||||
local function OnSendClick()
|
||||
if queue then
|
||||
return
|
||||
end
|
||||
if not lines or table.getn(lines) == 0 then
|
||||
return
|
||||
end
|
||||
if R.SendDisabled(currentSource()) then
|
||||
return
|
||||
end
|
||||
queue = R.QueueCreate(lines)
|
||||
frame:SetScript("OnUpdate", OnUpdateDrain)
|
||||
UpdateSendState()
|
||||
end
|
||||
|
||||
local function BuildReportFrame()
|
||||
if frame then
|
||||
return frame
|
||||
end
|
||||
local f = CreateFrame("Frame", "DopingControlReportFrame", UIParent)
|
||||
f:SetWidth(460)
|
||||
f:SetHeight(340)
|
||||
f:SetFrameStrata("DIALOG")
|
||||
f:SetPoint("CENTER", anchorFrame(), "CENTER", 0, 0)
|
||||
f:SetBackdrop(panelBackdrop())
|
||||
f:SetBackdropColor(1, 1, 1, 0.97)
|
||||
f:SetMovable(true)
|
||||
f:EnableMouse(true)
|
||||
f:RegisterForDrag("LeftButton")
|
||||
f:SetScript("OnDragStart", function() this:StartMoving() end)
|
||||
f:SetScript("OnDragStop", function() this:StopMovingOrSizing() end)
|
||||
f:SetClampedToScreen(true)
|
||||
|
||||
-- title (gold)
|
||||
local title = f:CreateFontString("DopingControlReportTitle",
|
||||
"ARTWORK", "GameFontNormal")
|
||||
title:SetPoint("TOPLEFT", f, "TOPLEFT", 16, -14)
|
||||
local g = DC.COLORS and DC.COLORS.goldHi
|
||||
if g then
|
||||
title:SetTextColor(g.r, g.g, g.b)
|
||||
else
|
||||
title:SetTextColor(0.85, 0.66, 0.31)
|
||||
end
|
||||
title:SetText("Report preview")
|
||||
|
||||
-- info line (line count + pacing note)
|
||||
infoText = f:CreateFontString("DopingControlReportInfo",
|
||||
"ARTWORK", "GameFontNormalSmall")
|
||||
infoText:SetPoint("TOPLEFT", f, "TOPLEFT", 16, -30)
|
||||
infoText:SetTextColor(0.6, 0.6, 0.6)
|
||||
infoText:SetText("")
|
||||
|
||||
-- close X
|
||||
local closeX = CreateFrame("Button", "DopingControlReportCloseX",
|
||||
f, "UIPanelCloseButton")
|
||||
closeX:SetPoint("TOPRIGHT", f, "TOPRIGHT", -8, -8)
|
||||
|
||||
-- scrollable preview: shows reportLines output EXACTLY as sent
|
||||
local scroll = CreateFrame("ScrollFrame", "DopingControlReportScroll",
|
||||
f, "UIPanelScrollFrameTemplate")
|
||||
scroll:SetPoint("TOPLEFT", f, "TOPLEFT", 16, -48)
|
||||
scroll:SetPoint("BOTTOMRIGHT", f, "BOTTOMRIGHT", -36, 44)
|
||||
|
||||
scrollChild = CreateFrame("Frame", "DopingControlReportScrollChild",
|
||||
scroll)
|
||||
scrollChild:SetWidth(400)
|
||||
scrollChild:SetHeight(1)
|
||||
scroll:SetScrollChild(scrollChild)
|
||||
|
||||
previewText = scrollChild:CreateFontString("DopingControlReportText",
|
||||
"ARTWORK", "GameFontHighlightSmall")
|
||||
previewText:SetPoint("TOPLEFT", scrollChild, "TOPLEFT", 0, 0)
|
||||
previewText:SetWidth(390)
|
||||
previewText:SetJustifyH("LEFT")
|
||||
previewText:SetJustifyV("TOP")
|
||||
-- mono-ish: 1.12 ships no true monospace; the condensed number
|
||||
-- font keeps the indented name lists readable
|
||||
previewText:SetFont("Fonts\\ARIALN.TTF", 12)
|
||||
|
||||
-- buttons
|
||||
sendBtn = CreateFrame("Button", "DopingControlReportSendButton",
|
||||
f, "UIPanelButtonTemplate")
|
||||
sendBtn:SetWidth(150)
|
||||
sendBtn:SetHeight(22)
|
||||
sendBtn:SetPoint("BOTTOMLEFT", f, "BOTTOMLEFT", 16, 14)
|
||||
sendBtn:SetText("Send to raid chat")
|
||||
sendBtn:SetScript("OnClick", OnSendClick)
|
||||
|
||||
local closeBtn = CreateFrame("Button", "DopingControlReportCloseButton",
|
||||
f, "UIPanelButtonTemplate")
|
||||
closeBtn:SetWidth(80)
|
||||
closeBtn:SetHeight(22)
|
||||
closeBtn:SetPoint("BOTTOMRIGHT", f, "BOTTOMRIGHT", -16, 14)
|
||||
closeBtn:SetText("Close")
|
||||
closeBtn:SetScript("OnClick", function() f:Hide() end)
|
||||
|
||||
if pfUI and pfUI.api then
|
||||
if pfUI.api.SkinButton then
|
||||
pfUI.api.SkinButton(sendBtn)
|
||||
pfUI.api.SkinButton(closeBtn)
|
||||
end
|
||||
if pfUI.api.SkinCloseButton then
|
||||
pfUI.api.SkinCloseButton(closeX)
|
||||
end
|
||||
end
|
||||
|
||||
-- invisible overlay for the disabled-in-test-mode tooltip
|
||||
-- (disabled buttons receive no mouse events on 1.12)
|
||||
sendBlocker = CreateFrame("Frame", "DopingControlReportSendBlocker", f)
|
||||
sendBlocker:SetAllPoints(sendBtn)
|
||||
sendBlocker:SetFrameLevel(sendBtn:GetFrameLevel() + 2)
|
||||
sendBlocker:EnableMouse(true)
|
||||
sendBlocker:SetScript("OnEnter", function()
|
||||
GameTooltip:SetOwner(this, "ANCHOR_RIGHT")
|
||||
GameTooltip:SetText("disabled in test mode", 1, 1, 1)
|
||||
GameTooltip:AddLine("Simulated names never reach real chat.", 0.8, 0.8, 0.8)
|
||||
GameTooltip:AddLine("The preview stays fully usable.", 0.8, 0.8, 0.8)
|
||||
GameTooltip:Show()
|
||||
end)
|
||||
sendBlocker:SetScript("OnLeave", function() GameTooltip:Hide() end)
|
||||
sendBlocker:Hide()
|
||||
|
||||
-- closing cancels a running drain (see header)
|
||||
f:SetScript("OnHide", function()
|
||||
if queue then
|
||||
queue = nil
|
||||
this:SetScript("OnUpdate", nil)
|
||||
end
|
||||
end)
|
||||
|
||||
-- ESC closes
|
||||
table.insert(UISpecialFrames, "DopingControlReportFrame")
|
||||
|
||||
f:Hide()
|
||||
frame = f
|
||||
return f
|
||||
end
|
||||
|
||||
-- Rebuild the preview content. vm/tab optional (see header).
|
||||
function R.Refresh(vm, tab)
|
||||
BuildReportFrame()
|
||||
tab = tab or currentTab()
|
||||
if vm then
|
||||
local label = (DC.TAB_LABEL and DC.TAB_LABEL[tab]) or tab
|
||||
lines = DC_Model.reportLines(vm, label, R.TimeStr())
|
||||
else
|
||||
lines = R.BuildLines(DC.store, db(), tab)
|
||||
end
|
||||
local n = table.getn(lines)
|
||||
previewText:SetText(table.concat(lines, "\n"))
|
||||
scrollChild:SetHeight(n * 14 + 24)
|
||||
infoText:SetText(string.format(
|
||||
"%d line%s - sent one per %.1f s", n, (n == 1) and "" or "s",
|
||||
R.SEND_INTERVAL))
|
||||
UpdateSendState()
|
||||
end
|
||||
|
||||
-- Public entry point (matrix "Report..." button, /dc report).
|
||||
function R.Open(vm, tab)
|
||||
local f = BuildReportFrame()
|
||||
R.Refresh(vm, tab)
|
||||
f:Show()
|
||||
end
|
||||
|
||||
function R.Toggle(vm, tab)
|
||||
local f = BuildReportFrame()
|
||||
if f:IsShown() then
|
||||
f:Hide()
|
||||
else
|
||||
R.Open(vm, tab)
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
+273
@@ -0,0 +1,273 @@
|
||||
-- DopingControl ui/widgets.lua
|
||||
-- Shared UI building blocks: panel backdrop, pooled-widget factories
|
||||
-- (cell / pill / badge / button), tooltip helpers, pfUI-aware font helper,
|
||||
-- change-guarded setters (only touch a region when the value changed).
|
||||
--
|
||||
-- Factory + pooling patterns follow common 1.12 addon practice
|
||||
-- (pfUI-style WHITE8X8 backdrops, pooled regions, save-and-call hooks).
|
||||
--
|
||||
-- Pure Lua 5.0 dofile-loadable: top level only defines tables/functions;
|
||||
-- CreateFrame & friends are only referenced INSIDE factory functions, which
|
||||
-- are never called offline.
|
||||
|
||||
DopingControl = DopingControl or {}
|
||||
local DC = DopingControl
|
||||
|
||||
DC_Widgets = DC_Widgets or {}
|
||||
local W = DC_Widgets
|
||||
|
||||
-- ------------------------------------------------------------------
|
||||
-- Texture / font constants
|
||||
-- ------------------------------------------------------------------
|
||||
W.SOLID_TEX = "Interface\\Buttons\\WHITE8X8" -- 1px-border backdrop base
|
||||
W.FONT = "Fonts\\FRIZQT__.TTF" -- default client font
|
||||
W.CHECK_TEX = "Interface\\Buttons\\UI-CheckBox-Check" -- HAS glyph (tinted green)
|
||||
W.CROSS_TEX = "Interface\\Buttons\\UI-GroupLoot-Pass-Up" -- MISSING glyph (tinted red)
|
||||
|
||||
-- Main window backdrop (textures shipped in DopingControl\textures\).
|
||||
W.PANEL_BACKDROP = {
|
||||
bgFile = "Interface\\AddOns\\DopingControl\\textures\\panel_bg",
|
||||
edgeFile = "Interface\\AddOns\\DopingControl\\textures\\panel_border",
|
||||
tile = true, tileSize = 128, edgeSize = 16,
|
||||
insets = { left = 5, right = 5, top = 5, bottom = 5 },
|
||||
}
|
||||
|
||||
-- Solid 1px-border backdrop for cells/pills/badges/buttons (pfUI-style
|
||||
-- WHITE8X8 recipe; bg + border colored via SetBackdropColor /
|
||||
-- SetBackdropBorderColor).
|
||||
W.SOLID_BACKDROP = {
|
||||
bgFile = W.SOLID_TEX,
|
||||
edgeFile = W.SOLID_TEX,
|
||||
tile = false, edgeSize = 1,
|
||||
insets = { left = 1, right = 1, top = 1, bottom = 1 },
|
||||
}
|
||||
|
||||
W.WHITE = { r = 1, g = 1, b = 1 }
|
||||
|
||||
-- ------------------------------------------------------------------
|
||||
-- Font helper (we keep OUR pixel
|
||||
-- size -- the matrix layout depends on it -- and only take pfUI's font
|
||||
-- FAMILY when pfUI is present; forcing pfUI's global font size would break
|
||||
-- the 30x23 cell grid).
|
||||
-- ------------------------------------------------------------------
|
||||
function W.ApplyFont(fs, size)
|
||||
size = size or 10
|
||||
if pfUI and pfUI.font_default then
|
||||
fs:SetFont(pfUI.font_default, size)
|
||||
else
|
||||
fs:SetFont(W.FONT, size)
|
||||
end
|
||||
end
|
||||
|
||||
-- ------------------------------------------------------------------
|
||||
-- Change-guarded setters (convention: cache last-applied value on the
|
||||
-- region as dcLast*; only call the real setter when the value changed.
|
||||
-- Color guards compare by TABLE REFERENCE -- callers always pass the
|
||||
-- shared DC.COLORS / DC.QUALITY tables, so this is exact and zero-alloc).
|
||||
-- ------------------------------------------------------------------
|
||||
function W.SetTextG(fs, text)
|
||||
if fs.dcLastText ~= text then
|
||||
fs.dcLastText = text
|
||||
fs:SetText(text)
|
||||
end
|
||||
end
|
||||
|
||||
function W.SetTextColorG(fs, c)
|
||||
if fs.dcLastColor ~= c then
|
||||
fs.dcLastColor = c
|
||||
fs:SetTextColor(c.r, c.g, c.b)
|
||||
end
|
||||
end
|
||||
|
||||
function W.SetBackdropG(f, bg, border)
|
||||
if f.dcLastBg ~= bg then
|
||||
f.dcLastBg = bg
|
||||
f:SetBackdropColor(bg.r, bg.g, bg.b, bg.a or 1)
|
||||
end
|
||||
if f.dcLastBorder ~= border then
|
||||
f.dcLastBorder = border
|
||||
f:SetBackdropBorderColor(border.r, border.g, border.b, 1)
|
||||
end
|
||||
end
|
||||
|
||||
function W.SetTextureG(tex, path)
|
||||
if tex.dcLastTex ~= path then
|
||||
tex.dcLastTex = path
|
||||
tex:SetTexture(path)
|
||||
end
|
||||
end
|
||||
|
||||
function W.SetVertexG(tex, c)
|
||||
if tex.dcLastVertex ~= c then
|
||||
tex.dcLastVertex = c
|
||||
tex:SetVertexColor(c.r, c.g, c.b)
|
||||
end
|
||||
end
|
||||
|
||||
function W.SetAlphaG(region, a)
|
||||
if region.dcLastAlpha ~= a then
|
||||
region.dcLastAlpha = a
|
||||
region:SetAlpha(a)
|
||||
end
|
||||
end
|
||||
|
||||
-- ------------------------------------------------------------------
|
||||
-- FontString factory
|
||||
-- ------------------------------------------------------------------
|
||||
function W.MakeText(parent, size, color, layer)
|
||||
local fs = parent:CreateFontString(nil, layer or "OVERLAY")
|
||||
W.ApplyFont(fs, size)
|
||||
if color then
|
||||
W.SetTextColorG(fs, color)
|
||||
end
|
||||
return fs
|
||||
end
|
||||
|
||||
-- ------------------------------------------------------------------
|
||||
-- Cell factory: 30x23 state cell (the 30x23 size is part of the fixed
|
||||
-- visual layout). Carries one texture region (check/cross glyph OR trinket
|
||||
-- item icon) and one FontString (?, skip dot, trinket monogram); the
|
||||
-- matrix paint code shows exactly one of them per state.
|
||||
-- Pool children stay anonymous (documented factory exception).
|
||||
-- ------------------------------------------------------------------
|
||||
function W.MakeCell(parent, name, w, h)
|
||||
local c = CreateFrame("Frame", name, parent)
|
||||
c:SetWidth(w or 30)
|
||||
c:SetHeight(h or 23)
|
||||
c:SetBackdrop(W.SOLID_BACKDROP)
|
||||
c:EnableMouse(true)
|
||||
c.icon = c:CreateTexture(nil, "ARTWORK")
|
||||
c.icon:SetPoint("CENTER", c, "CENTER", 0, 0)
|
||||
c.icon:SetWidth(16)
|
||||
c.icon:SetHeight(16)
|
||||
c.icon:Hide()
|
||||
c.text = c:CreateFontString(nil, "OVERLAY")
|
||||
W.ApplyFont(c.text, 9)
|
||||
c.text:SetPoint("CENTER", c, "CENTER", 0, 0)
|
||||
return c
|
||||
end
|
||||
|
||||
-- ------------------------------------------------------------------
|
||||
-- Pill factory (ready pill, coverage pill): bordered mini frame with a
|
||||
-- centered FontString; width is adjusted by the caller to fit the text.
|
||||
-- ------------------------------------------------------------------
|
||||
function W.MakePill(parent, name)
|
||||
local p = CreateFrame("Frame", name, parent)
|
||||
p:SetWidth(70)
|
||||
p:SetHeight(16)
|
||||
p:SetBackdrop(W.SOLID_BACKDROP)
|
||||
p:EnableMouse(true)
|
||||
p.text = p:CreateFontString(nil, "OVERLAY")
|
||||
W.ApplyFont(p.text, 9)
|
||||
p.text:SetPoint("CENTER", p, "CENTER", 0, 0)
|
||||
return p
|
||||
end
|
||||
|
||||
-- ------------------------------------------------------------------
|
||||
-- Role badge factory: clickable bordered button with centered label.
|
||||
-- Suggested roles render with a "?" suffix + dimmer border (matrix paints
|
||||
-- that -- 1.12 has no dashed borders, see the adaptation note there).
|
||||
-- ------------------------------------------------------------------
|
||||
function W.MakeBadge(parent, name)
|
||||
local b = CreateFrame("Button", name, parent)
|
||||
b:SetWidth(56)
|
||||
b:SetHeight(16)
|
||||
b:SetBackdrop(W.SOLID_BACKDROP)
|
||||
b.text = b:CreateFontString(nil, "OVERLAY")
|
||||
W.ApplyFont(b.text, 8)
|
||||
b.text:SetPoint("CENTER", b, "CENTER", 0, 0)
|
||||
b:RegisterForClicks("LeftButtonUp")
|
||||
return b
|
||||
end
|
||||
|
||||
-- ------------------------------------------------------------------
|
||||
-- Dark gold-trim button (titlebar Scan/Report/Options/X).
|
||||
-- ------------------------------------------------------------------
|
||||
function W.MakeButton(parent, name, label, width, height)
|
||||
local C = DC.COLORS
|
||||
local b = CreateFrame("Button", name, parent)
|
||||
b:SetWidth(width or 64)
|
||||
b:SetHeight(height or 19)
|
||||
b:SetBackdrop(W.SOLID_BACKDROP)
|
||||
W.SetBackdropG(b, C.panel2, C.goldSoft)
|
||||
b.text = b:CreateFontString(nil, "OVERLAY")
|
||||
W.ApplyFont(b.text, 10)
|
||||
b.text:SetPoint("CENTER", b, "CENTER", 0, 0)
|
||||
W.SetTextColorG(b.text, C.goldHi)
|
||||
b.text:SetText(label)
|
||||
b:SetScript("OnEnter", function()
|
||||
W.SetBackdropG(this, C.panel3, C.gold)
|
||||
end)
|
||||
b:SetScript("OnLeave", function()
|
||||
W.SetBackdropG(this, C.panel2, C.goldSoft)
|
||||
end)
|
||||
return b
|
||||
end
|
||||
|
||||
-- ------------------------------------------------------------------
|
||||
-- Tooltip helpers. Convention: the builder only ADDS
|
||||
-- lines; AttachTooltip calls SetOwner before and Show after -- AddLine
|
||||
-- never calls Show itself.
|
||||
-- AttachTooltip CHAINS any existing OnEnter/OnLeave (no HookScript on
|
||||
-- 1.12 -- standard save-and-call pattern).
|
||||
-- ------------------------------------------------------------------
|
||||
function W.TipLine(text, c, wrap)
|
||||
if not GameTooltip then
|
||||
return
|
||||
end
|
||||
local r, g, b = 1, 1, 1
|
||||
if c then
|
||||
r, g, b = c.r, c.g, c.b
|
||||
end
|
||||
if wrap then
|
||||
GameTooltip:AddLine(text, r, g, b, 1)
|
||||
else
|
||||
GameTooltip:AddLine(text, r, g, b)
|
||||
end
|
||||
end
|
||||
|
||||
-- Two-column tooltip line: label left, value flush RIGHT. This is what
|
||||
-- makes a breakdown read like an addition on paper -- the values line up
|
||||
-- in a column instead of trailing their labels at ragged positions. 1.12
|
||||
-- has no monospace font and no tab stops; AddDoubleLine is the only way to
|
||||
-- get a right-aligned column, and it exists in this client's FrameXML.
|
||||
-- `cr` defaults to the label color when omitted.
|
||||
function W.TipDouble(left, right, c, cr)
|
||||
if not GameTooltip then
|
||||
return
|
||||
end
|
||||
local r, g, b = 1, 1, 1
|
||||
if c then
|
||||
r, g, b = c.r, c.g, c.b
|
||||
end
|
||||
local r2, g2, b2 = r, g, b
|
||||
if cr then
|
||||
r2, g2, b2 = cr.r, cr.g, cr.b
|
||||
end
|
||||
GameTooltip:AddDoubleLine(left, right, r, g, b, r2, g2, b2)
|
||||
end
|
||||
|
||||
function W.AttachTooltip(frame, builder, anchor)
|
||||
frame.dcTipBuilder = builder
|
||||
frame.dcTipAnchor = anchor or "ANCHOR_LEFT"
|
||||
frame.dcOldEnter = frame:GetScript("OnEnter")
|
||||
frame.dcOldLeave = frame:GetScript("OnLeave")
|
||||
frame:SetScript("OnEnter", function()
|
||||
if this.dcOldEnter then
|
||||
this.dcOldEnter()
|
||||
end
|
||||
if GameTooltip and this.dcTipBuilder then
|
||||
GameTooltip:SetOwner(this, this.dcTipAnchor)
|
||||
this.dcTipBuilder(this)
|
||||
GameTooltip:Show()
|
||||
end
|
||||
end)
|
||||
frame:SetScript("OnLeave", function()
|
||||
if this.dcOldLeave then
|
||||
this.dcOldLeave()
|
||||
end
|
||||
if GameTooltip then
|
||||
GameTooltip:Hide()
|
||||
end
|
||||
end)
|
||||
end
|
||||
Reference in New Issue
Block a user