local NP = NanamiPlates local Settings = NP.Settings local optionsFrame = nil local minimapButton = nil -- ============================================ -- THEME HELPERS -- ============================================ local function C(key, fr, fg, fb, fa) return NP.GetThemeColor(key, fr, fg, fb, fa or 1) end local function MakeBackdrop(f, bgKey, brKey) local bgR, bgG, bgB, bgA = C(bgKey or "panelBg", 0.10, 0.06, 0.10, 0.95) local brR, brG, brB, brA = C(brKey or "panelBorder", 0.55, 0.30, 0.42, 0.9) f:SetBackdrop({ bgFile = "Interface\\Buttons\\WHITE8X8", edgeFile = "Interface\\Buttons\\WHITE8X8", tile = false, tileSize = 0, edgeSize = 1, insets = { left = 1, right = 1, top = 1, bottom = 1 } }) f:SetBackdropColor(bgR, bgG, bgB, bgA) f:SetBackdropBorderColor(brR, brG, brB, brA) end local function SectionLabel(parent, text, x, y) local lbl = parent:CreateFontString(nil, "OVERLAY") lbl:SetFont(NP.GetFont(), 11, NP.GetFontOutline()) lbl:SetPoint("TOPLEFT", parent, "TOPLEFT", x, y) local acR, acG, acB = C("accent", 1.0, 0.5, 0.8, 1) lbl:SetTextColor(acR, acG, acB, 0.85) lbl:SetText(text) return lbl end -- ============================================ -- CHECKBOX -- ============================================ local checkIdx = 0 local function CreateCheckbox(parent, label, settingKey, x, y, tooltip) checkIdx = checkIdx + 1 local size = 16 local frame = CreateFrame("Button", "NP_Chk" .. checkIdx, parent) frame:SetWidth(size) frame:SetHeight(size) frame:SetPoint("TOPLEFT", parent, "TOPLEFT", x, y) local cbBgR, cbBgG, cbBgB = C("checkBg", 0.08, 0.04, 0.08, 1) local cbBrR, cbBrG, cbBrB = C("checkBorder", 0.45, 0.25, 0.38, 1) frame:SetBackdrop({ bgFile = "Interface\\Buttons\\WHITE8X8", edgeFile = "Interface\\Buttons\\WHITE8X8", tile = false, tileSize = 0, edgeSize = 1, insets = { left = 0, right = 0, top = 0, bottom = 0 } }) frame:SetBackdropColor(cbBgR, cbBgG, cbBgB, 1) frame:SetBackdropBorderColor(cbBrR, cbBrG, cbBrB, 1) local mark = frame:CreateTexture(nil, "OVERLAY") mark:SetTexture("Interface\\Buttons\\WHITE8X8") mark:SetPoint("TOPLEFT", frame, "TOPLEFT", 3, -3) mark:SetPoint("BOTTOMRIGHT", frame, "BOTTOMRIGHT", -3, 3) local fillR, fillG, fillB = C("checkOn", 0.9, 0.45, 0.7, 1) mark:SetVertexColor(fillR, fillG, fillB, 1) local text = frame:CreateFontString(nil, "OVERLAY") text:SetFont(NP.GetFont(), 10, NP.GetFontOutline()) text:SetPoint("LEFT", frame, "RIGHT", 6, 0) local txtR, txtG, txtB = C("text", 0.85, 0.85, 0.85, 1) text:SetTextColor(txtR, txtG, txtB) text:SetText(label) local function Upd() if Settings[settingKey] then mark:Show() local onR, onG, onB = C("checkOn", 0.9, 0.45, 0.7, 1) frame:SetBackdropBorderColor(onR, onG, onB, 1) else mark:Hide() frame:SetBackdropBorderColor(cbBrR, cbBrG, cbBrB, 1) end end Upd() frame:SetScript("OnClick", function() Settings[settingKey] = not Settings[settingKey] NP:SaveSettings() Upd() end) local hvR, hvG, hvB = C("checkHoverBorder", 0.75, 0.40, 0.60, 1) frame:SetScript("OnEnter", function() if not Settings[settingKey] then frame:SetBackdropBorderColor(hvR, hvG, hvB, 1) end if tooltip and GameTooltip then GameTooltip:SetOwner(frame, "ANCHOR_RIGHT") GameTooltip:SetText(tooltip, 1, 1, 1, 1, true) GameTooltip:Show() end end) frame:SetScript("OnLeave", function() Upd() if GameTooltip then GameTooltip:Hide() end end) return frame end -- ============================================ -- SLIDER (with inline editable value) -- ============================================ local sliderIdx = 0 local function CreateSlider(parent, label, settingKey, minVal, maxVal, step, x, y, fmt, onChange) sliderIdx = sliderIdx + 1 fmt = fmt or "%d" local EDIT_W = 42 local ctr = CreateFrame("Frame", nil, parent) ctr:SetWidth(220) ctr:SetHeight(32) ctr:SetPoint("TOPLEFT", parent, "TOPLEFT", x, y) local nameT = ctr:CreateFontString(nil, "OVERLAY") nameT:SetFont(NP.GetFont(), 10, NP.GetFontOutline()) nameT:SetPoint("TOPLEFT", ctr, "TOPLEFT", 0, 0) local lblR, lblG, lblB = C("labelText", 0.75, 0.75, 0.75, 1) nameT:SetTextColor(lblR, lblG, lblB) -- Value display (click to edit) local valT = ctr:CreateFontString(nil, "OVERLAY") valT:SetFont(NP.GetFont(), 10, NP.GetFontOutline()) valT:SetPoint("TOPRIGHT", ctr, "TOPRIGHT", 0, 0) local valR, valG, valB = C("valueText", 0.9, 0.9, 0.9, 1) valT:SetTextColor(valR, valG, valB) -- EditBox for manual input (hidden by default) local editBox = CreateFrame("EditBox", "NP_SliderEdit" .. sliderIdx, ctr) editBox:SetWidth(EDIT_W) editBox:SetHeight(14) editBox:SetPoint("TOPRIGHT", ctr, "TOPRIGHT", 2, 2) editBox:SetFont(NP.GetFont(), 10, NP.GetFontOutline()) editBox:SetTextColor(1, 1, 1, 1) editBox:SetJustifyH("RIGHT") editBox:SetAutoFocus(false) editBox:SetMaxLetters(8) local eBgR, eBgG, eBgB = C("panelBg", 0.12, 0.08, 0.12, 1) local eBrR, eBrG, eBrB = C("accent", 1.0, 0.5, 0.8, 1) editBox:SetBackdrop({ bgFile = "Interface\\Buttons\\WHITE8X8", edgeFile = "Interface\\Buttons\\WHITE8X8", tile = false, tileSize = 0, edgeSize = 1, insets = { left = 2, right = 2, top = 0, bottom = 0 } }) editBox:SetBackdropColor(eBgR, eBgG, eBgB, 1) editBox:SetBackdropBorderColor(eBrR, eBrG, eBrB, 0.9) editBox:Hide() -- Click region over value text to open editbox local valBtn = CreateFrame("Button", nil, ctr) valBtn:SetWidth(EDIT_W + 4) valBtn:SetHeight(14) valBtn:SetPoint("TOPRIGHT", ctr, "TOPRIGHT", 2, 2) valBtn:SetFrameLevel(ctr:GetFrameLevel() + 1) local trkH = 6 local track = CreateFrame("Frame", nil, ctr) track:SetHeight(trkH) track:SetPoint("TOPLEFT", ctr, "TOPLEFT", 0, -14) track:SetPoint("TOPRIGHT", ctr, "TOPRIGHT", 0, -14) local trkR, trkG, trkB = C("sliderTrack", 0.12, 0.08, 0.12, 1) track:SetBackdrop({ bgFile = "Interface\\Buttons\\WHITE8X8", edgeFile = "Interface\\Buttons\\WHITE8X8", tile = false, tileSize = 0, edgeSize = 1, insets = { left = 0, right = 0, top = 0, bottom = 0 } }) track:SetBackdropColor(trkR, trkG, trkB, 1) local tBrR, tBrG, tBrB = C("panelBorder", 0.35, 0.20, 0.30, 1) track:SetBackdropBorderColor(tBrR, tBrG, tBrB, 0.5) local fill = track:CreateTexture(nil, "ARTWORK") fill:SetTexture("Interface\\Buttons\\WHITE8X8") fill:SetPoint("TOPLEFT", track, "TOPLEFT", 1, -1) fill:SetHeight(trkH - 2) local fR, fG, fB = C("sliderFill", 0.7, 0.35, 0.55, 1) fill:SetVertexColor(fR, fG, fB, 0.9) local thumb = CreateFrame("Frame", nil, track) thumb:SetWidth(12) thumb:SetHeight(12) thumb:SetFrameLevel(track:GetFrameLevel() + 2) local thR, thG, thB = C("sliderThumb", 0.9, 0.55, 0.75, 1) thumb:SetBackdrop({ bgFile = "Interface\\Buttons\\WHITE8X8", edgeFile = "Interface\\Buttons\\WHITE8X8", tile = false, tileSize = 0, edgeSize = 1, insets = { left = 0, right = 0, top = 0, bottom = 0 } }) thumb:SetBackdropColor(thR, thG, thB, 1) local thBR, thBG, thBB = C("panelBorder", 0.55, 0.30, 0.42, 1) thumb:SetBackdropBorderColor(thBR, thBG, thBB, 1) local function Norm(v) if maxVal == minVal then return 0 end return (v - minVal) / (maxVal - minVal) end local function FormatValue(v) if fmt == "pct" then return math.floor(v * 100 + 0.5) .. "%" else return string.format(fmt, v) end end local function Refresh(v) v = math.floor(v / step + 0.5) * step if v < minVal then v = minVal end if v > maxVal then v = maxVal end nameT:SetText(label) valT:SetText(FormatValue(v)) local n = Norm(v) local tw = track:GetWidth() if tw <= 0 then tw = 220 end fill:SetWidth(math.max(1, n * (tw - 2))) thumb:ClearAllPoints() thumb:SetPoint("CENTER", track, "LEFT", 1 + n * (tw - 2), 0) end local function ApplyValue(v) v = math.floor(v / step + 0.5) * step if v < minVal then v = minVal end if v > maxVal then v = maxVal end Settings[settingKey] = v NP:SaveSettings() Refresh(v) if onChange then onChange(v) end end -- EditBox: show on click, commit on Enter/Tab, cancel on Escape valBtn:SetScript("OnClick", function() local cur = Settings[settingKey] or minVal local rawVal if fmt == "pct" then rawVal = tostring(math.floor(cur * 100 + 0.5)) else rawVal = string.format(fmt, cur) end editBox:SetText(rawVal) valT:Hide() valBtn:Hide() editBox:Show() editBox:SetFocus() editBox:HighlightText() end) local hvUlR, hvUlG, hvUlB = C("accent", 1.0, 0.5, 0.8, 1) valBtn:SetScript("OnEnter", function() valT:SetTextColor(hvUlR, hvUlG, hvUlB) end) valBtn:SetScript("OnLeave", function() valT:SetTextColor(valR, valG, valB) end) local function CommitEdit() local txt = editBox:GetText() editBox:Hide() valT:Show() valBtn:Show() local num = tonumber(txt) if not num then Refresh(Settings[settingKey] or minVal) return end if fmt == "pct" then num = num / 100 end ApplyValue(num) end editBox:SetScript("OnEnterPressed", function() CommitEdit() end) editBox:SetScript("OnTabPressed", function() CommitEdit() end) editBox:SetScript("OnEscapePressed", function() editBox:Hide() valT:Show() valBtn:Show() Refresh(Settings[settingKey] or minVal) end) track:EnableMouse(true) local function HandleMouse() local tw = track:GetWidth() if tw <= 0 then return end local mx = GetCursorPosition() / track:GetEffectiveScale() local n = (mx - track:GetLeft()) / tw if n < 0 then n = 0 end if n > 1 then n = 1 end local v = math.floor((minVal + n * (maxVal - minVal)) / step + 0.5) * step ApplyValue(v) end track:SetScript("OnMouseDown", function() HandleMouse(); this.drag = true end) track:SetScript("OnMouseUp", function() this.drag = false end) track:SetScript("OnUpdate", function() if this.drag then HandleMouse() end end) ctr:SetScript("OnShow", function() Refresh(Settings[settingKey] or minVal) end) ctr.init = CreateFrame("Frame", nil, ctr) ctr.init:SetScript("OnUpdate", function() Refresh(Settings[settingKey] or minVal) this:SetScript("OnUpdate", nil) end) return ctr end -- ============================================ -- CYCLE BUTTON -- ============================================ local cycleIdx = 0 local function CreateCycleButton(parent, label, settingKey, options, x, y) cycleIdx = cycleIdx + 1 local frame = CreateFrame("Button", "NP_Cyc" .. cycleIdx, parent) frame:SetWidth(220) frame:SetHeight(20) frame:SetPoint("TOPLEFT", parent, "TOPLEFT", x, y) local nameT = frame:CreateFontString(nil, "OVERLAY") nameT:SetFont(NP.GetFont(), 10, NP.GetFontOutline()) nameT:SetPoint("LEFT", frame, "LEFT", 0, 0) local lblR, lblG, lblB = C("labelText", 0.75, 0.75, 0.75, 1) nameT:SetTextColor(lblR, lblG, lblB) nameT:SetText(label) local btn = CreateFrame("Button", nil, frame) btn:SetWidth(110) btn:SetHeight(18) btn:SetPoint("RIGHT", frame, "RIGHT", 0, 0) MakeBackdrop(btn, "buttonBg", "sepColor") local btnT = btn:CreateFontString(nil, "OVERLAY") btnT:SetFont(NP.GetFont(), 9, NP.GetFontOutline()) btnT:SetPoint("CENTER", btn, "CENTER", 0, 0) local bTR, bTG, bTB = C("buttonText", 0.85, 0.85, 0.85, 1) btnT:SetTextColor(bTR, bTG, bTB) local function GetLbl(v) for _, o in ipairs(options) do if o.value == v then return o.label end end return tostring(v) end local function Upd() btnT:SetText(GetLbl(Settings[settingKey])) end Upd() btn:SetScript("OnClick", function() local cur = Settings[settingKey] local ni = 1 for i, o in ipairs(options) do if o.value == cur then ni = i + 1; if ni > table.getn(options) then ni = 1 end; break end end Settings[settingKey] = options[ni].value NP:SaveSettings() Upd() end) local hBR, hBG, hBB = C("buttonHoverBg", 0.20, 0.12, 0.18, 1) local hAR, hAG, hAB = C("accent", 1.0, 0.5, 0.8, 1) btn:SetScript("OnEnter", function() btn:SetBackdropColor(hBR, hBG, hBB, 1) btn:SetBackdropBorderColor(hAR, hAG, hAB, 0.8) btnT:SetTextColor(1, 1, 1) end) btn:SetScript("OnLeave", function() MakeBackdrop(btn, "buttonBg", "sepColor") btnT:SetTextColor(bTR, bTG, bTB) end) return frame end -- ============================================ -- ROLE TOGGLE -- ============================================ local function CreateRoleButton(parent, x, y) local SW = 220 local SH = 26 local HALF = SW / 2 local frame = CreateFrame("Frame", "NP_Role", parent) frame:SetWidth(SW) frame:SetHeight(SH) frame:SetPoint("TOPLEFT", parent, "TOPLEFT", x, y) local bgR, bgG, bgB = C("panelBg", 0.08, 0.04, 0.08, 1) local brR, brG, brB = C("panelBorder", 0.45, 0.25, 0.38, 1) frame:SetBackdrop({ bgFile = "Interface\\Buttons\\WHITE8X8", edgeFile = "Interface\\Buttons\\WHITE8X8", tile = false, tileSize = 0, edgeSize = 1, insets = { left = 0, right = 0, top = 0, bottom = 0 } }) frame:SetBackdropColor(bgR, bgG, bgB, 1) frame:SetBackdropBorderColor(brR, brG, brB, 0.8) local btnTank = CreateFrame("Button", nil, frame) btnTank:SetWidth(HALF - 2) btnTank:SetHeight(SH - 4) btnTank:SetPoint("LEFT", frame, "LEFT", 2, 0) btnTank:SetBackdrop({ bgFile = "Interface\\Buttons\\WHITE8X8", edgeFile = "Interface\\Buttons\\WHITE8X8", tile = false, tileSize = 0, edgeSize = 1, insets = { left = 0, right = 0, top = 0, bottom = 0 } }) local tankT = btnTank:CreateFontString(nil, "OVERLAY") tankT:SetFont(NP.GetFont(), 10, NP.GetFontOutline()) tankT:SetPoint("CENTER", btnTank, "CENTER", 0, 0) tankT:SetText("坦克") local btnDPS = CreateFrame("Button", nil, frame) btnDPS:SetWidth(HALF - 2) btnDPS:SetHeight(SH - 4) btnDPS:SetPoint("RIGHT", frame, "RIGHT", -2, 0) btnDPS:SetBackdrop({ bgFile = "Interface\\Buttons\\WHITE8X8", edgeFile = "Interface\\Buttons\\WHITE8X8", tile = false, tileSize = 0, edgeSize = 1, insets = { left = 0, right = 0, top = 0, bottom = 0 } }) local dpsT = btnDPS:CreateFontString(nil, "OVERLAY") dpsT:SetFont(NP.GetFont(), 10, NP.GetFontOutline()) dpsT:SetPoint("CENTER", btnDPS, "CENTER", 0, 0) dpsT:SetText("输出") local function Upd() if (NP.playerRole or "DPS") == "TANK" then btnTank:SetBackdropColor(0.15, 0.45, 0.15, 1) btnTank:SetBackdropBorderColor(0.3, 0.8, 0.3, 0.9) tankT:SetTextColor(0.4, 1.0, 0.4) btnDPS:SetBackdropColor(bgR, bgG, bgB, 0.5) btnDPS:SetBackdropBorderColor(brR, brG, brB, 0.3) dpsT:SetTextColor(0.5, 0.5, 0.5) else btnTank:SetBackdropColor(bgR, bgG, bgB, 0.5) btnTank:SetBackdropBorderColor(brR, brG, brB, 0.3) tankT:SetTextColor(0.5, 0.5, 0.5) btnDPS:SetBackdropColor(0.45, 0.12, 0.12, 1) btnDPS:SetBackdropBorderColor(0.8, 0.3, 0.3, 0.9) dpsT:SetTextColor(1.0, 0.4, 0.4) end end Upd() local function SetRole(role) NP.playerRole = role NP:SaveSettings() Upd() if NanamiPlates_Threat then NanamiPlates_Threat.BroadcastTankMode(true) end NP.Print("角色: " .. role) end btnTank:SetScript("OnClick", function() SetRole("TANK") end) btnDPS:SetScript("OnClick", function() SetRole("DPS") end) return frame end -- ============================================ -- FORMAT OPTIONS -- ============================================ local hpFmtOpts = { { value = 0, label = "隐藏" }, { value = 1, label = "百分比" }, { value = 2, label = "当前值" }, { value = 3, label = "当前(百分比)" }, { value = 4, label = "当前/最大" }, { value = 5, label = "当前/最大 %" }, } -- ============================================ -- TAB DEFINITIONS -- ============================================ local TAB_DEFS = { { id = "hp", label = "血条" }, { id = "cast", label = "施法条" }, { id = "debuff", label = "减益" }, { id = "target", label = "目标" }, { id = "other", label = "其他" }, } -- ============================================ -- TAB CONTENT BUILDERS -- ============================================ local function BuildTab_HP(page) local L = 20 local R = 260 local y = -10 SectionLabel(page, "-- 敌方 --", L, y) y = y - 20 CreateSlider(page, "宽度", "healthbarWidth", 60, 220, 5, L, y) CreateSlider(page, "高度", "healthbarHeight", 4, 30, 1, R, y) y = y - 38 CreateSlider(page, "血量字号", "healthFontSize", 6, 18, 1, L, y) CreateCycleButton(page, "血量格式", "healthTextFormat", hpFmtOpts, R, y + 6) y = y - 38 CreateSlider(page, "名字字号", "nameFontSize", 6, 16, 1, L, y) CreateSlider(page, "等级字号", "levelFontSize", 6, 16, 1, R, y) y = y - 38 CreateSlider(page, "垂直偏移", "nameplateYOffset", -20, 40, 1, L, y) y = y - 50 SectionLabel(page, "-- 友方 --", L, y) y = y - 20 CreateSlider(page, "宽度", "friendHealthbarWidth", 40, 200, 5, L, y) CreateSlider(page, "高度", "friendHealthbarHeight", 2, 20, 1, R, y) y = y - 38 CreateSlider(page, "血量字号", "friendHealthFontSize", 6, 16, 1, L, y) CreateCycleButton(page, "血量格式", "friendHealthTextFormat", hpFmtOpts, R, y + 6) y = y - 38 CreateSlider(page, "名字字号", "friendNameFontSize", 6, 16, 1, L, y) CreateSlider(page, "等级字号", "friendLevelFontSize", 6, 16, 1, R, y) end local function BuildTab_Cast(page) local L = 20 local R = 260 local y = -10 SectionLabel(page, "-- 敌方施法条 --", L, y) y = y - 20 CreateSlider(page, "宽度", "castbarWidth", 60, 220, 5, L, y) CreateSlider(page, "高度", "castbarHeight", 4, 20, 1, R, y) y = y - 36 CreateCheckbox(page, "显示法术图标", "showCastbarIcon", L, y) y = y - 45 SectionLabel(page, "-- 友方施法条 --", L, y) y = y - 20 CreateSlider(page, "宽度", "friendCastbarWidth", 40, 200, 5, L, y) CreateSlider(page, "高度", "friendCastbarHeight", 2, 16, 1, R, y) y = y - 36 CreateCheckbox(page, "显示法术图标", "friendShowCastbarIcon", L, y) end local function BuildTab_Debuff(page) local L = 20 local R = 260 local y = -10 SectionLabel(page, "-- 减益效果 (Debuff) --", L, y) y = y - 22 CreateCheckbox(page, "显示减益计时器", "showDebuffTimers", L, y, "在减益图标上显示倒计时") y = y - 26 CreateCheckbox(page, "仅显示自己的减益", "showOnlyMyDebuffs", L, y, "只显示自己施放的减益效果") y = y - 32 CreateSlider(page, "图标大小", "debuffIconSize", 12, 40, 1, L, y) y = y - 50 SectionLabel(page, "-- 连击点 (星) --", L, y) y = y - 22 CreateCheckbox(page, "显示连击点", "showComboPoints", L, y, "盗贼和德鲁伊(猫形态)的连击点显示") y = y - 32 CreateSlider(page, "连击点大小", "comboPointsSize", 6, 20, 1, L, y) end local function CreateArrowStyleSelector(parent, x, y) local ARROW_TEXTURE = "Interface\\AddOns\\Nanami-Plates\\img\\arrow" local ARROW_TEXCOORDS = NP.ARROW_TEXCOORDS or { {0, 0.5, 0, 0.5}, {0.5, 1, 0, 0.5}, {0, 0.5, 0.5, 1}, {0.5, 1, 0.5, 1}, } local PREVIEW_SIZE = 40 local GAP = 8 local NUM_ARROWS = 4 local frame = CreateFrame("Frame", nil, parent) frame:SetWidth(NUM_ARROWS * (PREVIEW_SIZE + GAP)) frame:SetHeight(PREVIEW_SIZE + 20) frame:SetPoint("TOPLEFT", parent, "TOPLEFT", x, y) local lbl = frame:CreateFontString(nil, "OVERLAY") lbl:SetFont(NP.GetFont(), 10, NP.GetFontOutline()) lbl:SetPoint("TOPLEFT", frame, "TOPLEFT", 0, 0) local lblR, lblG, lblB = C("labelText", 0.75, 0.75, 0.75, 1) lbl:SetTextColor(lblR, lblG, lblB) lbl:SetText("箭头样式") local buttons = {} local acR, acG, acB = C("accent", 1.0, 0.5, 0.8, 1) local brR, brG, brB = C("panelBorder", 0.35, 0.20, 0.30, 1) local function UpdateSelection() local cur = Settings.targetArrowStyle or 1 for i, btn in ipairs(buttons) do if i == cur then btn:SetBackdropBorderColor(acR, acG, acB, 1) else btn:SetBackdropBorderColor(brR, brG, brB, 0.5) end end end for i = 1, NUM_ARROWS do local btn = CreateFrame("Button", nil, frame) btn:SetWidth(PREVIEW_SIZE) btn:SetHeight(PREVIEW_SIZE) btn:SetPoint("TOPLEFT", frame, "TOPLEFT", (i - 1) * (PREVIEW_SIZE + GAP), -16) btn:SetBackdrop({ bgFile = "Interface\\Buttons\\WHITE8X8", edgeFile = "Interface\\Buttons\\WHITE8X8", tile = false, tileSize = 0, edgeSize = 1, insets = { left = 0, right = 0, top = 0, bottom = 0 } }) local bgR, bgG, bgB = C("panelBg", 0.08, 0.04, 0.08, 1) btn:SetBackdropColor(bgR, bgG, bgB, 1) local tex = btn:CreateTexture(nil, "ARTWORK") tex:SetTexture(ARROW_TEXTURE) tex:SetPoint("TOPLEFT", btn, "TOPLEFT", 3, -3) tex:SetPoint("BOTTOMRIGHT", btn, "BOTTOMRIGHT", -3, 3) local coords = ARROW_TEXCOORDS[i] tex:SetTexCoord(coords[1], coords[2], coords[3], coords[4]) tex:SetVertexColor(acR, acG, acB, 1) btn.arrowIdx = i btn:SetScript("OnClick", function() Settings.targetArrowStyle = this.arrowIdx NP:SaveSettings() UpdateSelection() if NP.UpdateAllArrows then NP.UpdateAllArrows() end end) local hvR, hvG, hvB = C("checkHoverBorder", 0.75, 0.40, 0.60, 1) btn:SetScript("OnEnter", function() if Settings.targetArrowStyle ~= this.arrowIdx then this:SetBackdropBorderColor(hvR, hvG, hvB, 1) end end) btn:SetScript("OnLeave", function() UpdateSelection() end) table.insert(buttons, btn) end UpdateSelection() return frame end local function BuildTab_Target(page) local L = 20 local R = 260 local y = -10 SectionLabel(page, "-- 目标指示 --", L, y) y = y - 22 CreateCheckbox(page, "显示目标箭头 >> <<", "showTargetGlow", L, y, "在当前目标姓名板两侧显示箭头") y = y - 32 CreateArrowStyleSelector(page, L, y) y = y - 68 CreateSlider(page, "箭头大小", "targetArrowSize", 12, 48, 2, L, y, nil, function() if NP.UpdateAllArrows then NP.UpdateAllArrows() end end) CreateSlider(page, "箭头偏移", "targetArrowOffset", -20, 20, 1, R, y, nil, function() if NP.UpdateAllArrows then NP.UpdateAllArrows() end end) y = y - 38 CreateSlider(page, "箭头染色", "targetArrowTint", 0, 1.0, 0.05, L, y, "pct") CreateSlider(page, "非目标透明度", "nonTargetAlpha", 0.1, 1.0, 0.05, R, y, "pct") y = y - 50 SectionLabel(page, "-- 仇恨与角色 --", L, y) y = y - 26 CreateRoleButton(page, L, y) end local function BuildTab_Other(page) local L = 20 local y = -10 SectionLabel(page, "-- 杂项 --", L, y) y = y - 22 CreateCheckbox(page, "显示小动物姓名板", "showCritterNameplates", L, y, "显示兔子、松鼠等小动物的姓名板") y = y - 26 CreateCheckbox(page, "显示法力条", "showManaBar", L, y) y = y - 26 CreateCheckbox(page, "显示任务怪图标", "showQuestIcon", L, y, "在任务目标怪物的姓名板上显示\"!\"图标和进度 (配合pfQuest)") y = y - 26 CreateCheckbox(page, "堆叠姓名板", "enableStacking", L, y, "当多个姓名板重叠时,自动将它们上下分开排列。\n关闭时姓名板会自由重叠在3D位置上。") y = y - 50 local resetBtn = CreateFrame("Button", "NP_ResetBtn", page) resetBtn:SetWidth(220) resetBtn:SetHeight(26) resetBtn:SetPoint("TOPLEFT", page, "TOPLEFT", L, y) local rBgR, rBgG, rBgB = C("buttonBg", 0.18, 0.10, 0.15, 1) local rBrR, rBrG, rBrB = C("sepColor", 0.45, 0.25, 0.38, 1) resetBtn:SetBackdrop({ bgFile = "Interface\\Buttons\\WHITE8X8", edgeFile = "Interface\\Buttons\\WHITE8X8", tile = false, tileSize = 0, edgeSize = 1, insets = { left = 0, right = 0, top = 0, bottom = 0 } }) resetBtn:SetBackdropColor(rBgR, rBgG, rBgB, 0.94) resetBtn:SetBackdropBorderColor(rBrR, rBrG, rBrB, 1) local rT = resetBtn:CreateFontString(nil, "OVERLAY") rT:SetFont(NP.GetFont(), 10, NP.GetFontOutline()) rT:SetPoint("CENTER", resetBtn, "CENTER", 0, 0) rT:SetText("重置所有设置 (重载UI)") rT:SetTextColor(0.9, 0.4, 0.4) resetBtn:SetScript("OnClick", function() NanamiPlatesDB = nil; ReloadUI() end) resetBtn:SetScript("OnEnter", function() resetBtn:SetBackdropBorderColor(1, 0.3, 0.3, 1) rT:SetTextColor(1, 0.5, 0.5) end) resetBtn:SetScript("OnLeave", function() resetBtn:SetBackdropBorderColor(rBrR, rBrG, rBrB, 1) rT:SetTextColor(0.9, 0.4, 0.4) end) end local TAB_BUILDERS = { hp = BuildTab_HP, cast = BuildTab_Cast, debuff = BuildTab_Debuff, target = BuildTab_Target, other = BuildTab_Other, } -- ============================================ -- MAIN OPTIONS FRAME -- ============================================ local function CreateOptionsFrame() if optionsFrame then if optionsFrame:IsShown() then optionsFrame:Hide() else optionsFrame:Show() end return end local W = 540 local H = 440 local TAB_H = 28 local HEADER_H = 34 local f = CreateFrame("Frame", "NanamiPlatesOptions", UIParent) f:SetWidth(W) f:SetHeight(H) f:SetPoint("CENTER", UIParent, "CENTER", 0, 50) f:SetMovable(true) f:EnableMouse(false) f:SetFrameStrata("DIALOG") f:SetToplevel(true) f:SetClampedToScreen(true) MakeBackdrop(f) -- Title bar (only this area is draggable) local header = CreateFrame("Frame", nil, f) header:SetHeight(HEADER_H) header:SetPoint("TOPLEFT", f, "TOPLEFT", 1, -1) header:SetPoint("TOPRIGHT", f, "TOPRIGHT", -1, -1) header:EnableMouse(true) header:RegisterForDrag("LeftButton") header:SetScript("OnDragStart", function() f:StartMoving() end) header:SetScript("OnDragStop", function() f:StopMovingOrSizing() end) local hR, hG, hB = C("headerBg", 0.14, 0.08, 0.12, 1) header:SetBackdrop({ bgFile = "Interface\\Buttons\\WHITE8X8" }) header:SetBackdropColor(hR, hG, hB, 0.95) local acR, acG, acB = C("accent", 1.0, 0.5, 0.8, 1) -- Logo icon local logoIcon = header:CreateTexture(nil, "OVERLAY") logoIcon:SetTexture("Interface\\AddOns\\Nanami-Plates\\img\\icon") logoIcon:SetWidth(16) logoIcon:SetHeight(16) logoIcon:SetPoint("LEFT", header, "LEFT", 10, 0) logoIcon:SetVertexColor(acR, acG, acB, 0.9) local title = header:CreateFontString(nil, "OVERLAY") title:SetFont(NP.GetFont(), 13, NP.GetFontOutline()) title:SetPoint("LEFT", logoIcon, "RIGHT", 6, 0) title:SetTextColor(acR, acG, acB) title:SetText("Nanami-Plates") local verText = header:CreateFontString(nil, "OVERLAY") verText:SetFont(NP.GetFont(), 9, NP.GetFontOutline()) verText:SetPoint("LEFT", title, "RIGHT", 8, 0) verText:SetTextColor(0.5, 0.5, 0.5, 0.7) verText:SetText("v1.0") -- Close button with backdrop local closeBtn = CreateFrame("Button", nil, header) closeBtn:SetWidth(22) closeBtn:SetHeight(22) closeBtn:SetPoint("RIGHT", header, "RIGHT", -6, 0) closeBtn:SetBackdrop({ bgFile = "Interface\\Buttons\\WHITE8X8", edgeFile = "Interface\\Buttons\\WHITE8X8", tile = false, tileSize = 0, edgeSize = 1, insets = { left = 0, right = 0, top = 0, bottom = 0 } }) closeBtn:SetBackdropColor(0.15, 0.08, 0.12, 0.6) closeBtn:SetBackdropBorderColor(0.35, 0.20, 0.30, 0.5) local cT = closeBtn:CreateFontString(nil, "OVERLAY") cT:SetFont(NP.GetFont(), 13, NP.GetFontOutline()) cT:SetPoint("CENTER", closeBtn, "CENTER", 0, 0) cT:SetText("x") cT:SetTextColor(0.7, 0.4, 0.5) closeBtn:SetScript("OnClick", function() f:Hide() end) closeBtn:SetScript("OnEnter", function() cT:SetTextColor(1, 0.3, 0.4) closeBtn:SetBackdropColor(0.3, 0.08, 0.08, 0.8) closeBtn:SetBackdropBorderColor(0.8, 0.25, 0.25, 0.8) end) closeBtn:SetScript("OnLeave", function() cT:SetTextColor(0.7, 0.4, 0.5) closeBtn:SetBackdropColor(0.15, 0.08, 0.12, 0.6) closeBtn:SetBackdropBorderColor(0.35, 0.20, 0.30, 0.5) end) -- Header divider local headerDiv = f:CreateTexture(nil, "ARTWORK") headerDiv:SetTexture("Interface\\Buttons\\WHITE8X8") headerDiv:SetHeight(1) headerDiv:SetPoint("TOPLEFT", header, "BOTTOMLEFT", 0, 0) headerDiv:SetPoint("TOPRIGHT", header, "BOTTOMRIGHT", 0, 0) headerDiv:SetVertexColor(acR, acG, acB, 0.3) -- Tab bar local tabBar = CreateFrame("Frame", nil, f) tabBar:SetHeight(TAB_H) tabBar:SetPoint("TOPLEFT", header, "BOTTOMLEFT", 0, -1) tabBar:SetPoint("TOPRIGHT", header, "BOTTOMRIGHT", 0, -1) local tbBgR, tbBgG, tbBgB = C("sectionBg", 0.08, 0.05, 0.08, 1) tabBar:SetBackdrop({ bgFile = "Interface\\Buttons\\WHITE8X8" }) tabBar:SetBackdropColor(tbBgR, tbBgG, tbBgB, 0.9) -- Content area local content = CreateFrame("Frame", nil, f) content:SetPoint("TOPLEFT", tabBar, "BOTTOMLEFT", 0, -1) content:SetPoint("BOTTOMRIGHT", f, "BOTTOMRIGHT", -1, 1) -- Pages local pages = {} for _, def in ipairs(TAB_DEFS) do local page = CreateFrame("Frame", nil, content) page:SetAllPoints(content) page:Hide() if TAB_BUILDERS[def.id] then TAB_BUILDERS[def.id](page) end pages[def.id] = page end -- Tab buttons local tabs = {} local tabW = (W - 2) / table.getn(TAB_DEFS) local activeTabId = TAB_DEFS[1].id local tabBgNR, tabBgNG, tabBgNB = C("tabBg", 0.10, 0.06, 0.10, 1) local tabBrNR, tabBrNG, tabBrNB = C("tabBorder", 0.35, 0.20, 0.30, 1) local tabBgAR, tabBgAG, tabBgAB = C("tabActiveBg", 0.20, 0.12, 0.18, 1) local tabBrAR, tabBrAG, tabBrAB = C("tabActiveBorder", 0.9, 0.45, 0.7, 1) local tabTxtNR, tabTxtNG, tabTxtNB = C("tabText", 0.6, 0.6, 0.6, 1) local tabTxtAR, tabTxtAG, tabTxtAB = C("tabActiveText", 1.0, 0.9, 0.95, 1) local function SetActiveTab(id) activeTabId = id for _, def in ipairs(TAB_DEFS) do local tab = tabs[def.id] local pg = pages[def.id] if def.id == id then pg:Show() tab:SetBackdropColor(tabBgAR, tabBgAG, tabBgAB, 1) tab:SetBackdropBorderColor(tabBrNR, tabBrNG, tabBrNB, 0.3) tab.label:SetTextColor(tabTxtAR, tabTxtAG, tabTxtAB) tab.indicator:Show() else pg:Hide() tab:SetBackdropColor(tabBgNR, tabBgNG, tabBgNB, 1) tab:SetBackdropBorderColor(tabBrNR, tabBrNG, tabBrNB, 0.3) tab.label:SetTextColor(tabTxtNR, tabTxtNG, tabTxtNB) tab.indicator:Hide() end end end for i, def in ipairs(TAB_DEFS) do local tabId = def.id local tabLabel = def.label local tab = CreateFrame("Button", nil, tabBar) tab:SetWidth(tabW) tab:SetHeight(TAB_H) tab:SetPoint("TOPLEFT", tabBar, "TOPLEFT", (i - 1) * tabW, 0) tab:SetBackdrop({ bgFile = "Interface\\Buttons\\WHITE8X8", edgeFile = "Interface\\Buttons\\WHITE8X8", tile = false, tileSize = 0, edgeSize = 1, insets = { left = 0, right = 0, top = 0, bottom = 0 } }) tab.tabId = tabId local lbl = tab:CreateFontString(nil, "OVERLAY") lbl:SetFont(NP.GetFont(), 11, NP.GetFontOutline()) lbl:SetPoint("CENTER", tab, "CENTER", 0, 0) lbl:SetText(tabLabel) tab.label = lbl -- Active tab bottom indicator line local ind = tab:CreateTexture(nil, "OVERLAY") ind:SetTexture("Interface\\Buttons\\WHITE8X8") ind:SetHeight(2) ind:SetPoint("BOTTOMLEFT", tab, "BOTTOMLEFT", 4, 1) ind:SetPoint("BOTTOMRIGHT", tab, "BOTTOMRIGHT", -4, 1) ind:SetVertexColor(tabBrAR, tabBrAG, tabBrAB, 1) ind:Hide() tab.indicator = ind tab:SetScript("OnClick", function() SetActiveTab(this.tabId) end) tab:SetScript("OnEnter", function() if activeTabId ~= this.tabId then this:SetBackdropColor(tabBgAR * 0.7, tabBgAG * 0.7, tabBgAB * 0.7, 0.8) this.label:SetTextColor(0.85, 0.85, 0.85) end end) tab:SetScript("OnLeave", function() if activeTabId ~= this.tabId then this:SetBackdropColor(tabBgNR, tabBgNG, tabBgNB, 1) this:SetBackdropBorderColor(tabBrNR, tabBrNG, tabBrNB, 0.3) this.label:SetTextColor(tabTxtNR, tabTxtNG, tabTxtNB) end end) tabs[tabId] = tab end SetActiveTab(TAB_DEFS[1].id) optionsFrame = f tinsert(UISpecialFrames, "NanamiPlatesOptions") end -- ============================================ -- MINIMAP BUTTON -- ============================================ local function CreateMinimapButton() if minimapButton then return end local btnSize = 33 local btn = CreateFrame("Button", "NanamiPlatesMinimapBtn", Minimap) btn:SetWidth(btnSize) btn:SetHeight(btnSize) btn:SetFrameStrata("MEDIUM") btn:SetFrameLevel(Minimap:GetFrameLevel() + 5) btn:SetMovable(true) btn:EnableMouse(true) btn:RegisterForClicks("LeftButtonUp", "RightButtonUp") local savedAngle = NanamiPlatesDB and NanamiPlatesDB.minimapAngle or 220 local function UpdatePos(angle) local rad = math.rad(angle) btn:ClearAllPoints() btn:SetPoint("CENTER", Minimap, "CENTER", math.cos(rad) * 80, math.sin(rad) * 80) end UpdatePos(savedAngle) btn.angle = savedAngle local icon = btn:CreateTexture(nil, "BACKGROUND") icon:SetTexture("Interface\\AddOns\\Nanami-Plates\\img\\icon") icon:SetWidth(20) icon:SetHeight(20) icon:SetPoint("CENTER", btn, "CENTER", 0, 0) local iconR, iconG, iconB = C("accent", 1.0, 0.5, 0.8, 1) icon:SetVertexColor(iconR, iconG, iconB, 1) local overlay = btn:CreateTexture(nil, "OVERLAY") overlay:SetTexture("Interface\\Minimap\\MiniMap-TrackingBorder") overlay:SetWidth(52) overlay:SetHeight(52) overlay:SetPoint("CENTER", btn, "CENTER", 10, -10) btn:SetHighlightTexture("Interface\\Minimap\\UI-Minimap-ZoomButton-Highlight") local isDrag = false btn:RegisterForDrag("LeftButton") btn:SetScript("OnDragStart", function() isDrag = true end) btn:SetScript("OnDragStop", function() isDrag = false if not NanamiPlatesDB then NanamiPlatesDB = {} end NanamiPlatesDB.minimapAngle = btn.angle end) btn:SetScript("OnUpdate", function() if not isDrag then return end local cx, cy = GetCursorPosition() local s = Minimap:GetEffectiveScale() local mx, my = Minimap:GetCenter() local angle = math.deg(math.atan2(cy / s - my, cx / s - mx)) btn.angle = angle UpdatePos(angle) end) btn:SetScript("OnClick", function() CreateOptionsFrame() end) btn:SetScript("OnEnter", function() icon:SetVertexColor(1, 1, 1, 1) GameTooltip:SetOwner(this, "ANCHOR_LEFT") local aR, aG, aB = C("accent", 1.0, 0.5, 0.8, 1) GameTooltip:SetText("Nanami-Plates 姓名板", aR, aG, aB) GameTooltip:AddLine("左键点击打开设置", 0.8, 0.8, 0.8) GameTooltip:AddLine("拖拽移动按钮位置", 0.6, 0.6, 0.6) GameTooltip:Show() end) btn:SetScript("OnLeave", function() icon:SetVertexColor(iconR, iconG, iconB, 1) GameTooltip:Hide() end) minimapButton = btn end -- ============================================ -- INIT -- ============================================ local initFrame = CreateFrame("Frame") initFrame:RegisterEvent("PLAYER_LOGIN") initFrame:SetScript("OnEvent", function() CreateMinimapButton() end) -- ============================================ -- SLASH -- ============================================ SLASH_NANAMIPLATES1 = "/np" SLASH_NANAMIPLATES2 = "/nanamiplates" SlashCmdList["NANAMIPLATES"] = function(msg) msg = string.lower(msg or "") -- trim leading/trailing spaces msg = string.gsub(msg, "^%s+", "") msg = string.gsub(msg, "%s+$", "") if msg == "tank" then NP.playerRole = (NP.playerRole == "TANK") and "DPS" or "TANK" NP:SaveSettings() NP.Print("角色: " .. NP.playerRole) if NanamiPlates_Threat then NanamiPlates_Threat.BroadcastTankMode(true) end elseif msg == "reset" then NanamiPlatesDB = nil ReloadUI() elseif msg == "minimap" then if minimapButton then if minimapButton:IsShown() then minimapButton:Hide() else minimapButton:Show() end end elseif msg == "debug" then local p = function(s) DEFAULT_CHAT_FRAME:AddMessage("|cffff88cc[NP]|r " .. s) end if UnitExists("target") then local SDB = NanamiPlates_SpellDB p("|cff00ff00-- Debuff Debug --|r") if not SDB then p("|cffff0000SpellDB is nil!|r") elseif not SDB.textureToSpell then p("|cffff0000textureToSpell is nil!|r") else local count = 0 for _ in pairs(SDB.textureToSpell) do count = count + 1 end p("textureToSpell entries: " .. count) end for i = 1, 16 do local texture, stacks = UnitDebuff("target", i) if not texture then break end local texLookup = SDB and SDB.textureToSpell and SDB.textureToSpell[texture] local prioLookup = SDB and SDB.debuffPriority and SDB.debuffPriority[texture] local scanResult = SDB and SDB:ScanDebuff("target", i) or "nil" local dur = 0 if SDB and scanResult and scanResult ~= "nil" and scanResult ~= "" then dur = SDB:GetDuration(scanResult, 0) or 0 end p(i .. ": |cffffcc00" .. tostring(texture) .. "|r") p(" prio=|cffaaaaff" .. tostring(prioLookup) .. "|r tex=|cffaaffaa" .. tostring(texLookup) .. "|r scan=|cff00ffff" .. tostring(scanResult) .. "|r dur=" .. dur) end p("|cff00ff00-- End --|r") else p("No target") end else CreateOptionsFrame() end end