This commit is contained in:
rucky
2026-03-16 13:48:46 +08:00
commit 2a55dd6dad
93 changed files with 75075 additions and 0 deletions

590
Minimap.lua Normal file
View File

@@ -0,0 +1,590 @@
--------------------------------------------------------------------------------
-- Nanami-UI: Minimap Skin (Minimap.lua)
-- Custom cat-paw pixel art frame for the minimap
--------------------------------------------------------------------------------
SFrames.Minimap = SFrames.Minimap or {}
local MM = SFrames.Minimap
local _A = SFrames.ActiveTheme
local MAP_STYLES = {
{ key = "map", tex = "Interface\\AddOns\\Nanami-UI\\img\\map", label = "猫爪", plateY = -10, textColor = {0.45, 0.32, 0.20} },
{ key = "zs", tex = "Interface\\AddOns\\Nanami-UI\\img\\zs", label = "战士", plateY = -6, textColor = {1, 1, 1} },
{ key = "qs", tex = "Interface\\AddOns\\Nanami-UI\\img\\qs", label = "圣骑士", plateY = -6, textColor = {0.22, 0.13, 0.07} },
{ key = "lr", tex = "Interface\\AddOns\\Nanami-UI\\img\\lr", label = "猎人", plateY = -6, textColor = {1, 1, 1} },
{ key = "qxz", tex = "Interface\\AddOns\\Nanami-UI\\img\\qxz", label = "潜行者", plateY = -6, textColor = {1, 1, 1} },
{ key = "ms", tex = "Interface\\AddOns\\Nanami-UI\\img\\ms", label = "牧师", plateY = -6, textColor = {0.22, 0.13, 0.07} },
{ key = "sm", tex = "Interface\\AddOns\\Nanami-UI\\img\\sm", label = "萨满", plateY = -6, textColor = {1, 1, 1} },
{ key = "fs", tex = "Interface\\AddOns\\Nanami-UI\\img\\fs", label = "法师", plateY = -6, textColor = {1, 1, 1} },
{ key = "ss", tex = "Interface\\AddOns\\Nanami-UI\\img\\ss", label = "术士", plateY = -6, textColor = {1, 1, 1} },
{ key = "dly", tex = "Interface\\AddOns\\Nanami-UI\\img\\dly", label = "德鲁伊", plateY = -6, textColor = {0.22, 0.13, 0.07} },
}
local TEX_SIZE = 512
local CIRCLE_CX = 256
local CIRCLE_CY = 256
local CIRCLE_R = 210
local PLATE_X = 103
local PLATE_Y = 29
local PLATE_W = 200
local PLATE_H = 66
local BASE_SIZE = 180
local CLASS_STYLE_MAP = {
["Warrior"] = "zs", ["WARRIOR"] = "zs", ["战士"] = "zs",
["Paladin"] = "qs", ["PALADIN"] = "qs", ["圣骑士"] = "qs",
["Hunter"] = "lr", ["HUNTER"] = "lr", ["猎人"] = "lr",
["Rogue"] = "qxz", ["ROGUE"] = "qxz", ["潜行者"] = "qxz",
["Priest"] = "ms", ["PRIEST"] = "ms", ["牧师"] = "ms",
["Shaman"] = "sm", ["SHAMAN"] = "sm", ["萨满祭司"] = "sm",
["Mage"] = "fs", ["MAGE"] = "fs", ["法师"] = "fs",
["Warlock"] = "ss", ["WARLOCK"] = "ss", ["术士"] = "ss",
["Druid"] = "dly", ["DRUID"] = "dly", ["德鲁伊"] = "dly",
}
local DEFAULTS = {
enabled = true,
scale = 1.0,
showClock = true,
showCoords = true,
mapStyle = "auto",
posX = -5,
posY = -5,
mailIconX = nil,
mailIconY = nil,
}
local container, overlayFrame, overlayTex
local zoneFs, clockFs, clockBg, coordFs
local built = false
--------------------------------------------------------------------------------
-- Helpers
--------------------------------------------------------------------------------
local function GetDB()
if not SFramesDB then SFramesDB = {} end
if type(SFramesDB.Minimap) ~= "table" then SFramesDB.Minimap = {} end
local db = SFramesDB.Minimap
for k, v in pairs(DEFAULTS) do
if db[k] == nil then db[k] = v end
end
return db
end
local function ResolveStyleKey()
local key = GetDB().mapStyle or "auto"
if key == "auto" then
local localName, engName = UnitClass("player")
key = CLASS_STYLE_MAP[engName]
or CLASS_STYLE_MAP[localName]
or "map"
end
return key
end
local function GetCurrentStyle()
local key = ResolveStyleKey()
for _, s in ipairs(MAP_STYLES) do
if s.key == key then return s end
end
return MAP_STYLES[1]
end
local function GetMapTexture()
return GetCurrentStyle().tex
end
local function S(texPx, frameSize)
return texPx / TEX_SIZE * frameSize
end
local function FrameSize()
return math.floor(BASE_SIZE * ((GetDB().scale) or 1))
end
local function ApplyPosition()
if not container then return end
local db = GetDB()
local x = tonumber(db.posX) or -5
local y = tonumber(db.posY) or -5
container:ClearAllPoints()
container:SetPoint("TOPRIGHT", UIParent, "TOPRIGHT", x, y)
end
--------------------------------------------------------------------------------
-- Hide default Blizzard minimap chrome
-- MUST be called AFTER BuildFrame (Minimap is already reparented)
--------------------------------------------------------------------------------
local function HideDefaultElements()
local kill = {
MinimapBorder,
MinimapBorderTop,
MinimapZoomIn,
MinimapZoomOut,
MinimapToggleButton,
MiniMapWorldMapButton,
GameTimeFrame,
MinimapZoneTextButton,
MiniMapTracking,
MinimapBackdrop,
}
for _, f in ipairs(kill) do
if f then
f:Hide()
f.Show = function() end
end
end
-- Hide all tracking-related frames (Turtle WoW dual tracking, etc.)
local trackNames = {
"MiniMapTrackingButton", "MiniMapTrackingFrame",
"MiniMapTrackingIcon", "MiniMapTracking1", "MiniMapTracking2",
}
for _, name in ipairs(trackNames) do
local f = _G[name]
if f and f.Hide then
f:Hide()
f.Show = function() end
end
end
-- Also hide any tracking textures that are children of Minimap
if Minimap.GetChildren then
local children = { Minimap:GetChildren() }
for _, child in ipairs(children) do
local n = child.GetName and child:GetName()
if n and string.find(n, "Track") then
child:Hide()
child.Show = function() end
end
end
end
-- Move MinimapCluster off-screen instead of Hide()
-- Hide() would cascade-hide children that were still parented at load time
if MinimapCluster then
MinimapCluster:ClearAllPoints()
MinimapCluster:SetPoint("TOP", UIParent, "BOTTOM", 0, -500)
MinimapCluster:SetWidth(1)
MinimapCluster:SetHeight(1)
MinimapCluster:EnableMouse(false)
end
end
--------------------------------------------------------------------------------
-- Build
--------------------------------------------------------------------------------
local function BuildFrame()
if built then return end
built = true
local fs = FrameSize()
local mapDiam = math.floor(S((CIRCLE_R + 8) * 2, fs))
-- Main container
container = CreateFrame("Frame", "SFramesMinimapContainer", UIParent)
container:SetWidth(fs)
container:SetHeight(fs)
container:SetFrameStrata("LOW")
container:SetFrameLevel(1)
container:EnableMouse(false)
container:SetClampedToScreen(true)
-- Reparent the actual minimap into our container
Minimap:SetParent(container)
Minimap:ClearAllPoints()
Minimap:SetPoint("CENTER", container, "TOPLEFT",
S(CIRCLE_CX, fs), -S(CIRCLE_CY, fs))
Minimap:SetWidth(mapDiam)
Minimap:SetHeight(mapDiam)
Minimap:SetFrameStrata("LOW")
Minimap:SetFrameLevel(2)
Minimap:Show()
if Minimap.SetMaskTexture then
Minimap:SetMaskTexture("Textures\\MinimapMask")
end
-- Decorative overlay (map.tga with transparent circle)
overlayFrame = CreateFrame("Frame", nil, container)
overlayFrame:SetAllPoints(container)
overlayFrame:SetFrameStrata("LOW")
overlayFrame:SetFrameLevel(Minimap:GetFrameLevel() + 3)
overlayFrame:EnableMouse(false)
overlayTex = overlayFrame:CreateTexture(nil, "ARTWORK")
overlayTex:SetTexture(GetMapTexture())
overlayTex:SetAllPoints(overlayFrame)
-- Zone name on the scroll plate (horizontally centered on frame)
local style = GetCurrentStyle()
local pcy = S(PLATE_Y + PLATE_H / 2 + (style.plateY or -6), fs)
zoneFs = overlayFrame:CreateFontString(nil, "OVERLAY")
zoneFs:SetFont(SFrames:GetFont(), 11, "")
zoneFs:SetPoint("CENTER", container, "TOP", 0, -pcy)
zoneFs:SetWidth(S(PLATE_W + 60, fs))
zoneFs:SetHeight(S(PLATE_H, fs))
zoneFs:SetJustifyH("CENTER")
zoneFs:SetJustifyV("MIDDLE")
local tc = style.textColor or {0.22, 0.13, 0.07}
zoneFs:SetTextColor(tc[1], tc[2], tc[3])
-- Clock background (semi-transparent rounded)
clockBg = CreateFrame("Frame", nil, overlayFrame)
clockBg:SetFrameLevel(overlayFrame:GetFrameLevel() + 1)
clockBg:SetBackdrop({
bgFile = "Interface\\Tooltips\\UI-Tooltip-Background",
edgeFile = "Interface\\Tooltips\\UI-Tooltip-Border",
tile = true, tileSize = 16, edgeSize = 10,
insets = { left = 2, right = 2, top = 2, bottom = 2 },
})
local _clkBg = _A.clockBg or { 0, 0, 0, 0.55 }
local _clkBd = _A.clockBorder or { 0, 0, 0, 0.3 }
clockBg:SetBackdropColor(_clkBg[1], _clkBg[2], _clkBg[3], _clkBg[4])
clockBg:SetBackdropBorderColor(_clkBd[1], _clkBd[2], _clkBd[3], _clkBd[4])
clockBg:SetWidth(46)
clockBg:SetHeight(18)
clockBg:SetPoint("CENTER", container, "BOTTOM", 0,
S((TEX_SIZE - CIRCLE_CY - CIRCLE_R) / 2, fs))
-- Clock text
clockFs = clockBg:CreateFontString(nil, "OVERLAY")
clockFs:SetFont(SFrames:GetFont(), 10, "OUTLINE")
clockFs:SetPoint("CENTER", clockBg, "CENTER", 0, 0)
clockFs:SetJustifyH("CENTER")
local _clkTxt = _A.clockText or { 0.92, 0.84, 0.72 }
clockFs:SetTextColor(_clkTxt[1], _clkTxt[2], _clkTxt[3])
-- Coordinates (inside circle, near bottom)
coordFs = overlayFrame:CreateFontString(nil, "OVERLAY")
coordFs:SetFont(SFrames:GetFont(), 9, "OUTLINE")
coordFs:SetPoint("BOTTOM", Minimap, "BOTTOM", 0, 8)
coordFs:SetJustifyH("CENTER")
local _coordTxt = _A.coordText or { 0.80, 0.80, 0.80 }
coordFs:SetTextColor(_coordTxt[1], _coordTxt[2], _coordTxt[3])
MM.container = container
MM.overlayFrame = overlayFrame
end
--------------------------------------------------------------------------------
-- Interactions
--------------------------------------------------------------------------------
local function SetupScrollZoom()
Minimap:EnableMouseWheel(true)
Minimap:SetScript("OnMouseWheel", function()
if arg1 > 0 then
Minimap_ZoomIn()
else
Minimap_ZoomOut()
end
end)
end
local function SetupMouseHandler()
Minimap:SetScript("OnMouseUp", function()
if arg1 == "RightButton" then
if MiniMapTrackingDropDown then
ToggleDropDownMenu(1, nil, MiniMapTrackingDropDown, "cursor")
end
else
if Minimap_OnClick then
Minimap_OnClick(this)
end
end
end)
end
MM.ApplyPosition = ApplyPosition
--------------------------------------------------------------------------------
-- Reposition child icons
--------------------------------------------------------------------------------
local function SetupMailDragging()
if not MiniMapMailFrame then return end
MiniMapMailFrame:SetMovable(true)
MiniMapMailFrame:EnableMouse(true)
MiniMapMailFrame:RegisterForDrag("LeftButton")
MiniMapMailFrame:SetScript("OnDragStart", function()
MiniMapMailFrame:StartMoving()
end)
MiniMapMailFrame:SetScript("OnDragStop", function()
MiniMapMailFrame:StopMovingOrSizing()
local db = GetDB()
local cx, cy = MiniMapMailFrame:GetCenter()
local ux, uy = UIParent:GetCenter()
if cx and cy and ux and uy then
db.mailIconX = cx - ux
db.mailIconY = cy - uy
end
end)
end
local function RepositionIcons()
if MiniMapMailFrame then
local db = GetDB()
MiniMapMailFrame:ClearAllPoints()
if db.mailIconX and db.mailIconY then
MiniMapMailFrame:SetPoint("CENTER", UIParent, "CENTER", db.mailIconX, db.mailIconY)
else
local mapDiam = Minimap:GetWidth()
MiniMapMailFrame:SetPoint("RIGHT", Minimap, "RIGHT", 8, mapDiam * 0.12)
end
MiniMapMailFrame:SetFrameStrata("LOW")
MiniMapMailFrame:SetFrameLevel((overlayFrame and overlayFrame:GetFrameLevel() or 5) + 2)
SetupMailDragging()
end
if MiniMapBattlefieldFrame then
MiniMapBattlefieldFrame:ClearAllPoints()
MiniMapBattlefieldFrame:SetPoint("BOTTOMLEFT", Minimap, "BOTTOM", 15, -8)
MiniMapBattlefieldFrame:SetFrameStrata("LOW")
MiniMapBattlefieldFrame:SetFrameLevel((overlayFrame and overlayFrame:GetFrameLevel() or 5) + 2)
end
end
--------------------------------------------------------------------------------
-- Update helpers
--------------------------------------------------------------------------------
local function UpdateZoneText()
if not zoneFs then return end
local text = GetMinimapZoneText and GetMinimapZoneText() or ""
lastZoneText = text
zoneFs:SetText(text)
end
local function SetZoneMap()
if SetMapToCurrentZone then
pcall(SetMapToCurrentZone)
end
end
local clockTimer = 0
local coordTimer = 0
local zoneTimer = 0
local lastZoneText = ""
local function OnUpdate()
local dt = arg1 or 0
local db = GetDB()
-- Clock (every 1 s)
clockTimer = clockTimer + dt
if clockTimer >= 1 then
clockTimer = 0
if db.showClock and clockFs then
local timeStr
if date then
timeStr = date("%H:%M")
else
local h, m = GetGameTime()
timeStr = string.format("%02d:%02d", h, m)
end
clockFs:SetText(timeStr)
clockFs:Show()
if clockBg then clockBg:Show() end
elseif clockFs then
clockFs:Hide()
if clockBg then clockBg:Hide() end
end
end
-- Zone text (every 1 s) catches late API updates missed by events
zoneTimer = zoneTimer + dt
if zoneTimer >= 1 then
zoneTimer = 0
if zoneFs and GetMinimapZoneText then
local cur = GetMinimapZoneText() or ""
if cur ~= lastZoneText then
lastZoneText = cur
zoneFs:SetText(cur)
end
end
end
-- Coordinates (every 0.25 s)
coordTimer = coordTimer + dt
if coordTimer >= 0.25 then
coordTimer = 0
if db.showCoords and coordFs and GetPlayerMapPosition then
local ok, x, y = pcall(GetPlayerMapPosition, "player")
if ok and x and y and (x > 0 or y > 0) then
coordFs:SetText(string.format("%.1f, %.1f", x * 100, y * 100))
coordFs:Show()
else
coordFs:Hide()
end
elseif coordFs then
coordFs:Hide()
end
end
end
--------------------------------------------------------------------------------
-- Public API
--------------------------------------------------------------------------------
function MM:Refresh()
if not container then return end
local fs = FrameSize()
local mapDiam = math.floor(S((CIRCLE_R + 8) * 2, fs))
container:SetWidth(fs)
container:SetHeight(fs)
Minimap:ClearAllPoints()
Minimap:SetPoint("CENTER", container, "TOPLEFT",
S(CIRCLE_CX, fs), -S(CIRCLE_CY, fs))
Minimap:SetWidth(mapDiam)
Minimap:SetHeight(mapDiam)
if zoneFs then
local style = GetCurrentStyle()
local pcy = S(PLATE_Y + PLATE_H / 2 + (style.plateY or -6), fs)
zoneFs:ClearAllPoints()
zoneFs:SetPoint("CENTER", container, "TOP", 0, -pcy)
zoneFs:SetWidth(S(PLATE_W + 60, fs))
local tc = style.textColor or {0.22, 0.13, 0.07}
zoneFs:SetTextColor(tc[1], tc[2], tc[3])
end
if clockBg then
clockBg:ClearAllPoints()
clockBg:SetPoint("CENTER", container, "BOTTOM", 0,
S((TEX_SIZE - CIRCLE_CY - CIRCLE_R) / 2, fs))
end
if coordFs then
coordFs:ClearAllPoints()
coordFs:SetPoint("BOTTOM", Minimap, "BOTTOM", 0, 8)
end
if overlayTex then
overlayTex:SetTexture(GetMapTexture())
end
UpdateZoneText()
RepositionIcons()
end
MM.MAP_STYLES = MAP_STYLES
--------------------------------------------------------------------------------
-- Shield: re-apply our skin after other addons (ShaguTweaks etc.) touch Minimap
--------------------------------------------------------------------------------
local shielded = false
local function ShieldMinimap()
if shielded then return end
shielded = true
-- Override any external changes to Minimap parent / position / size
if Minimap:GetParent() ~= container then
Minimap:SetParent(container)
end
local fs = FrameSize()
local mapDiam = math.floor(S((CIRCLE_R + 8) * 2, fs))
Minimap:ClearAllPoints()
Minimap:SetPoint("CENTER", container, "TOPLEFT",
S(CIRCLE_CX, fs), -S(CIRCLE_CY, fs))
Minimap:SetWidth(mapDiam)
Minimap:SetHeight(mapDiam)
Minimap:SetFrameStrata("LOW")
Minimap:SetFrameLevel(2)
Minimap:Show()
if Minimap.SetMaskTexture then
Minimap:SetMaskTexture("Textures\\MinimapMask")
end
HideDefaultElements()
-- Kill any border/backdrop that ShaguTweaks may have injected
local regions = { Minimap:GetRegions() }
for _, r in ipairs(regions) do
if r and r:IsObjectType("Texture") then
local tex = r.GetTexture and r:GetTexture()
if tex and type(tex) == "string" then
local low = string.lower(tex)
if string.find(low, "border") or string.find(low, "backdrop")
or string.find(low, "overlay") or string.find(low, "background") then
r:Hide()
end
end
end
end
shielded = false
end
function MM:Initialize()
if not Minimap then return end
local db = GetDB()
if db.enabled == false then return end
local ok, err = pcall(function()
-- Build first (reparents Minimap), THEN hide old chrome
BuildFrame()
HideDefaultElements()
-- Ensure Minimap is visible after reparent
Minimap:Show()
SetupScrollZoom()
SetupMouseHandler()
-- Apply position from settings
ApplyPosition()
RepositionIcons()
-- Zone text events
SFrames:RegisterEvent("ZONE_CHANGED", function()
SetZoneMap()
UpdateZoneText()
end)
SFrames:RegisterEvent("ZONE_CHANGED_NEW_AREA", function()
SetZoneMap()
UpdateZoneText()
end)
SFrames:RegisterEvent("ZONE_CHANGED_INDOORS", function()
SetZoneMap()
UpdateZoneText()
end)
-- Re-apply after other addons finish loading (ShaguTweaks etc.)
SFrames:RegisterEvent("PLAYER_ENTERING_WORLD", function()
pcall(ShieldMinimap)
pcall(UpdateZoneText)
end)
-- Delayed zone text: GetMinimapZoneText() may be empty at PLAYER_LOGIN
local zoneDelay = CreateFrame("Frame")
local elapsed = 0
zoneDelay:SetScript("OnUpdate", function()
elapsed = elapsed + (arg1 or 0)
if elapsed >= 1 then
zoneDelay:SetScript("OnUpdate", nil)
pcall(SetZoneMap)
pcall(UpdateZoneText)
end
end)
-- Tick
container:SetScript("OnUpdate", OnUpdate)
-- First refresh
SetZoneMap()
UpdateZoneText()
MM:Refresh()
end)
if not ok then
DEFAULT_CHAT_FRAME:AddMessage("|cffff4444Nanami-UI Minimap error: " .. tostring(err) .. "|r")
end
end