Files
moon-well-ui/data/addon/ForeverUI/StanceBar.lua
T
Adrestie b063c4185a ForeverUI : barres d'action 2 à 5 placées comme celles de Camelot
Bottom Left / Bottom Right côte à côte au-dessus des barres d'expérience
et de réputation (à +22 et +606 du bord gauche de la barre principale),
postures et familier remontés d'un rang quand elles s'y trouvent ;
Right / Right 2 debout contre le bord droit, RIGHT (-5, -77). Douze
boutons de 45, écart 2, dans la strate de la barre principale (le 3.3.5
les mettait en HIGH, devant fenêtres et sacs), retenues dans l'écran,
déplaçables et redimensionnables en mode d'édition ; le 3.3.5 ne les
replace plus. Rien n'est disposé en combat.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-01 12:13:05 +02:00

263 lines
7.9 KiB
Lua

-- ForeverUI: the stance bar (forms, auras, aspects) with camelot's StanceBar look
-- (SmallActionButtonTemplate, BaseActionButtonMixin:UpdateButtonArt).
-- The client ShapeshiftButtons are secure: we keep and re-skin them, never in combat, when
-- the client forbids resizing, moving or showing a protected frame. /fui moves the bar.
local SIZE = 30 -- SmallActionButtonTemplate
local GAP = 2 -- minButtonPadding
local STEP = SIZE + GAP
local FRAME_W, FRAME_H = 35, 35 -- NormalTexture and PushedTexture
local STATE_W, STATE_H = 31.6, 30.9 -- highlight, checked, border, flash
local NUM_BUTTONS = 10 -- numButtons
local GRAYED = 0.4 -- icon when not castable
local L = ForeverUI.L
local ATLAS = {
normal = "ui-hud-actionbar-iconframe",
pushed = "ui-hud-actionbar-iconframe-down",
highlight = "ui-hud-actionbar-iconframe-mouseover",
slot = "ui-hud-actionbar-iconframe-slot",
background = "ui-hud-actionbar-iconframe-background",
}
local buttonCount = NUM_SHAPESHIFT_SLOTS or NUM_BUTTONS
local buttons = {}
-- The holder moves; the buttons anchor to it without changing parent, since reparenting a
-- secure frame is not allowed in combat.
local carrier = CreateFrame("Frame", "ForeverUIStanceBarHolder", UIParent)
carrier:SetWidth(STEP)
carrier:SetHeight(SIZE)
carrier:Hide()
-- The client rewrites its normal texture on each update: hide it and draw ours instead.
local function hideNormalTexture(button)
local normalFont = button:GetNormalTexture()
if normalFont then
normalFont:SetAlpha(0)
normalFont:SetVertexColor(1, 1, 1, 0)
end
end
-- The source anchors the four states TOPLEFT. On the small button that puts a 35 frame on a
-- 30 button and shifts its opening by 2.5 over the icon. Centered, the opening (26.6 at size
-- 35) fits in the 30 icon with 1.7 margin on every side, so the states are CENTERED.
-- add: use ADD blending (checked state)
local function applyState(texture, atlas, width, height, add)
if not texture then
return
end
ForeverUI.SetAtlas(texture, atlas, true)
texture:SetWidth(width)
texture:SetHeight(height)
texture:ClearAllPoints()
texture:SetPoint("CENTER", 0, 0)
texture:SetBlendMode(add and "ADD" or "BLEND")
end
local function applySkin(button)
if not button or button.foreverSkinned then
return
end
local name = button:GetName()
button:SetWidth(SIZE)
button:SetHeight(SIZE)
-- Hide the old art: floating background and gold frame.
local floatingBg = _G[name .. "FloatingBG"]
if floatingBg then
floatingBg:SetAlpha(0)
end
hideNormalTexture(button)
-- Empty slot under the icon: the two pieces from the source.
local background = button:CreateTexture(nil, "BACKGROUND")
ForeverUI.SetAtlas(background, ATLAS.background, true)
background:SetAllPoints(button)
local slot = button:CreateTexture(nil, "BACKGROUND")
ForeverUI.SetAtlas(slot, ATLAS.slot, true)
slot:SetAllPoints(button)
button.foreverBackground = background
button.foreverSlot = slot
local icon = _G[name .. "Icon"]
if icon then
icon:ClearAllPoints()
icon:SetAllPoints(button)
icon:SetTexCoord(0, 1, 0, 1)
icon:SetDrawLayer("BORDER")
button.foreverIcon = icon
end
-- Our own frame, out of the client's reach, where the source puts its NormalTexture:
-- OVERLAY, 35 x 35, centered (see applyState).
local frame = button:CreateTexture(nil, "OVERLAY")
applyState(frame, ATLAS.normal, FRAME_W, FRAME_H)
button.foreverFrame = frame
local pressed = button:GetPushedTexture()
applyState(pressed, ATLAS.pushed, FRAME_W, FRAME_H)
applyState(button:GetHighlightTexture(), ATLAS.highlight, STATE_W, STATE_H)
applyState(button:GetCheckedTexture(), ATLAS.highlight, STATE_W, STATE_H, true)
local cooldown = _G[name .. "Cooldown"]
if cooldown and icon then
cooldown:ClearAllPoints()
cooldown:SetPoint("TOPLEFT", icon, "TOPLEFT", 1.7, -1.7)
cooldown:SetPoint("BOTTOMRIGHT", icon, "BOTTOMRIGHT", -1, 1)
button.foreverCooldown = cooldown
end
local hotkey = _G[name .. "HotKey"]
if hotkey then
hotkey:ClearAllPoints()
hotkey:SetPoint("TOPRIGHT", button, "TOPRIGHT", -3, -4)
end
local quantity = _G[name .. "Count"]
if quantity then
quantity:ClearAllPoints()
quantity:SetPoint("BOTTOMRIGHT", button, "BOTTOMRIGHT", -3, 1)
end
button.foreverSkinned = true
table.insert(buttons, button)
end
local function skinAll()
for index = 1, buttonCount do
applySkin(_G["ShapeshiftButton" .. index])
end
-- The old bar has its own three-piece art.
for _, suffix in ipairs({ "Left", "Middle", "Right" }) do
local texture = _G["ShapeshiftBar" .. suffix]
if texture then
texture:SetAlpha(0)
end
end
end
-- camelot: StanceBarMixin:Update and ActionBarMixin:UpdateGridLayout. One row, buttons added
-- to the right with minButtonPadding, bar hidden while there is no form.
local function place()
local numForms = GetNumShapeshiftForms and GetNumShapeshiftForms() or 0
if numForms <= 0 then
carrier:Hide()
return
end
if InCombatLockdown() then
return -- secure buttons cannot be moved in combat
end
carrier:SetWidth(numForms * SIZE + (numForms - 1) * GAP)
carrier:SetHeight(SIZE)
for index = 1, buttonCount do
local button = _G["ShapeshiftButton" .. index]
if button then
if index <= numForms then
button:SetWidth(SIZE)
button:SetHeight(SIZE)
button:ClearAllPoints()
button:SetPoint("LEFT", carrier, "LEFT", (index - 1) * STEP, 0)
button:Show()
else
button:Hide()
end
end
end
carrier:Show()
end
-- camelot: StanceBarMixin:UpdateState.
local function updateState()
local numForms = GetNumShapeshiftForms and GetNumShapeshiftForms() or 0
for index = 1, buttonCount do
local button = _G["ShapeshiftButton" .. index]
if button and button.foreverSkinned and index <= numForms then
-- 3.3.5 returns (texture, name, isActive, isCastable), unlike the modern client.
local texture, _, active, castable = GetShapeshiftFormInfo(index)
local icon = button.foreverIcon
if icon then
icon:SetTexture(texture)
if castable then
icon:SetVertexColor(1, 1, 1)
else
icon:SetVertexColor(GRAYED, GRAYED, GRAYED)
end
end
local cooldown = button.foreverCooldown
if cooldown then
if texture then
cooldown:Show()
else
cooldown:Hide()
end
local start, duration, active = GetShapeshiftFormCooldown(index)
if CooldownFrame_SetTimer then
CooldownFrame_SetTimer(cooldown, start, duration, active)
elseif CooldownFrame_Set then
CooldownFrame_Set(cooldown, start, duration, active)
end
end
button:SetChecked(active and 1 or nil)
hideNormalTexture(button)
end
end
end
local function all()
skinAll()
place()
updateState()
end
ForeverUI.StanceBar = { Apply = all, Holder = carrier }
-- The client re-sets its own pieces on each update.
if hooksecurefunc then
for _, funcName in ipairs({ "ShapeshiftBar_Update", "ShapeshiftBar_UpdateState",
"ShapeshiftBar_ChangeForm" }) do
if type(_G[funcName]) == "function" then
hooksecurefunc(funcName, function()
place()
updateState()
end)
end
end
end
local listener = CreateFrame("Frame", "ForeverUIStanceBarWatcher")
listener:RegisterEvent("PLAYER_ENTERING_WORLD")
listener:RegisterEvent("PLAYER_REGEN_ENABLED")
listener:RegisterEvent("UPDATE_SHAPESHIFT_FORMS")
listener:RegisterEvent("UPDATE_SHAPESHIFT_FORM")
listener:RegisterEvent("UPDATE_SHAPESHIFT_USABLE")
listener:RegisterEvent("UPDATE_SHAPESHIFT_COOLDOWN")
listener:SetScript("OnEvent", all)
all()
-- Default: left edge aligned with the action bar (-25.5 - 562 = -587.5 from center), above
-- the experience and reputation bars, one row higher when action bar 2 or 3 is shown there
-- (MultiBars.lua). /fui moves it.
local function defaultY()
return 84 + (ForeverUI.MultiBars and ForeverUI.MultiBars.Lift() or 0)
end
ForeverUI.Layout.Register(carrier, "stances", L.STANCEBAR_EDIT_LABEL,
"BOTTOMLEFT", "BOTTOM", -587.5, defaultY())
ForeverUI.StanceBar.PlaceDefault = function()
ForeverUI.Layout.SetDefaults("stances", "BOTTOMLEFT", "BOTTOM", -587.5, defaultY())
end