1232 lines
44 KiB
Lua
1232 lines
44 KiB
Lua
-- UI/TrackerFrame.lua
|
||
-- Hail Mary Guild Tools – Tracker-Frame mit Pool-System
|
||
--
|
||
-- Features:
|
||
-- • Bar-Modus: UP/DOWN mit einstellbarem Spacing
|
||
-- • Icon-Modus: 4 Richtungen (UP/DOWN/LEFT/RIGHT), einstellbares Spacing
|
||
-- • Icon-Overlay: Sweep-Kreis ODER Text-Timer
|
||
-- • Text-Anchor: onIcon | above | below | left | right (kein Abschneiden / kein "...")
|
||
-- • Lock: blendet Titel und Hintergrund aus
|
||
|
||
local ADDON_NAME = "HailMaryGuildTools"
|
||
local HMGT = LibStub("AceAddon-3.0"):GetAddon(ADDON_NAME)
|
||
local L = LibStub("AceLocale-3.0"):GetLocale(ADDON_NAME)
|
||
local LSM = LibStub("LibSharedMedia-3.0")
|
||
|
||
HMGT.TrackerFrame = {}
|
||
local TF = HMGT.TrackerFrame
|
||
|
||
local MAX_ROWS = 40
|
||
local DEFAULT_FONT = "Fonts\\FRIZQT__.TTF"
|
||
local LABEL_H = 14 -- Pixel pro Text-Zeile außerhalb des Icons
|
||
local CHARGE_TEXT_Y_OFFSET = -5
|
||
local HEADER_HEIGHT = 20
|
||
|
||
local VALID_POINTS = {
|
||
TOPLEFT = true, TOP = true, TOPRIGHT = true,
|
||
LEFT = true, CENTER = true, RIGHT = true,
|
||
BOTTOMLEFT = true, BOTTOM = true, BOTTOMRIGHT = true,
|
||
}
|
||
|
||
local function ResolveAnchorTarget(anchorTo, anchorCustom)
|
||
if HMGT.GetAnchorTargetFrame then
|
||
return HMGT:GetAnchorTargetFrame(anchorTo, anchorCustom)
|
||
end
|
||
return UIParent
|
||
end
|
||
|
||
local HEADER_ANCHOR_MAP_UP = {
|
||
TOPLEFT = { point = "BOTTOMLEFT", y = -HEADER_HEIGHT },
|
||
TOP = { point = "BOTTOM", y = -HEADER_HEIGHT },
|
||
TOPRIGHT = { point = "BOTTOMRIGHT", y = -HEADER_HEIGHT },
|
||
LEFT = { point = "BOTTOMLEFT", y = -(HEADER_HEIGHT * 0.5) },
|
||
CENTER = { point = "BOTTOM", y = -(HEADER_HEIGHT * 0.5) },
|
||
RIGHT = { point = "BOTTOMRIGHT", y = -(HEADER_HEIGHT * 0.5) },
|
||
BOTTOMLEFT = { point = "BOTTOMLEFT", y = 0 },
|
||
BOTTOM = { point = "BOTTOM", y = 0 },
|
||
BOTTOMRIGHT = { point = "BOTTOMRIGHT", y = 0 },
|
||
}
|
||
|
||
local function UsesBottomHeader(settings)
|
||
return type(settings) == "table"
|
||
and settings.showBar == true
|
||
and settings.growDirection == "UP"
|
||
end
|
||
|
||
local function ApplyTitleLayout(frame)
|
||
if not frame or not frame.titleText then
|
||
return
|
||
end
|
||
|
||
frame.titleText:ClearAllPoints()
|
||
if UsesBottomHeader(frame._settings) then
|
||
frame.titleText:SetPoint("BOTTOMLEFT", frame, "BOTTOMLEFT", 4, 2)
|
||
else
|
||
frame.titleText:SetPoint("TOPLEFT", frame, "TOPLEFT", 4, -2)
|
||
end
|
||
end
|
||
|
||
-- ══════════════════════════════════════════════════════════════
|
||
-- FONT-HELFER
|
||
-- ══════════════════════════════════════════════════════════════
|
||
|
||
local function GetFontPath(fontName)
|
||
if not fontName or fontName == "" then return DEFAULT_FONT end
|
||
return LSM:Fetch("font", fontName) or DEFAULT_FONT
|
||
end
|
||
|
||
local function SafeSetFont(fs, path, size, outline)
|
||
-- SetFontObject (gesetzt bei Erstellung) "klebt" und überschreibt SetFont.
|
||
-- Erst nil setzen damit SetFont dauerhaft wirkt.
|
||
fs:SetFontObject(nil)
|
||
if not fs:SetFont(path or DEFAULT_FONT, size or 12, outline or "OUTLINE") then
|
||
fs:SetFont(DEFAULT_FONT, size or 12, outline or "OUTLINE")
|
||
end
|
||
end
|
||
|
||
local function Utf8Sub(text, charCount)
|
||
if type(text) ~= "string" then
|
||
return ""
|
||
end
|
||
if not charCount or charCount <= 0 then
|
||
return ""
|
||
end
|
||
if not utf8 or type(utf8.offset) ~= "function" then
|
||
return text:sub(1, charCount)
|
||
end
|
||
|
||
local cutoff = utf8.offset(text, charCount + 1)
|
||
if cutoff then
|
||
return text:sub(1, cutoff - 1)
|
||
end
|
||
return text
|
||
end
|
||
|
||
local function Utf8Len(text)
|
||
if type(text) ~= "string" then
|
||
return 0
|
||
end
|
||
if utf8 and type(utf8.len) == "function" then
|
||
local len = utf8.len(text)
|
||
if len then
|
||
return len
|
||
end
|
||
end
|
||
return #text
|
||
end
|
||
|
||
local measureFontString
|
||
|
||
local function GetIconTextBlockWidth(settings)
|
||
local iconSize = tonumber(settings and settings.iconSize) or 32
|
||
return math.max(60, math.floor(iconSize * 2.25))
|
||
end
|
||
|
||
local function GetMeasureFontString()
|
||
if not measureFontString then
|
||
measureFontString = UIParent:CreateFontString(nil, "OVERLAY")
|
||
measureFontString:SetWordWrap(false)
|
||
end
|
||
return measureFontString
|
||
end
|
||
|
||
local function TruncateTextToWidth(fontString, text, maxWidth)
|
||
if type(text) ~= "string" or text == "" or not maxWidth or maxWidth <= 0 then
|
||
return text or ""
|
||
end
|
||
|
||
local measure = GetMeasureFontString()
|
||
local fontPath, fontSize, fontFlags = fontString:GetFont()
|
||
if fontPath then
|
||
measure:SetFont(fontPath, fontSize or 12, fontFlags or "")
|
||
else
|
||
measure:SetFont(DEFAULT_FONT, 12, "OUTLINE")
|
||
end
|
||
|
||
measure:SetText(text)
|
||
if measure:GetStringWidth() <= maxWidth then
|
||
return text
|
||
end
|
||
|
||
local ellipsis = "..."
|
||
measure:SetText(ellipsis)
|
||
if measure:GetStringWidth() > maxWidth then
|
||
return ""
|
||
end
|
||
|
||
local low = 0
|
||
local high = Utf8Len(text)
|
||
while low < high do
|
||
local mid = math.ceil((low + high) / 2)
|
||
local candidate = Utf8Sub(text, mid) .. ellipsis
|
||
measure:SetText(candidate)
|
||
if measure:GetStringWidth() <= maxWidth then
|
||
low = mid
|
||
else
|
||
high = mid - 1
|
||
end
|
||
end
|
||
|
||
if low <= 0 then
|
||
return ellipsis
|
||
end
|
||
|
||
return Utf8Sub(text, low) .. ellipsis
|
||
end
|
||
|
||
local function GetVisibleContentBounds(frame)
|
||
if not frame then
|
||
return nil, nil
|
||
end
|
||
|
||
local top
|
||
local bottom
|
||
|
||
local function Consider(region)
|
||
if not region or not region.IsShown or not region:IsShown() then
|
||
return
|
||
end
|
||
|
||
local regionTop = region:GetTop()
|
||
local regionBottom = region:GetBottom()
|
||
if not regionTop or not regionBottom then
|
||
return
|
||
end
|
||
|
||
if not top or regionTop > top then
|
||
top = regionTop
|
||
end
|
||
if not bottom or regionBottom < bottom then
|
||
bottom = regionBottom
|
||
end
|
||
end
|
||
|
||
for i = 1, MAX_ROWS do
|
||
Consider(frame._barPool and frame._barPool[i] or nil)
|
||
Consider(frame._iconPool and frame._iconPool[i] or nil)
|
||
end
|
||
|
||
return top, bottom
|
||
end
|
||
|
||
local function CreateOutline(parent)
|
||
local outline = {
|
||
top = parent:CreateTexture(nil, "OVERLAY"),
|
||
bottom = parent:CreateTexture(nil, "OVERLAY"),
|
||
left = parent:CreateTexture(nil, "OVERLAY"),
|
||
right = parent:CreateTexture(nil, "OVERLAY"),
|
||
}
|
||
return outline
|
||
end
|
||
|
||
local function SetOutline(outline, target, r, g, b, a, enabled)
|
||
if not outline or not target then return end
|
||
|
||
if not enabled then
|
||
outline.top:Hide()
|
||
outline.bottom:Hide()
|
||
outline.left:Hide()
|
||
outline.right:Hide()
|
||
return
|
||
end
|
||
|
||
local rr = r or 1
|
||
local gg = g or 1
|
||
local bb = b or 1
|
||
local aa = a or 1
|
||
|
||
outline.top:SetColorTexture(rr, gg, bb, aa)
|
||
outline.bottom:SetColorTexture(rr, gg, bb, aa)
|
||
outline.left:SetColorTexture(rr, gg, bb, aa)
|
||
outline.right:SetColorTexture(rr, gg, bb, aa)
|
||
|
||
outline.top:ClearAllPoints()
|
||
outline.top:SetPoint("TOPLEFT", target, "TOPLEFT", -1, 1)
|
||
outline.top:SetPoint("TOPRIGHT", target, "TOPRIGHT", 1, 1)
|
||
outline.top:SetHeight(1)
|
||
|
||
outline.bottom:ClearAllPoints()
|
||
outline.bottom:SetPoint("BOTTOMLEFT", target, "BOTTOMLEFT", -1, -1)
|
||
outline.bottom:SetPoint("BOTTOMRIGHT", target, "BOTTOMRIGHT", 1, -1)
|
||
outline.bottom:SetHeight(1)
|
||
|
||
outline.left:ClearAllPoints()
|
||
outline.left:SetPoint("TOPLEFT", target, "TOPLEFT", -1, 0)
|
||
outline.left:SetPoint("BOTTOMLEFT", target, "BOTTOMLEFT", -1, 0)
|
||
outline.left:SetWidth(1)
|
||
|
||
outline.right:ClearAllPoints()
|
||
outline.right:SetPoint("TOPRIGHT", target, "TOPRIGHT", 1, 0)
|
||
outline.right:SetPoint("BOTTOMRIGHT", target, "BOTTOMRIGHT", 1, 0)
|
||
outline.right:SetWidth(1)
|
||
|
||
outline.top:Show()
|
||
outline.bottom:Show()
|
||
outline.left:Show()
|
||
outline.right:Show()
|
||
end
|
||
|
||
local function GetEntryStateKind(data)
|
||
local spellEntry = type(data) == "table" and data.spellEntry or nil
|
||
if not spellEntry or not HMGT_SpellData or not HMGT_SpellData.GetStateKind then
|
||
return nil
|
||
end
|
||
return HMGT_SpellData.GetStateKind(spellEntry)
|
||
end
|
||
|
||
local function IsAvailabilityEntry(data)
|
||
return GetEntryStateKind(data) == "availability"
|
||
end
|
||
|
||
local function GetEntryCountText(data)
|
||
if type(data) ~= "table" then
|
||
return nil
|
||
end
|
||
|
||
local maxCharges = tonumber(data.maxCharges)
|
||
if not maxCharges or maxCharges <= 1 then
|
||
return nil
|
||
end
|
||
|
||
local currentCharges = tonumber(data.currentCharges) or 0
|
||
currentCharges = math.max(0, math.min(maxCharges, currentCharges))
|
||
return string.format("%d/%d", currentCharges, maxCharges)
|
||
end
|
||
|
||
local function ShouldShowBarSpellTooltip(frame)
|
||
return type(frame) == "table"
|
||
and type(frame._settings) == "table"
|
||
and frame._settings.showBar == true
|
||
end
|
||
|
||
local function ShouldEnableBarMouse(frame)
|
||
if type(frame) ~= "table" or type(frame._settings) ~= "table" then
|
||
return false
|
||
end
|
||
|
||
return ShouldShowBarSpellTooltip(frame) or frame._settings.locked ~= true
|
||
end
|
||
|
||
local function ShowBlizzardSpellTooltip(anchor, data)
|
||
if not anchor or type(data) ~= "table" then
|
||
return
|
||
end
|
||
|
||
local spellId = tonumber(data.spellId)
|
||
if not spellId and type(data.spellEntry) == "table" then
|
||
spellId = tonumber(data.spellEntry.spellId)
|
||
end
|
||
if not spellId or spellId <= 0 then
|
||
return
|
||
end
|
||
|
||
GameTooltip:SetOwner(anchor, "ANCHOR_RIGHT")
|
||
|
||
local ok = false
|
||
if type(GameTooltip.SetSpellByID) == "function" then
|
||
ok = pcall(GameTooltip.SetSpellByID, GameTooltip, spellId)
|
||
end
|
||
if not ok then
|
||
pcall(GameTooltip.SetHyperlink, GameTooltip, ("spell:%d"):format(spellId))
|
||
end
|
||
|
||
HMGT:SafeShowTooltip(GameTooltip)
|
||
end
|
||
|
||
-- ══════════════════════════════════════════════════════════════
|
||
-- BAR-ROW: einmalige Erstellung (NIEMALS im Combat-Ticker!)
|
||
-- ══════════════════════════════════════════════════════════════
|
||
|
||
local function CreateBarRow(parent)
|
||
local row = CreateFrame("Frame", nil, parent)
|
||
row:SetHeight(20)
|
||
row:Hide()
|
||
|
||
row.icon = row:CreateTexture(nil, "ARTWORK")
|
||
row.icon:SetSize(20, 20)
|
||
row.icon:SetPoint("LEFT", row, "LEFT", 0, 0)
|
||
row.icon:SetTexCoord(0.07, 0.93, 0.07, 0.93)
|
||
|
||
row.bar = CreateFrame("StatusBar", nil, row)
|
||
row.bar:SetStatusBarTexture("Interface\\TargetingFrame\\UI-StatusBar") -- wird in UpdateBarRow überschrieben
|
||
row.bar:SetMinMaxValues(0, 1)
|
||
row.bar:SetValue(1)
|
||
row.bar:SetPoint("LEFT", row.icon, "RIGHT", 2, 0)
|
||
row.bar:SetPoint("RIGHT", row, "RIGHT", 0, 0)
|
||
row.bar:SetHeight(20)
|
||
row.bar._ownerRow = row
|
||
row.bar._trackerFrame = parent
|
||
row.barTexture = row.bar:CreateTexture(nil, "ARTWORK")
|
||
row.barTexture:SetTexture("Interface\\TargetingFrame\\UI-StatusBar")
|
||
row.bar:SetStatusBarTexture(row.barTexture)
|
||
row.bar:EnableMouse(false)
|
||
|
||
row.barBg = row.bar:CreateTexture(nil, "BACKGROUND")
|
||
row.barBg:SetAllPoints()
|
||
row.barBg:SetColorTexture(0.1, 0.1, 0.1, 0.8)
|
||
|
||
row.spark = row.bar:CreateTexture(nil, "OVERLAY")
|
||
row.spark:SetSize(8, 20)
|
||
row.spark:SetTexture("Interface\\CastingBar\\UI-CastingBar-Spark")
|
||
row.spark:SetBlendMode("ADD")
|
||
|
||
row.nameText = row.bar:CreateFontString(nil, "OVERLAY")
|
||
row.nameText:SetPoint("LEFT", row.bar, "LEFT", 4, 0)
|
||
row.nameText:SetJustifyH("LEFT")
|
||
row.nameText:SetFontObject(GameFontNormal)
|
||
|
||
row.timeText = row.bar:CreateFontString(nil, "OVERLAY")
|
||
row.timeText:SetPoint("RIGHT", row.bar, "RIGHT", -4, 0)
|
||
row.timeText:SetJustifyH("RIGHT")
|
||
row.timeText:SetFontObject(GameFontNormal)
|
||
|
||
row.border = CreateOutline(row)
|
||
|
||
row.bar:SetScript("OnEnter", function(bar)
|
||
local trackerFrame = bar._trackerFrame
|
||
local ownerRow = bar._ownerRow
|
||
if ShouldShowBarSpellTooltip(trackerFrame) and ownerRow and ownerRow._data then
|
||
ShowBlizzardSpellTooltip(bar, ownerRow._data)
|
||
return
|
||
end
|
||
if trackerFrame and trackerFrame._settings and trackerFrame._settings.locked ~= true then
|
||
GameTooltip:SetOwner(bar, "ANCHOR_RIGHT")
|
||
GameTooltip:SetText(L["TT_DRAG"])
|
||
HMGT:SafeShowTooltip(GameTooltip)
|
||
end
|
||
end)
|
||
row.bar:SetScript("OnLeave", function()
|
||
GameTooltip:Hide()
|
||
end)
|
||
row.bar:SetScript("OnMouseDown", function(bar, button)
|
||
if button ~= "LeftButton" then
|
||
return
|
||
end
|
||
local trackerFrame = bar._trackerFrame
|
||
local onDragStart = trackerFrame and trackerFrame:GetScript("OnDragStart")
|
||
if onDragStart then
|
||
onDragStart(trackerFrame)
|
||
end
|
||
end)
|
||
row.bar:SetScript("OnMouseUp", function(bar, button)
|
||
if button ~= "LeftButton" then
|
||
return
|
||
end
|
||
local trackerFrame = bar._trackerFrame
|
||
local onDragStop = trackerFrame and trackerFrame:GetScript("OnDragStop")
|
||
if onDragStop then
|
||
onDragStop(trackerFrame)
|
||
end
|
||
end)
|
||
|
||
return row
|
||
end
|
||
|
||
-- ══════════════════════════════════════════════════════════════
|
||
-- ICON-CELL: einmalige Erstellung
|
||
-- ══════════════════════════════════════════════════════════════
|
||
|
||
local function CreateIconCell(parent)
|
||
local cell = CreateFrame("Frame", nil, parent)
|
||
cell:SetSize(32, 32)
|
||
cell:Hide()
|
||
|
||
-- Icon-Bereich (immer sz×sz)
|
||
cell.iconFrame = CreateFrame("Frame", nil, cell)
|
||
cell.iconFrame:SetPoint("TOPLEFT", cell, "TOPLEFT", 0, 0)
|
||
cell.iconFrame:SetSize(32, 32)
|
||
|
||
cell.bg = cell.iconFrame:CreateTexture(nil, "BACKGROUND")
|
||
cell.bg:SetAllPoints(cell.iconFrame)
|
||
cell.bg:SetColorTexture(0, 0, 0, 0.6)
|
||
|
||
cell.icon = cell.iconFrame:CreateTexture(nil, "ARTWORK")
|
||
cell.icon:SetAllPoints(cell.iconFrame)
|
||
cell.icon:SetTexCoord(0.07, 0.93, 0.07, 0.93)
|
||
|
||
cell.cooldown = CreateFrame("Cooldown", nil, cell.iconFrame, "CooldownFrameTemplate")
|
||
cell.cooldown:SetAllPoints(cell.iconFrame)
|
||
cell.cooldown:SetDrawSwipe(true)
|
||
cell.cooldown:SetSwipeColor(0, 0, 0, 0.8)
|
||
cell.cooldown:SetHideCountdownNumbers(true)
|
||
|
||
cell.border = CreateOutline(cell)
|
||
|
||
-- Text-Labels – werden je nach textAnchor umgehängt.
|
||
-- Namen außerhalb des Icons werden auf Icon-Breite begrenzt und bei Bedarf gekürzt.
|
||
cell.nameText = cell:CreateFontString(nil, "OVERLAY")
|
||
cell.nameText:SetJustifyH("LEFT")
|
||
cell.nameText:SetWordWrap(false)
|
||
cell.nameText:SetFontObject(GameFontNormalSmall)
|
||
|
||
cell.timeText = cell:CreateFontString(nil, "OVERLAY")
|
||
cell.timeText:SetJustifyH("CENTER")
|
||
cell.timeText:SetWordWrap(false)
|
||
cell.timeText:SetFontObject(GameFontNormalSmall)
|
||
cell.chargeText = cell.iconFrame:CreateFontString(nil, "OVERLAY")
|
||
cell.chargeText:SetJustifyH("CENTER")
|
||
cell.chargeText:SetWordWrap(false)
|
||
cell.chargeText:SetFontObject(GameFontNormalSmall)
|
||
|
||
-- Zentrale Cooldown-Zeit (für Sweep-Modus)
|
||
cell.centerText = cell.iconFrame:CreateFontString(nil, "OVERLAY")
|
||
cell.centerText:SetJustifyH("CENTER")
|
||
cell.centerText:SetWordWrap(false)
|
||
cell.centerText:SetFontObject(GameFontNormalSmall)
|
||
|
||
-- Tooltip
|
||
cell:SetScript("OnEnter", function(c)
|
||
if not c._data then return end
|
||
ShowBlizzardSpellTooltip(c, c._data)
|
||
end)
|
||
cell:SetScript("OnLeave", function() GameTooltip:Hide() end)
|
||
|
||
return cell
|
||
end
|
||
|
||
-- ══════════════════════════════════════════════════════════════
|
||
-- HAUPTFRAME ERSTELLEN
|
||
-- ══════════════════════════════════════════════════════════════
|
||
|
||
function TF:CreateTrackerFrame(name, settings)
|
||
local frame = CreateFrame("Frame", "HMGT_Frame_" .. name, UIParent)
|
||
frame:SetSize(settings.width or 250, 30)
|
||
frame:SetPoint("TOPLEFT", UIParent, "TOPLEFT", settings.posX or 200, settings.posY or -200)
|
||
frame:SetClampedToScreen(true)
|
||
frame:SetMovable(true)
|
||
|
||
frame.bg = frame:CreateTexture(nil, "BACKGROUND")
|
||
frame.bg:SetAllPoints()
|
||
frame.bg:SetColorTexture(0, 0, 0, 0.5)
|
||
|
||
frame.titleText = frame:CreateFontString(nil, "OVERLAY")
|
||
frame.titleText:SetFontObject(GameFontNormalSmall)
|
||
frame.titleText:SetTextColor(0.8, 0.8, 0.8)
|
||
frame.titleText:SetText(name)
|
||
|
||
frame:EnableMouse(not settings.locked)
|
||
frame:RegisterForDrag("LeftButton")
|
||
frame:SetScript("OnDragStart", function(f)
|
||
if not f._settings.locked then
|
||
f._hmgtDragging = true
|
||
f:StartMoving()
|
||
end
|
||
end)
|
||
frame:SetScript("OnDragStop", function(f)
|
||
f._hmgtDragging = nil
|
||
f:StopMovingOrSizing()
|
||
local l = f:GetLeft()
|
||
local visualTop = f:GetTop()
|
||
if UsesBottomHeader(f._settings) then
|
||
local bottom = f:GetBottom()
|
||
if bottom then
|
||
visualTop = bottom + HEADER_HEIGHT
|
||
end
|
||
end
|
||
if l and visualTop then
|
||
f._settings.posX = l
|
||
f._settings.posY = visualTop - UIParent:GetHeight()
|
||
-- Draggen setzt immer den absoluten Anker auf UIParent.
|
||
f._settings.anchorTo = "UIParent"
|
||
f._settings.anchorPoint = "TOPLEFT"
|
||
f._settings.anchorRelPoint = "TOPLEFT"
|
||
f._settings.anchorX = f._settings.posX
|
||
f._settings.anchorY = f._settings.posY
|
||
TF:ApplyAnchor(f)
|
||
end
|
||
end)
|
||
frame:SetScript("OnEnter", function(f)
|
||
if not f._settings.locked then
|
||
GameTooltip:SetOwner(f, "ANCHOR_RIGHT")
|
||
GameTooltip:SetText(L["TT_DRAG"])
|
||
HMGT:SafeShowTooltip(GameTooltip)
|
||
end
|
||
end)
|
||
frame:SetScript("OnLeave", function() GameTooltip:Hide() end)
|
||
|
||
frame._settings = settings
|
||
frame._name = name
|
||
|
||
frame._barPool = {}
|
||
frame._iconPool = {}
|
||
for i = 1, MAX_ROWS do
|
||
frame._barPool[i] = CreateBarRow(frame)
|
||
frame._iconPool[i] = CreateIconCell(frame)
|
||
end
|
||
|
||
ApplyTitleLayout(frame)
|
||
|
||
-- Initialen Lock-Status anwenden
|
||
TF:SetLocked(frame, settings.locked)
|
||
TF:ApplyAnchor(frame)
|
||
|
||
return frame
|
||
end
|
||
|
||
-- ══════════════════════════════════════════════════════════════
|
||
-- BAR-ROW UPDATE
|
||
-- ══════════════════════════════════════════════════════════════
|
||
|
||
local function UpdateBarRow(frame, index, data)
|
||
local row = frame._barPool[index]
|
||
if not row then return end
|
||
|
||
local s = frame._settings
|
||
local h = s.barHeight or 20
|
||
local gap = s.barSpacing or 2
|
||
|
||
row:SetWidth(frame:GetWidth())
|
||
row:SetAlpha(data.outOfRange and (data.outOfRangeAlpha or 0.4) or 1)
|
||
row:SetHeight(h)
|
||
row:ClearAllPoints()
|
||
|
||
if s.growDirection == "UP" then
|
||
row:SetPoint("BOTTOMLEFT", frame, "BOTTOMLEFT", 0, HEADER_HEIGHT + ((index - 1) * (h + gap)))
|
||
else
|
||
row:SetPoint("TOPLEFT", frame, "TOPLEFT", 0, -((index - 1) * (h + gap)) - HEADER_HEIGHT)
|
||
end
|
||
|
||
row.icon:SetSize(h, h)
|
||
row.icon:SetTexture(
|
||
data.spellEntry and HMGT_SpellData.GetSpellIcon(data.spellEntry.spellId)
|
||
or "Interface\\Icons\\INV_Misc_QuestionMark"
|
||
)
|
||
|
||
local remaining = data.remaining
|
||
local total = data.total
|
||
local progress = (total > 0) and (remaining / total) or 0
|
||
|
||
local r, g, b = 0.2, 0.8, 0.2
|
||
if s.colorByClass and data.class then
|
||
r, g, b = HMGT:GetClassColor(data.class)
|
||
else
|
||
if remaining > 10 then
|
||
r, g, b = 1.0, 0.2, 0.2
|
||
elseif remaining > 5 then
|
||
r, g, b = 1.0, 0.8, 0.2
|
||
else
|
||
r, g, b = 0.2, 0.8, 0.2
|
||
end
|
||
end
|
||
|
||
-- Bar-Textur aus Einstellungen
|
||
local barTex = LSM:Fetch("statusbar", s.barTexture or "Blizzard")
|
||
or "Interface\\TargetingFrame\\UI-StatusBar"
|
||
if row.barTexture then
|
||
row.barTexture:SetTexture(barTex)
|
||
row.bar:SetStatusBarTexture(row.barTexture)
|
||
else
|
||
row.bar:SetStatusBarTexture(barTex)
|
||
end
|
||
|
||
local bc = s.borderColor or {}
|
||
local br = bc.r or bc[1] or 1
|
||
local bg = bc.g or bc[2] or 1
|
||
local bb = bc.b or bc[3] or 1
|
||
local ba = bc.a or bc[4] or 1
|
||
SetOutline(row.border, row.bar, br, bg, bb, ba, s.borderEnabled == true)
|
||
|
||
row.bar:SetHeight(h)
|
||
row.bar:SetStatusBarColor(r, g, b, 0.85)
|
||
if row.barTexture then
|
||
row.barTexture:SetVertexColor(r, g, b, 0.85)
|
||
end
|
||
row.bar:SetMinMaxValues(0, 1)
|
||
row.bar:SetValue(progress)
|
||
row.spark:SetHeight(h)
|
||
|
||
local bw = row.bar:GetWidth() or 200
|
||
row.spark:ClearAllPoints()
|
||
row.spark:SetPoint("CENTER", row.bar, "LEFT", bw * progress, 0)
|
||
|
||
local fp = GetFontPath(s.font)
|
||
local fsz = s.fontSize or 12
|
||
local fo = s.fontOutline or "OUTLINE"
|
||
SafeSetFont(row.nameText, fp, fsz, fo)
|
||
SafeSetFont(row.timeText, fp, fsz, fo)
|
||
row._data = data
|
||
row.bar:EnableMouse(ShouldEnableBarMouse(frame))
|
||
|
||
local displayName
|
||
if data.spellEntry and s.showPlayerName then
|
||
displayName = (data.playerName or "") .. " – " .. data.spellEntry.name
|
||
elseif data.spellEntry then
|
||
displayName = data.spellEntry.name
|
||
else
|
||
displayName = data.playerName or ""
|
||
end
|
||
row.nameText:SetText(displayName)
|
||
row.nameText:SetTextColor(1, 1, 1)
|
||
if s.showPlayerName then
|
||
row.nameText:SetText(data.playerName or "")
|
||
else
|
||
row.nameText:SetText("")
|
||
end
|
||
|
||
local chargesTxt = GetEntryCountText(data)
|
||
|
||
if remaining > 0 then
|
||
local t = HMGT:FormatTime(remaining)
|
||
row.timeText:SetText(chargesTxt and (chargesTxt .. " " .. t) or t)
|
||
row.timeText:SetTextColor(1, 1, 1)
|
||
else
|
||
row.timeText:SetText(chargesTxt or "|cff00ff00Bereit|r")
|
||
end
|
||
|
||
row:Show()
|
||
end
|
||
|
||
-- ══════════════════════════════════════════════════════════════
|
||
-- ICON-ZELLEN: Layout-Berechnung
|
||
-- ══════════════════════════════════════════════════════════════
|
||
|
||
--- Berechnet für eine Zelle (index) die xOff/yOff relativ zum Frame-Anchor,
|
||
--- die Zelldimensionen sowie ob TOPRIGHT statt TOPLEFT als Anchor benutzt wird.
|
||
local function CalcCellLayout(frame, index)
|
||
local s = frame._settings
|
||
local sz = s.iconSize or 32
|
||
local n = s.iconCols or 6
|
||
local gap = s.iconSpacing or 2
|
||
local dir = s.growDirection or "DOWN"
|
||
local anchor = s.textAnchor or "below"
|
||
local overlay = s.iconOverlay or "sweep"
|
||
local isSweep = (overlay == "sweep" or overlay == "swipe")
|
||
local showN = s.showPlayerName
|
||
local textBlockW = GetIconTextBlockWidth(s)
|
||
|
||
local extraW = 0
|
||
local extraH = 0
|
||
if anchor == "left" or anchor == "right" then
|
||
if isSweep then
|
||
extraW = showN and (textBlockW + 4) or 0
|
||
else
|
||
extraW = textBlockW + 4
|
||
end
|
||
elseif isSweep then
|
||
extraH = showN and LABEL_H or 0
|
||
elseif anchor ~= "onIcon" then
|
||
extraH = showN and (LABEL_H * 2) or LABEL_H
|
||
end
|
||
|
||
local cellW = sz + extraW
|
||
local cellH = sz + extraH
|
||
local step = gap -- Pixel zwischen Zellen
|
||
|
||
local col, row, xOff, yOff, useRight
|
||
|
||
if dir == "DOWN" or dir == "UP" then
|
||
col = (index - 1) % n
|
||
row = math.floor((index - 1) / n)
|
||
xOff = col * (cellW + step)
|
||
if dir == "DOWN" then
|
||
yOff = -(row * (cellH + step)) - 20
|
||
useRight = false
|
||
else
|
||
-- UP: Zellen wachsen von unten nach oben
|
||
yOff = row * (cellH + step)
|
||
useRight = false
|
||
end
|
||
else
|
||
-- LEFT / RIGHT: n Icons pro Spalte
|
||
row = (index - 1) % n
|
||
col = math.floor((index - 1) / n)
|
||
yOff = -(row * (cellH + step)) - 20
|
||
if dir == "RIGHT" then
|
||
xOff = col * (cellW + step)
|
||
useRight = false
|
||
else -- LEFT: vom rechten Rand aufbauen
|
||
xOff = -(col * (cellW + step))
|
||
useRight = true
|
||
end
|
||
end
|
||
|
||
return xOff, yOff, sz, cellW, cellH, useRight
|
||
end
|
||
|
||
--- Gibt die Gesamt-Breite und -Höhe des Icon-Rasters zurück
|
||
local function CalcIconGridSize(count, s)
|
||
local sz = s.iconSize or 32
|
||
local n = s.iconCols or 6
|
||
local gap = s.iconSpacing or 2
|
||
local dir = s.growDirection or "DOWN"
|
||
local anchor = s.textAnchor or "below"
|
||
local overlay = s.iconOverlay or "sweep"
|
||
local isSweep = (overlay == "sweep" or overlay == "swipe")
|
||
local showN = s.showPlayerName
|
||
local textBlockW = GetIconTextBlockWidth(s)
|
||
|
||
local extraW = 0
|
||
local extraH = 0
|
||
if anchor == "left" or anchor == "right" then
|
||
if isSweep then
|
||
extraW = showN and (textBlockW + 4) or 0
|
||
else
|
||
extraW = textBlockW + 4
|
||
end
|
||
elseif isSweep then
|
||
extraH = showN and LABEL_H or 0
|
||
elseif anchor ~= "onIcon" then
|
||
extraH = showN and (LABEL_H * 2) or LABEL_H
|
||
end
|
||
|
||
local cellH = sz + extraH
|
||
local cellW = sz + extraW
|
||
|
||
if dir == "DOWN" or dir == "UP" then
|
||
local cols = math.min(count, n)
|
||
local rows = math.ceil(count / n)
|
||
return cols * (cellW + gap) - gap,
|
||
20 + rows * (cellH + gap) - gap
|
||
else
|
||
local rows = math.min(count, n)
|
||
local cols = math.ceil(count / n)
|
||
return cols * (cellW + gap) - gap,
|
||
20 + rows * (cellH + gap) - gap
|
||
end
|
||
end
|
||
|
||
-- ══════════════════════════════════════════════════════════════
|
||
-- ICON-CELL UPDATE
|
||
-- ══════════════════════════════════════════════════════════════
|
||
|
||
local function UpdateIconCell(frame, index, data)
|
||
local cell = frame._iconPool[index]
|
||
if not cell then return end
|
||
|
||
local s = frame._settings
|
||
local sz = s.iconSize or 32
|
||
local overlay = s.iconOverlay or "sweep"
|
||
local isSweep = (overlay == "sweep" or overlay == "swipe")
|
||
local anchor = s.textAnchor or "below"
|
||
local showN = s.showPlayerName
|
||
local textBlockW = GetIconTextBlockWidth(s)
|
||
|
||
local xOff, yOff, _, cellW, cellH, useRight = CalcCellLayout(frame, index)
|
||
|
||
-- ── Zell- und iconFrame-Größe ────────────────────────────
|
||
cell:SetSize(cellW, cellH)
|
||
cell:SetAlpha(data.outOfRange and (data.outOfRangeAlpha or 0.4) or 1)
|
||
cell.iconFrame:SetSize(sz, sz)
|
||
cell.iconFrame:ClearAllPoints()
|
||
|
||
-- Icon-Bereich positionieren (abhängig von textAnchor)
|
||
if anchor == "left" then
|
||
cell.iconFrame:SetPoint("TOPRIGHT", cell, "TOPRIGHT", 0, 0)
|
||
elseif anchor == "right" then
|
||
cell.iconFrame:SetPoint("TOPLEFT", cell, "TOPLEFT", 0, 0)
|
||
elseif anchor == "above" and overlay == "timer" then
|
||
-- Texte oben, Icon unten
|
||
cell.iconFrame:SetPoint("BOTTOMLEFT", cell, "BOTTOMLEFT", 0, 0)
|
||
else
|
||
-- Icon oben (Standard: below / onIcon)
|
||
cell.iconFrame:SetPoint("TOPLEFT", cell, "TOPLEFT", 0, 0)
|
||
end
|
||
|
||
-- ── Zell-Position im Frame ───────────────────────────────
|
||
cell:ClearAllPoints()
|
||
if useRight then
|
||
cell:SetPoint("TOPRIGHT", frame, "TOPRIGHT", xOff, yOff)
|
||
else
|
||
cell:SetPoint("TOPLEFT", frame, "TOPLEFT", xOff, yOff)
|
||
end
|
||
|
||
-- ── Icon-Textur ──────────────────────────────────────────
|
||
cell.icon:SetTexture(
|
||
data.spellEntry and HMGT_SpellData.GetSpellIcon(data.spellEntry.spellId)
|
||
or "Interface\\Icons\\INV_Misc_QuestionMark"
|
||
)
|
||
|
||
-- ── Icon-Rahmen: Textur + Größe dynamisch ────────────────
|
||
-- UI-ActionButton-Border: 64px Textur, ~11px Padding pro Seite
|
||
-- → Border muss sz*(64/42) groß sein damit die 42px-Mitte = sz
|
||
-- Für benutzerdefinierte Texturen: fester Offset von sz*0.25
|
||
local bc = s.borderColor or {}
|
||
local br = bc.r or bc[1] or 1
|
||
local bg = bc.g or bc[2] or 1
|
||
local bb = bc.b or bc[3] or 1
|
||
local ba = bc.a or bc[4] or 1
|
||
SetOutline(cell.border, cell.iconFrame, br, bg, bb, ba, s.borderEnabled == true)
|
||
|
||
local remaining = data.remaining
|
||
local total = data.total
|
||
local countText = GetEntryCountText(data)
|
||
local hasCharges = countText ~= nil
|
||
local isAvailabilityEntry = IsAvailabilityEntry(data)
|
||
local showRaidTimeOnIcon = s.showRemainingOnIcon == true and remaining > 0
|
||
local showChargeOnIcon = hasCharges and ((s.showChargesOnIcon == true) or isAvailabilityEntry)
|
||
|
||
local fp = GetFontPath(s.font)
|
||
local fsz = math.max(8, (s.fontSize or 12) - 2)
|
||
local fo = s.fontOutline or "OUTLINE"
|
||
cell.chargeText:SetText("")
|
||
cell.chargeText:Hide()
|
||
|
||
-- ── Overlay-Modus ────────────────────────────────────────
|
||
if isSweep then
|
||
-- Sweep-Kreis
|
||
cell.cooldown:Show()
|
||
if remaining > 0 and total > 0 then
|
||
cell.cooldown:SetCooldown(GetTime() - (total - remaining), total)
|
||
else
|
||
cell.cooldown:Clear()
|
||
end
|
||
cell.icon:SetVertexColor(1, 1, 1)
|
||
|
||
-- Mittige Restzeit im Icon anzeigen
|
||
cell.centerText:ClearAllPoints()
|
||
cell.centerText:SetPoint("CENTER", cell.iconFrame, "CENTER", 0, 0)
|
||
SafeSetFont(cell.centerText, fp, fsz, fo)
|
||
if remaining > 0 then
|
||
cell.centerText:SetText(HMGT:FormatTime(remaining))
|
||
cell.centerText:SetTextColor(1, 1, 1)
|
||
cell.centerText:Show()
|
||
else
|
||
cell.centerText:SetText("")
|
||
cell.centerText:Hide()
|
||
end
|
||
|
||
-- Im Icon-Modus Spielernamen optional unter dem Icon anzeigen.
|
||
cell.nameText:ClearAllPoints()
|
||
SafeSetFont(cell.nameText, fp, fsz, fo)
|
||
if showN then
|
||
if anchor == "left" then
|
||
cell.nameText:SetPoint("RIGHT", cell.iconFrame, "LEFT", -4, 0)
|
||
cell.nameText:SetWidth(textBlockW)
|
||
cell.nameText:SetJustifyH("RIGHT")
|
||
cell.nameText:SetText(TruncateTextToWidth(cell.nameText, data.playerName or "", textBlockW))
|
||
elseif anchor == "right" then
|
||
cell.nameText:SetPoint("LEFT", cell.iconFrame, "RIGHT", 4, 0)
|
||
cell.nameText:SetWidth(textBlockW)
|
||
cell.nameText:SetJustifyH("LEFT")
|
||
cell.nameText:SetText(TruncateTextToWidth(cell.nameText, data.playerName or "", textBlockW))
|
||
else
|
||
cell.nameText:SetPoint("TOPLEFT", cell.iconFrame, "BOTTOMLEFT", 0, -2)
|
||
cell.nameText:SetWidth(sz)
|
||
cell.nameText:SetJustifyH("LEFT")
|
||
cell.nameText:SetText(TruncateTextToWidth(cell.nameText, data.playerName or "", sz))
|
||
end
|
||
cell.nameText:SetTextColor(0.9, 0.9, 0.9)
|
||
cell.nameText:Show()
|
||
else
|
||
cell.nameText:SetText("")
|
||
cell.nameText:Hide()
|
||
end
|
||
-- Im Sweep-Modus bei Charge-Spells die Aufladungen anzeigen.
|
||
if showChargeOnIcon then
|
||
cell.chargeText:ClearAllPoints()
|
||
cell.chargeText:SetPoint("BOTTOM", cell.iconFrame, "BOTTOM", 0, CHARGE_TEXT_Y_OFFSET)
|
||
SafeSetFont(cell.chargeText, fp, math.max(7, fsz - 1), fo)
|
||
cell.chargeText:SetText(countText)
|
||
cell.chargeText:SetTextColor(0.85, 0.95, 1)
|
||
cell.chargeText:Show()
|
||
|
||
cell.timeText:SetText("")
|
||
cell.timeText:Hide()
|
||
elseif countText then
|
||
cell.timeText:ClearAllPoints()
|
||
cell.timeText:SetPoint("BOTTOMRIGHT", cell.iconFrame, "BOTTOMRIGHT", -2, 2)
|
||
SafeSetFont(cell.timeText, fp, math.max(7, fsz - 1), fo)
|
||
cell.timeText:SetText(countText)
|
||
cell.timeText:SetTextColor(0.85, 0.95, 1)
|
||
cell.timeText:Show()
|
||
else
|
||
cell.timeText:SetText("")
|
||
cell.timeText:Hide()
|
||
end
|
||
|
||
else
|
||
-- Timer-Modus: kein Sweep, Icon grau wenn auf CD
|
||
cell.cooldown:Clear()
|
||
cell.cooldown:Hide()
|
||
cell.centerText:SetText("")
|
||
cell.centerText:Hide()
|
||
cell.icon:SetVertexColor(remaining > 0 and 0.5 or 1, remaining > 0 and 0.5 or 1, remaining > 0 and 0.5 or 1)
|
||
|
||
-- ── Texte: onIcon ────────────────────────────────────
|
||
if anchor == "onIcon" then
|
||
-- Name oben links auf dem Icon (Overlay)
|
||
cell.nameText:ClearAllPoints()
|
||
cell.nameText:SetPoint("TOPLEFT", cell.iconFrame, "TOPLEFT", 2, -2)
|
||
SafeSetFont(cell.nameText, fp, fsz, fo)
|
||
if showN then
|
||
local short = (data.playerName or ""):match("^(%a%a%a)") or
|
||
(data.playerName or ""):sub(1, 3)
|
||
cell.nameText:SetText(short)
|
||
cell.nameText:SetTextColor(1, 1, 1)
|
||
cell.nameText:Show()
|
||
else
|
||
cell.nameText:SetText("")
|
||
cell.nameText:Hide()
|
||
end
|
||
-- Zeit unten mittig auf dem Icon
|
||
cell.timeText:ClearAllPoints()
|
||
if showChargeOnIcon then
|
||
cell.timeText:SetPoint("CENTER", cell.iconFrame, "CENTER", 0, 0)
|
||
else
|
||
cell.timeText:SetPoint("BOTTOM", cell.iconFrame, "BOTTOM", 0, 2)
|
||
end
|
||
SafeSetFont(cell.timeText, fp, fsz, fo)
|
||
cell.timeText:Show()
|
||
|
||
-- ── Texte: below (Standard) ──────────────────────────
|
||
elseif anchor == "below" then
|
||
-- Icon ist am TOP der Zelle
|
||
if showN then
|
||
cell.nameText:ClearAllPoints()
|
||
cell.nameText:SetPoint("TOPLEFT", cell.iconFrame, "BOTTOMLEFT", 0, -2)
|
||
cell.nameText:SetWidth(sz)
|
||
SafeSetFont(cell.nameText, fp, fsz, fo)
|
||
cell.nameText:SetText(TruncateTextToWidth(cell.nameText, data.playerName or "", sz))
|
||
cell.nameText:SetTextColor(0.9, 0.9, 0.9)
|
||
cell.nameText:Show()
|
||
|
||
cell.timeText:ClearAllPoints()
|
||
if showRaidTimeOnIcon then
|
||
cell.timeText:SetPoint("CENTER", cell.iconFrame, "CENTER", 0, 0)
|
||
else
|
||
cell.timeText:SetPoint("TOP", cell.iconFrame, "BOTTOM", 0, -(LABEL_H + 4))
|
||
end
|
||
else
|
||
cell.nameText:SetText("")
|
||
cell.nameText:Hide()
|
||
cell.timeText:ClearAllPoints()
|
||
if showRaidTimeOnIcon then
|
||
cell.timeText:SetPoint("CENTER", cell.iconFrame, "CENTER", 0, 0)
|
||
else
|
||
cell.timeText:SetPoint("TOP", cell.iconFrame, "BOTTOM", 0, -2)
|
||
end
|
||
end
|
||
SafeSetFont(cell.timeText, fp, fsz, fo)
|
||
cell.timeText:Show()
|
||
|
||
-- ── Texte: left / right ──────────────────────────────
|
||
elseif anchor == "left" or anchor == "right" then
|
||
local justify = anchor == "left" and "RIGHT" or "LEFT"
|
||
local point = anchor == "left" and "RIGHT" or "LEFT"
|
||
local relPoint = anchor == "left" and "LEFT" or "RIGHT"
|
||
local xOffset = anchor == "left" and -4 or 4
|
||
|
||
cell.nameText:ClearAllPoints()
|
||
cell.nameText:SetWidth(textBlockW)
|
||
cell.nameText:SetJustifyH(justify)
|
||
SafeSetFont(cell.nameText, fp, fsz, fo)
|
||
|
||
cell.timeText:ClearAllPoints()
|
||
cell.timeText:SetWidth(textBlockW)
|
||
cell.timeText:SetJustifyH(justify)
|
||
SafeSetFont(cell.timeText, fp, fsz, fo)
|
||
|
||
if showN then
|
||
cell.nameText:SetPoint(point, cell.iconFrame, relPoint, xOffset, -((LABEL_H * 0.5) + 1))
|
||
cell.nameText:SetText(TruncateTextToWidth(cell.nameText, data.playerName or "", textBlockW))
|
||
cell.nameText:SetTextColor(0.9, 0.9, 0.9)
|
||
cell.nameText:Show()
|
||
if showRaidTimeOnIcon then
|
||
cell.timeText:SetPoint("CENTER", cell.iconFrame, "CENTER", 0, 0)
|
||
cell.timeText:SetWidth(sz)
|
||
cell.timeText:SetJustifyH("CENTER")
|
||
else
|
||
cell.timeText:SetPoint(point, cell.iconFrame, relPoint, xOffset, (LABEL_H * 0.5) + 1)
|
||
end
|
||
else
|
||
cell.nameText:SetText("")
|
||
cell.nameText:Hide()
|
||
if showRaidTimeOnIcon then
|
||
cell.timeText:SetPoint("CENTER", cell.iconFrame, "CENTER", 0, 0)
|
||
cell.timeText:SetWidth(sz)
|
||
cell.timeText:SetJustifyH("CENTER")
|
||
else
|
||
cell.timeText:SetPoint(point, cell.iconFrame, relPoint, xOffset, 0)
|
||
end
|
||
end
|
||
cell.timeText:Show()
|
||
|
||
-- ── Texte: above ─────────────────────────────────────
|
||
else -- above
|
||
-- Icon ist am BOTTOM der Zelle (iconFrame wurde oben bereits nach unten gesetzt)
|
||
if showN then
|
||
cell.nameText:ClearAllPoints()
|
||
cell.nameText:SetPoint("BOTTOMLEFT", cell.iconFrame, "TOPLEFT", 0, 4)
|
||
cell.nameText:SetWidth(sz)
|
||
SafeSetFont(cell.nameText, fp, fsz, fo)
|
||
cell.nameText:SetText(TruncateTextToWidth(cell.nameText, data.playerName or "", sz))
|
||
cell.nameText:SetTextColor(0.9, 0.9, 0.9)
|
||
cell.nameText:Show()
|
||
|
||
cell.timeText:ClearAllPoints()
|
||
if showRaidTimeOnIcon then
|
||
cell.timeText:SetPoint("CENTER", cell.iconFrame, "CENTER", 0, 0)
|
||
else
|
||
cell.timeText:SetPoint("BOTTOM", cell.iconFrame, "TOP", 0, LABEL_H + 6)
|
||
end
|
||
else
|
||
cell.nameText:SetText("")
|
||
cell.nameText:Hide()
|
||
cell.timeText:ClearAllPoints()
|
||
if showRaidTimeOnIcon then
|
||
cell.timeText:SetPoint("CENTER", cell.iconFrame, "CENTER", 0, 0)
|
||
else
|
||
cell.timeText:SetPoint("BOTTOM", cell.iconFrame, "TOP", 0, 4)
|
||
end
|
||
end
|
||
SafeSetFont(cell.timeText, fp, fsz, fo)
|
||
cell.timeText:Show()
|
||
end
|
||
|
||
if showChargeOnIcon then
|
||
cell.chargeText:ClearAllPoints()
|
||
cell.chargeText:SetPoint("BOTTOM", cell.iconFrame, "BOTTOM", 0, CHARGE_TEXT_Y_OFFSET)
|
||
SafeSetFont(cell.chargeText, fp, math.max(7, fsz - 1), fo)
|
||
cell.chargeText:SetText(countText)
|
||
cell.chargeText:SetTextColor(0.85, 0.95, 1)
|
||
cell.chargeText:Show()
|
||
end
|
||
|
||
-- Timer-Farbe
|
||
if remaining > 0 then
|
||
local timeStr = HMGT:FormatTime(remaining)
|
||
if countText and not showChargeOnIcon then
|
||
timeStr = string.format("%s %s", countText, timeStr)
|
||
end
|
||
cell.timeText:SetText(timeStr)
|
||
if remaining < 5 then cell.timeText:SetTextColor(1, 0.2, 0.2)
|
||
elseif remaining < 15 then cell.timeText:SetTextColor(1, 0.8, 0.2)
|
||
else cell.timeText:SetTextColor(0.9, 0.9, 0.9) end
|
||
else
|
||
if isAvailabilityEntry and showChargeOnIcon then
|
||
cell.timeText:SetText("")
|
||
cell.timeText:Hide()
|
||
elseif countText and not showChargeOnIcon then
|
||
cell.timeText:SetText(countText)
|
||
elseif s.showReadyText == false then
|
||
cell.timeText:SetText("")
|
||
cell.timeText:Hide()
|
||
else
|
||
cell.timeText:SetText("|cff00ff00Bereit|r")
|
||
end
|
||
end
|
||
end
|
||
|
||
cell._data = data
|
||
cell:Show()
|
||
end
|
||
|
||
-- ══════════════════════════════════════════════════════════════
|
||
-- HAUPTUPDATE
|
||
-- ══════════════════════════════════════════════════════════════
|
||
|
||
function TF:UpdateFrame(frame, entries, skipFilter)
|
||
local s = frame._settings
|
||
if (not skipFilter) and HMGT.FilterDisplayEntries then
|
||
entries = HMGT:FilterDisplayEntries(s, entries) or entries
|
||
end
|
||
|
||
for i = 1, MAX_ROWS do
|
||
frame._barPool[i]:Hide()
|
||
frame._iconPool[i]:Hide()
|
||
end
|
||
|
||
if #entries == 0 then
|
||
frame:SetHeight(HEADER_HEIGHT)
|
||
ApplyTitleLayout(frame)
|
||
return
|
||
end
|
||
|
||
local h = s.barHeight or 20
|
||
local gap = s.barSpacing or 2
|
||
|
||
if s.showBar then
|
||
local count = math.min(#entries, MAX_ROWS)
|
||
frame:SetHeight(HEADER_HEIGHT + count * (h + gap))
|
||
frame:SetWidth(s.width or 250)
|
||
for i = 1, count do
|
||
UpdateBarRow(frame, i, entries[i])
|
||
end
|
||
else
|
||
local count = math.min(#entries, MAX_ROWS)
|
||
local w, ht = CalcIconGridSize(count, s)
|
||
frame:SetWidth(math.max(w, s.iconSize or 32))
|
||
frame:SetHeight(math.max(ht, 20))
|
||
for i = 1, count do
|
||
UpdateIconCell(frame, i, entries[i])
|
||
end
|
||
end
|
||
|
||
SafeSetFont(
|
||
frame.titleText,
|
||
GetFontPath(s.font),
|
||
math.max(9, (s.fontSize or 12) - 1),
|
||
s.fontOutline or "OUTLINE"
|
||
)
|
||
ApplyTitleLayout(frame)
|
||
end
|
||
|
||
-- ══════════════════════════════════════════════════════════════
|
||
-- HILFSFUNKTIONEN
|
||
-- ══════════════════════════════════════════════════════════════
|
||
|
||
function TF:SetTitle(frame, title)
|
||
frame.titleText:SetText(title or "")
|
||
end
|
||
|
||
function TF:GetContentTopInset(frame)
|
||
if not frame or not frame.GetTop then
|
||
return 0
|
||
end
|
||
|
||
local frameTop = frame:GetTop()
|
||
local contentTop = GetVisibleContentBounds(frame)
|
||
if not frameTop or not contentTop then
|
||
return 0
|
||
end
|
||
|
||
return math.max(0, frameTop - contentTop)
|
||
end
|
||
|
||
--- Gesperrt: Maus deaktivieren + Titel/Hintergrund ausblenden
|
||
function TF:SetLocked(frame, locked)
|
||
frame:EnableMouse(not locked)
|
||
if locked then
|
||
frame.bg:Hide()
|
||
frame.titleText:Hide()
|
||
else
|
||
frame.bg:Show()
|
||
frame.bg:SetColorTexture(0, 0, 0, 0.5)
|
||
frame.titleText:Show()
|
||
end
|
||
end
|
||
|
||
function TF:Clear(frame)
|
||
for i = 1, MAX_ROWS do
|
||
frame._barPool[i]:Hide()
|
||
frame._iconPool[i]:Hide()
|
||
end
|
||
frame:SetHeight(HEADER_HEIGHT)
|
||
end
|
||
|
||
function TF:ApplyAnchor(frame)
|
||
if not frame or not frame._settings then return end
|
||
|
||
local s = frame._settings
|
||
local anchorPoint = VALID_POINTS[s.anchorPoint] and s.anchorPoint or "TOPLEFT"
|
||
local relPoint = VALID_POINTS[s.anchorRelPoint] and s.anchorRelPoint or "TOPLEFT"
|
||
local x = s.anchorX
|
||
local y = s.anchorY
|
||
local target = ResolveAnchorTarget(s.anchorTo, s.anchorCustom)
|
||
|
||
if x == nil then x = s.posX or 0 end
|
||
if y == nil then y = s.posY or 0 end
|
||
|
||
if UsesBottomHeader(s) then
|
||
local mapped = HEADER_ANCHOR_MAP_UP[anchorPoint]
|
||
if mapped then
|
||
anchorPoint = mapped.point
|
||
y = y + mapped.y
|
||
end
|
||
end
|
||
|
||
frame:ClearAllPoints()
|
||
frame:SetPoint(anchorPoint, target, relPoint, x, y)
|
||
end
|
||
|