Files
ShaguTweaks-ClassicAPI/mods/dark-ui-elements.lua
T

998 lines
37 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.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 },
}
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)
if string.find(lower, "interface\\mailframe\\mailitemborder", 1, true) then
-- This includes the brown inset of each inbox slot, not just its edge.
return true
end
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 IsSlotArtwork(path)
if type(path) ~= "string" then return end
path = string.lower(path)
return string.find(path, "interface\\buttons\\ui-slot-background", 1, true)
or string.find(path, "bagshui\\images\\itemslot\\emptyslot", 1, true)
end
-- Leave the original artwork white. Four narrow copies color only its rim.
-- Anchor to the owning button (not a Texture region), so pooled Bagshui
-- buttons carry every strip with them when resized, moved or hidden.
local function TintSlotBorder(texture, button)
local path = texture:GetTexture()
local strips = texture.stSlotEdges
if not IsSlotArtwork(path) then
if strips then
for i = 1, 4 do strips[i]:Hide() end
end
return
end
if not strips then
strips = {}
texture.stSlotEdges = strips
for i = 1, 4 do
strips[i] = button:CreateTexture(nil, "BORDER")
strips[i].stSlotEdge = true
end
strips[1]:SetPoint("TOPLEFT", button, "TOPLEFT", -1, 1)
strips[1]:SetPoint("TOPRIGHT", button, "TOPRIGHT", 1, 1)
strips[1]:SetHeight(3)
strips[2]:SetPoint("BOTTOMLEFT", button, "BOTTOMLEFT", -1, -1)
strips[2]:SetPoint("BOTTOMRIGHT", button, "BOTTOMRIGHT", 1, -1)
strips[2]:SetHeight(3)
strips[3]:SetPoint("TOPLEFT", button, "TOPLEFT", -1, -2)
strips[3]:SetPoint("BOTTOMLEFT", button, "BOTTOMLEFT", -1, 2)
strips[3]:SetWidth(3)
strips[4]:SetPoint("TOPRIGHT", button, "TOPRIGHT", 1, -2)
strips[4]:SetPoint("BOTTOMRIGHT", button, "BOTTOMRIGHT", 1, 2)
strips[4]:SetWidth(3)
end
local left, top, bottomLeft, bottom, right = texture:GetTexCoord()
local w, h = button:GetWidth() + 2, button:GetHeight() + 2
if w <= 6 or h <= 6 then w, h = 39, 39 end
local dx, dy = (right - left) * 3 / w, (bottom - top) * 3 / h
texture:SetVertexColor(1, 1, 1, 1)
strips[1]:SetTexCoord(left, right, top, top + dy)
strips[2]:SetTexCoord(left, right, bottom - dy, bottom)
strips[3]:SetTexCoord(left, left + dx, top + dy, bottom - dy)
strips[4]:SetTexCoord(right - dx, right, top + dy, bottom - dy)
for i = 1, 4 do
strips[i]:SetTexture(path)
strips[i]:SetVertexColor(edge.r, edge.g, edge.b, 1)
strips[i]:SetAlpha(texture:GetAlpha())
strips[i]:Show()
end
end
-- TrinketMenu owns its styling; its future fork must not compete with this
-- palette, including the initial recursive pass over UIParent.
local function IsExcludedFrame(frame)
local name = frame and frame.GetName and frame:GetName()
return name and string.find(name, "TrinketMenu", 1, true) == 1
end
local function RestoreQuestParchment(frame)
local name = frame.GetName and frame:GetName()
if not name or not (string.find(name, "^QuestFrame.*Panel$")
or name == "GossipFrameGreetingPanel") then return end
for _, source in pairs({frame:GetRegions()}) do
local path = source.GetTexture and source:GetTexture()
if type(path) == "string" and not source.stQuestPaper and not source.stOriginalQuestPaper then
local lower = string.lower(path)
local left = string.find(lower, "%-topleft$") or string.find(lower, "%-botleft$")
local right = string.find(lower, "%-topright$") or string.find(lower, "%-botright$")
local top = string.find(lower, "%-topleft$") or string.find(lower, "%-topright$")
if (left or right) and (string.find(lower, "quest", 1, true) or string.find(lower, "gossip", 1, true)) then
-- Native 256px tiles contain both the frame and torn parchment.
-- Copy only the paper area; leave the palette on the outer artwork.
local x1, x2 = left and 22 or 0, left and 256 or 67
local y1, y2 = top and 74 or 0, top and 256 or 148
-- Use a higher layer than both the native BACKGROUND tiles and
-- BORDER material textures. Equal-layer texture batching can draw
-- tinted native tiles over only some of our restored paper pieces.
local paper = frame:CreateTexture(nil, "ARTWORK")
paper.stOriginalQuestPaper = true
paper:SetTexture(path)
local tileWidth = left and 256 or 128
paper:SetTexCoord(x1/tileWidth, x2/tileWidth, y1/256, y2/256)
local sx, sy = source:GetWidth()/tileWidth, source:GetHeight()/256
paper:SetWidth((x2-x1)*sx)
paper:SetHeight((y2-y1)*sy)
paper:SetPoint("TOPLEFT", source, "TOPLEFT", x1*sx, -y1*sy)
paper:SetVertexColor(1, 1, 1, 1)
source.stQuestPaper = paper
end
end
end
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
if IsExcludedFrame(frame) 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 region.stOriginalQuestPaper then
-- Untinted copies of native quest/gossip parchment.
elseif region.stSlotEdge then
-- Border strips are maintained by their source texture.
elseif IsSlotArtwork(region:GetTexture()) then
TintSlotBorder(region, frame)
elseif name == "InboxFrame" then
-- The inbox artwork includes its parchment. Tinting it turns the
-- paper orange; covering it with stationery leaves a straight seam.
-- Keep these regions stock; child buttons and the mail header still
-- receive the palette through their own frames.
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
if frame and frame.GetRegions then RestoreQuestParchment(frame) 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", "OctoBus", "RollFor", "VCB_",
"aux_frame", "AuxCraft", "AuxDragOutline",
-- Filled mail slots get rarity borders after their item texture changes.
"MailAttachment", "SendMailPackageButton",
}
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 not self.stPreserveRarityColor and 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)
local r, g, b, a
if self.stPreserveRarityColor and self.GetBackdropBorderColor then
r, g, b, a = self:GetBackdropBorderColor()
end
setBackdrop(self, backdrop)
if backdrop and backdrop.edgeFile then
if r then
setColor(self, r, g, b, a)
else
setColor(self, edge.r, edge.g, edge.b, 1)
end
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
if IsExcludedFrame(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 IsExcludedFrame(child) then
-- Leave the whole TrinketMenu subtree to its own styling code.
elseif 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
if IsExcludedFrame(frame) 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()
-- Bagshui restores its empty-slot artwork to white whenever items are
-- assigned. Reapply only to that background, leaving icons, quality
-- borders and user-configured solid background colors alone.
HookAddonOrVariable("Bagshui", function()
local ui = _G.Bagshui and _G.Bagshui.prototypes and _G.Bagshui.prototypes.Ui
if not ui or not ui.UpdateItemButtonColorsAndBadges then return end
ShaguTweaks.hooksecurefunc(ui, "UpdateItemButtonColorsAndBadges", function(self, button)
local data = button and button.bagshuiData
local background = data and data.buttonComponents and data.buttonComponents.background
if background then TintSlotBorder(background, button) end
end)
end)
-- 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
-- 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)
if not title then return end
local copy = holder:CreateFontString(nil, "OVERLAY", "GameFontNormal")
local font = title.GetFontObject and title:GetFontObject()
if font then copy:SetFontObject(font) 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)
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)
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
-- MailFrame's four art tiles contain both the metal frame and the paper.
-- Restore cropped copies of the original paper above the tinted tiles, below
-- the inbox's child frames. Parent them to InboxFrame so they hide on the
-- Send Mail and TurtleMail Log tabs. Matching the original pixels preserves the torn
-- edges and tile seams without replacing the parchment with stationery.
local function RestoreMailParchment()
if not InboxFrame or InboxFrame.stOriginalParchment then return end
InboxFrame.stOriginalParchment = true
local tiles = {
{ "MailFrameTopLeft", 22, 74, 256, 256 },
{ "MailFrameTopRight", 0, 74, 80, 256 },
{ "MailFrameBotLeft", 22, 0, 256, 170 },
{ "MailFrameBotRight", 0, 0, 80, 170 },
}
for i = 1, table.getn(tiles) do
local tile = tiles[i]
local source = _G[tile[1]]
if source and source:GetTexture() then
local w, h = source:GetWidth(), source:GetHeight()
local paper = InboxFrame:CreateTexture(nil, "BACKGROUND")
paper:SetTexture(source:GetTexture())
paper:SetVertexColor(1, 1, 1, 1)
paper:SetTexCoord(tile[2] / w, tile[4] / w, tile[3] / h, tile[5] / h)
paper:SetWidth(tile[4] - tile[2])
paper:SetHeight(tile[5] - tile[3])
paper:SetPoint("TOPLEFT", source, "TOPLEFT", tile[2], -tile[3])
end
end
end
module.enable = function(self)
ShaguTweaks.DarkMode = true
Apply(module.config["darkmode.color"], module.config["darkmode.color"], true)
RestoreMailParchment()
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)
-- 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
local function GoldenUnitNameBar(frame)
local title = frame and frame.name
if not title or frame.stGoldenNameBar then return end
local parent = title:GetParent()
local point, relative, relativePoint, x, y = title:GetPoint(1)
if not parent or not point then return end
local bar = CreateFrame("Frame", nil, parent)
bar:SetFrameLevel(parent:GetFrameLevel() + 1)
bar:SetWidth(119)
bar:SetHeight(14)
bar:SetPoint(point, relative, relativePoint, x, y)
bar.texture = bar:CreateTexture(nil, "BACKGROUND")
-- Opaque stone from the same native title strip used by the character
-- window, excluding its metal edges and portrait. No world bleed-through.
bar.texture:SetTexture("Interface\\PaperDollInfoFrame\\UI-Character-General-TopLeft")
bar.texture:SetTexCoord(80/256, 255/256, 18/256, 32/256)
bar.texture:SetAllPoints(bar)
local color = golden.config["goldmode.bar"]
bar.texture:SetVertexColor((tonumber(tint.r) or 1) * (tonumber(color.r) or .8),
(tonumber(tint.g) or .78) * (tonumber(color.g) or .63),
(tonumber(tint.b) or .4) * (tonumber(color.b) or .62), 1)
local originalAlpha = title:GetAlpha()
bar.title = RedrawTitle(bar, title)
frame.stGoldenNameBar = bar
local function UpdateNameBar()
if UnitExists("target") and not UnitIsPlayer("target") then
title:SetAlpha(0)
bar:Show()
else
bar:Hide()
title:SetAlpha(originalAlpha)
end
end
bar:RegisterEvent("PLAYER_TARGET_CHANGED")
bar:RegisterEvent("PLAYER_ENTERING_WORLD")
bar:SetScript("OnEvent", UpdateNameBar)
ShaguTweaks.HookScript(frame, "OnShow", UpdateNameBar)
UpdateNameBar()
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)
ShadeTitleBars(golden.config["goldmode.bar"])
-- The opaque title artwork is only for NPC targets. Player names retain
-- their native/class-colored background, including our own player frame.
-- Big Health moves the name into the enlarged health fill. An opaque
-- header there would cover its upper portion, regardless of module order.
if not ShaguTweaks_config or ShaguTweaks_config[T["Unit Frame Big Health for Target"]] ~= 1 then
GoldenUnitNameBar(TargetFrame)
end
RestoreMailParchment()
golden.rollforLoot = function(frame) ShadeRollForLoot(frame, golden.config["goldmode.bar"]) end
if _G.RollForLootFrameHeader then golden.rollforLoot(_G.RollForLootFrameHeader) end
end