Add files via upload
This commit is contained in:
@@ -0,0 +1,434 @@
|
||||
BNP = BNP or {}
|
||||
|
||||
local MIN_SCALE = 0.50
|
||||
local MAX_SCALE = 1.50
|
||||
local DEFAULT_SCALE = 1.00
|
||||
|
||||
local function Clamp(value)
|
||||
if value < MIN_SCALE then return MIN_SCALE end
|
||||
if value > MAX_SCALE then return MAX_SCALE end
|
||||
return value
|
||||
end
|
||||
|
||||
function BNP:GetNameplateScale()
|
||||
BNP_DB = BNP_DB or {}
|
||||
return Clamp(tonumber(BNP_DB.nameplateScale) or DEFAULT_SCALE)
|
||||
end
|
||||
|
||||
local function EffectiveScale()
|
||||
local uiScale = 1.0
|
||||
if UIParent and UIParent.GetScale then
|
||||
uiScale = UIParent:GetScale() or 1.0
|
||||
end
|
||||
return uiScale * BNP:GetNameplateScale()
|
||||
end
|
||||
|
||||
local function CaptureAnchorState(object, plate, wrapper)
|
||||
if not object or not object.GetPoint then return nil end
|
||||
|
||||
local state = {
|
||||
object = object,
|
||||
points = {},
|
||||
}
|
||||
|
||||
local count = 1
|
||||
if object.GetNumPoints then
|
||||
count = object:GetNumPoints() or 1
|
||||
if count < 1 then count = 1 end
|
||||
end
|
||||
|
||||
local i
|
||||
for i = 1, count do
|
||||
local point, relativeTo, relativePoint, x, y = object:GetPoint(i)
|
||||
if point then
|
||||
if relativeTo == plate then
|
||||
relativeTo = wrapper
|
||||
end
|
||||
|
||||
state.points[table.getn(state.points) + 1] = {
|
||||
point = point,
|
||||
relativeTo = relativeTo or wrapper,
|
||||
relativePoint = relativePoint or point,
|
||||
x = x or 0,
|
||||
y = y or 0,
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
return state
|
||||
end
|
||||
|
||||
local function AnchorStateMatches(state)
|
||||
if not state or not state.object or not state.object.GetPoint then return false end
|
||||
|
||||
local object = state.object
|
||||
local expectedCount = table.getn(state.points)
|
||||
local currentCount = expectedCount
|
||||
|
||||
if object.GetNumPoints then
|
||||
currentCount = object:GetNumPoints() or 0
|
||||
end
|
||||
|
||||
if currentCount ~= expectedCount then
|
||||
return false
|
||||
end
|
||||
|
||||
local i
|
||||
for i = 1, expectedCount do
|
||||
local expected = state.points[i]
|
||||
local point, relativeTo, relativePoint, x, y = object:GetPoint(i)
|
||||
|
||||
if point ~= expected.point
|
||||
or relativeTo ~= expected.relativeTo
|
||||
or relativePoint ~= expected.relativePoint
|
||||
or math.abs((x or 0) - expected.x) > 0.01
|
||||
or math.abs((y or 0) - expected.y) > 0.01 then
|
||||
return false
|
||||
end
|
||||
end
|
||||
|
||||
return true
|
||||
end
|
||||
|
||||
local function RestoreAnchorState(state, wrapper)
|
||||
if not state or not state.object then return end
|
||||
|
||||
local object = state.object
|
||||
|
||||
if object.GetParent and object.SetParent and object:GetParent() ~= wrapper then
|
||||
object:SetParent(wrapper)
|
||||
end
|
||||
|
||||
-- Do nothing in the common case. Blizzard normally only rewrites a subset
|
||||
-- of nameplate anchors while moving, most notably the level FontString.
|
||||
if AnchorStateMatches(state) then return end
|
||||
|
||||
if not object.ClearAllPoints or not object.SetPoint then return end
|
||||
|
||||
object:ClearAllPoints()
|
||||
|
||||
local i
|
||||
for i = 1, table.getn(state.points) do
|
||||
local p = state.points[i]
|
||||
object:SetPoint(
|
||||
p.point,
|
||||
p.relativeTo or wrapper,
|
||||
p.relativePoint or p.point,
|
||||
p.x,
|
||||
p.y
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
local function SetHoverGlow(plate, shown)
|
||||
if not plate or not plate.glow then return end
|
||||
|
||||
if shown then
|
||||
if plate.glow.Show then
|
||||
plate.glow:Show()
|
||||
end
|
||||
else
|
||||
if plate.glow.Hide then
|
||||
plate.glow:Hide()
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- SuperWoW exposes the attached unit GUID through nameplate:GetName(1).
|
||||
-- Its SetMouseoverUnit(unit) API is specifically intended for custom unit
|
||||
-- frames: set it on OnEnter and clear it on OnLeave. BNP's scale/Y-offset
|
||||
-- wrapper owns the mouse hit area, so without this bridge the client never
|
||||
-- receives a real "mouseover" unit even though clicks are forwarded to the
|
||||
-- original Blizzard nameplate.
|
||||
local function GetPlateMouseoverUnit(plate)
|
||||
if not plate or not plate.GetName then return nil end
|
||||
|
||||
local ok, unit = pcall(function() return plate:GetName(1) end)
|
||||
if not ok or not unit then return nil end
|
||||
|
||||
if UnitExists then
|
||||
local existsOK, exists = pcall(UnitExists, unit)
|
||||
if existsOK and not exists then return nil end
|
||||
end
|
||||
|
||||
return unit
|
||||
end
|
||||
|
||||
local function ShowPlateTooltip(wrapper, unit)
|
||||
if not wrapper or not unit or not GameTooltip or not GameTooltip.SetUnit then return end
|
||||
|
||||
-- SetMouseoverUnit() has already made "mouseover" a valid unit. Prefer
|
||||
-- that token so tooltip addons see the same unit surface as mouseover macros.
|
||||
if GameTooltip_SetDefaultAnchor then
|
||||
GameTooltip_SetDefaultAnchor(GameTooltip, wrapper)
|
||||
elseif GameTooltip.SetOwner then
|
||||
GameTooltip:SetOwner(wrapper, "ANCHOR_CURSOR")
|
||||
end
|
||||
|
||||
local ok = pcall(function() GameTooltip:SetUnit("mouseover") end)
|
||||
if not ok then
|
||||
pcall(function() GameTooltip:SetUnit(unit) end)
|
||||
end
|
||||
|
||||
if GameTooltip.Show then GameTooltip:Show() end
|
||||
end
|
||||
|
||||
local function SetWrapperMouseover(plate, wrapper)
|
||||
local unit = GetPlateMouseoverUnit(plate)
|
||||
if not unit then return end
|
||||
|
||||
-- Keep ownership so a late OnLeave from an overlapping/recycled plate cannot
|
||||
-- clear the mouseover unit that a newer wrapper has just established.
|
||||
BNP.BNPMouseoverOwner = wrapper
|
||||
wrapper.BNPMouseoverUnit = unit
|
||||
|
||||
if SetMouseoverUnit then
|
||||
local ok = pcall(SetMouseoverUnit, unit)
|
||||
if ok then
|
||||
ShowPlateTooltip(wrapper, unit)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local function ClearWrapperMouseover(wrapper)
|
||||
if BNP.BNPMouseoverOwner ~= wrapper then return end
|
||||
|
||||
BNP.BNPMouseoverOwner = nil
|
||||
wrapper.BNPMouseoverUnit = nil
|
||||
|
||||
if SetMouseoverUnit then
|
||||
pcall(SetMouseoverUnit)
|
||||
end
|
||||
|
||||
if GameTooltip and GameTooltip.Hide then
|
||||
GameTooltip:Hide()
|
||||
end
|
||||
end
|
||||
|
||||
local function SetYOffsetMouseMode(plate, wrapper, offset)
|
||||
if not plate or not wrapper then return end
|
||||
|
||||
-- Always use the visual wrapper as BNP's interactive frame, including at
|
||||
-- Y Offset 0. Previously offset 0 used Blizzard's native Button while
|
||||
-- positive offsets used BNPScaleWrapper. That split mouse path caused the
|
||||
-- original Blizzard mouseover glow to disappear at offset 0.
|
||||
--
|
||||
-- Keeping clicks, hit geometry and hover feedback on the same frame also
|
||||
-- guarantees that the clickable area follows the visible plate at every
|
||||
-- configured Y offset.
|
||||
if plate.EnableMouse then
|
||||
plate:EnableMouse(false)
|
||||
end
|
||||
|
||||
if wrapper.EnableMouse then
|
||||
wrapper:EnableMouse(true)
|
||||
end
|
||||
|
||||
if wrapper.RegisterForClicks then
|
||||
-- Preserve the classic Blizzard nameplate mouse behavior:
|
||||
-- left click selects the unit, right click selects it and starts auto attack.
|
||||
wrapper:RegisterForClicks("LeftButtonUp", "RightButtonUp")
|
||||
end
|
||||
|
||||
if not wrapper.BNPClickForwardInstalled then
|
||||
wrapper:SetScript("OnClick", function()
|
||||
-- The wrapper exists only because BNP visually offsets/scales the original
|
||||
-- Blizzard plate. Keep Blizzard's native nameplate click semantics by
|
||||
-- forwarding the ACTUAL mouse button to the original Button.
|
||||
--
|
||||
-- parent:Click() without an argument always simulates LeftButton. That was
|
||||
-- the reason right click turned into a plain target-select while BNP was
|
||||
-- enabled. Do not call AttackTarget() manually here: it toggles auto attack
|
||||
-- and is not equivalent to Blizzard's native nameplate right-click path.
|
||||
local mouseButton = arg1 or "LeftButton"
|
||||
local current = this or wrapper
|
||||
local parent = current and current.plate or plate
|
||||
if parent and parent:IsShown() and parent.Click then
|
||||
parent:Click(mouseButton)
|
||||
end
|
||||
end)
|
||||
wrapper.BNPClickForwardInstalled = true
|
||||
end
|
||||
|
||||
if not wrapper.BNPMouseoverFeedbackInstalled then
|
||||
wrapper:SetScript("OnEnter", function()
|
||||
local current = this or wrapper
|
||||
local parent = current and current.plate or plate
|
||||
SetHoverGlow(parent, true)
|
||||
SetWrapperMouseover(parent, current)
|
||||
end)
|
||||
|
||||
wrapper:SetScript("OnLeave", function()
|
||||
local current = this or wrapper
|
||||
local parent = current and current.plate or plate
|
||||
SetHoverGlow(parent, false)
|
||||
ClearWrapperMouseover(current)
|
||||
end)
|
||||
|
||||
-- A projected nameplate can disappear/recycle while the cursor is still
|
||||
-- inside its old bounds. Clear SuperWoW's synthetic mouseover in that case
|
||||
-- as well so "mouseover" can never remain stuck on a vanished unit.
|
||||
wrapper:SetScript("OnHide", function()
|
||||
local current = this or wrapper
|
||||
ClearWrapperMouseover(current)
|
||||
end)
|
||||
|
||||
wrapper.BNPMouseoverFeedbackInstalled = true
|
||||
end
|
||||
end
|
||||
|
||||
local function ApplyWrapperOffset(plate, wrapper)
|
||||
if not plate or not wrapper then return end
|
||||
|
||||
local offset = 0
|
||||
if BNP.GetNameplateYOffset then
|
||||
offset = BNP:GetNameplateYOffset()
|
||||
end
|
||||
|
||||
-- Move only the child visual container. The WorldFrame-projected Blizzard
|
||||
-- nameplate itself keeps its original anchor and can continue to be
|
||||
-- repositioned by the client every frame.
|
||||
wrapper:ClearAllPoints()
|
||||
wrapper:SetPoint("TOPLEFT", plate, "TOPLEFT", 0, offset)
|
||||
wrapper:SetPoint("BOTTOMRIGHT", plate, "BOTTOMRIGHT", 0, offset)
|
||||
|
||||
SetYOffsetMouseMode(plate, wrapper, offset)
|
||||
end
|
||||
|
||||
local function EnsureScaleWrapper(plate)
|
||||
if not plate or plate.BNPScaleWrapper then return end
|
||||
|
||||
local originalWidth = plate:GetWidth()
|
||||
local originalHeight = plate:GetHeight()
|
||||
|
||||
local wrapper = CreateFrame("Button", nil, plate)
|
||||
wrapper:SetAllPoints(plate)
|
||||
wrapper.plate = plate
|
||||
wrapper.originalWidth = originalWidth
|
||||
wrapper.originalHeight = originalHeight
|
||||
wrapper.BNPAnchorStates = {}
|
||||
wrapper:EnableMouse(false)
|
||||
|
||||
plate.BNPScaleWrapper = wrapper
|
||||
|
||||
-- Capture the ORIGINAL Blizzard anchors before changing parentage.
|
||||
-- Those stored anchors become BNP's authoritative visual layout whenever
|
||||
-- Y-offset is enabled.
|
||||
if plate.healthbar then
|
||||
local state = CaptureAnchorState(plate.healthbar, plate, wrapper)
|
||||
if state then
|
||||
table.insert(wrapper.BNPAnchorStates, state)
|
||||
end
|
||||
|
||||
plate.healthbar:SetParent(wrapper)
|
||||
RestoreAnchorState(state, wrapper)
|
||||
plate.healthbar:SetFrameLevel(1)
|
||||
end
|
||||
|
||||
local regions = { plate:GetRegions() }
|
||||
local i, object
|
||||
for i, object in pairs(regions) do
|
||||
if object and object.SetParent then
|
||||
local state = CaptureAnchorState(object, plate, wrapper)
|
||||
if state then
|
||||
table.insert(wrapper.BNPAnchorStates, state)
|
||||
end
|
||||
|
||||
object:SetParent(wrapper)
|
||||
RestoreAnchorState(state, wrapper)
|
||||
end
|
||||
end
|
||||
|
||||
ApplyWrapperOffset(plate, wrapper)
|
||||
end
|
||||
|
||||
function BNP:ApplyNameplateScale(plate)
|
||||
if not plate then return end
|
||||
EnsureScaleWrapper(plate)
|
||||
|
||||
local wrapper = plate.BNPScaleWrapper
|
||||
if not wrapper then return end
|
||||
|
||||
local scale = EffectiveScale()
|
||||
wrapper:SetScale(scale)
|
||||
ApplyWrapperOffset(plate, wrapper)
|
||||
|
||||
-- ShaguTweaks also adjusts the original frame bounds so Blizzard's world
|
||||
-- positioning remains correct while the child visuals honor UI scale.
|
||||
plate:SetWidth(wrapper.originalWidth * scale)
|
||||
plate:SetHeight(wrapper.originalHeight * scale)
|
||||
end
|
||||
|
||||
function BNP:ApplyNameplateScaleAll()
|
||||
local plate
|
||||
for plate in pairs(self.plates) do
|
||||
self:ApplyNameplateScale(plate)
|
||||
end
|
||||
end
|
||||
|
||||
function BNP:ApplyNameplateYOffsetAll()
|
||||
local plate
|
||||
for plate in pairs(self.plates or {}) do
|
||||
if plate then
|
||||
self:ApplyNameplateScale(plate)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function BNP:MaintainNameplateYOffset(plate)
|
||||
if not plate or not plate:IsShown() then return end
|
||||
if not self.GetNameplateYOffset or self:GetNameplateYOffset() <= 0 then return end
|
||||
|
||||
EnsureScaleWrapper(plate)
|
||||
|
||||
local wrapper = plate.BNPScaleWrapper
|
||||
if not wrapper then return end
|
||||
|
||||
-- Never rediscover regions through plate:GetRegions() here. Once BNP moves
|
||||
-- Blizzard regions into the wrapper they are no longer guaranteed to be
|
||||
-- returned by plate:GetRegions(). Keep and maintain the original region
|
||||
-- objects captured during wrapper creation instead.
|
||||
local states = wrapper.BNPAnchorStates or {}
|
||||
local i
|
||||
for i = 1, table.getn(states) do
|
||||
RestoreAnchorState(states[i], wrapper)
|
||||
end
|
||||
|
||||
ApplyWrapperOffset(plate, wrapper)
|
||||
end
|
||||
|
||||
function BNP:SetNameplateScale(value)
|
||||
value = tonumber(value)
|
||||
if not value then
|
||||
self:Print("Usage: /bnp scale 0.5-1.5")
|
||||
return
|
||||
end
|
||||
|
||||
value = Clamp(value)
|
||||
BNP_DB = BNP_DB or {}
|
||||
BNP_DB.nameplateScale = value
|
||||
self:ApplyNameplateScaleAll()
|
||||
|
||||
self:Print("Nameplate scale multiplier set to " .. tostring(value) .. ".")
|
||||
end
|
||||
|
||||
function BNP:ResetNameplateScale()
|
||||
BNP_DB = BNP_DB or {}
|
||||
BNP_DB.nameplateScale = DEFAULT_SCALE
|
||||
self:ApplyNameplateScaleAll()
|
||||
self:Print("Nameplate scale multiplier reset to 1.0 (Shagu/UI scale behavior).")
|
||||
end
|
||||
|
||||
if BNP.libnameplate then
|
||||
table.insert(BNP.libnameplate.OnInit, function(plate)
|
||||
local current = plate or this
|
||||
if not current then return end
|
||||
BNP:ApplyNameplateScale(current)
|
||||
end)
|
||||
|
||||
table.insert(BNP.libnameplate.OnShow, function(plate)
|
||||
local current = plate or this
|
||||
if not current then return end
|
||||
BNP:ApplyNameplateScale(current)
|
||||
end)
|
||||
end
|
||||
Reference in New Issue
Block a user