forked from Dusk/ShaguTweaks-ClassicAPI
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019W3rwusM4i1CYaHQAYixJ7
795 lines
28 KiB
Lua
795 lines
28 KiB
Lua
local _G = ShaguTweaks.GetGlobalEnv()
|
|
local T = ShaguTweaks.T
|
|
local HookAddonOrVariable = ShaguTweaks.HookAddonOrVariable
|
|
local GetExpansion = ShaguTweaks.GetExpansion
|
|
local AddBorder = ShaguTweaks.AddBorder
|
|
|
|
local module = ShaguTweaks:register({
|
|
title = T["Darkened UI"],
|
|
description = T["Turns the entire interface into darker colors."],
|
|
expansions = { ["vanilla"] = true },
|
|
category = T["Interface"],
|
|
enabled = nil,
|
|
config = {
|
|
["darkmode.color"] = { r = .3, g = .3, b = .3, a = .9 }
|
|
}
|
|
})
|
|
|
|
-- Golden UI runs the same recolor pass with a gold palette (like the WoW
|
|
-- Forever client): textures are tinted gold, borders and tooltip edges get a
|
|
-- softer gold, and backgrounds keep their own colors.
|
|
local golden = ShaguTweaks:register({
|
|
title = T["Golden UI"],
|
|
description = T["Turns the borders of the interface, tooltips and dialogs gold, like the WoW Forever client. Darkened UI takes priority when both are enabled."],
|
|
expansions = { ["vanilla"] = true },
|
|
category = T["Interface"],
|
|
enabled = nil,
|
|
config = {
|
|
["goldmode.color"] = { r = 1, g = .78, b = .4, a = 1 },
|
|
["goldmode.border"] = { r = .8, g = .62, b = .32, a = 1 },
|
|
["goldmode.text"] = { r = .84, g = .72, b = .48, a = 1 },
|
|
["goldmode.bar"] = { r = .8, g = .63, b = .62, a = 1 },
|
|
}
|
|
})
|
|
|
|
-- Active palette, set by whichever of the two modules is enabled:
|
|
-- tint goes on textures, edge on backdrop borders and added button borders.
|
|
local tint, edge
|
|
|
|
local blacklist = {
|
|
["Solid Texture"] = true,
|
|
["WHITE8X8"] = true,
|
|
["StatusBar"] = true,
|
|
["BarFill"] = true,
|
|
["Portrait"] = true,
|
|
["Button"] = true,
|
|
["Icon"] = true,
|
|
["AddOns"] = true,
|
|
["StationeryTest"] = true,
|
|
["TargetDead"] = true, -- LootFrame Icon
|
|
["^KeyRing"] = true, -- bag frame
|
|
["GossipIcon"] = true,
|
|
["WorldMap\\(.+)\\"] = true,
|
|
["PetHappiness"] = true,
|
|
["Elite"] = true,
|
|
["Rare"] = true,
|
|
["ColorPickerWheel"] = true,
|
|
["ComboPoint"] = true,
|
|
["Skull"] = true,
|
|
|
|
-- LFT:
|
|
["battlenetworking0"] = true,
|
|
["damage"] = true,
|
|
["tank"] = true,
|
|
["healer"] = true,
|
|
}
|
|
|
|
local regionskips = {
|
|
-- colorpicker gradient
|
|
["ColorPickerFrame"] = { [15] = true },
|
|
-- EQL3: background art tiles on the main frame - BookIcon(1), TopLeft(2), Top_Switch_Off(3), BottomLeft(4), Bottom_Switch_Off(5), Top_Switch_On(6), Bottom_Switch_On(7)
|
|
["EQL3_QuestLogFrame"] = { [1]=true, [2]=true, [3]=true, [4]=true, [5]=true, [6]=true, [7]=true },
|
|
-- EQL3: parchment tiles on the description panel - TopMiddle(1), TopRight(2), BottomMiddle(3), BottomRight(4)
|
|
["EQL3_QuestLogFrame_Description"] = { [1]=true, [2]=true, [3]=true, [4]=true },
|
|
}
|
|
|
|
local backgrounds = {
|
|
["^SpellBookFrame$"] = { 325, 355, 17, -74 },
|
|
["^ItemTextFrame$"] = { 300, 355, 24, -74 },
|
|
["^QuestLogDetailScrollFrame$"] = { QuestLogDetailScrollChildFrame:GetWidth(), QuestLogDetailScrollChildFrame:GetHeight(), 0, 0 },
|
|
["^QuestFrame(.+)Panel$"] = { 300, 330, 24, -82 },
|
|
["^GossipFrameGreetingPanel$"] = { 300, 330, 24, -82 },
|
|
-- the mail inbox uses the same parchment art as ItemTextFrame; the send
|
|
-- page already has its own stationery on top
|
|
["^InboxFrame$"] = { 300, 355, 24, -74 },
|
|
}
|
|
|
|
local borders = {
|
|
["ShapeshiftButton"] = 3,
|
|
["BuffButton"] = 3,
|
|
["TargetFrameBuff"] = 3,
|
|
["TempEnchant"] = 3,
|
|
["SpellButton"] = 3,
|
|
["SpellBookSkillLineTab"] = 3,
|
|
["ActionButton%d+$"] = 3,
|
|
["MultiBar(.+)Button%d+$"] = 3,
|
|
["KeyRingButton"] = 2,
|
|
["ActionBarUpButton"] = -3,
|
|
["ActionBarDownButton"] = -3,
|
|
["MainMenuBarPerformanceBarFrameButton"] = { -12, -1, -8, 4 },
|
|
["Character(.+)Slot$"] = 3,
|
|
["Inspect(.+)Slot$"] = 3,
|
|
["ContainerFrame(.+)Item"] = 3,
|
|
["MainMenuBarBackpackButton$"] = 3,
|
|
["CharacterBag(.+)Slot$"] = 3,
|
|
["ChatFrame(.+)Button"] = -2,
|
|
["PetFrameHappiness"] = 2,
|
|
["MicroButton"] = { -21, 0, 0, 0 },
|
|
-- VCB's buff and weapon-buff buttons, like the Blizzard buff buttons
|
|
["^VCB_BF_BUFF_BUTTON%d+$"] = 3,
|
|
["^VCB_BF_WEAPON_BUTTON%d+$"] = 3,
|
|
}
|
|
|
|
local addonframes = {
|
|
["Blizzard_TalentUI"] = { "TalentFrame" },
|
|
["Blizzard_AuctionUI"] = { "AuctionFrame", "AuctionDressUpFrame" },
|
|
["Blizzard_CraftUI"] = { "CraftFrame" },
|
|
["Blizzard_InspectUI"] = {
|
|
"InspectPaperDollFrame",
|
|
"InspectHonorFrame",
|
|
"InspectFrameTab1",
|
|
"InspectFrameTab2",
|
|
},
|
|
["Blizzard_MacroUI"] = { "MacroFrame", "MacroPopupFrame" },
|
|
["Blizzard_RaidUI"] = { "ReadyCheckFrame" },
|
|
["Blizzard_TradeSkillUI"] = { "TradeSkillFrame" },
|
|
["Blizzard_TrainerUI"] = { "ClassTrainerFrame" },
|
|
["Artisan"] = { "ArtisanFrame" },
|
|
}
|
|
|
|
-- Turtle WoW exposes these extra inspect pages. Keep them out of the generic
|
|
-- Vanilla path so missing Turtle-only frames don't leave compatibility lurkers
|
|
-- registered for the whole session.
|
|
local isTurtle = TargetHPText and TargetHPPercText
|
|
if isTurtle then
|
|
table.insert(addonframes["Blizzard_InspectUI"], "InspectArenaFrame")
|
|
table.insert(addonframes["Blizzard_InspectUI"], "InspectTalentsFrame")
|
|
table.insert(addonframes["Blizzard_InspectUI"], "InspectFrameTab3")
|
|
table.insert(addonframes["Blizzard_InspectUI"], "InspectFrameTab4")
|
|
end
|
|
|
|
-- sizing is a bit different on tbc
|
|
if GetExpansion() == "tbc" then
|
|
borders["BuffButton"] = 2
|
|
borders["TempEnchant"] = 2
|
|
borders["MicroButton"] = { -21, 0, 0, 0 }
|
|
end
|
|
|
|
-- Frame art that ships inside an addon (the "AddOns" entry above skips
|
|
-- addon textures, which are mostly icons) but should follow the palette.
|
|
local whitelist = {
|
|
"outfitter\\textures\\outfitterframe",
|
|
"outfitter\\textures\\scrollbartrench",
|
|
-- Artisan swaps in its own lower half of the profession window
|
|
"artisan\\textures\\bottomleft",
|
|
"artisan\\textures\\bottomright",
|
|
-- RollFor's classic loot window
|
|
"rollfor\\assets\\og-loot-frame",
|
|
}
|
|
|
|
-- Addon windows that build their rows while opening: recolored every time
|
|
-- they are shown (and once more on the next frame, after the rows exist).
|
|
local showframes = {
|
|
["RollForLootFrameHeader"] = true,
|
|
}
|
|
|
|
local function IsBlacklisted(texture)
|
|
local name = texture:GetName()
|
|
local texture = texture:GetTexture()
|
|
if not texture then return true end
|
|
|
|
local lower = string.lower(texture)
|
|
for i = 1, table.getn(whitelist) do
|
|
if string.find(lower, whitelist[i], 1, true) then return nil end
|
|
end
|
|
|
|
if name then
|
|
for entry in pairs(blacklist) do
|
|
if string.find(name, entry, 1) then return true end
|
|
end
|
|
end
|
|
|
|
for entry in pairs(blacklist) do
|
|
if string.find(texture, entry, 1) then return true end
|
|
end
|
|
|
|
return nil
|
|
end
|
|
|
|
local function AddSpecialBackground(frame, w, h, x, y)
|
|
frame.Material = frame.Material or frame:CreateTexture(nil, "OVERLAY")
|
|
frame.Material:SetTexture("Interface\\Stationery\\StationeryTest1")
|
|
frame.Material:SetWidth(w)
|
|
frame.Material:SetHeight(h)
|
|
frame.Material:SetPoint("TOPLEFT", frame, x, y)
|
|
frame.Material:SetVertexColor(.8, .8, .8)
|
|
end
|
|
|
|
local function DarkenFrame(frame, r, g, b, a)
|
|
-- dont't do anything if neither Darkened UI nor Golden UI is enabled
|
|
if not tint then return end
|
|
|
|
-- set defaults
|
|
if not r and not g and not b then
|
|
r, g, b, a = tint.r, tint.g, tint.b, tint.a
|
|
end
|
|
|
|
-- iterate through all subframes
|
|
if frame and frame.GetChildren then
|
|
for _, frame in pairs({frame:GetChildren()}) do
|
|
DarkenFrame(frame, r, g, b, a)
|
|
end
|
|
end
|
|
|
|
-- set vertex on all regions
|
|
if frame and frame.GetRegions then
|
|
-- read name
|
|
local name = frame.GetName and frame:GetName()
|
|
|
|
-- set the palette's backdrop border color everywhere
|
|
frame:SetBackdropBorderColor(edge.r, edge.g, edge.b, edge.a)
|
|
|
|
-- add special backgrounds to quests and such
|
|
for pattern, inset in pairs(backgrounds) do
|
|
if name and string.find(name, pattern) then AddSpecialBackground(frame, inset[1], inset[2], inset[3], inset[4]) end
|
|
end
|
|
|
|
-- add black borders around specified buttons
|
|
for pattern, inset in pairs(borders) do
|
|
if name and string.find(name, pattern) then AddBorder(frame, inset, edge) end
|
|
end
|
|
|
|
-- scan through all regions (textures)
|
|
for id, region in pairs({frame:GetRegions()}) do
|
|
if region.SetVertexColor and region:GetObjectType() == "Texture" then
|
|
if region:GetTexture() and string.find(region:GetTexture(), "UI%-Panel%-Button%-Up") then
|
|
-- monochrome buttons
|
|
-- region:SetDesaturated(true)
|
|
elseif name and id and regionskips[name] and regionskips[name][id] then
|
|
-- skip special regions
|
|
elseif region.GetBlendMode and region:GetBlendMode() == "ADD" then
|
|
-- skip blend over textures
|
|
elseif IsBlacklisted(region) then
|
|
-- skip blacklisted texture names
|
|
else
|
|
region:SetVertexColor(r,g,b,a)
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
-- register darken frame to global
|
|
ShaguTweaks.DarkenFrame = DarkenFrame
|
|
|
|
-- Other addons that draw their own tooltip-style borders. Their frames (and
|
|
-- everything inside them) keep following the palette even when the addon
|
|
-- sets its border color again later, or builds a window only when opened.
|
|
local ADDON_PREFIXES = {
|
|
"ShaguDPS", "Bagshui", "Outfitter", "SoulstoneTracker", "SSWarlock",
|
|
"SSTClassic", "SSTracker", "TrinketMenu", "OctoBus", "RollFor", "VCB_",
|
|
}
|
|
|
|
local function IsAddonFrameName(name)
|
|
if not name then return end
|
|
for i = 1, table.getn(ADDON_PREFIXES) do
|
|
if string.find(name, ADDON_PREFIXES[i], 1, true) == 1 then return true end
|
|
end
|
|
end
|
|
|
|
-- Only light grey/white/silver borders follow the palette. Colored borders
|
|
-- (item quality, hover highlights, class or theme colors) and dark ones stay
|
|
-- as the addon set them.
|
|
local function IsLightNeutral(r, g, b)
|
|
r, g, b = tonumber(r) or 0, tonumber(g) or 0, tonumber(b) or 0
|
|
local hi, lo = math.max(r, g, b), math.min(r, g, b)
|
|
return hi - lo < .1 and hi >= .35
|
|
end
|
|
|
|
local function LockBorder(frame)
|
|
if not frame or frame.stBorderLock or not frame.SetBackdropBorderColor then return end
|
|
frame.stBorderLock = true
|
|
|
|
local setColor, setBackdrop = frame.SetBackdropBorderColor, frame.SetBackdrop
|
|
|
|
frame.SetBackdropBorderColor = function(self, r, g, b, a)
|
|
if IsLightNeutral(r, g, b) then
|
|
setColor(self, edge.r, edge.g, edge.b, a or 1)
|
|
else
|
|
setColor(self, r, g, b, a)
|
|
end
|
|
end
|
|
|
|
-- a new backdrop starts with a white border
|
|
if setBackdrop then
|
|
frame.SetBackdrop = function(self, backdrop)
|
|
setBackdrop(self, backdrop)
|
|
if backdrop and backdrop.edgeFile then setColor(self, edge.r, edge.g, edge.b, 1) end
|
|
end
|
|
end
|
|
|
|
-- run the current color through the filter once
|
|
if frame.GetBackdropBorderColor then
|
|
local ok, r, g, b, a = pcall(frame.GetBackdropBorderColor, frame)
|
|
if ok and r then frame:SetBackdropBorderColor(r, g, b, a) end
|
|
end
|
|
end
|
|
|
|
-- Border textures (VCB's buff and weapon borders): same rule, on the
|
|
-- texture's vertex color.
|
|
local function LockTextureBorder(texture)
|
|
if texture.stBorderLock or not texture.SetVertexColor then return end
|
|
texture.stBorderLock = true
|
|
|
|
local setColor = texture.SetVertexColor
|
|
texture.SetVertexColor = function(self, r, g, b, a)
|
|
if IsLightNeutral(r, g, b) then
|
|
setColor(self, edge.r, edge.g, edge.b, a or 1)
|
|
else
|
|
setColor(self, r, g, b, a)
|
|
end
|
|
end
|
|
|
|
if texture.GetVertexColor then
|
|
local ok, r, g, b, a = pcall(texture.GetVertexColor, texture)
|
|
if ok and r then texture:SetVertexColor(r, g, b, a) end
|
|
end
|
|
end
|
|
|
|
local function LockTree(frame)
|
|
if not frame then return end
|
|
LockBorder(frame)
|
|
if frame.GetRegions then
|
|
local regions = { frame:GetRegions() }
|
|
for i = 1, table.getn(regions) do
|
|
local region = regions[i]
|
|
local name = region.GetName and region:GetName()
|
|
if name and string.find(name, "Border$") and region.GetObjectType and region:GetObjectType() == "Texture" then
|
|
LockTextureBorder(region)
|
|
end
|
|
end
|
|
end
|
|
if frame.GetChildren then
|
|
local children = { frame:GetChildren() }
|
|
for i = 1, table.getn(children) do LockTree(children[i]) end
|
|
end
|
|
end
|
|
|
|
-- find the addon frames that exist already, anywhere below UIParent
|
|
local function FindAddonFrames(frame)
|
|
local children = { frame:GetChildren() }
|
|
for i = 1, table.getn(children) do
|
|
local child = children[i]
|
|
if IsAddonFrameName(child.GetName and child:GetName()) then
|
|
LockTree(child)
|
|
else
|
|
FindAddonFrames(child)
|
|
end
|
|
end
|
|
end
|
|
|
|
-- The round border of addon minimap buttons (most use the stock tracking
|
|
-- border texture). Those buttons are often made after login.
|
|
local function TintMinimapButton(frame)
|
|
if not frame or not frame.GetRegions then return end
|
|
local regions = { frame:GetRegions() }
|
|
for i = 1, table.getn(regions) do
|
|
local region = regions[i]
|
|
local texture = region.GetTexture and region:GetTexture()
|
|
if texture and string.find(string.lower(texture), "minimap%-trackingborder") then
|
|
region:SetVertexColor(tint.r, tint.g, tint.b, 1)
|
|
end
|
|
end
|
|
end
|
|
|
|
local function RecolorOnShow(frame)
|
|
if not frame or frame.stRecolorOnShow then return end
|
|
frame.stRecolorOnShow = true
|
|
if golden.rollforLoot and frame:GetName() == "RollForLootFrameHeader" then golden.rollforLoot(frame) end
|
|
ShaguTweaks.HookScript(frame, "OnShow", function()
|
|
DarkenFrame(frame)
|
|
if _G.C_Timer and _G.C_Timer.After then
|
|
_G.C_Timer.After(0, function() DarkenFrame(frame) end)
|
|
end
|
|
end)
|
|
if frame:IsShown() then DarkenFrame(frame) end
|
|
end
|
|
|
|
local function LockAddonFrames()
|
|
FindAddonFrames(UIParent)
|
|
|
|
for name in pairs(showframes) do RecolorOnShow(_G[name]) end
|
|
|
|
if Minimap then
|
|
local buttons = { Minimap:GetChildren() }
|
|
for i = 1, table.getn(buttons) do TintMinimapButton(buttons[i]) end
|
|
end
|
|
|
|
-- frames those addons create later: named after the addon, or created
|
|
-- inside one of their frames (templates bring their children along)
|
|
local create = CreateFrame
|
|
CreateFrame = function(a1, a2, a3, a4, a5, a6)
|
|
local frame = create(a1, a2, a3, a4, a5, a6)
|
|
if frame and (IsAddonFrameName(a2) or (type(a3) == "table" and a3.stBorderLock)) then
|
|
LockTree(frame)
|
|
end
|
|
if frame and type(a2) == "string" and showframes[a2] then RecolorOnShow(frame) end
|
|
-- buttons that get an extra border (VCB buffs made after login)
|
|
if frame and type(a2) == "string" then
|
|
for pattern, inset in pairs(borders) do
|
|
if string.find(a2, pattern) then AddBorder(frame, inset, edge) end
|
|
end
|
|
end
|
|
-- minimap buttons get their border texture right after they are made
|
|
if frame and a3 and a3 == Minimap then
|
|
if _G.C_Timer and _G.C_Timer.After then
|
|
_G.C_Timer.After(0, function() TintMinimapButton(frame) end)
|
|
end
|
|
end
|
|
return frame
|
|
end
|
|
end
|
|
|
|
local function Apply(tintColor, edgeColor, darkBackgrounds)
|
|
if tint then return end
|
|
tint, edge = tintColor, edgeColor
|
|
ShaguTweaks.UITint = tint
|
|
ShaguTweaks.UIBorder = edge
|
|
|
|
local name, original, r, g, b
|
|
local hookBuffButton_Update = BuffButton_Update
|
|
function BuffButton_Update(buttonName, index, filter)
|
|
hookBuffButton_Update(buttonName, index, filter)
|
|
|
|
-- tbc passes buttonName and index arguments, vanilla uses "this" context
|
|
name = buttonName and index and buttonName .. index or this:GetName()
|
|
original = _G[name.."Border"]
|
|
|
|
if original and this.ShaguTweaks_border then
|
|
r, g, b = original:GetVertexColor()
|
|
this.ShaguTweaks_border:SetBackdropBorderColor(r, g, b, 1)
|
|
original:SetAlpha(0)
|
|
elseif not original and _G[name] then
|
|
-- tbc buff buttons don't have borders, so we
|
|
-- need to manually add a dark one.
|
|
AddBorder(_G[name], 2, edge)
|
|
end
|
|
end
|
|
|
|
TOOLTIP_DEFAULT_COLOR.r = edge.r
|
|
TOOLTIP_DEFAULT_COLOR.g = edge.g
|
|
TOOLTIP_DEFAULT_COLOR.b = edge.b
|
|
|
|
-- Darkened UI also greys the tooltip background; Golden UI keeps it.
|
|
if darkBackgrounds then
|
|
TOOLTIP_DEFAULT_BACKGROUND_COLOR.r = tint.r
|
|
TOOLTIP_DEFAULT_BACKGROUND_COLOR.g = tint.g
|
|
TOOLTIP_DEFAULT_BACKGROUND_COLOR.b = tint.b
|
|
end
|
|
|
|
DarkenFrame(UIParent)
|
|
DarkenFrame(WorldMapFrame)
|
|
DarkenFrame(DropDownList1)
|
|
DarkenFrame(DropDownList2)
|
|
DarkenFrame(DropDownList3)
|
|
LockAddonFrames()
|
|
|
|
-- the loot window gets its art set again when it opens
|
|
if LootFrame then
|
|
ShaguTweaks.HookScript(LootFrame, "OnShow", function() DarkenFrame(LootFrame) end)
|
|
end
|
|
|
|
-- align all actionbutton textures
|
|
local bars = { "Action", "BonusAction", "MultiBarBottomLeft", "MultiBarBottomRight", "MultiBarLeft", "MultiBarRight", "Shapeshift" }
|
|
for _, prefix in pairs(bars) do
|
|
for i = 1, NUM_ACTIONBAR_BUTTONS do
|
|
local button = _G[prefix .. "Button" .. i]
|
|
local texture = _G[prefix.."Button"..i.."NormalTexture"]
|
|
|
|
if button and texture then
|
|
texture:SetWidth(60)
|
|
texture:SetHeight(60)
|
|
texture:SetPoint("CENTER", 0, 0)
|
|
ShaguTweaks.AddBorder(button, 3)
|
|
end
|
|
end
|
|
end
|
|
|
|
for _, button in pairs({ MinimapZoomOut, MinimapZoomIn }) do
|
|
for _, func in pairs({ "GetNormalTexture", "GetDisabledTexture", "GetPushedTexture" }) do
|
|
if button[func] then
|
|
local tex = button[func](button)
|
|
if tex then
|
|
tex:SetVertexColor(math.min(tint.r+.2, 1), math.min(tint.g+.2, 1), math.min(tint.b+.2, 1), 1)
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
HookAddonOrVariable("Blizzard_AuctionUI", function()
|
|
for i = 1, 15 do
|
|
local tex = _G["AuctionFilterButton"..i]:GetNormalTexture()
|
|
tex:SetVertexColor(tint.r, tint.g, tint.b, 1)
|
|
end
|
|
|
|
for i = 1, 8 do
|
|
_G["BrowseButton"..i.."Left"]:SetVertexColor(tint.r, tint.g, tint.b, 1)
|
|
_G["BrowseButton"..i.."Right"]:SetVertexColor(tint.r, tint.g, tint.b, 1)
|
|
end
|
|
end)
|
|
|
|
for addon, data in pairs(addonframes) do
|
|
for _, frame in pairs(data) do
|
|
local frame = frame
|
|
HookAddonOrVariable(frame, function()
|
|
DarkenFrame(_G[frame])
|
|
end)
|
|
end
|
|
end
|
|
|
|
-- Turtle's inspect talent tree is rebuilt by TWTalentFrame_Update().
|
|
-- Its background textures are assigned during that update, so the initial
|
|
-- InspectTalentsFrame pass may happen too early. Re-darken the parent after
|
|
-- each rebuild; it already contains TWTalentFrame, so one recursive scan is
|
|
-- sufficient and avoids traversing the same tree twice.
|
|
if isTurtle then
|
|
HookAddonOrVariable("TWTalentFrame", function()
|
|
if not _G.TWTalentFrame or not _G.TWTalentFrame_Update then return end
|
|
|
|
if not _G.TWTalentFrame.ShaguTweaksDarkHook then
|
|
_G.TWTalentFrame.ShaguTweaksDarkHook = true
|
|
ShaguTweaks.hooksecurefunc("TWTalentFrame_Update", function()
|
|
DarkenFrame(_G.InspectTalentsFrame or _G.TWTalentFrame)
|
|
end)
|
|
end
|
|
|
|
DarkenFrame(_G.InspectTalentsFrame or _G.TWTalentFrame)
|
|
end)
|
|
end
|
|
|
|
HookAddonOrVariable("Blizzard_TimeManager", function()
|
|
DarkenFrame(TimeManagerClockButton)
|
|
end)
|
|
|
|
HookAddonOrVariable("GameTooltipStatusBarBackdrop", function()
|
|
DarkenFrame(_G["GameTooltipStatusBarBackdrop"])
|
|
end)
|
|
|
|
-- Prefer BlizzNameplatesPlus' own nameplate provider whenever available.
|
|
-- This is capability-based instead of version-based, so BNP updates keep
|
|
-- working as long as its public libnameplate callback API remains available.
|
|
local bnpNameplates = BNP
|
|
and BNP.libnameplate
|
|
and BNP.libnameplate.OnInit
|
|
and BNP.libnameplate.OnShow
|
|
|
|
if bnpNameplates then
|
|
local pending = {}
|
|
local updater = CreateFrame("Frame")
|
|
|
|
local function StopUpdaterIfIdle()
|
|
if not next(pending) then
|
|
updater:SetScript("OnUpdate", nil)
|
|
end
|
|
end
|
|
|
|
local function ReapplyBNPDarkMode(plate)
|
|
plate = plate or this
|
|
if not plate or not plate:IsShown() then return end
|
|
if plate.darkened then return end
|
|
|
|
plate.darkened = true
|
|
DarkenFrame(plate)
|
|
|
|
-- Blizzard can restore some nameplate textures immediately after OnShow.
|
|
-- Queue one delayed pass, then stop completely. If BNP's own Shagu
|
|
-- compatibility already darkened the plate first, the marker above makes
|
|
-- this path a no-op and avoids duplicate work.
|
|
pending[plate] = GetTime() + 0.08
|
|
if not updater:GetScript("OnUpdate") then
|
|
updater:SetScript("OnUpdate", function()
|
|
local now = GetTime()
|
|
local current, due
|
|
for current, due in pairs(pending) do
|
|
if now >= due then
|
|
pending[current] = nil
|
|
if current and current:IsShown() then
|
|
current.darkened = nil
|
|
DarkenFrame(current)
|
|
current.darkened = true
|
|
end
|
|
end
|
|
end
|
|
StopUpdaterIfIdle()
|
|
end)
|
|
end
|
|
end
|
|
|
|
table.insert(BNP.libnameplate.OnInit, ReapplyBNPDarkMode)
|
|
table.insert(BNP.libnameplate.OnShow, ReapplyBNPDarkMode)
|
|
else
|
|
-- Native ShaguTweaks nameplates only need darkening when a plate is first
|
|
-- discovered or shown. Avoid keeping a per-frame libnameplate callback
|
|
-- alive just to test the permanent "darkened" marker.
|
|
local pending = {}
|
|
local updater = CreateFrame("Frame")
|
|
|
|
local function StopUpdaterIfIdle()
|
|
if not next(pending) then
|
|
updater:SetScript("OnUpdate", nil)
|
|
end
|
|
end
|
|
|
|
local function ReapplyShaguDarkMode(plate)
|
|
plate = plate or this
|
|
if not plate or not plate:IsShown() then return end
|
|
|
|
plate.darkened = nil
|
|
DarkenFrame(plate)
|
|
plate.darkened = true
|
|
|
|
-- Some clients restore nameplate textures immediately after OnShow.
|
|
-- Re-apply once after that short window, then stop completely.
|
|
pending[plate] = GetTime() + 0.08
|
|
if not updater:GetScript("OnUpdate") then
|
|
updater:SetScript("OnUpdate", function()
|
|
local now = GetTime()
|
|
local current, due
|
|
for current, due in pairs(pending) do
|
|
if now >= due then
|
|
pending[current] = nil
|
|
if current and current:IsShown() then
|
|
current.darkened = nil
|
|
DarkenFrame(current)
|
|
current.darkened = true
|
|
end
|
|
end
|
|
end
|
|
StopUpdaterIfIdle()
|
|
end)
|
|
end
|
|
end
|
|
|
|
table.insert(ShaguTweaks.libnameplate.OnInit, ReapplyShaguDarkMode)
|
|
table.insert(ShaguTweaks.libnameplate.OnShow, ReapplyShaguDarkMode)
|
|
end
|
|
end
|
|
|
|
module.enable = function(self)
|
|
ShaguTweaks.DarkMode = true
|
|
Apply(module.config["darkmode.color"], module.config["darkmode.color"], true)
|
|
end
|
|
|
|
-- Title bars: the dark strips the window titles sit on. Golden UI
|
|
-- multiplies them toward WoW Forever's red-brown with a MOD layer. Measured
|
|
-- from the window art: rows below the frame's top edge, and how far the strip
|
|
-- runs from the left (after the portrait) and from the right (before the
|
|
-- close button).
|
|
local STRIP_A = { 72, -16, -60, -34 } -- character, spellbook, mail, quest log art
|
|
local STRIP_B = { 72, -15, -62, -33 } -- merchant, friends art
|
|
|
|
local function Shade(parent, layer, color)
|
|
local shade = parent:CreateTexture(nil, layer)
|
|
shade:SetTexture(1, 1, 1, 1)
|
|
shade:SetVertexColor(tonumber(color.r) or .8, tonumber(color.g) or .63, tonumber(color.b) or .62, 1)
|
|
shade:SetBlendMode("MOD")
|
|
return shade
|
|
end
|
|
|
|
local function PlaceStrip(shade, frame, strip)
|
|
shade:ClearAllPoints()
|
|
shade:SetPoint("TOPLEFT", frame, "TOPLEFT", strip[1], strip[2])
|
|
shade:SetPoint("BOTTOMRIGHT", frame, "TOPRIGHT", strip[3], strip[4])
|
|
end
|
|
|
|
-- The shade has to sit above the window art, which would darken a title
|
|
-- that is drawn in the same frame. Those titles are drawn again on top and
|
|
-- the original is kept, invisible, for its position and text.
|
|
local function RedrawTitle(holder, title, gold)
|
|
if not title then return end
|
|
local copy = holder:CreateFontString(nil, "OVERLAY", "GameFontNormal")
|
|
local font = not gold and title.GetFontObject and title:GetFontObject()
|
|
if font then copy:SetFontObject(font) end
|
|
-- titles drawn in white (Turtle's quest log) get the same gold as the rest
|
|
if gold and NORMAL_FONT_COLOR then
|
|
copy:SetTextColor(NORMAL_FONT_COLOR.r, NORMAL_FONT_COLOR.g, NORMAL_FONT_COLOR.b)
|
|
end
|
|
copy:SetAllPoints(title)
|
|
if title.GetJustifyH then copy:SetJustifyH(title:GetJustifyH()) end
|
|
copy:SetText(title:GetText())
|
|
title:SetAlpha(0)
|
|
|
|
local setText = title.SetText
|
|
title.SetText = function(self, text)
|
|
setText(self, text)
|
|
copy:SetText(text)
|
|
end
|
|
return copy
|
|
end
|
|
|
|
local function ShadeTitleBar(frame, strip, title, color, level, gold)
|
|
if not frame or frame.stTitleBar then return end
|
|
local bar = CreateFrame("Frame", nil, frame)
|
|
bar:SetAllPoints(frame)
|
|
if level then bar:SetFrameLevel(level) end
|
|
frame.stTitleBar = bar
|
|
bar.shade = Shade(bar, "BACKGROUND", color)
|
|
PlaceStrip(bar.shade, frame, strip)
|
|
bar.title = RedrawTitle(bar, title, gold)
|
|
return bar
|
|
end
|
|
|
|
-- frames whose titles live in child frames (or above the art layer): the
|
|
-- shade is a plain texture in the window itself, nothing to redraw
|
|
local function ShadeStripInFrame(frame, strip, layer, color)
|
|
if not frame or frame.stTitleShade then return end
|
|
frame.stTitleShade = Shade(frame, layer, color)
|
|
PlaceStrip(frame.stTitleShade, frame, strip)
|
|
end
|
|
|
|
local function ShadeTitleBars(color)
|
|
-- world map: its strip is 3 to 21 px below the top of its art (384 above
|
|
-- the frame center), from the left corner to the window buttons
|
|
if WorldMapFrame and WorldMapFrameTitle and not WorldMapFrame.stTitleBar then
|
|
local bar = ShadeTitleBar(WorldMapFrame, { 0, 0, 0, 0 }, WorldMapFrameTitle, color)
|
|
bar.shade:ClearAllPoints()
|
|
bar.shade:SetPoint("TOPLEFT", WorldMapFrame, "CENTER", -510, 381)
|
|
bar.shade:SetPoint("BOTTOMRIGHT", WorldMapFrame, "CENTER", 455, 363)
|
|
|
|
-- the stone art is lighter around the seam of its first two pieces
|
|
-- (measured in 8 px columns): darken those columns a bit more so the
|
|
-- bar comes out even
|
|
local even = {
|
|
{ -272, .81 }, { -264, .76 },
|
|
{ -256, .66 }, { -248, .52 }, { -240, .49 }, { -232, .47 }, { -224, .53 }, { -216, .54 },
|
|
{ -208, .52 }, { -200, .58 }, { -192, .69 }, { -184, .76 }, { -176, .68 }, { -168, .68 },
|
|
{ -160, .69 }, { -152, .71 }, { -144, .78 }, { -136, .86 },
|
|
}
|
|
for i = 1, table.getn(even) do
|
|
local x, f = even[i][1], even[i][2]
|
|
local fix = Shade(bar, "BACKGROUND", { r = f, g = f, b = f })
|
|
fix:SetPoint("TOPLEFT", WorldMapFrame, "CENTER", x, 381)
|
|
fix:SetPoint("BOTTOMRIGHT", WorldMapFrame, "CENTER", x + 8, 363)
|
|
end
|
|
end
|
|
|
|
ShadeTitleBar(MerchantFrame, STRIP_B, MerchantNameText, color)
|
|
ShadeTitleBar(SpellBookFrame, STRIP_A, SpellBookTitleText, color)
|
|
ShadeTitleBar(FriendsFrame, STRIP_B, FriendsFrameTitleText, color)
|
|
-- the quest log is wider: its strip ends before the close button
|
|
ShadeTitleBar(QuestLogFrame, { 72, -16, -121, -34 }, QuestLogTitleText, color, nil, true)
|
|
|
|
-- mail: the art is on the BORDER layer, the titles are on the page frames
|
|
ShadeStripInFrame(MailFrame, STRIP_A, "ARTWORK", color)
|
|
|
|
-- character: the art is on the tab pages (PaperDollFrame and the others),
|
|
-- the name on CharacterNameFrame. The shade goes between the two.
|
|
if CharacterFrame and PaperDollFrame and CharacterNameFrame then
|
|
local level = PaperDollFrame:GetFrameLevel() + 1
|
|
if CharacterNameFrame:GetFrameLevel() <= level then CharacterNameFrame:SetFrameLevel(level + 1) end
|
|
ShadeTitleBar(CharacterFrame, STRIP_A, nil, color, level)
|
|
end
|
|
end
|
|
|
|
-- RollFor's classic loot window: the art is on the BACKGROUND layer and the
|
|
-- "Items" title on ARTWORK, so the shade fits between them. The strip runs
|
|
-- 12 to 30 px down, from the portrait to 25 px before the right edge (the
|
|
-- window width changes with the item names).
|
|
local function ShadeRollForLoot(frame, color)
|
|
ShadeStripInFrame(frame, { 68, -12, -25, -30 }, "BORDER", color)
|
|
end
|
|
|
|
golden.enable = function(self)
|
|
-- Both recolor the same textures; Darkened UI wins when both are ticked.
|
|
if ShaguTweaks_config and ShaguTweaks_config[T["Darkened UI"]] == 1 then return end
|
|
ShaguTweaks.GoldMode = true
|
|
Apply(golden.config["goldmode.color"], golden.config["goldmode.border"], false)
|
|
|
|
-- the softer, browner gold of WoW Forever's titles and labels instead of
|
|
-- the stock bright yellow (every text that uses the "normal" gold font)
|
|
local text = golden.config["goldmode.text"]
|
|
local r, g, b = tonumber(text.r) or .84, tonumber(text.g) or .72, tonumber(text.b) or .48
|
|
if NORMAL_FONT_COLOR then
|
|
NORMAL_FONT_COLOR.r, NORMAL_FONT_COLOR.g, NORMAL_FONT_COLOR.b = r, g, b
|
|
end
|
|
local fonts = { "GameFontNormal", "GameFontNormalSmall", "GameFontNormalLarge", "GameFontNormalHuge" }
|
|
for i = 1, table.getn(fonts) do
|
|
local font = _G[fonts[i]]
|
|
if font and font.SetTextColor then font:SetTextColor(r, g, b) end
|
|
end
|
|
|
|
ShadeTitleBars(golden.config["goldmode.bar"])
|
|
golden.rollforLoot = function(frame) ShadeRollForLoot(frame, golden.config["goldmode.bar"]) end
|
|
if _G.RollForLootFrameHeader then golden.rollforLoot(_G.RollForLootFrameHeader) end
|
|
end
|