Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
312 changes: 301 additions & 11 deletions Bars/SecondaryResourceBar.lua
Original file line number Diff line number Diff line change
Expand Up @@ -230,11 +230,34 @@ function SecondaryResourceBarMixin:ApplyVisibilitySettings(layoutName, inCombat)
local data = self:GetData(layoutName)
if not data then return end

-- If using Blizzard bar, delegate to UseBlizzardBarMode which handles
-- both compatible and incompatible specs (falling back to custom bar)
if data.useBlizzardBar then
self:UseBlizzardBarMode(layoutName, inCombat)
return
end

self:HideBlizzardSecondaryResource(layoutName, data)

addonTable.PowerBarMixin.ApplyVisibilitySettings(self, layoutName, inCombat)
end

function SecondaryResourceBarMixin:OnShow()
local data = self:GetData()

if data and data.positionMode ~= nil and data.positionMode ~= "Self" then
self:ApplyLayout()
end
end

function SecondaryResourceBarMixin:OnHide()
local data = self:GetData()

if data and data.positionMode ~= nil and data.positionMode ~= "Self" then
self:ApplyLayout()
end
end

function SecondaryResourceBarMixin:HideBlizzardSecondaryResource(layoutName, data)
data = data or self:GetData(layoutName)
if not data then return end
Expand All @@ -243,18 +266,9 @@ function SecondaryResourceBarMixin:HideBlizzardSecondaryResource(layoutName, dat
if data.hideBlizzardSecondaryResourceUi == nil or InCombatLockdown() then return end

local playerClass = select(2, UnitClass("player"))
local blizzardResourceFrames = {
["DEATHKNIGHT"] = RuneFrame,
["DRUID"] = DruidComboPointBarFrame,
["EVOKER"] = EssencePlayerFrame,
["MAGE"] = MageArcaneChargesFrame,
["MONK"] = MonkHarmonyBarFrame,
["PALADIN"] = PaladinPowerBarFrame,
["ROGUE"] = RogueComboPointBarFrame,
["WARLOCK"] = WarlockPowerFrame,
}

for class, f in pairs(blizzardResourceFrames) do
for class, frameData in pairs(addonTable.blizzardResourceFrames) do
local f = frameData.frame
if f and playerClass == class then
if data.hideBlizzardSecondaryResourceUi == true then
if LEM:IsInEditMode() then
Expand All @@ -271,6 +285,256 @@ function SecondaryResourceBarMixin:HideBlizzardSecondaryResource(layoutName, dat
end
end

-- Returns the Blizzard frame for the class (regardless of spec compatibility)
function SecondaryResourceBarMixin:GetBlizzardFrameForClass()
local playerClass = select(2, UnitClass("player"))
local frameData = addonTable.blizzardResourceFrames[playerClass]
return frameData and frameData.frame
end

-- Returns the Blizzard frame only if it matches the current spec's resource
function SecondaryResourceBarMixin:GetBlizzardResourceFrame()
local playerClass = select(2, UnitClass("player"))
local frameData = addonTable.blizzardResourceFrames[playerClass]
if not frameData then
return nil
end

-- Check if the current spec's resource matches what the Blizzard bar displays
-- If not, return nil to fall back to the custom bar
local currentResource = self:GetResource()
if currentResource ~= frameData.resource then
return nil
end

return frameData.frame
end

-- Returns the screen-space offset between the frame's center and the
-- average center of its visible children, plus the ratio of visual content
-- size to frame size. For frames like RogueComboPointBarFrame where the content
-- extends beyond the frame bounds (e.g. 7 combo points in a 5-CP-wide frame),
-- this lets us position and size the placeholder accurately.
-- Returns: offsetX, offsetY, widthRatio, heightRatio
function SecondaryResourceBarMixin:GetBlizzardFrameContentOffset(blizzardFrame)
local frameCX, frameCY = blizzardFrame:GetCenter()
if not frameCX or not frameCY then return 0, 0, 1, 1 end

local children = {blizzardFrame:GetChildren()}
if #children == 0 then return 0, 0, 1, 1 end

local sumCX, sumCY, count = 0, 0, 0
local minLeft, maxRight = math.huge, -math.huge
local minBottom, maxTop = math.huge, -math.huge

for _, child in ipairs(children) do
if child:IsShown() then
local cx, cy = child:GetCenter()
local left, right = child:GetLeft(), child:GetRight()
local bottom, top = child:GetBottom(), child:GetTop()
if cx and cy and left and right and bottom and top then
sumCX = sumCX + cx
sumCY = sumCY + cy
count = count + 1
minLeft = math.min(minLeft, left)
maxRight = math.max(maxRight, right)
minBottom = math.min(minBottom, bottom)
maxTop = math.max(maxTop, top)
end
end
end

if count == 0 then return 0, 0, 1, 1 end

local offsetX = (sumCX / count) - frameCX
local offsetY = (sumCY / count) - frameCY

-- Only return offset if significant (avoids floating point noise on centered frames)
if math.abs(offsetX) < 0.5 then offsetX = 0 end
if math.abs(offsetY) < 0.5 then offsetY = 0 end

-- Compute ratio of visual content size to frame size
local frameLeft, frameRight = blizzardFrame:GetLeft(), blizzardFrame:GetRight()
local frameBottom, frameTop = blizzardFrame:GetBottom(), blizzardFrame:GetTop()
local widthRatio, heightRatio = 1, 1

if frameLeft and frameRight and frameBottom and frameTop then
local frameScreenW = frameRight - frameLeft
local frameScreenH = frameTop - frameBottom
if frameScreenW > 0 then
local visualScreenW = maxRight - minLeft
local r = visualScreenW / frameScreenW
if math.abs(r - 1) > 0.05 then widthRatio = r end
end
if frameScreenH > 0 then
local visualScreenH = maxTop - minBottom
local r = visualScreenH / frameScreenH
if math.abs(r - 1) > 0.05 then heightRatio = r end
end
end

return offsetX, offsetY, widthRatio, heightRatio
end

-- Determine if bar should be visible based on visibility settings.
-- Uses the parent's ApplyVisibilitySettings to decide, then reads the result
-- from self.Frame:IsShown(). This avoids duplicating the parent's visibility logic.
function SecondaryResourceBarMixin:ShouldBeVisible(layoutName, inCombat)
if LEM:IsInEditMode() then
return true
end

addonTable.PowerBarMixin.ApplyVisibilitySettings(self, layoutName, inCombat)
local visible = self.Frame:IsShown()
self.Frame:Hide()
return visible
end

function SecondaryResourceBarMixin:UseBlizzardBarMode(layoutName, inCombat)
local data = self:GetData(layoutName)
if not data then return end

local blizzardFrame = self:GetBlizzardResourceFrame()
if not blizzardFrame then
-- No compatible Blizzard bar for current spec, fall back to custom bar
-- Hide the class's Blizzard bar if it exists (e.g., Chi bar for Brewmaster)
local classBlizzardFrame = self:GetBlizzardFrameForClass()
if classBlizzardFrame and data.useBlizzardBar and not InCombatLockdown() then
classBlizzardFrame:Hide()
end
-- Use the custom bar as if useBlizzardBar wasn't enabled
self.Frame:Show()
self.Frame:SetAlpha(1.0)
addonTable.PowerBarMixin.ApplyLayout(self, layoutName)
addonTable.PowerBarMixin.ApplyVisibilitySettings(self, layoutName, inCombat)
self:UpdateDisplay(true)
return
end

if data.useBlizzardBar then
-- One-time setup: intercept SetPoint and ClearAllPoints so that Blizzard's
-- layout system and other addons cannot move the frame while active.
-- Intercepts are installed once and never removed; behavior is controlled
-- by _useBlizzardBarActive (block/passthrough) and _allowBlizzardFramePositioning.
if not self._blizzardFrameIntercepted then
self._blizzardFrameIntercepted = true

local originalSetPoint = blizzardFrame.SetPoint
local originalClearAllPoints = blizzardFrame.ClearAllPoints
blizzardFrame.SetPoint = function(frame, ...)
if not self._useBlizzardBarActive or self._allowBlizzardFramePositioning then
originalSetPoint(frame, ...)
end
end
blizzardFrame.ClearAllPoints = function(frame, ...)
if not self._useBlizzardBarActive or self._allowBlizzardFramePositioning then
originalClearAllPoints(frame, ...)
end
end

-- Reparent to UIParent so the frame renders independently of
-- PlayerFrame (which addons like ElvUI may hide or reparent)
self._originalParent = blizzardFrame:GetParent()

-- OnShow hook: re-apply positioning when the frame is re-shown
-- (e.g. after Toggle User Interface with Alt+Z)
blizzardFrame:HookScript("OnShow", function()
if self._useBlizzardBarActive then
self:UseBlizzardBarMode()
end
end)
end

self._useBlizzardBarActive = true
blizzardFrame:SetParent(UIParent)

local shouldBeVisible = self:ShouldBeVisible(layoutName, inCombat)

-- Show the Blizzard frame and use SetAlpha for visibility control
blizzardFrame:Show()
blizzardFrame:SetAlpha(shouldBeVisible and 1 or 0)

-- Get the saved position
local point, relativeFrame, relativePoint, x, y = self:GetPoint(layoutName, true)

-- Apply scale
local scale = data.scale or self.defaults.scale or 1
blizzardFrame:SetScale(scale)

-- Compute content offset and size ratio for frames where visual content
-- isn't centered or extends beyond the frame (e.g. Rogue combo points with talents)
local contentOffsetX, contentOffsetY, widthRatio, heightRatio = self:GetBlizzardFrameContentOffset(blizzardFrame)

-- Position the Blizzard frame using the original methods via our flag
self._allowBlizzardFramePositioning = true

-- Size placeholder to match Blizzard bar's visual content
local blizzardWidth, blizzardHeight = blizzardFrame:GetSize()
if blizzardWidth and blizzardHeight and blizzardWidth > 0 and blizzardHeight > 0 then
self.Frame:SetSize(blizzardWidth * scale * widthRatio, blizzardHeight * scale * heightRatio)
end

-- Position placeholder at the saved location using the user's anchor point
self.Frame:ClearAllPoints()
self.Frame:SetPoint(point, relativeFrame, relativePoint, x, y)

-- Anchor Blizzard bar CENTER-to-CENTER with the placeholder, compensating
-- for content offset so the visual content aligns with the placeholder.
-- Using the placeholder as an anchor in both modes ensures Edit Mode and
-- normal mode always produce the same position regardless of anchor point.
blizzardFrame:ClearAllPoints()
blizzardFrame:SetPoint("CENTER", self.Frame, "CENTER", -contentOffsetX, -contentOffsetY)

if LEM:IsInEditMode() then
-- In Edit Mode, show the placeholder for drag-and-drop positioning
self.Frame:Show()
self.Frame:SetAlpha(0.5)
self.TextFrame:Hide()
else
-- In normal mode, hide the placeholder (it still serves as a position anchor)
self.Frame:Hide()
end

self._allowBlizzardFramePositioning = false

if blizzardFrame.SetIgnoreParentScale then
blizzardFrame:SetIgnoreParentScale(false)
end
else
-- Restore Blizzard bar to default behavior
self._useBlizzardBarActive = false

if self._blizzardFrameIntercepted and self._originalParent then
blizzardFrame:SetParent(self._originalParent)
end

blizzardFrame:ClearAllPoints()
blizzardFrame:SetPoint("TOP", PlayerFrame, "BOTTOM", 0, 16)
blizzardFrame:SetScale(1)

self:HideBlizzardSecondaryResource(layoutName)

-- Show our custom bar
self.Frame:Show()
self.Frame:SetAlpha(1.0)
self:ApplyVisibilitySettings(layoutName)
self:ApplyLayout(layoutName)
end
end

function SecondaryResourceBarMixin:ApplyLayout(layoutName, force)
local data = self:GetData(layoutName)

-- If using Blizzard bar, delegate to UseBlizzardBarMode which handles
-- both compatible and incompatible specs (falling back to custom bar)
if data and data.useBlizzardBar then
self:UseBlizzardBarMode(layoutName)
return
end

addonTable.PowerBarMixin.ApplyLayout(self, layoutName, force)
end

addonTable.SecondaryResourceBarMixin = SecondaryResourceBarMixin

addonTable.RegisteredBar = addonTable.RegisteredBar or {}
Expand All @@ -285,6 +549,7 @@ addonTable.RegisteredBar.SecondaryResourceBar = {
x = 0,
y = -40,
hideBlizzardSecondaryResourceUi = false,
useBlizzardBar = false,
hideManaOnRole = {},
showManaAsPercent = false,
showTicks = true,
Expand Down Expand Up @@ -334,6 +599,31 @@ addonTable.RegisteredBar.SecondaryResourceBar = {
bar:HideBlizzardSecondaryResource(layoutName)
end,
tooltip = L["HIDE_BLIZZARD_UI_SECONDARY_POWER_BAR_TOOLTIP"],
isEnabled = function(layoutName)
local data = SenseiClassResourceBarDB[dbName][layoutName]
return not (data and data.useBlizzardBar)
end,
},
{
parentId = L["CATEGORY_BAR_VISIBILITY"],
order = 106,
name = L["USE_BLIZZARD_BAR"],
kind = LEM.SettingType.Checkbox,
default = defaults.useBlizzardBar,
get = function(layoutName)
local data = SenseiClassResourceBarDB[dbName][layoutName]
if data and data.useBlizzardBar ~= nil then
return data.useBlizzardBar
else
return defaults.useBlizzardBar
end
end,
set = function(layoutName, value)
SenseiClassResourceBarDB[dbName][layoutName] = SenseiClassResourceBarDB[dbName][layoutName] or CopyTable(defaults)
SenseiClassResourceBarDB[dbName][layoutName].useBlizzardBar = value
bar:UseBlizzardBarMode(layoutName)
end,
tooltip = L["USE_BLIZZARD_BAR_TOOLTIP"],
},
{
parentId = L["CATEGORY_BAR_SETTINGS"],
Expand Down
12 changes: 12 additions & 0 deletions Constants.lua
Original file line number Diff line number Diff line change
Expand Up @@ -351,6 +351,18 @@ addonTable.tickedPowerTypes = {
["WHIRLWIND"] = true,
}

-- Map of Blizzard frames and the resource types they display
addonTable.blizzardResourceFrames = {
["DEATHKNIGHT"] = { frame = RuneFrame, resource = Enum.PowerType.Runes },
["DRUID"] = { frame = DruidComboPointBarFrame, resource = Enum.PowerType.ComboPoints },
["EVOKER"] = { frame = EssencePlayerFrame, resource = Enum.PowerType.Essence },
["MAGE"] = { frame = MageArcaneChargesFrame, resource = Enum.PowerType.ArcaneCharges },
["MONK"] = { frame = MonkHarmonyBarFrame, resource = Enum.PowerType.Chi },
["PALADIN"] = { frame = PaladinPowerBarFrame, resource = Enum.PowerType.HolyPower },
["ROGUE"] = { frame = RogueComboPointBarFrame, resource = Enum.PowerType.ComboPoints },
["WARLOCK"] = { frame = WarlockPowerFrame, resource = Enum.PowerType.SoulShards },
}

-- Power types that are fragmented (multiple independent segments)
addonTable.fragmentedPowerTypes = {
[Enum.PowerType.ComboPoints] = true,
Expand Down
2 changes: 2 additions & 0 deletions Locales/enUS.lua
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,8 @@ local baseLocale = {
["HIDE_BLIZZARD_UI_SECONDARY_POWER_BAR_TOOLTIP"] = "Hides the default Blizzard secondary resource UI (e.g. Rune Frame for Death Knights)",
["ENABLE_HP_BAR_MOUSE_INTERACTION"] = "Clickable Health Bar",
["ENABLE_HP_BAR_MOUSE_INTERACTION_TOOLTIP"] = "Enable default Player Frame click behavior on the Health Bar.",
["USE_BLIZZARD_BAR"] = "Use Blizzard Bar",
["USE_BLIZZARD_BAR_TOOLTIP"] = "Replace the custom bar with Blizzard's default secondary resource bar, preserving all animations and styling. The bar will be positioned according to your settings.",

-- Position & Size category - Edit Mode
["CATEGORY_POSITION_AND_SIZE"] = "Position & Size",
Expand Down
2 changes: 2 additions & 0 deletions Locales/koKR.lua
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,8 @@ local baseLocale = {
["HIDE_BLIZZARD_UI_SECONDARY_POWER_BAR_TOOLTIP"] = "블리자드 기본 보조 자원 UI를 숨깁니다 (예: 죽음의 기사 룬 프레임)",
["ENABLE_HP_BAR_MOUSE_INTERACTION"] = "생명력 바 마우스 상호작용",
["ENABLE_HP_BAR_MOUSE_INTERACTION_TOOLTIP"] = "생명력 바에서 기본 플레이어 프레임 클릭 동작을 활성화합니다.",
["USE_BLIZZARD_BAR"] = "블리자드 바 사용",
["USE_BLIZZARD_BAR_TOOLTIP"] = "사용자 정의 바를 블리자드 기본 보조 자원 바로 대체합니다. 모든 애니메이션과 스타일이 유지됩니다. 바는 설정에 따라 배치됩니다.",

-- Position & Size category - Edit Mode
["CATEGORY_POSITION_AND_SIZE"] = "위치 및 크기",
Expand Down
2 changes: 2 additions & 0 deletions Locales/ruRU.lua
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,8 @@ local baseLocale = {
["HIDE_BLIZZARD_UI_SECONDARY_POWER_BAR_TOOLTIP"] = "Скрывает стандартный интерфейс вторичных ресурсов (напр. руны ДК)",
["ENABLE_HP_BAR_MOUSE_INTERACTION"] = "Кликабельная панель здоровья",
["ENABLE_HP_BAR_MOUSE_INTERACTION_TOOLTIP"] = "Включает стандартное взаимодействие с рамкой персонажа при нажатии на панель здоровья.",
["USE_BLIZZARD_BAR"] = "Использовать панель Blizzard",
["USE_BLIZZARD_BAR_TOOLTIP"] = "Заменяет пользовательскую панель на стандартную панель вторичного ресурса Blizzard, сохраняя все анимации и стили. Панель будет расположена в соответствии с вашими настройками.",

-- Position & Size category - Edit Mode
["CATEGORY_POSITION_AND_SIZE"] = "Положение и размер",
Expand Down
Loading