Files
Nanami-Plates/Options.lua
2026-03-20 10:20:05 +08:00

1073 lines
39 KiB
Lua

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