Files
Nanami-UI/Tweaks.lua
2026-03-16 13:48:46 +08:00

1052 lines
38 KiB
Lua

--------------------------------------------------------------------------------
-- Nanami-UI: Tweaks -- Ported from ShaguTweaks
-- 1. Auto Stance - auto switch warrior/druid stance on spell cast
-- 2. SuperWoW - GUID-based cast/channel data for SuperWoW client
-- 3. Turtle Compat - hide TW's overlapping target HP text, etc.
-- 4. Cooldown Numbers - show remaining cooldown time as text overlay
-- 5. Dark UI - darken the entire interface
-- 6. WorldMap Window - turn fullscreen map into a movable/scalable window
-- 7. Auto Dismount - cancel shapeshift/mount when casting incompatible spells
--------------------------------------------------------------------------------
SFrames.Tweaks = SFrames.Tweaks or {}
local Tweaks = SFrames.Tweaks
SFrames.castdb = SFrames.castdb or {}
local function GetTweaksCfg()
if not SFramesDB or type(SFramesDB.Tweaks) ~= "table" then
return { autoStance = true, superWoW = true, turtleCompat = true,
cooldownNumbers = true, darkUI = false, worldMapWindow = false }
end
return SFramesDB.Tweaks
end
local function strsplit(delimiter, str)
local result = {}
local pattern = "([^" .. delimiter .. "]+)"
for match in string.gfind(str, pattern) do
table.insert(result, match)
end
return unpack(result)
end
-- hooksecurefunc polyfill for WoW 1.12 (vanilla)
local _hooks = {}
local _hooksecurefunc = hooksecurefunc
if not _hooksecurefunc then
_hooksecurefunc = function(tbl, name, func)
if type(tbl) == "string" then
func = name
name = tbl
tbl = getfenv(0)
end
if not tbl or not tbl[name] then return end
local key = tostring(func)
_hooks[key] = {}
_hooks[key].old = tbl[name]
_hooks[key].new = func
tbl[name] = function(a1,a2,a3,a4,a5,a6,a7,a8,a9,a10)
local r1,r2,r3,r4,r5 = _hooks[key].old(a1,a2,a3,a4,a5,a6,a7,a8,a9,a10)
pcall(_hooks[key].new, a1,a2,a3,a4,a5,a6,a7,a8,a9,a10)
return r1,r2,r3,r4,r5
end
end
end
--------------------------------------------------------------------------------
-- Auto Stance
-- When a spell fails because you're in the wrong stance/form, automatically
-- switch to the required one. Works for warriors, druids, etc.
--------------------------------------------------------------------------------
local function InitAutoStance()
local frame = CreateFrame("Frame", "NanamiAutoStance")
local scanString = string.gsub(SPELL_FAILED_ONLY_SHAPESHIFT, "%%s", "(.+)")
frame:RegisterEvent("UI_ERROR_MESSAGE")
frame:SetScript("OnEvent", function()
for stances in string.gfind(arg1, scanString) do
for _, stance in pairs({ strsplit(",", stances) }) do
CastSpellByName(string.gsub(stance, "^%s*(.-)%s*$", "%1"))
end
end
end)
end
--------------------------------------------------------------------------------
-- Auto Dismount / Cancel Shapeshift
-- When casting a spell that fails because you are mounted or shapeshifted,
-- automatically cancel the mount/shapeshift buff so the next cast succeeds.
--------------------------------------------------------------------------------
local function InitAutoDismount()
local dismount = CreateFrame("Frame", "NanamiAutoDismount")
local scanner = CreateFrame("GameTooltip", "NanamiDismountScan", nil, "GameTooltipTemplate")
scanner:SetOwner(WorldFrame, "ANCHOR_NONE")
local mountStrings = {
"^Increases speed by (.+)%%",
"^Erhöht Tempo um (.+)%%",
"^Aumenta la velocidad en un (.+)%%",
"^Augmente la vitesse de (.+)%%",
"^Скорость увеличена на (.+)%%",
"^이동 속도 (.+)%%만큼 증가",
"^速度提高(.+)%%",
"speed based on", "Slow and steady...", "Riding",
"Lento y constante...", "Aumenta la velocidad según tu habilidad de Montar.",
"根据您的骑行技能提高速度。", "根据骑术技能提高速度。", "又慢又稳......",
}
local shapeshiftIcons = {
"ability_racial_bearform", "ability_druid_catform",
"ability_druid_travelform", "spell_nature_forceofnature",
"ability_druid_aquaticform", "spell_nature_spiritwolf",
"ability_druid_treeoflife", "ability_druid_stagform",
}
local errorStrings = {}
local errorGlobals = {
"SPELL_FAILED_NOT_MOUNTED", "ERR_ATTACK_MOUNTED", "ERR_TAXIPLAYERALREADYMOUNTED",
"SPELL_FAILED_NOT_SHAPESHIFT", "SPELL_FAILED_NO_ITEMS_WHILE_SHAPESHIFTED",
"SPELL_NOT_SHAPESHIFTED", "SPELL_NOT_SHAPESHIFTED_NOSPACE",
"ERR_CANT_INTERACT_SHAPESHIFTED", "ERR_NOT_WHILE_SHAPESHIFTED",
"ERR_NO_ITEMS_WHILE_SHAPESHIFTED", "ERR_TAXIPLAYERSHAPESHIFTED",
"ERR_MOUNT_SHAPESHIFTED",
}
for _, name in pairs(errorGlobals) do
local val = getfenv(0)[name]
if val then table.insert(errorStrings, val) end
end
dismount:RegisterEvent("UI_ERROR_MESSAGE")
dismount:SetScript("OnEvent", function()
if arg1 == SPELL_FAILED_NOT_STANDING then
SitOrStand()
return
end
local matched = false
for _, err in pairs(errorStrings) do
if arg1 == err then matched = true break end
end
if not matched then return end
for i = 0, 31 do
scanner:ClearLines()
scanner:SetPlayerBuff(i)
for line = 1, scanner:NumLines() do
local text = getfenv(0)["NanamiDismountScanTextLeft" .. line]
if text and text:GetText() then
for _, str in pairs(mountStrings) do
if string.find(text:GetText(), str) then
CancelPlayerBuff(i)
return
end
end
end
end
local buff = GetPlayerBuffTexture(i)
if buff then
for _, icon in pairs(shapeshiftIcons) do
if string.find(string.lower(buff), icon) then
CancelPlayerBuff(i)
return
end
end
end
end
end)
end
--------------------------------------------------------------------------------
-- SuperWoW Compatibility
-- Provides GUID-based cast/channel data when SuperWoW client mod is active.
-- Data stored in SFrames.castdb[guid] for consumption by castbar features.
--------------------------------------------------------------------------------
local function InitSuperWoW()
if not GetPlayerBuffID or not CombatLogAdd or not SpellInfo then return end
local castdb = SFrames.castdb
local frame = CreateFrame("Frame", "NanamiSuperWoW")
frame:RegisterEvent("UNIT_CASTEVENT")
frame:SetScript("OnEvent", function()
if arg3 == "START" or arg3 == "CAST" or arg3 == "CHANNEL" then
local guid = arg1
local spell, icon, _
if SpellInfo and SpellInfo(arg4) then
spell, _, icon = SpellInfo(arg4)
end
spell = spell or UNKNOWN
icon = icon or "Interface\\Icons\\INV_Misc_QuestionMark"
if not castdb[guid] then castdb[guid] = {} end
castdb[guid].cast = spell
castdb[guid].rank = nil
castdb[guid].start = GetTime()
castdb[guid].casttime = arg5
castdb[guid].icon = icon
castdb[guid].channel = (arg3 == "CHANNEL") or false
SFrames.superwow_active = true
elseif arg3 == "FAIL" then
local guid = arg1
if castdb[guid] then
castdb[guid].cast = nil
castdb[guid].rank = nil
castdb[guid].start = nil
castdb[guid].casttime = nil
castdb[guid].icon = nil
castdb[guid].channel = nil
end
end
end)
end
--------------------------------------------------------------------------------
-- Turtle WoW Compatibility
-- Hides TW's built-in target HP text that overlaps with Nanami-UI frames,
-- and applies other TW-specific fixes.
--------------------------------------------------------------------------------
local function ApplyWorldMapWindowLayout()
WorldMapFrame:SetMovable(true)
WorldMapFrame:EnableMouse(true)
WorldMapFrame:SetScale(.85)
WorldMapFrame:ClearAllPoints()
WorldMapFrame:SetPoint("CENTER", UIParent, "CENTER", 0, 30)
WorldMapFrame:SetWidth(WorldMapButton:GetWidth() + 15)
WorldMapFrame:SetHeight(WorldMapButton:GetHeight() + 55)
if WorldMapFrameTitle then
WorldMapFrameTitle:SetPoint("TOP", WorldMapFrame, 0, 17)
end
BlackoutWorld:Hide()
end
local function InitTurtleCompat()
if not TargetHPText or not TargetHPPercText then return end
TargetHPText:Hide()
TargetHPText.Show = function() return end
TargetHPPercText:Hide()
TargetHPPercText.Show = function() return end
if WorldMapFrame_Maximize then
local origMaximize = WorldMapFrame_Maximize
WorldMapFrame_Maximize = function()
origMaximize()
local cfg = GetTweaksCfg()
if cfg.worldMapWindow ~= false then
ApplyWorldMapWindowLayout()
elseif WorldMapFrameTitle then
WorldMapFrameTitle:SetPoint("TOP", WorldMapFrame, 0, 17)
end
end
end
end
--------------------------------------------------------------------------------
-- WorldMap Window
-- Turn the fullscreen world map into a movable, scalable window.
-- Ctrl+Scroll to zoom, Shift+Scroll to change transparency, drag to move.
--------------------------------------------------------------------------------
local function HookScript(frame, script, func)
local prev = frame:GetScript(script)
frame:SetScript(script, function(a1,a2,a3,a4,a5,a6,a7,a8,a9)
if prev then prev(a1,a2,a3,a4,a5,a6,a7,a8,a9) end
func(a1,a2,a3,a4,a5,a6,a7,a8,a9)
end)
end
local function InitWorldMapWindow()
if Cartographer or METAMAP_TITLE then return end
table.insert(UISpecialFrames, "WorldMapFrame")
local _G = getfenv(0)
_G.ToggleWorldMap = function()
if WorldMapFrame:IsShown() then
WorldMapFrame:Hide()
else
WorldMapFrame:Show()
end
end
UIPanelWindows["WorldMapFrame"] = { area = "center" }
HookScript(WorldMapFrame, "OnShow", function()
this:EnableKeyboard(false)
this:EnableMouseWheel(1)
WorldMapFrame:SetScale(.85)
WorldMapFrame:SetAlpha(1)
WorldMapFrame:SetFrameStrata("FULLSCREEN_DIALOG")
end)
HookScript(WorldMapFrame, "OnMouseWheel", function()
if IsShiftKeyDown() then
local newAlpha = WorldMapFrame:GetAlpha() + arg1 / 10
if newAlpha < 0.2 then newAlpha = 0.2 end
if newAlpha > 1 then newAlpha = 1 end
WorldMapFrame:SetAlpha(newAlpha)
elseif IsControlKeyDown() then
local newScale = WorldMapFrame:GetScale() + arg1 / 10
if newScale < 0.4 then newScale = 0.4 end
if newScale > 1.5 then newScale = 1.5 end
WorldMapFrame:SetScale(newScale)
end
end)
HookScript(WorldMapFrame, "OnMouseDown", function()
WorldMapFrame:StartMoving()
end)
HookScript(WorldMapFrame, "OnMouseUp", function()
WorldMapFrame:StopMovingOrSizing()
end)
ApplyWorldMapWindowLayout()
-- WorldMapTooltip: raw textures on a child frame (SetBackdrop is unreliable)
if WorldMapTooltip and not WorldMapTooltip._nanamiBG then
WorldMapTooltip._nanamiBG = true
local wmtBgFrame = CreateFrame("Frame", nil, WorldMapTooltip)
wmtBgFrame:SetAllPoints(WorldMapTooltip)
wmtBgFrame:SetFrameLevel(math.max(0, WorldMapTooltip:GetFrameLevel()))
local bg = wmtBgFrame:CreateTexture(nil, "BACKGROUND")
bg:SetTexture("Interface\\Buttons\\WHITE8X8")
bg:SetVertexColor(0.05, 0.05, 0.05, 1)
bg:SetAllPoints(wmtBgFrame)
local function MakeEdge(p1, r1, p2, r2, w, h)
local t = wmtBgFrame:CreateTexture(nil, "BORDER")
t:SetTexture("Interface\\Buttons\\WHITE8X8")
t:SetVertexColor(0.25, 0.25, 0.25, 1)
t:SetPoint(p1, WorldMapTooltip, r1)
t:SetPoint(p2, WorldMapTooltip, r2)
if w then t:SetWidth(w) end
if h then t:SetHeight(h) end
end
MakeEdge("TOPLEFT","TOPLEFT","TOPRIGHT","TOPRIGHT", nil, 1)
MakeEdge("BOTTOMLEFT","BOTTOMLEFT","BOTTOMRIGHT","BOTTOMRIGHT", nil, 1)
MakeEdge("TOPLEFT","TOPLEFT","BOTTOMLEFT","BOTTOMLEFT", 1, nil)
MakeEdge("TOPRIGHT","TOPRIGHT","BOTTOMRIGHT","BOTTOMRIGHT", 1, nil)
end
end
--------------------------------------------------------------------------------
-- Cooldown Numbers
-- Display remaining duration as text on every cooldown frame (>= 2 sec).
--------------------------------------------------------------------------------
local function TimeConvert(remaining)
local color = "|cffffffff"
if remaining < 5 then
color = "|cffff5555"
elseif remaining < 10 then
color = "|cffffff55"
end
if remaining < 60 then
return color .. math.ceil(remaining)
elseif remaining < 3600 then
return color .. math.ceil(remaining / 60) .. "m"
elseif remaining < 86400 then
return color .. math.ceil(remaining / 3600) .. "h"
else
return color .. math.ceil(remaining / 86400) .. "d"
end
end
local function CooldownOnUpdate()
local parent = this:GetParent()
if not parent then this:Hide() return end
if not this.tick then this.tick = GetTime() + 0.1 end
if this.tick > GetTime() then return end
this.tick = GetTime() + 0.1
this:SetAlpha(parent:GetAlpha())
if this.start < GetTime() then
local remaining = this.duration - (GetTime() - this.start)
if remaining > 0 then
this.text:SetText(TimeConvert(remaining))
else
this:Hide()
end
else
local time = time()
local startupTime = time - GetTime()
local cdTime = (2 ^ 32) / 1000 - this.start
local cdStartTime = startupTime - cdTime
local cdEndTime = cdStartTime + this.duration
local remaining = cdEndTime - time
if remaining >= 0 then
this.text:SetText(TimeConvert(remaining))
else
this:Hide()
end
end
end
local function IsActionBarButtonName(name)
if not name then return false end
return string.find(name, "^ActionButton%d+$")
or string.find(name, "^BonusActionButton%d+$")
or string.find(name, "^MultiBarBottomLeftButton%d+$")
or string.find(name, "^MultiBarBottomRightButton%d+$")
or string.find(name, "^MultiBarLeftButton%d+$")
or string.find(name, "^MultiBarRightButton%d+$")
or string.find(name, "^PetActionButton%d+$")
or string.find(name, "^ShapeshiftButton%d+$")
end
local function UpdateActionBarCooldownMask(cooldown)
if not cooldown then return end
if cooldown.cooldownmask then
cooldown.cooldownmask:SetAllPoints(cooldown)
cooldown.cooldownmask:SetFrameStrata(cooldown:GetFrameStrata())
cooldown.cooldownmask:SetFrameLevel(cooldown:GetFrameLevel() + 1)
end
if cooldown.cooldowntext then
cooldown.cooldowntext:SetAllPoints(cooldown)
cooldown.cooldowntext:SetFrameStrata(cooldown:GetFrameStrata())
cooldown.cooldowntext:SetFrameLevel(cooldown:GetFrameLevel() + 2)
end
end
local function CreateCoolDown(cooldown, start, duration)
if not cooldown then return end
local parent = cooldown:GetParent()
if not parent then return end
if cooldown.readable then return end
local parentname = parent and parent.GetName and parent:GetName()
parentname = parentname or "UnknownCooldownFrame"
cooldown.cooldowntext = CreateFrame("Frame", parentname .. "NanamiCDText", cooldown)
cooldown.cooldowntext:SetAllPoints(cooldown)
cooldown.cooldowntext:SetFrameStrata(cooldown:GetFrameStrata())
cooldown.cooldowntext:SetFrameLevel(cooldown:GetFrameLevel() + 2)
cooldown.cooldowntext.text = cooldown.cooldowntext:CreateFontString(
parentname .. "NanamiCDFont", "OVERLAY")
local isActionBar = IsActionBarButtonName(parentname)
local size = parent:GetHeight() or 0
size = size > 0 and size * 0.64 or 12
size = size > 14 and 14 or size
if isActionBar then
local bigSize = size * 1.22
if bigSize < 13 then bigSize = 13 end
if bigSize > 18 then bigSize = 18 end
cooldown.cooldowntext.text:SetFont(STANDARD_TEXT_FONT, bigSize, "THICKOUTLINE")
else
cooldown.cooldowntext.text:SetFont(STANDARD_TEXT_FONT, size, "OUTLINE")
end
cooldown.cooldowntext.text:SetDrawLayer("OVERLAY", 7)
if isActionBar then
cooldown.cooldownmask = CreateFrame("Frame", parentname .. "NanamiCDMask", cooldown)
cooldown.cooldownmask:SetAllPoints(cooldown)
cooldown.cooldownmask:SetFrameStrata(cooldown:GetFrameStrata())
cooldown.cooldownmask:SetFrameLevel(cooldown:GetFrameLevel() + 1)
local mask = cooldown.cooldownmask:CreateTexture(nil, "BACKGROUND")
mask:SetTexture("Interface\\Buttons\\WHITE8X8")
mask:SetAllPoints(cooldown.cooldownmask)
mask:SetDrawLayer("BACKGROUND", 0)
mask:SetVertexColor(0, 0, 0, 0.45)
cooldown.cooldownmask.mask = mask
cooldown.cooldowntext.text:SetPoint("CENTER", cooldown.cooldowntext, "CENTER", 0, 0)
else
cooldown.cooldowntext.text:SetPoint("CENTER", cooldown.cooldowntext, "CENTER", 0, 0)
end
cooldown.cooldowntext:SetScript("OnUpdate", CooldownOnUpdate)
end
local function SetCooldown(frame, start, duration, enable)
if not frame then return end
if frame.noCooldownCount then return end
if not duration or duration < 2 then
if frame.cooldowntext then
frame.cooldowntext:Hide()
end
if frame.cooldownmask then
frame.cooldownmask:Hide()
end
return
end
if not frame.cooldowntext then
CreateCoolDown(frame, start, duration)
end
if frame.cooldowntext then
UpdateActionBarCooldownMask(frame)
if start > 0 and duration > 0 and (not enable or enable > 0) then
if frame.cooldownmask then frame.cooldownmask:Show() end
frame.cooldowntext:Show()
else
if frame.cooldownmask then frame.cooldownmask:Hide() end
frame.cooldowntext:Hide()
end
frame.cooldowntext.start = start
frame.cooldowntext.duration = duration
end
end
local function InitCooldownNumbers()
_hooksecurefunc("CooldownFrame_SetTimer", SetCooldown)
end
--------------------------------------------------------------------------------
-- Quest Watch Timed Quest Countdown
-- Append remaining time for timed quests in the watched quest list (outside
-- quest detail UI), so players can see countdown directly on the main HUD.
--------------------------------------------------------------------------------
local function FormatQuestCountdown(seconds)
local s = tonumber(seconds) or 0
if s <= 0 then return "0s" end
local h = math.floor(s / 3600)
local m = math.floor(math.mod(s, 3600) / 60)
local sec = math.floor(math.mod(s, 60))
if h > 0 then
return string.format("%dh %02dm %02ds", h, m, sec)
elseif m > 0 then
return string.format("%dm %02ds", m, sec)
end
return string.format("%ds", sec)
end
local function InitQuestWatchCountdown()
if not QuestWatch_Update or not GetNumQuestWatches or not GetQuestLogTitle
or not GetQuestIndexForWatch or not GetQuestLogTimeLeft then
return
end
local function StripNanamiCountdown(text)
if not text then return text end
return string.gsub(text, " |cffffcc00%[剩余: [^%]]+%]|r$", "")
end
local applying = false
local function ApplyQuestWatchCountdown()
if applying then return end
applying = true
local timedByTitle = {}
local watchCount = tonumber(GetNumQuestWatches()) or 0
for i = 1, watchCount do
local questIndex = GetQuestIndexForWatch(i)
if questIndex and questIndex > 0 then
local title, level, tag, isHeader = GetQuestLogTitle(questIndex)
if title and not isHeader then
local timeLeft = GetQuestLogTimeLeft(questIndex)
if timeLeft and timeLeft > 0 then
timedByTitle[title] = FormatQuestCountdown(timeLeft)
end
end
end
end
local lineCount = tonumber(QUESTWATCHLINES) or 0
for i = 1, lineCount do
local fs = _G["QuestWatchLine" .. i]
if fs and fs.GetText and fs.SetText then
local text = fs:GetText()
if text and text ~= "" then
local clean = StripNanamiCountdown(text)
local timerText = timedByTitle[clean]
if timerText then
fs:SetText(clean .. " |cffffcc00[剩余: " .. timerText .. "]|r")
elseif clean ~= text then
fs:SetText(clean)
end
end
end
end
applying = false
end
_hooksecurefunc("QuestWatch_Update", ApplyQuestWatchCountdown)
local ticker = CreateFrame("Frame", "NanamiQuestWatchCountdownTicker")
ticker._e = 0
ticker:SetScript("OnUpdate", function()
this._e = (this._e or 0) + (arg1 or 0)
if this._e < 1.0 then return end
this._e = 0
if (tonumber(GetNumQuestWatches()) or 0) > 0 then
pcall(ApplyQuestWatchCountdown)
end
end)
end
--------------------------------------------------------------------------------
-- Dark UI
-- Turns the entire interface into darker colors by applying vertex color
-- tinting to all UI textures recursively.
--------------------------------------------------------------------------------
local function HookAddonOrVariable(addon, func)
local lurker = CreateFrame("Frame", nil)
lurker.func = func
lurker:RegisterEvent("ADDON_LOADED")
lurker:RegisterEvent("VARIABLES_LOADED")
lurker:RegisterEvent("PLAYER_ENTERING_WORLD")
lurker:SetScript("OnEvent", function()
if IsAddOnLoaded(addon) or getfenv(0)[addon] then
this:func()
this:UnregisterAllEvents()
end
end)
end
local borderBackdrop = {
edgeFile = "Interface\\Tooltips\\UI-Tooltip-Border",
tile = true, tileSize = 8, edgeSize = 16,
insets = { left = 0, right = 0, top = 0, bottom = 0 }
}
local function AddBorder(frame, inset, color)
if not frame then return end
if frame.NanamiBorder then return frame.NanamiBorder end
local top, right, bottom, left
if type(inset) == "table" then
top, right, bottom, left = unpack(inset)
left, bottom = -left, -bottom
end
frame.NanamiBorder = CreateFrame("Frame", nil, frame)
frame.NanamiBorder:SetPoint("TOPLEFT", frame, "TOPLEFT",
(left or -inset), (top or inset))
frame.NanamiBorder:SetPoint("BOTTOMRIGHT", frame, "BOTTOMRIGHT",
(right or inset), (bottom or -inset))
frame.NanamiBorder:SetBackdrop(borderBackdrop)
if color then
frame.NanamiBorder:SetBackdropBorderColor(color.r, color.g, color.b, 1)
end
return frame.NanamiBorder
end
local darkColor = { r = .3, g = .3, b = .3, a = .9 }
local darkBlacklist = {
["Solid Texture"] = true, ["WHITE8X8"] = true, ["StatusBar"] = true,
["BarFill"] = true, ["Portrait"] = true, ["Button"] = true,
["Icon"] = true, ["AddOns"] = true, ["StationeryTest"] = true,
["TargetDead"] = true, ["^KeyRing"] = true, ["GossipIcon"] = true,
["WorldMap\\(.+)\\"] = true, ["PetHappiness"] = true,
["Elite"] = true, ["Rare"] = true, ["ColorPickerWheel"] = true,
["ComboPoint"] = true, ["Skull"] = true,
["battlenetworking0"] = true, ["damage"] = true,
["tank"] = true, ["healer"] = true,
}
local darkRegionSkips = {
["ColorPickerFrame"] = { [15] = true }
}
local darkBackgrounds = {
["^SpellBookFrame$"] = { 325, 355, 17, -74 },
["^ItemTextFrame$"] = { 300, 355, 24, -74 },
}
local darkBorders = {
["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,
["Character(.+)Slot$"] = 3, ["Inspect(.+)Slot$"] = 3,
["ContainerFrame(.+)Item"] = 3, ["MainMenuBarBackpackButton$"] = 3,
["CharacterBag(.+)Slot$"] = 3, ["ChatFrame(.+)Button"] = -2,
["PetFrameHappiness"] = 2, ["MicroButton"] = { -21, 0, 0, 0 },
}
local darkAddonFrames = {
["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" },
-- ClassTrainerFrame replaced by TrainerUI.lua
}
local function IsDarkBlacklisted(texture)
local name = texture:GetName()
local tex = texture:GetTexture()
if not tex then return true end
if name then
for entry in pairs(darkBlacklist) do
if string.find(name, entry, 1) then return true end
end
end
for entry in pairs(darkBlacklist) do
if string.find(tex, entry, 1) then return true end
end
return nil
end
local function AddSpecialBackground(frame, w, h, x, y)
frame.NanamiMaterial = frame.NanamiMaterial or frame:CreateTexture(nil, "OVERLAY")
frame.NanamiMaterial:SetTexture("Interface\\Stationery\\StationeryTest1")
frame.NanamiMaterial:SetWidth(w)
frame.NanamiMaterial:SetHeight(h)
frame.NanamiMaterial:SetPoint("TOPLEFT", frame, x, y)
frame.NanamiMaterial:SetVertexColor(.8, .8, .8)
end
local darkFrameSkips = {
["^SFramesChat"] = true,
["^SFramesPlayer"] = true,
["^SFramesTarget"] = true,
["^SFramesParty"] = true,
["^SFramesRaid"] = true,
["^SFramesMBuff"] = true,
["^SFramesMDebuff"] = true,
["^GameTooltip"] = true,
}
local function DarkenFrame(frame, r, g, b, a)
if not r and not g and not b then
r, g, b, a = darkColor.r, darkColor.g, darkColor.b, darkColor.a
end
local fname = frame and frame.GetName and frame:GetName()
if fname then
for pattern in pairs(darkFrameSkips) do
if string.find(fname, pattern) then return end
end
end
if frame and frame.GetChildren then
for _, child in pairs({ frame:GetChildren() }) do
DarkenFrame(child, r, g, b, a)
end
end
if frame and frame.GetRegions then
local name = frame.GetName and frame:GetName()
if frame.SetBackdropBorderColor then
frame:SetBackdropBorderColor(darkColor.r, darkColor.g, darkColor.b, darkColor.a)
end
for pattern, inset in pairs(darkBackgrounds) do
if name and string.find(name, pattern) then
AddSpecialBackground(frame, inset[1], inset[2], inset[3], inset[4])
end
end
for pattern, inset in pairs(darkBorders) do
if name and string.find(name, pattern) then
AddBorder(frame, inset, darkColor)
end
end
for id, region in pairs({ frame:GetRegions() }) do
if region.SetVertexColor and region:GetObjectType() == "Texture" then
if name and id and darkRegionSkips[name] and darkRegionSkips[name][id] then
-- skip
elseif region.GetBlendMode and region:GetBlendMode() == "ADD" then
-- skip blend textures
elseif IsDarkBlacklisted(region) then
-- skip blacklisted
else
region:SetVertexColor(r, g, b, a)
end
end
end
end
end
--------------------------------------------------------------------------------
-- StaticPopup Theme Skin
--------------------------------------------------------------------------------
local function InitPopupSkin()
local popupSkinned = {}
local font = (SFrames and SFrames.GetFont and SFrames:GetFont()) or "Fonts\\ARKai_T.ttf"
local _A = SFrames.ActiveTheme
local P = {
bg = _A.panelBg or { 0.12, 0.06, 0.10, 0.95 },
border = _A.panelBorder or { 0.55, 0.30, 0.42, 0.9 },
btnBg = _A.btnBg or { 0.18, 0.10, 0.15, 0.94 },
btnBd = _A.btnBorder or { 0.50, 0.30, 0.40, 0.80 },
text = _A.nameText or { 0.90, 0.88, 0.94 },
}
local function SkinButton(btn)
if not btn then return end
local regions = { btn:GetRegions() }
for _, r in ipairs(regions) do
if r and r.SetTexture and r:GetObjectType() == "Texture" then
local tex = r:GetTexture()
if tex and type(tex) == "string" and (string.find(tex, "UI%-Panel") or string.find(tex, "UI%-DialogBox")) then
r:Hide()
end
end
end
btn:SetBackdrop({
bgFile = "Interface\\Buttons\\WHITE8X8",
edgeFile = "Interface\\Buttons\\WHITE8X8",
tile = false, tileSize = 0, edgeSize = 1,
insets = { left = 1, right = 1, top = 1, bottom = 1 },
})
btn:SetBackdropColor(P.btnBg[1], P.btnBg[2], P.btnBg[3], P.btnBg[4])
btn:SetBackdropBorderColor(P.btnBd[1], P.btnBd[2], P.btnBd[3], P.btnBd[4])
local fs = btn:GetFontString()
if fs then
fs:SetFont(font, 12, "OUTLINE")
fs:SetTextColor(P.text[1], P.text[2], P.text[3])
end
end
local function SkinPopupFrame(frame)
if not frame then return end
local frameName = frame:GetName()
if not frameName then return end
if not popupSkinned[frameName] then
popupSkinned[frameName] = true
local regions = { frame:GetRegions() }
for _, r in ipairs(regions) do
if r and r:GetObjectType() == "Texture" then
local dl = r:GetDrawLayer()
if dl == "BACKGROUND" or dl == "BORDER" or dl == "ARTWORK" then
local tex = r:GetTexture() or ""
if type(tex) == "string" and (string.find(tex, "UI%-DialogBox") or string.find(tex, "UI%-Panel")) then
r:Hide()
end
end
end
end
frame:SetBackdrop({
bgFile = "Interface\\Tooltips\\UI-Tooltip-Background",
edgeFile = "Interface\\Tooltips\\UI-Tooltip-Border",
tile = true, tileSize = 16, edgeSize = 14,
insets = { left = 3, right = 3, top = 3, bottom = 3 },
})
frame:SetBackdropColor(P.bg[1], P.bg[2], P.bg[3], P.bg[4])
frame:SetBackdropBorderColor(P.border[1], P.border[2], P.border[3], P.border[4])
local textFS = _G[frameName .. "Text"]
if textFS and textFS.SetFont then
textFS:SetFont(font, 13, "OUTLINE")
textFS:SetTextColor(P.text[1], P.text[2], P.text[3])
end
local editBox = _G[frameName .. "EditBox"]
if editBox then
editBox:SetBackdrop({
bgFile = "Interface\\Buttons\\WHITE8X8",
edgeFile = "Interface\\Buttons\\WHITE8X8",
tile = false, edgeSize = 1,
insets = { left = 1, right = 1, top = 1, bottom = 1 },
})
editBox:SetBackdropColor(0.05, 0.03, 0.05, 0.9)
editBox:SetBackdropBorderColor(P.btnBd[1], P.btnBd[2], P.btnBd[3], P.btnBd[4])
editBox:SetFont(font, 12, "OUTLINE")
editBox:SetTextColor(P.text[1], P.text[2], P.text[3])
end
local moneyFrame = _G[frameName .. "MoneyFrame"]
if moneyFrame then
local mRegions = { moneyFrame:GetRegions() }
for _, r in ipairs(mRegions) do
if r and r.SetFont then r:SetFont(font, 12, "OUTLINE") end
end
end
end
for _, suffix in ipairs({"Button1", "Button2", "Button3"}) do
local btn = _G[frameName .. suffix]
if btn then SkinButton(btn) end
end
end
for i = 1, 4 do
local f = _G["StaticPopup" .. i]
if f then pcall(SkinPopupFrame, f) end
end
if StaticPopup_Show then
local origShow = StaticPopup_Show
StaticPopup_Show = function(a1, a2, a3, a4)
local dialog = origShow(a1, a2, a3, a4)
if dialog then
pcall(SkinPopupFrame, dialog)
end
return dialog
end
end
end
local function InitDarkUI()
local hookBuffButton_Update = BuffButton_Update
BuffButton_Update = function(buttonName, index, filter)
hookBuffButton_Update(buttonName, index, filter)
local name = buttonName and index and buttonName .. index or this:GetName()
local original = getfenv(0)[name .. "Border"]
if original and this.NanamiBorder then
local r, g, b = original:GetVertexColor()
this.NanamiBorder:SetBackdropBorderColor(r, g, b, 1)
original:SetAlpha(0)
end
end
TOOLTIP_DEFAULT_COLOR.r = darkColor.r
TOOLTIP_DEFAULT_COLOR.g = darkColor.g
TOOLTIP_DEFAULT_COLOR.b = darkColor.b
TOOLTIP_DEFAULT_BACKGROUND_COLOR.r = darkColor.r
TOOLTIP_DEFAULT_BACKGROUND_COLOR.g = darkColor.g
TOOLTIP_DEFAULT_BACKGROUND_COLOR.b = darkColor.b
DarkenFrame(UIParent)
DarkenFrame(WorldMapFrame)
DarkenFrame(DropDownList1)
DarkenFrame(DropDownList2)
DarkenFrame(DropDownList3)
local bars = { "Action", "BonusAction", "MultiBarBottomLeft",
"MultiBarBottomRight", "MultiBarLeft", "MultiBarRight", "Shapeshift" }
for _, prefix in pairs(bars) do
for i = 1, NUM_ACTIONBAR_BUTTONS do
local button = getfenv(0)[prefix .. "Button" .. i]
local texture = getfenv(0)[prefix .. "Button" .. i .. "NormalTexture"]
if button and texture then
texture:SetWidth(60)
texture:SetHeight(60)
texture:SetPoint("CENTER", 0, 0)
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(darkColor.r + .2, darkColor.g + .2, darkColor.b + .2, 1)
end
end
end
end
for addon, data in pairs(darkAddonFrames) do
local skip = false
if SFramesDB and SFramesDB.enableTradeSkill ~= false then
if addon == "Blizzard_TradeSkillUI" or addon == "Blizzard_CraftUI" then
skip = true
end
end
if not skip then
for _, frameName in pairs(data) do
local fn = frameName
HookAddonOrVariable(fn, function()
DarkenFrame(getfenv(0)[fn])
end)
end
end
end
HookAddonOrVariable("Blizzard_TimeManager", function()
DarkenFrame(TimeManagerClockButton)
end)
HookAddonOrVariable("GameTooltipStatusBarBackdrop", function()
DarkenFrame(getfenv(0)["GameTooltipStatusBarBackdrop"])
end)
end
--------------------------------------------------------------------------------
-- Module API
--------------------------------------------------------------------------------
function Tweaks:Initialize()
local cfg = GetTweaksCfg()
if cfg.autoStance ~= false then
local ok, err = pcall(InitAutoStance)
if not ok then
DEFAULT_CHAT_FRAME:AddMessage("|cffff4444Nanami-UI: AutoStance init failed: " .. tostring(err) .. "|r")
end
end
if cfg.autoDismount ~= false then
local ok, err = pcall(InitAutoDismount)
if not ok then
DEFAULT_CHAT_FRAME:AddMessage("|cffff4444Nanami-UI: AutoDismount init failed: " .. tostring(err) .. "|r")
end
end
if cfg.superWoW ~= false then
local ok, err = pcall(InitSuperWoW)
if not ok then
DEFAULT_CHAT_FRAME:AddMessage("|cffff4444Nanami-UI: SuperWoW init failed: " .. tostring(err) .. "|r")
end
end
if cfg.turtleCompat ~= false then
local ok, err = pcall(InitTurtleCompat)
if not ok then
DEFAULT_CHAT_FRAME:AddMessage("|cffff4444Nanami-UI: TurtleCompat init failed: " .. tostring(err) .. "|r")
end
end
if SFrames.WorldMap and SFrames.WorldMap.initialized then
-- New WorldMap module has taken over; skip legacy code
elseif cfg.worldMapWindow ~= false then
local ok, err = pcall(InitWorldMapWindow)
if not ok then
DEFAULT_CHAT_FRAME:AddMessage("|cffff4444Nanami-UI: WorldMapWindow init failed: " .. tostring(err) .. "|r")
end
end
if cfg.cooldownNumbers ~= false then
local ok, err = pcall(InitCooldownNumbers)
if not ok then
DEFAULT_CHAT_FRAME:AddMessage("|cffff4444Nanami-UI: CooldownNumbers init failed: " .. tostring(err) .. "|r")
end
end
do
local ok, err = pcall(InitQuestWatchCountdown)
if not ok then
DEFAULT_CHAT_FRAME:AddMessage("|cffff4444Nanami-UI: QuestWatchCountdown init failed: " .. tostring(err) .. "|r")
end
end
if cfg.darkUI then
local ok, err = pcall(InitDarkUI)
if not ok then
DEFAULT_CHAT_FRAME:AddMessage("|cffff4444Nanami-UI: DarkUI init failed: " .. tostring(err) .. "|r")
end
end
do
local ok, err = pcall(InitPopupSkin)
if not ok then
DEFAULT_CHAT_FRAME:AddMessage("|cffff4444Nanami-UI: PopupSkin init failed: " .. tostring(err) .. "|r")
end
end
end