From 147a692bf003fd25db6a29c1135c06a7b8f935d6 Mon Sep 17 00:00:00 2001 From: sdubrez <22425976+imsamdez@users.noreply.github.com> Date: Wed, 13 May 2026 11:42:15 +0200 Subject: [PATCH] feat(EllesmereUIDamageMeters): implement roster panel functionality and data refresh logic Added a new roster panel with functions for managing group member data, including debounced refresh on world events and player entry. Introduced helper functions for building the roster unit list and handling visibility. Also included a new image asset for the roster panel. --- .../EllesmereUIDamageMeters.lua | 581 +++++++++++++++++- EllesmereUIDamageMeters/Media/dm_roster.png | Bin 0 -> 1102 bytes 2 files changed, 579 insertions(+), 2 deletions(-) create mode 100644 EllesmereUIDamageMeters/Media/dm_roster.png diff --git a/EllesmereUIDamageMeters/EllesmereUIDamageMeters.lua b/EllesmereUIDamageMeters/EllesmereUIDamageMeters.lua index 5f82007a..13cdb18e 100644 --- a/EllesmereUIDamageMeters/EllesmereUIDamageMeters.lua +++ b/EllesmereUIDamageMeters/EllesmereUIDamageMeters.lua @@ -229,6 +229,8 @@ local _inCombat = false local _playerGUID local _windows = {} -- array of active window tables ns._windows = _windows +-- Forward declarations: instanceFrame handlers call Schedule before roster block defines it. +local RefreshRosterPanelData, ScheduleRosterPanelRefresh ns._DM_TYPE_NAMES = DM_TYPE_NAMES local _combatStartTime = 0 -- GetTime() at combat start local _combatEndTime = 0 -- GetTime() at combat end (frozen) @@ -316,6 +318,7 @@ instanceFrame:SetScript("OnEvent", function(_, event) w._cachedTargets = nil w.Refresh() end + ScheduleRosterPanelRefresh() end) end elseif event == "DAMAGE_METER_RESET" then @@ -328,12 +331,14 @@ instanceFrame:SetScript("OnEvent", function(_, event) w._cachedTargets = nil w.Refresh() end + ScheduleRosterPanelRefresh() elseif event == "PLAYER_ENTERING_WORLD" then -- Refresh after zone-in to pick up visibility/data changes for _, w in ipairs(_windows) do w._barCacheKey = nil w._barSources = nil end + ScheduleRosterPanelRefresh() C_Timer.After(0.5, function() for _, w in ipairs(_windows) do w.Refresh() end end) @@ -1436,6 +1441,543 @@ end local _edmMenuAnchor = nil -- tracks which button opened the menu (for toggle) +------------------------------------------------------------------------------- +-- Group roster panel (shared singleton). Data refresh: debounced world events + +-- login (RefreshRosterPanelData). Hover only anchors/shows and repaints from +-- _rosterUnitsSnapshot + _rosterStatsCache (no APIs). Stale inspect/ilvl reads +-- keep last good values per GUID. INSPECT_READY stays registered for the queue. +------------------------------------------------------------------------------- +local _rosterPanel, _rosterList +local _rosterAnchorBtn, _rosterAnchorWin +local _rosterHideHandle +local _rosterUnitsSnapshot = {} +local _rosterInspectQueue = {} +local _rosterRefreshTimer +-- Last displayed stats per unit GUID (ilvl string, spec file id). Survives failed re-queries (inspect throttle). +local _rosterStatsCache = {} +local ROSTER_INSPECT_CHAIN_DELAY = 0.55 +local ROSTER_ROW_H = 20 +local ROSTER_PANEL_PAD = 0 +local ROSTER_HEADER_H = 16 +local ROSTER_ROW_POOL = 40 +-- Vertical gap when anchoring roster above the header button (positive = panel shifts up). +-- Helps keep the bottom ilvl column clear of common cursor rings / soft-target circles. +local ROSTER_POPUP_GAP_Y = 14 + +local function CancelRosterHideTimer() + if _rosterHideHandle then + _rosterHideHandle:Cancel() + _rosterHideHandle = nil + end +end + +local function ScheduleRosterHide() + CancelRosterHideTimer() + _rosterHideHandle = C_Timer.NewTimer(0.12, function() + _rosterHideHandle = nil + if not _rosterPanel or not _rosterPanel:IsShown() then return end + local over = _rosterPanel:IsMouseOver() + or (_rosterAnchorBtn and _rosterAnchorBtn:IsMouseOver()) + if not over then + _rosterPanel:Hide() + _rosterAnchorBtn = nil + _rosterAnchorWin = nil + end + end) +end + +local function HideRosterPanelNow() + CancelRosterHideTimer() + wipe(_rosterInspectQueue) + if _rosterPanel then + _rosterPanel:Hide() + end + _rosterAnchorBtn = nil + _rosterAnchorWin = nil +end + +-- Meter window used for combat-source spec fallback (prefer hovered window, else any visible, else first). +local function GetRosterMeterWin(pref) + if pref and pref.frame then return pref end + if _rosterAnchorWin and _rosterAnchorWin.frame then return _rosterAnchorWin end + for _, w in ipairs(_windows) do + if w and w.frame and w.frame:IsShown() then return w end + end + return _windows[1] +end + +local function BuildRosterUnitList() + local units = {} + if IsInRaid() then + local n = GetNumGroupMembers() + for i = 1, n do + local u = _raidUnits[i] + if u and UnitExists(u) then units[#units + 1] = u end + end + elseif IsInGroup() then + units[#units + 1] = "player" + local n = GetNumGroupMembers() - 1 + for i = 1, n do + local u = _partyUnits[i] + if u and UnitExists(u) then units[#units + 1] = u end + end + else + units[1] = "player" + end + return units +end + +local function FindMeterSourceForGUID(W, guid) + if not guid or not W or not W._lastSession or not W._lastSession.combatSources then return nil end + if issecretvalue and issecretvalue(guid) then return nil end + local playerGuid = UnitGUID("player") + for _, src in ipairs(W._lastSession.combatSources) do + if playerGuid and guid == playerGuid and src.isLocalPlayer then + return src + end + local sg = src.sourceGUID + if sg and not (issecretvalue and issecretvalue(sg)) then + if sg == guid or tostring(sg) == tostring(guid) then return src end + end + end + return nil +end + +-- Roster spec icon: numeric file IDs only (same contract as meter specIconID / ResolveIcon). +-- APIs read via { GetSpecializationInfo(...) } so indices stay aligned. Player uses live GSI first, +-- then meter session; other units prefer meter, then inspect+ByID. +local function RosterGsiTable(specIndex) + if not GetSpecializationInfo or not specIndex or specIndex <= 0 then return nil end + local ok, t = pcall(function() return { GetSpecializationInfo(specIndex) } end) + if not ok or type(t) ~= "table" then return nil end + return t +end + +local function RosterGsiByIDTable(specID) + if not GetSpecializationInfoByID or not specID or specID <= 0 then return nil end + local ok, t = pcall(function() return { GetSpecializationInfoByID(specID) } end) + if not ok or type(t) ~= "table" then return nil end + return t +end + +local function RosterIconFileIDFromSpecTable(t) + if not t then return nil end + local icon = t[4] + if type(icon) == "number" and icon ~= 0 then return icon end + if type(icon) == "string" and icon ~= "" then + local n = tonumber(icon) + if n and n ~= 0 then return n end + end + return nil +end + +local function RosterPlayerSpecFileID() + local idx = GetSpecialization and GetSpecialization() or 0 + if idx > 0 then + local t = RosterGsiTable(idx) + local fid = RosterIconFileIDFromSpecTable(t) + if fid then return fid end + if t and type(t[1]) == "number" and t[1] > 0 then + fid = RosterIconFileIDFromSpecTable(RosterGsiByIDTable(t[1])) + if fid then return fid end + end + end + if C_SpecializationInfo and C_SpecializationInfo.GetSpecialization then + local cx = C_SpecializationInfo.GetSpecialization() or 0 + if cx > 0 then + local fid = RosterIconFileIDFromSpecTable(RosterGsiTable(cx)) + if fid then return fid end + fid = RosterIconFileIDFromSpecTable(RosterGsiByIDTable(cx)) + if fid then return fid end + if C_SpecializationInfo.GetSpecializationInfo then + local ok, sid = pcall(function() return select(1, C_SpecializationInfo.GetSpecializationInfo(cx)) end) + if ok and type(sid) == "number" and sid > 0 then + fid = RosterIconFileIDFromSpecTable(RosterGsiByIDTable(sid)) + if fid then return fid end + end + end + end + end + return nil +end + +local function RosterResolveSpecIconFileID(unit, W) + -- Self: live APIs first (spec swap / idle meter), then meter session if present. + if unit == "player" then + local fid = RosterPlayerSpecFileID() + if fid then return fid end + local guid = UnitGUID("player") + if W and guid then + local src = FindMeterSourceForGUID(W, guid) + if src and type(src.specIconID) == "number" and src.specIconID ~= 0 then + return src.specIconID + end + end + return nil + end + local guid = unit and UnitGUID(unit) + if W and guid then + local src = FindMeterSourceForGUID(W, guid) + if src and type(src.specIconID) == "number" and src.specIconID ~= 0 then + return src.specIconID + end + end + if guid and GetInspectSpecialization then + local ok, inspId = pcall(GetInspectSpecialization, unit) + if ok and type(inspId) == "number" and inspId > 0 then + return RosterIconFileIDFromSpecTable(RosterGsiByIDTable(inspId)) + end + end + return nil +end + +local function SetRosterRowClassBackground(row, classFile) + if not row then return end + if not row.classBg then + row.classBg = row:CreateTexture(nil, "BACKGROUND", nil, -8) + row.classBg:SetAllPoints() + end + local cc = classFile and RAID_CLASS_COLORS and RAID_CLASS_COLORS[classFile] + if cc then + row.classBg:SetColorTexture(cc.r, cc.g, cc.b, 0.32) + row.classBg:Show() + else + row.classBg:SetColorTexture(0.1, 0.1, 0.12, 0.55) + row.classBg:Show() + end +end + +local function SetRosterSpecIcon(tex, fileID) + if fileID and type(fileID) == "number" and fileID ~= 0 then + tex:SetTexture(fileID) + tex:SetTexCoord(0.08, 0.92, 0.08, 0.92) + tex:SetDesaturated(false) + tex:SetVertexColor(1, 1, 1, 1) + tex:Show() + else + tex:Hide() + end +end + +local function RosterRefreshRowStats(unit, row, meterPrefW) + if not row or not unit then return end + local guid = UnitGUID(unit) + local prev = (guid and _rosterStatsCache[guid]) or {} + + local ilvlStr = "—" + if unit == "player" then + local avg, avgEq = GetAverageItemLevel() + local v = avgEq or avg + if v and v > 0 then ilvlStr = tostring(math.floor(v + 0.5)) + elseif prev.ilvlStr and prev.ilvlStr ~= "—" then ilvlStr = prev.ilvlStr end + elseif C_PaperDollInfo and C_PaperDollInfo.GetInspectItemLevel then + local ok, v = pcall(C_PaperDollInfo.GetInspectItemLevel, unit) + if ok and v and type(v) == "number" and v > 0 then ilvlStr = tostring(math.floor(v + 0.5)) + elseif prev.ilvlStr and prev.ilvlStr ~= "—" then ilvlStr = prev.ilvlStr end + elseif prev.ilvlStr and prev.ilvlStr ~= "—" then + ilvlStr = prev.ilvlStr + end + row.ilvlFS:SetText(ilvlStr) + + local specFid + if row.specIcon then + local Wctx = GetRosterMeterWin(meterPrefW) + local fidNew = RosterResolveSpecIconFileID(unit, Wctx) + if fidNew and fidNew ~= 0 then specFid = fidNew + elseif prev.specFileID and prev.specFileID ~= 0 then specFid = prev.specFileID end + SetRosterSpecIcon(row.specIcon, specFid) + end + + if guid then + _rosterStatsCache[guid] = { + ilvlStr = ilvlStr, + specFileID = specFid or prev.specFileID, + } + end +end + +local function RosterNotifyInspectQueueHeadDelayed() + if not _rosterInspectQueue or #_rosterInspectQueue == 0 then return end + if InCombatLockdown() then return end + local u = _rosterInspectQueue[1] + if u and UnitExists(u) and CanInspect(u) then NotifyInspect(u) end +end + +local function RosterHandleInspectReady(guid) + if not _rosterPanel or not guid then return end + -- Refresh ilevel/spec for whichever roster unit this GUID belongs to. + for i = 1, #_rosterUnitsSnapshot do + local u = _rosterUnitsSnapshot[i] + if u and u ~= "player" and UnitGUID(u) == guid then + local row = _rosterPanel._rows and _rosterPanel._rows[i] + if row then RosterRefreshRowStats(u, row) end + break + end + end + -- Advance our sequential NotifyInspect queue only when the ready GUID matches the head. + if #_rosterInspectQueue == 0 then return end + local head = _rosterInspectQueue[1] + if head and UnitGUID(head) == guid then + table.remove(_rosterInspectQueue, 1) + if #_rosterInspectQueue > 0 and not InCombatLockdown() then + C_Timer.After(ROSTER_INSPECT_CHAIN_DELAY, RosterNotifyInspectQueueHeadDelayed) + end + end +end + +local function RosterBeginInspectQueue() + wipe(_rosterInspectQueue) + for i = 1, #_rosterUnitsSnapshot do + local u = _rosterUnitsSnapshot[i] + if u and u ~= "player" and UnitExists(u) and CanInspect(u) then + _rosterInspectQueue[#_rosterInspectQueue + 1] = u + end + end + if #_rosterInspectQueue == 0 then return end + if InCombatLockdown() then return end + local u = _rosterInspectQueue[1] + if u and UnitExists(u) and CanInspect(u) then + NotifyInspect(u) + end +end + +local function EnsureRosterRow(panel, idx) + local row = panel._rows[idx] + if row then + if row.durFS then + row.durFS:Hide() + row.durFS:SetParent(nil) + row.durFS = nil + end + if row.classIcon then + row.classIcon:Hide() + row.classIcon:SetParent(nil) + row.classIcon = nil + end + if not row.classBg then + row.classBg = row:CreateTexture(nil, "BACKGROUND", nil, -8) + row.classBg:SetAllPoints() + row.classBg:SetColorTexture(0.1, 0.1, 0.12, 0.55) + end + row.specIcon:ClearAllPoints() + row.specIcon:SetPoint("LEFT", row, "LEFT", 4, 0) + row.ilvlFS:ClearAllPoints() + row.ilvlFS:SetPoint("RIGHT", row, "RIGHT", -4, 0) + row.nameFS:ClearAllPoints() + row.nameFS:SetPoint("LEFT", row.specIcon, "RIGHT", 6, 0) + row.nameFS:SetPoint("RIGHT", row.ilvlFS, "LEFT", -4, 0) + row.nameFS:SetShadowColor(0, 0, 0, 0.8) + row.nameFS:SetShadowOffset(1, -1) + return row + end + local fontPath = (EUI.GetFontPath and EUI.GetFontPath()) or "Fonts\\FRIZQT__.TTF" + local outline = (EUI.GetFontOutlineFlag and EUI.GetFontOutlineFlag()) or "" + row = CreateFrame("Frame", nil, panel._list) + row:SetHeight(ROSTER_ROW_H) + row.classBg = row:CreateTexture(nil, "BACKGROUND", nil, -8) + row.classBg:SetAllPoints() + row.classBg:SetColorTexture(0.1, 0.1, 0.12, 0.55) + row.specIcon = row:CreateTexture(nil, "ARTWORK") + row.specIcon:SetSize(16, 16) + row.specIcon:SetPoint("LEFT", row, "LEFT", 4, 0) + row.ilvlFS = row:CreateFontString(nil, "OVERLAY") + row.ilvlFS:SetFont(fontPath, 11, outline) + row.ilvlFS:SetWidth(40) + row.ilvlFS:SetJustifyH("RIGHT") + row.ilvlFS:SetPoint("RIGHT", row, "RIGHT", -4, 0) + row.nameFS = row:CreateFontString(nil, "OVERLAY") + row.nameFS:SetFont(fontPath, 11, outline) + row.nameFS:SetPoint("LEFT", row.specIcon, "RIGHT", 6, 0) + row.nameFS:SetPoint("RIGHT", row.ilvlFS, "LEFT", -4, 0) + row.nameFS:SetJustifyH("LEFT") + row.nameFS:SetShadowColor(0, 0, 0, 0.8) + row.nameFS:SetShadowOffset(1, -1) + panel._rows[idx] = row + return row +end + +local function EnsureRosterPanel() + if _rosterPanel then return end + local RS = EUI.RESKIN or {} + _rosterPanel = CreateFrame("Frame", "EllesmereUIDMRosterPanel", UIParent) + _rosterPanel:SetFrameStrata("FULLSCREEN_DIALOG") + _rosterPanel:SetFrameLevel(205) + _rosterPanel:SetClampedToScreen(true) + _rosterPanel:EnableMouse(true) + _rosterPanel:Hide() + local bg = _rosterPanel:CreateTexture(nil, "BACKGROUND") + bg:SetAllPoints() + bg:SetColorTexture(RS.BG_R or 0.067, RS.BG_G or 0.067, RS.BG_B or 0.067, RS.CTX_ALPHA or 0.95) + local PP_L = EUI.PP + if PP_L and PP_L.CreateBorder then PP_L.CreateBorder(_rosterPanel, 1, 1, 1, RS.BRD_ALPHA or 0.18, 1) end + _rosterPanel:RegisterEvent("PLAYER_REGEN_DISABLED") + _rosterPanel:RegisterEvent("INSPECT_READY") + _rosterPanel:SetScript("OnEvent", function(self, event, ...) + if event == "PLAYER_REGEN_DISABLED" then + self:Hide() + elseif event == "INSPECT_READY" then + local guid = ... + RosterHandleInspectReady(guid) + end + end) + _rosterPanel:SetScript("OnHide", function(self) + CancelRosterHideTimer() + wipe(_rosterInspectQueue) + end) + _rosterPanel:SetScript("OnEnter", function() CancelRosterHideTimer() end) + _rosterPanel:SetScript("OnLeave", function() ScheduleRosterHide() end) + + local fontPath = (EUI.GetFontPath and EUI.GetFontPath()) or "Fonts\\FRIZQT__.TTF" + local outline = (EUI.GetFontOutlineFlag and EUI.GetFontOutlineFlag()) or "" + local hdr = CreateFrame("Frame", nil, _rosterPanel) + hdr:SetHeight(ROSTER_HEADER_H) + hdr:SetPoint("TOPLEFT", _rosterPanel, "TOPLEFT", 0, 0) + hdr:SetPoint("TOPRIGHT", _rosterPanel, "TOPRIGHT", 0, 0) + local hdrSep = hdr:CreateTexture(nil, "ARTWORK") + hdrSep:SetHeight(1) + hdrSep:SetPoint("BOTTOMLEFT", hdr, "BOTTOMLEFT", 0, 0) + hdrSep:SetPoint("BOTTOMRIGHT", hdr, "BOTTOMRIGHT", 0, 0) + hdrSep:SetColorTexture(1, 1, 1, 0.12) + local hdrIlvl = hdr:CreateFontString(nil, "OVERLAY") + hdrIlvl:SetFont(fontPath, 10, outline) + hdrIlvl:SetJustifyH("RIGHT") + hdrIlvl:SetText("ilvl") + hdrIlvl:SetTextColor(0.75, 0.75, 0.82, 1) + hdrIlvl:SetPoint("RIGHT", hdr, "RIGHT", -4, 0) + hdrIlvl:SetWidth(40) + _rosterPanel._header = hdr + _rosterPanel._headerIlvl = hdrIlvl + + _rosterList = CreateFrame("Frame", nil, _rosterPanel) + _rosterList:SetPoint("TOPLEFT", _rosterPanel, "TOPLEFT", 0, -ROSTER_HEADER_H) + _rosterList:SetPoint("BOTTOMRIGHT", _rosterPanel, "BOTTOMRIGHT", 0, 0) + _rosterPanel._list = _rosterList + _rosterPanel._rows = {} +end + +RefreshRosterPanelData = function(preferredW) + EnsureRosterPanel() + wipe(_rosterInspectQueue) + local Wctx = GetRosterMeterWin(preferredW) + local units = BuildRosterUnitList() + local keep = {} + for i = 1, #units do + local u = units[i] + local g = u and UnitGUID(u) + if g then keep[g] = true end + end + for g in pairs(_rosterStatsCache) do + if not keep[g] then _rosterStatsCache[g] = nil end + end + local n = #units + local rowW = 268 + for i = 1, ROSTER_ROW_POOL do + local row = EnsureRosterRow(_rosterPanel, i) + if i <= n then + local unit = units[i] + local _, classFile = UnitClass(unit) + local name = UnitName(unit) or "?" + if issecretvalue and issecretvalue(name) then name = "?" end + name = StripRealm(name) or name + SetRosterRowClassBackground(row, classFile) + row.nameFS:SetText(name) + row.nameFS:SetTextColor(1, 1, 1, 1) + RosterRefreshRowStats(unit, row, Wctx) + row.ilvlFS:SetTextColor(0.85, 0.85, 0.9, 1) + row:SetWidth(rowW) + row:ClearAllPoints() + row:SetPoint("TOPLEFT", _rosterList, "TOPLEFT", 0, -(i - 1) * ROSTER_ROW_H) + row:Show() + else + row:Hide() + end + end + wipe(_rosterUnitsSnapshot) + for i = 1, n do _rosterUnitsSnapshot[i] = units[i] end + local listH = math.max(ROSTER_ROW_H, n * ROSTER_ROW_H) + _rosterList:SetSize(rowW, listH) + local panelH = ROSTER_HEADER_H + listH + ROSTER_PANEL_PAD * 2 + local panelW = rowW + ROSTER_PANEL_PAD * 2 + _rosterPanel:SetSize(panelW, panelH) + C_Timer.After(0, function() + if not _rosterPanel then return end + if InCombatLockdown() then return end + RosterBeginInspectQueue() + end) +end + +-- Repaint roster from snapshot + _rosterStatsCache only (no inspect / ilvl API calls). Used on header hover. +local function RosterApplyPanelLayoutNoRefresh() + if not _rosterPanel then return end + local n = #_rosterUnitsSnapshot + local rowW = 268 + for i = 1, ROSTER_ROW_POOL do + local row = EnsureRosterRow(_rosterPanel, i) + if i <= n then + local unit = _rosterUnitsSnapshot[i] + local _, classFile = UnitClass(unit) + local name = UnitName(unit) or "?" + if issecretvalue and issecretvalue(name) then name = "?" end + name = StripRealm(name) or name + SetRosterRowClassBackground(row, classFile) + row.nameFS:SetText(name) + row.nameFS:SetTextColor(1, 1, 1, 1) + local g = unit and UnitGUID(unit) + local c = g and _rosterStatsCache[g] + row.ilvlFS:SetText((c and c.ilvlStr) or "—") + if row.specIcon then SetRosterSpecIcon(row.specIcon, c and c.specFileID) end + row.ilvlFS:SetTextColor(0.85, 0.85, 0.9, 1) + row:SetWidth(rowW) + row:ClearAllPoints() + row:SetPoint("TOPLEFT", _rosterList, "TOPLEFT", 0, -(i - 1) * ROSTER_ROW_H) + row:Show() + else + row:Hide() + end + end + local listH = math.max(ROSTER_ROW_H, n * ROSTER_ROW_H) + _rosterList:SetSize(rowW, listH) + local panelH = ROSTER_HEADER_H + listH + ROSTER_PANEL_PAD * 2 + local panelW = rowW + ROSTER_PANEL_PAD * 2 + _rosterPanel:SetSize(panelW, panelH) +end + +ScheduleRosterPanelRefresh = function() + if _rosterRefreshTimer then _rosterRefreshTimer:Cancel() end + _rosterRefreshTimer = C_Timer.NewTimer(0.12, function() + _rosterRefreshTimer = nil + RefreshRosterPanelData(nil) + end) +end + +local function ShowRosterPanel(anchorBtn, W) + if not anchorBtn or not W then return end + if _edmMenu and _edmMenu:IsShown() then + _edmMenu:Hide() + if _edmSub then _edmSub:Hide() end + _edmMenuAnchor = nil + end + CancelRosterHideTimer() + EnsureRosterPanel() + _rosterAnchorBtn = anchorBtn + _rosterAnchorWin = W + RosterApplyPanelLayoutNoRefresh() + _rosterPanel:ClearAllPoints() + _rosterPanel:SetPoint("BOTTOMRIGHT", anchorBtn, "TOPRIGHT", 0, ROSTER_POPUP_GAP_Y) + _rosterPanel:Show() +end + +-- World-driven roster row refreshes (debounced burst from roster/equipment events). +do + local f = CreateFrame("Frame") + f:RegisterEvent("GROUP_ROSTER_UPDATE") + f:RegisterEvent("PLAYER_SPECIALIZATION_CHANGED") + f:RegisterEvent("PLAYER_EQUIPMENT_CHANGED") + f:RegisterEvent("PLAYER_ENTERING_WORLD") + f:RegisterUnitEvent("UNIT_INVENTORY_CHANGED", "player") + f:SetScript("OnEvent", function() ScheduleRosterPanelRefresh() end) +end + local function ShowEDMMenu(items, anchorBtn) if not _edmMenu then _edmMenu = MakeMenuPanel(0) @@ -1459,6 +2001,8 @@ local function ShowEDMMenu(items, anchorBtn) return end + HideRosterPanelNow() + local function dismiss() _edmMenu:Hide(); if _edmSub then _edmSub:Hide() end end LayoutMenu(_edmMenu, items, dismiss) _edmMenu:ClearAllPoints() @@ -1939,8 +2483,39 @@ local function CreateDMWindow(winIdx) end end) - -- Ordered list of header buttons for live resize/reposition - W.hdrBtns = { W.settingsBtn, W.segmentBtn, W.modeBtn, W.resetBtn, W.winActionBtn } + do + local rosterXOff = -(btnSize * 5 + btnPad * 6 + 2) + local rosterBtn = CreateFrame("Button", nil, header) + rosterBtn:SetSize(btnSize, btnSize) + rosterBtn:SetPoint("RIGHT", header, "RIGHT", rosterXOff, 0) + rosterBtn:SetFrameLevel(header:GetFrameLevel() + 2) + local ir0, ig0, ib0 = GetIconColor() + local rosterIcon = rosterBtn:CreateTexture(nil, "ARTWORK") + rosterIcon:SetAllPoints() + rosterIcon:SetTexture(MEDIA .. "dm_roster.png") + rosterIcon:SetDesaturated(true) + rosterIcon:SetVertexColor(ir0, ig0, ib0, ICON_ALPHA) + W.hdrIcons[#W.hdrIcons + 1] = rosterIcon + rosterBtn:SetScript("OnEnter", function(self) + local r, g, b = GetIconColor() + rosterIcon:SetVertexColor(r, g, b, ICON_HOVER_ALPHA) + if _edmMenu and _edmMenu:IsShown() and _edmMenuAnchor == self then return end + if EUI.HideWidgetTooltip then EUI.HideWidgetTooltip() end + ShowRosterPanel(self, W) + end) + rosterBtn:SetScript("OnLeave", function() + local r, g, b = GetIconColor() + rosterIcon:SetVertexColor(r, g, b, ICON_ALPHA) + ScheduleRosterHide() + end) + rosterBtn:SetScript("OnClick", function() + if EUI.HideWidgetTooltip then EUI.HideWidgetTooltip() end + end) + W.rosterBtn = rosterBtn + end + + -- Ordered list of header buttons for live resize/reposition (roster = leftmost / highest index) + W.hdrBtns = { W.settingsBtn, W.segmentBtn, W.modeBtn, W.resetBtn, W.winActionBtn, W.rosterBtn } -- Option: hide header icons until the title bar is hovered ApplyHeaderButtonsHoverVisibility(W, cfg) @@ -3488,6 +4063,7 @@ local function CreateDMWindow(winIdx) -- Destroy --------------------------------------------------------------------------- function W.Destroy() + if _rosterAnchorWin == W then HideRosterPanelNow() end if W._hoverTicker then W._hoverTicker:Cancel() end unlockFadeFrame:SetScript("OnUpdate", nil) resizeFrame:SetScript("OnUpdate", nil) @@ -3981,6 +4557,7 @@ initFrame:SetScript("OnEvent", function(self) EnsureTooltipFrame() local sc = (cfg.hoverTooltipScale or 100) / 100 if _ttFrame then _ttFrame:SetScale(sc); _ttLastScale = sc end + C_Timer.After(0, function() RefreshRosterPanelData(nil) end) return end _windows[winIdx] = CreateDMWindow(winIdx) diff --git a/EllesmereUIDamageMeters/Media/dm_roster.png b/EllesmereUIDamageMeters/Media/dm_roster.png new file mode 100644 index 0000000000000000000000000000000000000000..47a5e557d05f73269a7cc780866cf98c36b1f2f3 GIT binary patch literal 1102 zcmV-U1hM;xP))=_P?4sN{(E`^q(gVo`6L|yNF)-8L?V$$BognD75oDb zG1r%CT{gO`b!l}u>hgWHTD5SlXu~sI8VpnEvW9a*8@17;Lm7$&3(f^Q^9rZYz`0=E znp2!y-Fkt>>H_bq9XjN&B)WqV z!yFtkJJzNc>7!xoFIfHUhz=zzfxvj@AjBMixjMu!KJ#>>JkRrae4g)u=e$DC?t1oa!g(jGh z@;h|z5VyT|a04^e2ltRChy^k?6SPr|0a7@P!wenJr-P3VAmhk7%GrIUFIn%=bH836 zaOnOsm_v_*doA;oJ$oL}A!qGdhy@N^m}cV|joalooZZUv9%^5qABq+G2|8Q`bP&Y= z(Qz0}upyU{p3**sjv!*SFy zJN>t72Y7Ew3)3M(DTd_i`R|R6y>=kuo7S@&ZH4j;56wi;lT(h7-#`ScPTU(CTUVew z-F_qKOgRkMV&G#14hM~7GKdHcbBh=nV5_rY?Yod2vkk#v#GkYqlY+y8)L44WbqwE! z1nN14pdgbQ1BVyXQDq!6Il7eLFcL48%g2$-(;OLTLL|hieaM`%&v(eYxaR}Vvis2} z?$~5RiTo~b81!0!OwJw!2jIx=E6LA*$*pm@FEm$yeg|0H`QGF%D1gH?RgD>h!*o^j zIDqai%-cYF3VuA3Ok`snNwY3AP^Fb|4O|70@EEUp`bsxKvfVAZ^Y}h40t2VeCQo4o z*6`9};1*?PffLg{l%RZMvz|Hq)$ literal 0 HcmV?d00001