-------------------------------------------------------------------------------- -- 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