forked from Dusk/ShaguTweaks-ClassicAPI
352 lines
15 KiB
Lua
352 lines
15 KiB
Lua
local _G = ShaguTweaks.GetGlobalEnv()
|
|
local T = ShaguTweaks.T
|
|
local API = ShaguTweaks.API
|
|
|
|
local module = ShaguTweaks:register({
|
|
title = T["Standalone Player Frame"],
|
|
description = T["Separates the existing player portrait, name, health and power bars. Hold Ctrl+Shift+Alt to drag each piece; right-click with those keys held to reset it. Big Health also enlarges the power bar."],
|
|
expansions = { ["vanilla"] = true },
|
|
category = T["Unit Frames"],
|
|
enabled = nil,
|
|
})
|
|
|
|
local Unlocked = ShaguTweaks.API.AreMoveModifiersDown
|
|
|
|
local function Position(holder, point)
|
|
holder:ClearAllPoints()
|
|
holder:SetPoint("TOPLEFT", UIParent, "BOTTOMLEFT", point.x, point.y)
|
|
end
|
|
|
|
local function StopDrag(holder)
|
|
if not holder.dragging then return end
|
|
holder:StopMovingOrSizing()
|
|
holder:SetUserPlaced(false)
|
|
holder.dragging = nil
|
|
holder.ignoreClick = true
|
|
local x, y = holder:GetLeft(), holder:GetTop()
|
|
if x and y then
|
|
holder.positions[holder.key] = { x = x, y = y }
|
|
Position(holder, holder.positions[holder.key])
|
|
end
|
|
end
|
|
|
|
local function MouseDown()
|
|
local holder = this.standaloneHolder
|
|
holder.ignoreClick = nil
|
|
if Unlocked() and arg1 == "RightButton" then
|
|
StopDrag(holder)
|
|
holder.positions[holder.key] = nil
|
|
Position(holder, holder.defaultPosition)
|
|
holder.ignoreClick = true
|
|
elseif not Unlocked() and this.standaloneMouseDown then
|
|
this.standaloneMouseDown()
|
|
end
|
|
end
|
|
|
|
local function DragStart()
|
|
if not Unlocked() then return end
|
|
local holder = this.standaloneHolder
|
|
holder.dragging = true
|
|
holder:StartMoving()
|
|
end
|
|
|
|
local function DragStop()
|
|
StopDrag(this.standaloneHolder)
|
|
end
|
|
|
|
local function UnitClick()
|
|
local holder = this.standaloneHolder
|
|
if holder.ignoreClick or holder.dragging or Unlocked() then return end
|
|
if arg1 == "RightButton" and not SpellIsTargeting() then
|
|
ToggleDropDownMenu(1, nil, PlayerFrameDropDown, holder:GetName(), 0, 0)
|
|
elseif this.standaloneMouseUp then
|
|
this.standaloneMouseUp()
|
|
else
|
|
PlayerFrame_OnClick(arg1)
|
|
end
|
|
end
|
|
|
|
local function BindHandle(handle, holder)
|
|
handle.standaloneHolder = holder
|
|
handle.standaloneMouseDown = handle:GetScript("OnMouseDown")
|
|
handle:RegisterForDrag("LeftButton")
|
|
handle:EnableMouse(true)
|
|
handle:SetScript("OnMouseDown", MouseDown)
|
|
handle:SetScript("OnDragStart", DragStart)
|
|
handle:SetScript("OnDragStop", DragStop)
|
|
if handle:GetObjectType() == "Button" then
|
|
handle:RegisterForClicks("LeftButtonUp", "RightButtonUp")
|
|
handle:SetScript("OnClick", UnitClick)
|
|
else
|
|
handle.standaloneMouseUp = handle:GetScript("OnMouseUp")
|
|
handle:SetScript("OnMouseUp", UnitClick)
|
|
end
|
|
end
|
|
|
|
local function MoveRegion(region, holder, x, y)
|
|
if not region then return end
|
|
region:SetParent(holder)
|
|
region:ClearAllPoints()
|
|
region:SetPoint("TOPLEFT", holder, "TOPLEFT", x, y)
|
|
end
|
|
|
|
module.enable = function(self)
|
|
if self.scheduled then return end
|
|
self.scheduled = true
|
|
local function Setup()
|
|
if self.installed then return end
|
|
if not PlayerFrame or not PlayerPortrait or not PlayerFrameTexture
|
|
or not PlayerFrameHealthBar or not PlayerFrameManaBar then return end
|
|
if not PlayerFrameHealthBar:GetLeft() or not PlayerFrameHealthBar:GetTop()
|
|
or not PlayerPortrait:GetLeft() or not PlayerPortrait:GetTop() then return end
|
|
self.installed = true
|
|
ShaguTweaks_config.StandalonePlayerFrame = ShaguTweaks_config.StandalonePlayerFrame or {}
|
|
local positions = ShaguTweaks_config.StandalonePlayerFrame
|
|
local scale = PlayerFrame:GetEffectiveScale() / UIParent:GetEffectiveScale()
|
|
local big = ShaguTweaks_config[T["Unit Frame Big Health for Player"]] == 1
|
|
local width = PlayerFrameHealthBar:GetWidth()
|
|
local healthHeight = PlayerFrameHealthBar:GetHeight()
|
|
local manaHeight = big and healthHeight or PlayerFrameManaBar:GetHeight()
|
|
local left = PlayerFrameHealthBar:GetLeft() - 4
|
|
local top = PlayerFrameHealthBar:GetTop() + 3
|
|
local portraitLeft, portraitTop = PlayerPortrait:GetLeft() - 8, PlayerPortrait:GetTop() + 4
|
|
|
|
local function Holder(key, w, h, x, y)
|
|
local holder = CreateFrame("Button", "ShaguTweaksPlayer" .. key, UIParent)
|
|
holder:SetScale(scale)
|
|
holder:SetWidth(w)
|
|
holder:SetHeight(h)
|
|
holder:SetMovable(true)
|
|
holder:SetClampedToScreen(true)
|
|
holder:SetFrameStrata(PlayerFrame:GetFrameStrata())
|
|
holder:SetFrameLevel(PlayerFrame:GetFrameLevel() + 3)
|
|
holder.unit = "player"
|
|
holder.key, holder.positions = key, positions
|
|
holder.defaultPosition = { x = x, y = y }
|
|
Position(holder, positions[key] or holder.defaultPosition)
|
|
BindHandle(holder, holder)
|
|
holder:SetScript("OnHide", function() StopDrag(this) end)
|
|
holder:SetScript("OnReceiveDrag", PlayerFrame_OnReceiveDrag)
|
|
if UnitFrame_OnEnter and UnitFrame_OnLeave and UnitFrame_OnUpdate then
|
|
holder:SetScript("OnEnter", function()
|
|
if Unlocked() then return end
|
|
UnitFrame_OnEnter()
|
|
if this.updateTooltip then this:SetScript("OnUpdate", UnitFrame_OnUpdate) end
|
|
end)
|
|
holder:SetScript("OnLeave", function()
|
|
UnitFrame_OnLeave()
|
|
this:SetScript("OnUpdate", nil)
|
|
end)
|
|
end
|
|
return holder
|
|
end
|
|
|
|
local portrait = Holder("Portrait", 80, 80, portraitLeft, portraitTop)
|
|
local name = Holder("Name", width + 8, 23, left, top + 25)
|
|
local health = Holder("Health", width + 8, healthHeight + 6, left, top)
|
|
local mana = Holder("Power", width + 8, manaHeight + 6, left, top - healthHeight - 8)
|
|
ShaguTweaks.StandalonePlayerFrame = { portrait = portrait, name = name, health = health, power = mana }
|
|
|
|
-- Split the native artwork before changing its source texture's anchors.
|
|
-- Preserve its UV orientation (the player's stock artwork is mirrored).
|
|
local art = PlayerFrameTexture
|
|
local path = art:GetTexture()
|
|
local u, v, unused, bottom, right = art:GetTexCoord()
|
|
local artWidth, artHeight = PlayerFrame:GetWidth(), PlayerFrame:GetHeight()
|
|
local r, g, b, a = art:GetVertexColor()
|
|
local function Crop(holder, x, y, w, h)
|
|
local texture = holder:CreateTexture(nil, "OVERLAY")
|
|
texture:SetTexture(path)
|
|
texture:SetTexCoord(u + (right-u)*x/artWidth, u + (right-u)*(x+w)/artWidth,
|
|
v + (bottom-v)*y/artHeight, v + (bottom-v)*(y+h)/artHeight)
|
|
texture:SetAllPoints(holder)
|
|
texture:SetVertexColor(r, g, b, a)
|
|
return texture
|
|
end
|
|
-- Keep the native level badge on the left. Complete the right side from
|
|
-- the clean upper-left quadrant, leaving space for the live resting icon
|
|
-- without mirroring a second level badge into the artwork. The native
|
|
-- resting/combat icon shares the original left badge's position.
|
|
portrait.art = Crop(portrait, 34, 8, 40, 72)
|
|
portrait.artRight = Crop(portrait, 74, 8, -40, 36)
|
|
portrait.artBottomRight = Crop(portrait, 74, 44, -40, -36)
|
|
local function Piece(texture, x, y, height)
|
|
texture:ClearAllPoints()
|
|
texture:SetPoint("TOPLEFT", portrait, "TOPLEFT", x, y)
|
|
texture:SetWidth(40)
|
|
texture:SetHeight(height)
|
|
end
|
|
Piece(portrait.art, 0, 0, 72)
|
|
Piece(portrait.artRight, 40, 0, 36)
|
|
Piece(portrait.artBottomRight, 40, -36, 36)
|
|
|
|
-- The native lower-left quadrant includes a dark rectangular shadow
|
|
-- beside the level badge. Use the same clean quadrant as the lower right,
|
|
-- then restore only the badge circle, not its surrounding shadow.
|
|
portrait.badge = Crop(portrait, 37, 50, 32, 32)
|
|
portrait.badge:ClearAllPoints()
|
|
portrait.badge:SetPoint("TOPLEFT", portrait, "TOPLEFT", 3, -42)
|
|
portrait.badge:SetWidth(32)
|
|
portrait.badge:SetHeight(32)
|
|
if type(portrait.badge.SetMask) == "function" then
|
|
portrait.art:SetTexCoord(u + (right-u)*34/artWidth, u + (right-u)*74/artWidth,
|
|
v + (bottom-v)*8/artHeight, v + (bottom-v)*44/artHeight)
|
|
Piece(portrait.art, 0, 0, 36)
|
|
portrait.artBottomLeft = Crop(portrait, 34, 44, 40, -36)
|
|
Piece(portrait.artBottomLeft, 0, -36, 36)
|
|
portrait.badge:SetMask("Interface\\CharacterFrame\\TempPortraitAlphaMask")
|
|
-- Keep the badge above every ring piece but below the number/state icons.
|
|
portrait.badgeOverlay = CreateFrame("Frame", nil, portrait)
|
|
portrait.badgeOverlay:SetAllPoints(portrait)
|
|
portrait.badgeOverlay:SetFrameLevel(portrait:GetFrameLevel() + 1)
|
|
portrait.badge:SetParent(portrait.badgeOverlay)
|
|
else
|
|
-- Older clients retain the original left half and its native badge.
|
|
portrait.badge:Hide()
|
|
end
|
|
|
|
-- The combined frame texture has a portrait arc at the left ends of its
|
|
-- bars. A separate Blizzard nine-slice edge gives clean rounded corners
|
|
-- at both normal and Big Health heights, without clipping the bar fill.
|
|
local border = {
|
|
edgeFile = "Interface\\Tooltips\\UI-Tooltip-Border",
|
|
edgeSize = 12, tile = true, tileSize = 8,
|
|
insets = { left = 3, right = 3, top = 3, bottom = 3 },
|
|
}
|
|
local color = ShaguTweaks.UIBorder
|
|
for _, holder in ipairs({ name, health, mana }) do
|
|
holder:SetBackdrop(border)
|
|
holder:SetBackdropBorderColor(color and color.r or 1, color and color.g or 1,
|
|
color and color.b or 1, 1)
|
|
end
|
|
|
|
-- Keep status/role indicators with the portrait. Their native event
|
|
-- handlers and parent visibility continue running unchanged.
|
|
local indicators = { "PlayerHitIndicator", "PlayerPVPIcon", "PlayerPVPIconHitArea",
|
|
"PlayerLeaderIcon", "PlayerMasterIcon", "PlayerRestIcon", "PlayerAttackIcon",
|
|
"PlayerAttackBackground", "PlayerStatusGlow", "PlayerFrameGroupIndicator" }
|
|
local indicatorPoints = {}
|
|
for i = 1, table.getn(indicators) do
|
|
local region = _G[indicators[i]]
|
|
if region and region:GetLeft() and region:GetTop() then
|
|
indicatorPoints[i] = { region:GetLeft() - portraitLeft, region:GetTop() - portraitTop }
|
|
end
|
|
end
|
|
-- Snapshot first: some indicators are anchored to other indicators.
|
|
for i = 1, table.getn(indicators) do
|
|
local point = indicatorPoints[i]
|
|
if point then MoveRegion(_G[indicators[i]], portrait, point[1], point[2]) end
|
|
end
|
|
MoveRegion(PlayerPortrait, portrait, 8, -4)
|
|
if _G.PlayerLevelText then
|
|
-- Put the native text above the ring and portrait, centered over the
|
|
-- native left-hand badge. The stock level-update code keeps owning it.
|
|
local levelX, levelY = 19, -58
|
|
if PlayerLevelText.GetCenter then
|
|
local x, y = PlayerLevelText:GetCenter()
|
|
if x and y then levelX, levelY = x - portraitLeft, y - portraitTop end
|
|
end
|
|
portrait.levelOverlay = CreateFrame("Frame", nil, portrait)
|
|
portrait.levelOverlay:SetAllPoints(portrait)
|
|
portrait.levelOverlay:SetFrameLevel(portrait:GetFrameLevel() + 5)
|
|
PlayerLevelText:SetParent(portrait.levelOverlay)
|
|
PlayerLevelText:ClearAllPoints()
|
|
PlayerLevelText:SetPoint("CENTER", portrait.levelOverlay, "TOPLEFT", levelX, levelY)
|
|
PlayerLevelText:SetDrawLayer("BACKGROUND")
|
|
PlayerLevelText:SetAlpha(1)
|
|
PlayerLevelText:SetText(UnitLevel("player"))
|
|
PlayerLevelText:Show()
|
|
-- Keep the level above the ring but BELOW its native state icons.
|
|
-- Their existing Show/Hide logic then covers the number while resting
|
|
-- or in combat and reveals it again without adding another event loop.
|
|
for _, key in ipairs({ "PlayerRestIcon", "PlayerAttackIcon", "PlayerAttackBackground", "PlayerStatusGlow" }) do
|
|
local indicator = _G[key]
|
|
if indicator then indicator:SetParent(portrait.levelOverlay) end
|
|
end
|
|
end
|
|
local playerName = PlayerFrame.name or _G.PlayerName
|
|
if playerName then
|
|
playerName:SetParent(name)
|
|
playerName:ClearAllPoints()
|
|
playerName:SetPoint("CENTER", name, "CENTER", 0, 0)
|
|
end
|
|
MoveRegion(_G.PlayerFrameNameBackground, name, 4, -2)
|
|
-- Big Health deliberately hides the stock name background. Keep its
|
|
-- color source for that module, but draw a separate class strip here.
|
|
name.classBackground = name:CreateTexture(nil, "BACKGROUND")
|
|
name.classBackground:SetPoint("TOPLEFT", name, "TOPLEFT", 4, -3)
|
|
name.classBackground:SetPoint("BOTTOMRIGHT", name, "BOTTOMRIGHT", -4, 3)
|
|
local healthTexture = PlayerFrameHealthBar:GetStatusBarTexture()
|
|
name.classBackground:SetTexture(healthTexture and healthTexture:GetTexture()
|
|
or "Interface\\TargetingFrame\\UI-StatusBar")
|
|
local _, class = UnitClass("player")
|
|
local palette = ShaguTweaks.ModernClassColors or RAID_CLASS_COLORS
|
|
local classColor = palette and palette[class]
|
|
local cr, cg, cb = PlayerFrameHealthBar:GetStatusBarColor()
|
|
name.classBackground:SetVertexColor(classColor and classColor.r or cr,
|
|
classColor and classColor.g or cg, classColor and classColor.b or cb, 1)
|
|
if ShaguTweaks_config[T["Hide Player Name"]] == 1 then name:Hide() end
|
|
|
|
local function DetachBar(bar, holder, h)
|
|
-- Vanilla's UnitFrameManaBar_Update calls UpdateManaType(GetParent()).
|
|
-- Keep that parent contract as well as the bar's own unit/events.
|
|
holder.healthbar = PlayerFrameHealthBar
|
|
holder.manabar = PlayerFrameManaBar
|
|
bar:SetParent(holder)
|
|
bar:ClearAllPoints()
|
|
bar:SetPoint("TOPLEFT", holder, "TOPLEFT", 4, -3)
|
|
bar:SetWidth(width)
|
|
bar:SetHeight(h)
|
|
BindHandle(bar, holder)
|
|
local background = bar:CreateTexture(nil, "BACKGROUND")
|
|
background:SetTexture(0, 0, 0, .5)
|
|
background:SetAllPoints(bar)
|
|
if bar.TextString then
|
|
bar.TextString:SetParent(bar)
|
|
bar.TextString:ClearAllPoints()
|
|
bar.TextString:SetPoint("CENTER", bar, "CENTER", 0, 0)
|
|
end
|
|
end
|
|
DetachBar(PlayerFrameHealthBar, health, healthHeight)
|
|
DetachBar(PlayerFrameManaBar, mana, manaHeight)
|
|
if _G.PlayerFrameAlternatePowerBar then
|
|
local alternate = _G.PlayerFrameAlternatePowerBar
|
|
alternate:SetParent(mana)
|
|
alternate:ClearAllPoints()
|
|
alternate:SetPoint("TOPLEFT", mana, "BOTTOMLEFT", 4, -2)
|
|
alternate:SetWidth(width)
|
|
end
|
|
|
|
-- Hide the now-redundant shared backing, without hiding PlayerFrame: its
|
|
-- native events, pet frame, menu and status scripts still need to run.
|
|
local function Suppress(region)
|
|
if not region then return end
|
|
local setAlpha = region.SetAlpha
|
|
setAlpha(region, 0)
|
|
ShaguTweaks.hooksecurefunc(region, "SetAlpha", function(object) setAlpha(object, 0) end)
|
|
end
|
|
Suppress(PlayerFrameTexture)
|
|
Suppress(_G.PlayerFrameBackground)
|
|
Suppress(_G.PlayerFrameNameBackground)
|
|
PlayerFrame:EnableMouse(false)
|
|
|
|
if PlayerStatusTexture then
|
|
MoveRegion(PlayerStatusTexture, portrait, 1, 0)
|
|
local function CropStatus()
|
|
PlayerStatusTexture:SetTexCoord(0, 72/256, 0, 66/128)
|
|
PlayerStatusTexture:SetWidth(72)
|
|
PlayerStatusTexture:SetHeight(66)
|
|
end
|
|
if type(PlayerStatusTexture.SetMask) == "function" then
|
|
PlayerStatusTexture:SetMask("Interface\\CharacterFrame\\TempPortraitAlphaMask")
|
|
end
|
|
ShaguTweaks.hooksecurefunc("PlayerFrame_UpdateStatus", CropStatus)
|
|
CropStatus()
|
|
end
|
|
end
|
|
|
|
-- Wait until all modules and their one-shot OnUpdates have initialized.
|
|
-- This also captures the player's restored layout-cache position.
|
|
API.AfterWorldReady(Setup, true)
|
|
end
|