Files
Nanami-UI/SetupWizard.lua
rucky ec9e3c29d6 完成多出修改
修复拾取界面点击无效问题
修复宠物训练界面不显示训练点问题
新增天赋分享到聊天界面
修复飞行界面无法关闭问题
修复术士宠物的显示问题
为天赋界面添加默认数据库支持
框架现在也可自主选择是否启用
玩家框架和目标框架可取消显示3D头像以及透明度修改
背包和银行也添加透明度自定义支持
优化tooltip性能和背包物品显示方式
修复布局模式在ui缩放后不能正常定位的问题
添加硬核模式危险和死亡的工会通报
添加拾取和已拾取的框体
等等
2026-03-23 10:25:25 +08:00

1371 lines
56 KiB
Lua
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

--------------------------------------------------------------------------------
-- Nanami-UI: Setup Wizard (SetupWizard.lua)
-- First-run guided configuration & re-run from ConfigUI
--------------------------------------------------------------------------------
SFrames.SetupWizard = {}
local SW = SFrames.SetupWizard
--------------------------------------------------------------------------------
-- Local state
--------------------------------------------------------------------------------
local overlay, panel, contentScroll, contentChild
local headerTitle, stepLabel
local btnPrev, btnNext, btnSkip
local stepDots = {}
local stepPages = {}
local stepBuilders = {}
local stepList = {}
local currentStep = 1
local runMode = "firstrun"
local completeCb = nil
local choices = {}
local built = false
local PANEL_W, PANEL_H = 620, 470
local CONTENT_W = PANEL_W - 40
local CONTENT_H = PANEL_H - 110
local wid = 0
local function WN(p) wid = wid + 1; return "NanamiWiz" .. p .. wid end
local function Clamp(v, lo, hi)
if v < lo then return lo end
if v > hi then return hi end
return v
end
local function T() return SFrames.ActiveTheme end
--------------------------------------------------------------------------------
-- Lightweight widget helpers (self-contained, mirror ConfigUI style)
--------------------------------------------------------------------------------
local function HideTex(tex)
if not tex then return end
if tex.SetTexture then tex:SetTexture(nil) end
tex:Hide()
end
-- Styled button (matches ConfigUI StyleButton)
local function MakeButton(parent, text, w, h, onClick)
local btn = CreateFrame("Button", WN("Btn"), parent, "UIPanelButtonTemplate")
btn:SetWidth(w); btn:SetHeight(h)
btn:SetText(text)
btn:SetScript("OnClick", onClick)
local t = T()
btn:SetBackdrop({
bgFile = "Interface\\Tooltips\\UI-Tooltip-Background",
edgeFile = "Interface\\Tooltips\\UI-Tooltip-Border",
tile = true, tileSize = 16, edgeSize = 14,
insets = { left = 3, right = 3, top = 3, bottom = 3 },
})
HideTex(btn:GetNormalTexture())
HideTex(btn:GetPushedTexture())
HideTex(btn:GetHighlightTexture())
HideTex(btn:GetDisabledTexture())
local nm = btn:GetName() or ""
for _, suf in ipairs({"Left","Right","Middle"}) do
local tx = _G[nm..suf]; if tx then tx:SetAlpha(0); tx:Hide() end
end
local function SetVis(state)
local bg, bd, tx = t.buttonBg, t.buttonBorder, t.buttonText
if btn.sfActive then
bg, bd, tx = t.buttonActiveBg, t.buttonActiveBorder, t.buttonActiveText
elseif state == "hover" then
bg = t.buttonHoverBg; bd = t.btnHoverBd or t.buttonActiveBorder; tx = t.buttonActiveText
elseif state == "down" then bg = t.buttonDownBg
elseif state == "disabled" then bg = t.buttonDisabledBg; tx = t.buttonDisabledText end
if btn.SetBackdropColor then btn:SetBackdropColor(bg[1],bg[2],bg[3],bg[4]) end
if btn.SetBackdropBorderColor then btn:SetBackdropBorderColor(bd[1],bd[2],bd[3],bd[4]) end
local fs = btn:GetFontString(); if fs then fs:SetTextColor(tx[1],tx[2],tx[3]) end
end
btn.RefreshVisual = function()
if btn.sfActive then SetVis("active")
elseif btn.IsEnabled and not btn:IsEnabled() then SetVis("disabled")
else SetVis("normal") end
end
btn:SetScript("OnEnter", function() if this.IsEnabled and this:IsEnabled() and not this.sfActive then SetVis("hover") end end)
btn:SetScript("OnLeave", function() if this.IsEnabled and this:IsEnabled() and not this.sfActive then SetVis("normal") end end)
btn:SetScript("OnMouseDown", function() if this.IsEnabled and this:IsEnabled() and not this.sfActive then SetVis("down") end end)
btn:SetScript("OnMouseUp", function() if this.IsEnabled and this:IsEnabled() and not this.sfActive then SetVis("hover") end end)
btn:RefreshVisual()
return btn
end
local function MakeCheck(parent, label, x, y, getter, setter, onChange)
local name = WN("Chk")
local cb = CreateFrame("CheckButton", name, parent, "UICheckButtonTemplate")
cb:SetWidth(18); cb:SetHeight(18)
cb:SetPoint("TOPLEFT", parent, "TOPLEFT", x, y)
HideTex(cb:GetNormalTexture()); HideTex(cb:GetPushedTexture())
HideTex(cb:GetHighlightTexture()); HideTex(cb:GetDisabledTexture())
if cb.SetCheckedTexture then cb:SetCheckedTexture("") end
if cb.SetDisabledCheckedTexture then cb:SetDisabledCheckedTexture("") end
local t = T()
local box = CreateFrame("Frame", nil, cb)
box:SetPoint("TOPLEFT", cb, "TOPLEFT", 0, 0)
box:SetPoint("BOTTOMRIGHT", cb, "BOTTOMRIGHT", 0, 0)
box:SetFrameLevel(cb:GetFrameLevel()+1)
box: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 function ApplyVis(checked)
if checked then
box:SetBackdropColor(t.checkFill[1], t.checkFill[2], t.checkFill[3], 0.88)
box:SetBackdropBorderColor(t.checkFill[1], t.checkFill[2], t.checkFill[3], 1)
else
box:SetBackdropColor(t.checkBg[1], t.checkBg[2], t.checkBg[3], t.checkBg[4])
box:SetBackdropBorderColor(t.checkBorder[1], t.checkBorder[2], t.checkBorder[3], t.checkBorder[4])
end
end
local txt = _G[name.."Text"]
if txt then
txt:ClearAllPoints()
txt:SetPoint("LEFT", cb, "RIGHT", 4, 0)
txt:SetFont(SFrames:GetFont(), 11, "OUTLINE")
txt:SetText(label)
txt:SetTextColor(t.text[1], t.text[2], t.text[3])
end
cb:SetScript("OnEnter", function()
if not this._chk then
box:SetBackdropBorderColor(t.checkHoverBorder[1], t.checkHoverBorder[2], t.checkHoverBorder[3], t.checkHoverBorder[4])
end
end)
cb:SetScript("OnLeave", function()
if not this._chk then
box:SetBackdropBorderColor(t.checkBorder[1], t.checkBorder[2], t.checkBorder[3], t.checkBorder[4])
end
end)
cb:SetScript("OnClick", function()
local v = (this:GetChecked() == 1 or this:GetChecked() == true) and true or false
this._chk = v; ApplyVis(v)
setter(v)
if onChange then onChange(v) end
PlaySound(v and "igMainMenuOptionCheckBoxOn" or "igMainMenuOptionCheckBoxOff")
end)
cb.Refresh = function()
local v = getter() and true or false
cb:SetChecked(v and 1 or 0); cb._chk = v; ApplyVis(v)
end
cb:Refresh()
return cb
end
local function MakeSlider(parent, label, x, y, w, lo, hi, step, getter, setter, fmt)
local sn = WN("Sld")
local s = CreateFrame("Slider", sn, parent, "OptionsSliderTemplate")
s:SetWidth(w); s:SetHeight(20)
s:SetPoint("TOPLEFT", parent, "TOPLEFT", x, y)
s:SetMinMaxValues(lo, hi); s:SetValueStep(step)
if s.SetObeyStepOnDrag then s:SetObeyStepOnDrag(true) end
local low = _G[sn.."Low"]; if low then low:SetText(tostring(lo)) end
local high = _G[sn.."High"]; if high then high:SetText(tostring(hi)) end
local text = _G[sn.."Text"]
local t = T()
-- style track
local regions = { s:GetRegions() }
for i = 1, table.getn(regions) do
local r = regions[i]
if r and r.GetObjectType and r:GetObjectType() == "Texture" then r:SetTexture(nil) end
end
local track = s:CreateTexture(nil, "BACKGROUND")
track:SetTexture("Interface\\Buttons\\WHITE8X8")
track:SetPoint("LEFT", s, "LEFT", 0, 0); track:SetPoint("RIGHT", s, "RIGHT", 0, 0)
track:SetHeight(4)
track:SetVertexColor(t.sliderTrack[1], t.sliderTrack[2], t.sliderTrack[3], t.sliderTrack[4])
local fill = s:CreateTexture(nil, "ARTWORK")
fill:SetTexture("Interface\\Buttons\\WHITE8X8")
fill:SetPoint("LEFT", track, "LEFT", 0, 0)
fill:SetPoint("TOP", track, "TOP", 0, 0); fill:SetPoint("BOTTOM", track, "BOTTOM", 0, 0)
fill:SetWidth(1)
fill:SetVertexColor(t.sliderFill[1], t.sliderFill[2], t.sliderFill[3], t.sliderFill[4])
if s.SetThumbTexture then s:SetThumbTexture("Interface\\Buttons\\WHITE8X8") end
local thumb = s.GetThumbTexture and s:GetThumbTexture()
if thumb then thumb:SetWidth(8); thumb:SetHeight(14)
thumb:SetVertexColor(t.sliderThumb[1], t.sliderThumb[2], t.sliderThumb[3], t.sliderThumb[4])
end
local block = false
local function UpdateFill()
local mn, mx = s:GetMinMaxValues(); local v = s:GetValue() or mn
local pct = 0; if mx > mn then pct = Clamp((v-mn)/(mx-mn), 0, 1) end
local pw = math.floor((s:GetWidth() or 1) * pct + 0.5); if pw < 1 then pw = 1 end
fill:SetWidth(pw)
end
local function UpdateLabel(v)
local d = fmt and fmt(v) or v
if text then text:SetText(label..": "..tostring(d)) end
end
s:SetScript("OnValueChanged", function()
if block then return end
local raw = this:GetValue() or lo
local v; if step >= 1 then v = math.floor(raw+0.5) else v = math.floor(raw/step+0.5)*step end
v = Clamp(v, lo, hi); UpdateLabel(v); UpdateFill(); setter(v)
end)
s.Refresh = function()
local v = Clamp(tonumber(getter()) or lo, lo, hi)
block = true; s:SetValue(v); block = false; UpdateLabel(v); UpdateFill()
end
if low then low:SetTextColor(t.dimText[1], t.dimText[2], t.dimText[3])
low:ClearAllPoints(); low:SetPoint("TOPLEFT", s, "BOTTOMLEFT", 0, 0) end
if high then high:SetTextColor(t.dimText[1], t.dimText[2], t.dimText[3])
high:ClearAllPoints(); high:SetPoint("TOPRIGHT", s, "BOTTOMRIGHT", 0, 0) end
if text then text:SetTextColor(t.text[1], t.text[2], t.text[3])
text:ClearAllPoints(); text:SetPoint("BOTTOM", s, "TOP", 0, 2) end
s:Refresh()
return s
end
local function MakeLabel(parent, text, x, y, size, r, g, b)
local fs = parent:CreateFontString(nil, "OVERLAY")
fs:SetFont(SFrames:GetFont(), size or 11, "OUTLINE")
fs:SetPoint("TOPLEFT", parent, "TOPLEFT", x, y)
fs:SetText(text); fs:SetTextColor(r or 1, g or 1, b or 1)
return fs
end
local function MakeDesc(parent, text, x, y, maxW)
local fs = parent:CreateFontString(nil, "OVERLAY")
fs:SetFont(SFrames:GetFont(), 9, "OUTLINE")
fs:SetPoint("TOPLEFT", parent, "TOPLEFT", x, y)
if maxW then fs:SetWidth(maxW); fs:SetJustifyH("LEFT") end
fs:SetText(text); fs:SetTextColor(0.65, 0.58, 0.62)
return fs
end
local function MakeSection(parent, title, x, y, w, h, iconKey)
local sec = CreateFrame("Frame", WN("Sec"), parent)
sec:SetWidth(w); sec:SetHeight(h)
sec:SetPoint("TOPLEFT", parent, "TOPLEFT", x, y)
sec:SetBackdrop({
bgFile = "Interface\\Tooltips\\UI-Tooltip-Background",
edgeFile = "Interface\\Tooltips\\UI-Tooltip-Border",
tile = true, tileSize = 16, edgeSize = 14,
insets = { left = 3, right = 3, top = 3, bottom = 3 },
})
local t = T()
sec:SetBackdropColor(t.sectionBg[1], t.sectionBg[2], t.sectionBg[3], t.sectionBg[4])
sec:SetBackdropBorderColor(t.sectionBorder[1], t.sectionBorder[2], t.sectionBorder[3], t.sectionBorder[4])
local anchorX = 10
if iconKey and SFrames and SFrames.CreateIcon then
local ico = SFrames:CreateIcon(sec, iconKey, 12)
ico:SetDrawLayer("OVERLAY")
ico:SetPoint("TOPLEFT", sec, "TOPLEFT", 10, -9)
ico:SetVertexColor(t.title[1], t.title[2], t.title[3])
anchorX = 26
end
local tfs = sec:CreateFontString(nil, "OVERLAY")
tfs:SetFont(SFrames:GetFont(), 11, "OUTLINE")
tfs:SetPoint("TOPLEFT", sec, "TOPLEFT", anchorX, -9)
tfs:SetText(title); tfs:SetTextColor(t.title[1], t.title[2], t.title[3])
local div = sec:CreateTexture(nil, "ARTWORK")
div:SetTexture("Interface\\Buttons\\WHITE8X8"); div:SetHeight(1)
div:SetPoint("TOPLEFT", sec, "TOPLEFT", 8, -24)
div:SetPoint("TOPRIGHT", sec, "TOPRIGHT", -8, -24)
div:SetVertexColor(t.sectionBorder[1], t.sectionBorder[2], t.sectionBorder[3], 0.6)
return sec
end
-- Toggle-group: row of small buttons, one active at a time
local function MakeBtnGroup(parent, x, y, opts, getter, setter)
local btns = {}
local bx = x
for _, o in ipairs(opts) do
local b = MakeButton(parent, o.label, o.w or 60, 22, nil)
b:ClearAllPoints(); b:SetPoint("TOPLEFT", parent, "TOPLEFT", bx, y)
b.key = o.key
b:SetScript("OnClick", function()
setter(this.key)
for _, bb in ipairs(btns) do bb.sfActive = (bb.key == this.key); bb:RefreshVisual() end
PlaySound("igMainMenuOptionCheckBoxOn")
end)
table.insert(btns, b); bx = bx + (o.w or 60) + 4
end
local grp = {}
grp.Refresh = function()
local v = getter()
for _, bb in ipairs(btns) do bb.sfActive = (bb.key == v); bb:RefreshVisual() end
end
grp.Refresh()
return grp
end
--------------------------------------------------------------------------------
-- Default / current choices
--------------------------------------------------------------------------------
local function GetDefaultChoices()
return {
themePreset = "pink",
useClassTheme = false,
enableMerchant = true,
enableQuestUI = true,
enableQuestLogSkin = true,
enableTrainer = true,
enableSpellBook = true,
enableTradeSkill = true,
charPanelEnable = true,
enableMail = true,
enablePetStable = true,
enableSocial = true,
enableInspect = true,
enableFlightMap = true,
enableChat = true,
translateEnabled = false,
enableUnitFrames = true,
enableActionBars = true,
showPetBar = true,
showStanceBar = true,
showRightBars = true,
buttonRounded = false,
buttonInnerShadow = false,
minimapEnabled = true,
minimapShowClock = true,
minimapShowCoords = true,
minimapMapStyle = "auto",
minimapMapShape = "circle",
buffEnabled = true,
buffIconSize = 30,
buffIconsPerRow = 8,
buffGrowDir = "LEFT",
buffShowTimer = true,
afkEnabled = true,
afkDelay = 5,
afkOutsideRest = false,
mapRevealEnabled = true,
mapRevealAlpha = 0.7,
worldMapEnabled = true,
hcGlobalDisable = false,
iconSet = "icon",
}
end
local function GetCurrentChoices()
local c = GetDefaultChoices()
if not SFramesDB then return c end
local db = SFramesDB
if db.Theme then
if db.Theme.preset ~= nil then c.themePreset = db.Theme.preset end
if db.Theme.useClassTheme ~= nil then c.useClassTheme = db.Theme.useClassTheme end
if db.Theme.iconSet ~= nil then c.iconSet = db.Theme.iconSet end
end
if db.enableMerchant ~= nil then c.enableMerchant = db.enableMerchant end
if db.enableQuestUI ~= nil then c.enableQuestUI = db.enableQuestUI end
if db.enableQuestLogSkin ~= nil then c.enableQuestLogSkin = db.enableQuestLogSkin end
if db.enableTrainer ~= nil then c.enableTrainer = db.enableTrainer end
if db.enableSpellBook ~= nil then c.enableSpellBook = db.enableSpellBook end
if db.enableTradeSkill ~= nil then c.enableTradeSkill = db.enableTradeSkill end
if db.charPanelEnable ~= nil then c.charPanelEnable = db.charPanelEnable end
if db.enableMail ~= nil then c.enableMail = db.enableMail end
if db.enablePetStable ~= nil then c.enablePetStable = db.enablePetStable end
if db.enableSocial ~= nil then c.enableSocial = db.enableSocial end
if db.enableInspect ~= nil then c.enableInspect = db.enableInspect end
if db.enableFlightMap ~= nil then c.enableFlightMap = db.enableFlightMap end
if db.enableChat ~= nil then c.enableChat = db.enableChat end
if db.enableUnitFrames ~= nil then c.enableUnitFrames = db.enableUnitFrames end
if db.afkEnabled ~= nil then c.afkEnabled = db.afkEnabled end
if type(db.afkDelay) == "number" then c.afkDelay = db.afkDelay end
if db.afkOutsideRest ~= nil then c.afkOutsideRest = db.afkOutsideRest end
if db.Chat and db.Chat.translateEnabled ~= nil then c.translateEnabled = db.Chat.translateEnabled end
if db.Chat and db.Chat.hcGlobalDisable ~= nil then c.hcGlobalDisable = db.Chat.hcGlobalDisable end
if db.ActionBars then
if db.ActionBars.enable ~= nil then c.enableActionBars = db.ActionBars.enable end
if db.ActionBars.showPetBar ~= nil then c.showPetBar = db.ActionBars.showPetBar end
if db.ActionBars.showStanceBar ~= nil then c.showStanceBar = db.ActionBars.showStanceBar end
if db.ActionBars.showRightBars ~= nil then c.showRightBars = db.ActionBars.showRightBars end
if db.ActionBars.buttonRounded ~= nil then c.buttonRounded = db.ActionBars.buttonRounded end
if db.ActionBars.buttonInnerShadow ~= nil then c.buttonInnerShadow = db.ActionBars.buttonInnerShadow end
end
if db.Minimap then
if db.Minimap.enabled ~= nil then c.minimapEnabled = db.Minimap.enabled end
if db.Minimap.showClock ~= nil then c.minimapShowClock = db.Minimap.showClock end
if db.Minimap.showCoords ~= nil then c.minimapShowCoords = db.Minimap.showCoords end
if db.Minimap.mapStyle ~= nil then c.minimapMapStyle = db.Minimap.mapStyle end
if db.Minimap.mapShape ~= nil then c.minimapMapShape = db.Minimap.mapShape end
end
if db.MinimapBuffs then
if db.MinimapBuffs.enabled ~= nil then c.buffEnabled = db.MinimapBuffs.enabled end
if type(db.MinimapBuffs.iconSize) == "number" then c.buffIconSize = db.MinimapBuffs.iconSize end
if type(db.MinimapBuffs.iconsPerRow) == "number" then c.buffIconsPerRow = db.MinimapBuffs.iconsPerRow end
if db.MinimapBuffs.growDirection ~= nil then c.buffGrowDir = db.MinimapBuffs.growDirection end
if db.MinimapBuffs.showTimer ~= nil then c.buffShowTimer = db.MinimapBuffs.showTimer end
end
if db.MapReveal then
if db.MapReveal.enabled ~= nil then c.mapRevealEnabled = db.MapReveal.enabled end
if type(db.MapReveal.unexploredAlpha) == "number" then c.mapRevealAlpha = db.MapReveal.unexploredAlpha end
end
if db.WorldMap then
if db.WorldMap.enabled ~= nil then c.worldMapEnabled = db.WorldMap.enabled end
end
return c
end
--------------------------------------------------------------------------------
-- Apply choices → SFramesDB
--------------------------------------------------------------------------------
local function ApplyChoices()
if not SFramesDB then SFramesDB = {} end
local c = choices
if type(SFramesDB.Theme) ~= "table" then SFramesDB.Theme = {} end
SFramesDB.Theme.preset = c.themePreset
SFramesDB.Theme.useClassTheme = c.useClassTheme
SFramesDB.Theme.iconSet = c.iconSet or "icon"
SFramesDB.enableMerchant = c.enableMerchant
SFramesDB.enableQuestUI = c.enableQuestUI
SFramesDB.enableQuestLogSkin = c.enableQuestLogSkin
SFramesDB.enableTrainer = c.enableTrainer
SFramesDB.enableSpellBook = c.enableSpellBook
SFramesDB.enableTradeSkill = c.enableTradeSkill
SFramesDB.charPanelEnable = c.charPanelEnable
SFramesDB.enableMail = c.enableMail
SFramesDB.enablePetStable = c.enablePetStable
SFramesDB.enableSocial = c.enableSocial
SFramesDB.enableInspect = c.enableInspect
SFramesDB.enableFlightMap = c.enableFlightMap
SFramesDB.enableChat = c.enableChat
if type(SFramesDB.Chat) ~= "table" then SFramesDB.Chat = {} end
SFramesDB.Chat.translateEnabled = c.translateEnabled
SFramesDB.Chat.hcGlobalDisable = c.hcGlobalDisable
SFramesDB.enableUnitFrames = c.enableUnitFrames
if type(SFramesDB.ActionBars) ~= "table" then SFramesDB.ActionBars = {} end
SFramesDB.ActionBars.enable = c.enableActionBars
SFramesDB.ActionBars.showPetBar = c.showPetBar
SFramesDB.ActionBars.showStanceBar = c.showStanceBar
SFramesDB.ActionBars.showRightBars = c.showRightBars
SFramesDB.ActionBars.buttonRounded = c.buttonRounded
SFramesDB.ActionBars.buttonInnerShadow = c.buttonInnerShadow
if type(SFramesDB.Minimap) ~= "table" then SFramesDB.Minimap = {} end
SFramesDB.Minimap.enabled = c.minimapEnabled
SFramesDB.Minimap.showClock = c.minimapShowClock
SFramesDB.Minimap.showCoords = c.minimapShowCoords
SFramesDB.Minimap.mapStyle = c.minimapMapStyle
SFramesDB.Minimap.mapShape = c.minimapMapShape
if type(SFramesDB.MinimapBuffs) ~= "table" then SFramesDB.MinimapBuffs = {} end
SFramesDB.MinimapBuffs.enabled = c.buffEnabled
SFramesDB.MinimapBuffs.iconSize = c.buffIconSize
SFramesDB.MinimapBuffs.iconsPerRow = c.buffIconsPerRow
SFramesDB.MinimapBuffs.growDirection = c.buffGrowDir
SFramesDB.MinimapBuffs.showTimer = c.buffShowTimer
SFramesDB.afkEnabled = c.afkEnabled
SFramesDB.afkDelay = c.afkDelay
SFramesDB.afkOutsideRest = c.afkOutsideRest
if type(SFramesDB.MapReveal) ~= "table" then SFramesDB.MapReveal = {} end
SFramesDB.MapReveal.enabled = c.mapRevealEnabled
SFramesDB.MapReveal.unexploredAlpha = c.mapRevealAlpha
if type(SFramesDB.WorldMap) ~= "table" then SFramesDB.WorldMap = {} end
SFramesDB.WorldMap.enabled = c.worldMapEnabled
SFrames.Theme:Apply(c.useClassTheme and SFrames.Theme:GetCurrentPreset() or c.themePreset)
SFramesDB.setupComplete = true
end
--------------------------------------------------------------------------------
-- Step builders (lazy — only called first time user navigates to that step)
--------------------------------------------------------------------------------
-- Step 1: Welcome -----------------------------------------------------------
local function BuildWelcome(page)
local t = T()
local font = SFrames:GetFont()
local logoIco = SFrames:CreateIcon(page, "logo", 52)
logoIco:SetDrawLayer("OVERLAY")
logoIco:SetPoint("TOP", page, "TOP", 0, -44)
local logo = page:CreateFontString(nil, "OVERLAY")
logo:SetFont(font, 22, "OUTLINE")
logo:SetPoint("TOP", logoIco, "BOTTOM", 0, -8)
logo:SetText("Nanami-UI")
logo:SetTextColor(t.accentLight[1], t.accentLight[2], t.accentLight[3])
local sub = page:CreateFontString(nil, "OVERLAY")
sub:SetFont(font, 11, "OUTLINE")
sub:SetPoint("TOP", logo, "BOTTOM", 0, -4)
sub:SetText("v1.0.0")
sub:SetTextColor(t.dimText[1], t.dimText[2], t.dimText[3])
local tagline = page:CreateFontString(nil, "OVERLAY")
tagline:SetFont(font, 10, "OUTLINE")
tagline:SetPoint("TOP", sub, "BOTTOM", 0, -14)
tagline:SetWidth(440); tagline:SetJustifyH("CENTER")
tagline:SetText("一站式现代 UI 重塑方案,为经典怀旧而生")
tagline:SetTextColor(t.accentLight[1], t.accentLight[2], t.accentLight[3])
local desc = page:CreateFontString(nil, "OVERLAY")
desc:SetFont(font, 9.5, "OUTLINE")
desc:SetPoint("TOP", tagline, "BOTTOM", 0, -12)
desc:SetWidth(440); desc:SetJustifyH("CENTER")
desc:SetText(
"9 套可选主题配色 + 职业自适应配色\n"..
"全面重设计:商人 / 任务 / 技能书 / 邮箱 / 社交 等 12+ 界面\n"..
"现代化单位框体、动作条、聊天框与 Buff 栏\n"..
"AI 翻译、飞行助手、地图增强、AFK 待机动画\n"..
"轻量高效,所有功能均可自由开关\n\n"..
"接下来将引导你完成基础配置,\n"..
"你也可以随时跳过,直接使用默认设置。"
)
desc:SetTextColor(0.78, 0.72, 0.76)
local startBtn = MakeButton(page, "开始配置", 160, 32, function()
SW:GoTo(2)
end)
startBtn:SetPoint("TOP", desc, "BOTTOM", 0, -18)
local skipBtn = MakeButton(page, "跳过,使用默认配置", 200, 28, function()
SW:DoSkip()
end)
skipBtn:SetPoint("TOP", startBtn, "BOTTOM", 0, -10)
page:SetHeight(CONTENT_H)
end
-- Step 2: Theme & Skins ----------------------------------------------------
local function BuildTheme(page)
local t = T()
local font = SFrames:GetFont()
local sec1 = MakeSection(page, "主题配色", 0, 0, CONTENT_W, 100, "star")
local presets = SFrames.Theme.PresetOrder
local swatches = {}
for idx = 1, table.getn(presets) do
local key = presets[idx]
local p = SFrames.Theme.Presets[key]
local sw = CreateFrame("Button", WN("Sw"), sec1)
sw:SetWidth(28); sw:SetHeight(28)
sw:SetPoint("TOPLEFT", sec1, "TOPLEFT", 14 + (idx-1)*34, -34)
sw:SetBackdrop({
bgFile = "Interface\\Buttons\\WHITE8X8",
edgeFile = "Interface\\Tooltips\\UI-Tooltip-Border",
tile = false, edgeSize = 8,
insets = { left = 2, right = 2, top = 2, bottom = 2 },
})
local hr, hg, hb = SFrames.Theme.HSVtoRGB(p.hue, 0.40 * (p.satMul or 1), 0.80)
sw:SetBackdropColor(hr, hg, hb, 1)
sw.presetKey = key
sw:SetScript("OnClick", function()
choices.themePreset = this.presetKey
choices.useClassTheme = false
SFrames.Theme:Apply(this.presetKey)
SW:RefreshPanelColors()
for _, s in ipairs(swatches) do
if s.presetKey == this.presetKey then
s:SetBackdropBorderColor(1,1,1,1)
else
s:SetBackdropBorderColor(0.25,0.25,0.25,1)
end
end
if page._classCheck then page._classCheck:Refresh() end
PlaySound("igMainMenuOptionCheckBoxOn")
end)
local active = choices.themePreset
if key == active and not choices.useClassTheme then
sw:SetBackdropBorderColor(1,1,1,1)
else
sw:SetBackdropBorderColor(0.25,0.25,0.25,1)
end
sw:SetScript("OnEnter", function()
this:SetBackdropBorderColor(0.8,0.8,0.8,1)
GameTooltip:SetOwner(this, "ANCHOR_TOP")
GameTooltip:SetText(SFrames.Theme.Presets[this.presetKey].name)
GameTooltip:Show()
end)
sw:SetScript("OnLeave", function()
local act = choices.themePreset
if this.presetKey == act and not choices.useClassTheme then
this:SetBackdropBorderColor(1,1,1,1)
else
this:SetBackdropBorderColor(0.25,0.25,0.25,1)
end
GameTooltip:Hide()
end)
table.insert(swatches, sw)
end
page._classCheck = MakeCheck(sec1, "根据当前职业自动选择主题色", 14, -72,
function() return choices.useClassTheme end,
function(v) choices.useClassTheme = v
if v then
SFrames.Theme:Apply(SFrames.Theme:GetCurrentPreset())
else
SFrames.Theme:Apply(choices.themePreset)
end
SW:RefreshPanelColors()
end)
-- Icon set picker (right after theme color)
local secIcon = MakeSection(page, "图标风格", 0, -108, CONTENT_W, 90, "star")
MakeDesc(secIcon, "选择图标风格8 套可选,重载 UI 后生效)", 14, -28, CONTENT_W - 30)
local ISET_NAMES = { "icon", "icon2", "icon3", "icon4", "icon5", "icon6", "icon7", "icon8" }
local ISET_SIZE = 36
local ISET_GAP = 8
local wFaction = UnitFactionGroup and UnitFactionGroup("player") or "Alliance"
local wFKey = (wFaction == "Horde") and "horde" or "alliance"
local wFCoords = SFrames.ICON_TCOORDS and SFrames.ICON_TCOORDS[wFKey]
if not wFCoords then
wFCoords = (wFKey == "horde") and { 0.75, 0.875, 0, 0.125 } or { 0.625, 0.75, 0, 0.125 }
end
local isetBtns = {}
for idx = 1, table.getn(ISET_NAMES) do
local setKey = ISET_NAMES[idx]
local texPath = "Interface\\AddOns\\Nanami-UI\\img\\" .. setKey
local sx = 14 + (idx - 1) * (ISET_SIZE + ISET_GAP)
local ib = CreateFrame("Button", WN("IS"), secIcon)
ib:SetWidth(ISET_SIZE); ib:SetHeight(ISET_SIZE)
ib:SetPoint("TOPLEFT", secIcon, "TOPLEFT", sx, -44)
ib: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 },
})
ib:SetBackdropColor(0.08, 0.08, 0.1, 0.85)
ib:SetBackdropBorderColor(0.3, 0.3, 0.35, 1)
local pv = ib:CreateTexture(nil, "ARTWORK")
pv:SetTexture(texPath)
pv:SetTexCoord(wFCoords[1], wFCoords[2], wFCoords[3], wFCoords[4])
pv:SetPoint("CENTER", ib, "CENTER", 0, 0)
pv:SetWidth(ISET_SIZE - 6); pv:SetHeight(ISET_SIZE - 6)
ib.setKey = setKey
ib:SetScript("OnClick", function()
choices.iconSet = this.setKey
for _, b in ipairs(isetBtns) do
if b.setKey == this.setKey then
b:SetBackdropBorderColor(1, 0.85, 0.6, 1)
else
b:SetBackdropBorderColor(0.3, 0.3, 0.35, 1)
end
end
PlaySound("igMainMenuOptionCheckBoxOn")
end)
ib:SetScript("OnEnter", function()
this:SetBackdropBorderColor(0.7, 0.7, 0.7, 1)
GameTooltip:SetOwner(this, "ANCHOR_NONE")
GameTooltip:ClearAllPoints()
GameTooltip:SetPoint("BOTTOM", this, "TOP", 0, 6)
local num = this.setKey == "icon" and "1" or string.sub(this.setKey, 5)
GameTooltip:SetText("图标风格 " .. num .. (this.setKey == "icon" and " (默认)" or ""))
GameTooltip:Show()
GameTooltip:SetFrameStrata("TOOLTIP")
GameTooltip:Raise()
end)
ib:SetScript("OnLeave", function()
local cur = choices.iconSet or "icon"
if this.setKey == cur then
this:SetBackdropBorderColor(1, 0.85, 0.6, 1)
else
this:SetBackdropBorderColor(0.3, 0.3, 0.35, 1)
end
GameTooltip:Hide()
end)
local cur = choices.iconSet or "icon"
if setKey == cur then
ib:SetBackdropBorderColor(1, 0.85, 0.6, 1)
end
table.insert(isetBtns, ib)
end
-- Skin toggles (below icon picker)
local sec2 = MakeSection(page, "界面皮肤替换", 0, -206, CONTENT_W, 150, "settings")
MakeDesc(sec2, "选择启用哪些 Nanami-UI 美化界面(替换原生窗口)", 14, -28, CONTENT_W - 30)
local skins = {
{ key = "enableMerchant", label = "商人界面" },
{ key = "enableQuestUI", label = "任务/NPC 对话" },
{ key = "enableQuestLogSkin",label = "任务日志" },
{ key = "enableSpellBook", label = "技能书" },
{ key = "enableTradeSkill", label = "专业技能" },
{ key = "enableTrainer", label = "训练师" },
{ key = "charPanelEnable", label = "人物面板" },
{ key = "enableMail", label = "邮箱" },
{ key = "enablePetStable", label = "兽栏" },
{ key = "enableSocial", label = "社交" },
{ key = "enableInspect", label = "观察面板" },
{ key = "enableFlightMap", label = "飞行地图" },
}
local col, row = 0, 0
for i = 1, table.getn(skins) do
local sk = skins[i]
local cx = 14 + col * 180
local cy = -44 - row * 22
MakeCheck(sec2, sk.label, cx, cy,
function() return choices[sk.key] end,
function(v) choices[sk.key] = v end)
col = col + 1
if col >= 3 then col = 0; row = row + 1 end
end
page:SetHeight(370)
end
-- Step 3: Chat & Translation ------------------------------------------------
local function BuildChat(page)
local t = T()
local sec1 = MakeSection(page, "聊天框", 0, 0, CONTENT_W, 80, "chat")
local chatCb = MakeCheck(sec1, "启用 Nanami-UI 聊天框加强", 14, -34,
function() return choices.enableChat end,
function(v) choices.enableChat = v end)
MakeDesc(sec1, "标签式多频道、频道过滤、样式美化、功能增强", 36, -52, CONTENT_W - 50)
page._chatCb = chatCb
local sec2 = MakeSection(page, "AI 翻译", 0, -90, CONTENT_W, 150, "ai")
local transCb = MakeCheck(sec2, "启用聊天频道 AI 自动翻译", 14, -34,
function() return choices.translateEnabled end,
function(v)
choices.translateEnabled = v
if v then
choices.enableChat = true
if page._chatCb then page._chatCb:Refresh() end
end
end)
MakeDesc(sec2,
"需要 STranslate 外部程序和 API 支持。\n若不使用翻译功能STranslate 无需运行。\n\n启用翻译将自动启用聊天框加强。\n翻译频道的详细配置请在聊天设置中调整。",
14, -56, CONTENT_W - 30)
page:SetHeight(250)
end
-- Step 4: Unit Frames + Action Bars (combined) -----------------------------
local function BuildFramesAndBars(page)
-- Unit Frames section
local sec1 = MakeSection(page, "单位框体", 0, 0, CONTENT_W, 90, "character")
MakeCheck(sec1, "启用 Nanami-UI 单位框体", 14, -34,
function() return choices.enableUnitFrames end,
function(v) choices.enableUnitFrames = v end)
MakeDesc(sec1,
"替换原生玩家、目标、宠物、小队、团队框体为现代极简风格",
14, -56, CONTENT_W - 30)
-- Action Bars section
local sec2 = MakeSection(page, "动作条", 0, -100, CONTENT_W, 210, "attack")
local subFrame = CreateFrame("Frame", nil, sec2)
subFrame:SetPoint("TOPLEFT", sec2, "TOPLEFT", 0, -60)
subFrame:SetWidth(CONTENT_W); subFrame:SetHeight(140)
MakeCheck(sec2, "启用 Nanami-UI 动作条接管", 14, -34,
function() return choices.enableActionBars end,
function(v)
choices.enableActionBars = v
if v then subFrame:Show() else subFrame:Hide() end
end)
MakeDesc(sec2, "需要 /reload 生效", 36, -52, 300)
MakeCheck(subFrame, "显示宠物动作条", 30, -6,
function() return choices.showPetBar end,
function(v) choices.showPetBar = v end)
MakeCheck(subFrame, "显示姿态栏 / 形态栏", 30, -30,
function() return choices.showStanceBar end,
function(v) choices.showStanceBar = v end)
MakeCheck(subFrame, "显示右侧栏", 30, -54,
function() return choices.showRightBars end,
function(v) choices.showRightBars = v end)
MakeCheck(subFrame, "按钮圆角", 30, -78,
function() return choices.buttonRounded end,
function(v) choices.buttonRounded = v end)
MakeCheck(subFrame, "按钮内阴影", 230, -78,
function() return choices.buttonInnerShadow end,
function(v) choices.buttonInnerShadow = v end)
if not choices.enableActionBars then subFrame:Hide() end
page:SetHeight(320)
end
-- Step 6: Extras (with sub-options) -----------------------------------------
local function BuildExtras(page)
local items = {}
local allControls = {}
local function AddFeature(label, desc, enableKey, subBuilder, iconKey)
table.insert(items, { label = label, desc = desc, key = enableKey, subBuilder = subBuilder, icon = iconKey })
end
AddFeature("小地图美化", "自定义小地图外观", "minimapEnabled", function(p, x, y)
MakeCheck(p, "显示时钟", x, y,
function() return choices.minimapShowClock end,
function(v) choices.minimapShowClock = v end)
MakeCheck(p, "显示坐标", x + 160, y,
function() return choices.minimapShowCoords end,
function(v) choices.minimapShowCoords = v end)
local function GetMode()
local shape = choices.minimapMapShape or "circle"
if shape == "square1" or shape == "square2" then return "square" end
if (choices.minimapMapStyle or "auto") == "auto" then return "auto" end
return "round"
end
local circleFrame = CreateFrame("Frame", nil, p)
circleFrame:SetPoint("TOPLEFT", p, "TOPLEFT", x, y - 50)
circleFrame:SetWidth(CONTENT_W)
circleFrame:SetHeight(56)
circleFrame:Hide()
MakeLabel(circleFrame, "圆形边框:", 0, 0, 10, 0.78, 0.72, 0.76)
local mapStyles = SFrames.Minimap and SFrames.Minimap.MAP_STYLES or {}
local CS, CG = 28, 4
local circleBtns = {}
local autoStyleBtn = MakeButton(circleFrame, "自动", 38, CS, nil)
autoStyleBtn:ClearAllPoints()
autoStyleBtn:SetPoint("TOPLEFT", circleFrame, "TOPLEFT", 0, -16)
local function RefreshCircleHighlight()
local cur = choices.minimapMapStyle or "auto"
local matched = (cur == "auto")
for _, b in ipairs(circleBtns) do
if b.styleKey == cur then
b:SetBackdropBorderColor(1, 0.78, 0.2, 1)
matched = true
else
b:SetBackdropBorderColor(0.3, 0.3, 0.35, 1)
end
end
if not matched then
choices.minimapMapStyle = "auto"
end
autoStyleBtn.sfActive = ((choices.minimapMapStyle or "auto") == "auto")
autoStyleBtn:RefreshVisual()
end
autoStyleBtn:SetScript("OnClick", function()
choices.minimapMapStyle = "auto"
RefreshCircleHighlight()
PlaySound("igMainMenuOptionCheckBoxOn")
end)
for idx, style in ipairs(mapStyles) do
local sb = CreateFrame("Button", WN("CS"), circleFrame)
sb:SetWidth(CS); sb:SetHeight(CS)
sb:SetPoint("TOPLEFT", circleFrame, "TOPLEFT",
42 + (idx - 1) * (CS + CG), -16)
sb:SetBackdrop({
bgFile = "Interface\\Tooltips\\UI-Tooltip-Background",
edgeFile = "Interface\\Tooltips\\UI-Tooltip-Border",
tile = true, tileSize = 16, edgeSize = 8,
insets = { left = 2, right = 2, top = 2, bottom = 2 },
})
sb:SetBackdropColor(0.08, 0.08, 0.1, 0.85)
local ptex = sb:CreateTexture(nil, "ARTWORK")
ptex:SetTexture(style.tex)
ptex:SetPoint("CENTER")
ptex:SetWidth(CS - 4); ptex:SetHeight(CS - 4)
sb.styleKey = style.key
sb._sfLabel = style.label
sb:SetScript("OnClick", function()
choices.minimapMapStyle = this.styleKey
RefreshCircleHighlight()
PlaySound("igMainMenuOptionCheckBoxOn")
end)
sb:SetScript("OnEnter", function()
this:SetBackdropBorderColor(0.7, 0.7, 0.7, 1)
GameTooltip:SetOwner(this, "ANCHOR_TOP")
GameTooltip:SetText(this._sfLabel)
GameTooltip:Show()
end)
sb:SetScript("OnLeave", function()
local cur = choices.minimapMapStyle or "auto"
if this.styleKey == cur then
this:SetBackdropBorderColor(1, 0.78, 0.2, 1)
else
this:SetBackdropBorderColor(0.3, 0.3, 0.35, 1)
end
GameTooltip:Hide()
end)
table.insert(circleBtns, sb)
end
RefreshCircleHighlight()
MakeLabel(p, "地图风格:", x, y - 26, 10, 0.78, 0.72, 0.76)
MakeBtnGroup(p, x + 60, y - 24,
{ {key="auto", label="自动", w=50}, {key="round", label="圆形", w=50}, {key="square", label="方形", w=50} },
GetMode,
function(v)
if v == "auto" then
choices.minimapMapShape = "circle"
choices.minimapMapStyle = "auto"
circleFrame:Hide()
elseif v == "round" then
choices.minimapMapShape = "circle"
if choices.minimapMapStyle == "auto" then
choices.minimapMapStyle = "map"
end
RefreshCircleHighlight()
circleFrame:Show()
elseif v == "square" then
choices.minimapMapShape = "square1"
circleFrame:Hide()
end
end)
if GetMode() == "round" then circleFrame:Show() end
return 110
end, "worldmap")
AddFeature("Buff 栏", "自定义 Buff/Debuff 显示", "buffEnabled", function(p, x, y)
MakeSlider(p, "图标大小", x, y - 10, 180, 16, 50, 1,
function() return choices.buffIconSize end,
function(v) choices.buffIconSize = v end)
MakeSlider(p, "每行数量", x + 220, y - 10, 180, 4, 16, 1,
function() return choices.buffIconsPerRow end,
function(v) choices.buffIconsPerRow = v end)
MakeLabel(p, "增长方向:", x, y - 52, 10, 0.78, 0.72, 0.76)
MakeBtnGroup(p, x + 60, y - 50,
{ {key="LEFT", label="向左", w=52}, {key="RIGHT", label="向右", w=52} },
function() return choices.buffGrowDir end,
function(v) choices.buffGrowDir = v end)
MakeCheck(p, "显示计时器", x + 220, y - 50,
function() return choices.buffShowTimer end,
function(v) choices.buffShowTimer = v end)
return 78
end, "buff")
AddFeature("AFK 待机动画", "进入暂离后显示全屏待机画面", "afkEnabled", function(p, x, y)
MakeSlider(p, "触发延迟(分钟)", x, y - 10, 200, 1, 30, 1,
function() return choices.afkDelay end,
function(v) choices.afkDelay = v end)
MakeCheck(p, "仅在休息区触发", x + 250, y - 4,
function() return choices.afkOutsideRest end,
function(v) choices.afkOutsideRest = v end)
return 48
end, "camp")
AddFeature("地图迷雾揭示", "显示未探索区域的轮廓", "mapRevealEnabled", function(p, x, y)
MakeSlider(p, "未探索区域透明度", x, y - 10, 240, 0, 1, 0.05,
function() return choices.mapRevealAlpha end,
function(v) choices.mapRevealAlpha = v end,
function(v) return string.format("%.0f%%", v * 100) end)
return 48
end, "search")
AddFeature("世界地图皮肤", "美化世界地图界面", "worldMapEnabled", nil, "worldmap")
AddFeature("飞行助手", "美化飞行界面 + 飞行进度条", "enableFlightMap", nil, "mount")
-- Build layout
local yOff = -4
for i = 1, table.getn(items) do
local item = items[i]
local sec = MakeSection(page, item.label, 0, yOff, CONTENT_W, 30, item.icon)
MakeDesc(sec, item.desc, 14, -30, CONTENT_W - 30)
sec:SetHeight(50)
local subFrame = nil
local subH = 0
if item.subBuilder then
subFrame = CreateFrame("Frame", nil, page)
subFrame:SetWidth(CONTENT_W)
end
local cb = MakeCheck(sec, "启用", CONTENT_W - 80, -8,
function() return choices[item.key] end,
function(v)
choices[item.key] = v
if subFrame then
if v then subFrame:Show() else subFrame:Hide() end
end
end)
if subFrame then
subH = item.subBuilder(subFrame, 14, -4) or 40
subFrame:SetHeight(subH)
subFrame:SetPoint("TOPLEFT", page, "TOPLEFT", 0, yOff - 50)
if not choices[item.key] then subFrame:Hide() end
yOff = yOff - 50 - subH - 6
else
yOff = yOff - 56
end
end
page:SetHeight(math.abs(yOff) + 10)
end
-- Step 7: Hardcore (conditional) --------------------------------------------
local function BuildHardcore(page)
local sec = MakeSection(page, "硬核模式", 0, 0, CONTENT_W, 120, "skull")
MakeDesc(sec, "检测到你的角色处于硬核模式,建议启用硬核频道接收\n以获取硬核死亡消息通知。", 14, -30, CONTENT_W - 30)
MakeCheck(sec, "启用硬核频道接收", 14, -68,
function() return not choices.hcGlobalDisable end,
function(v) choices.hcGlobalDisable = not v end)
MakeDesc(sec, "关闭将全局屏蔽硬核频道消息", 36, -86, 300)
page:SetHeight(140)
end
-- Complete page --------------------------------------------------------------
local function BuildComplete(page)
local t = T()
local font = SFrames:GetFont()
local doneIco = SFrames:CreateIcon(page, "logo", 48)
doneIco:SetDrawLayer("OVERLAY")
doneIco:SetPoint("TOP", page, "TOP", 0, -16)
doneIco:SetVertexColor(t.accentLight[1], t.accentLight[2], t.accentLight[3])
local icon = page:CreateFontString(nil, "OVERLAY")
icon:SetFont(font, 28, "OUTLINE")
icon:SetPoint("TOP", doneIco, "BOTTOM", 0, -4)
icon:SetText("=^_^=")
icon:SetTextColor(t.accentLight[1], t.accentLight[2], t.accentLight[3])
local title = page:CreateFontString(nil, "OVERLAY")
title:SetFont(font, 16, "OUTLINE")
title:SetPoint("TOP", icon, "BOTTOM", 0, -10)
title:SetText("配置完成!")
title:SetTextColor(t.title[1], t.title[2], t.title[3])
local desc = page:CreateFontString(nil, "OVERLAY")
desc:SetFont(font, 10, "OUTLINE")
desc:SetPoint("TOP", title, "BOTTOM", 0, -14)
desc:SetWidth(420); desc:SetJustifyH("CENTER")
desc:SetTextColor(0.78, 0.72, 0.76)
if runMode == "rerun" then
desc:SetText("设置已更新。部分选项需要 /reload 才能完全生效。\n你可以随时通过 /nui config 或 ESC 菜单调整设置。")
else
desc:SetText("所有设置已保存。\n你可以随时通过 /nui config 或 ESC 菜单调整设置。")
end
local doneBtn = MakeButton(page, "完成", 140, 32, function()
SW:DoComplete()
end)
doneBtn:SetPoint("TOP", desc, "BOTTOM", 0, -24)
page:SetHeight(CONTENT_H)
end
--------------------------------------------------------------------------------
-- Build main frame (once)
--------------------------------------------------------------------------------
local function EnsureWizardFrame()
if built then return end
built = true
local t = T()
-- Full-screen overlay
overlay = CreateFrame("Frame", "NanamiWizOverlay", UIParent)
overlay:SetFrameStrata("DIALOG")
overlay:SetAllPoints(UIParent)
overlay:EnableMouse(true)
overlay:SetScript("OnMouseDown", function() end)
local bg = overlay:CreateTexture(nil, "BACKGROUND")
bg:SetAllPoints(overlay)
bg:SetTexture(0, 0, 0, 0.6)
-- Center panel
panel = CreateFrame("Frame", "NanamiWizPanel", overlay)
panel:SetWidth(PANEL_W); panel:SetHeight(PANEL_H)
panel:SetPoint("CENTER", UIParent, "CENTER", 0, 0)
panel:SetBackdrop({
bgFile = "Interface\\Tooltips\\UI-Tooltip-Background",
edgeFile = "Interface\\Tooltips\\UI-Tooltip-Border",
tile = true, tileSize = 16, edgeSize = 14,
insets = { left = 3, right = 3, top = 3, bottom = 3 },
})
panel:SetBackdropColor(t.panelBg[1], t.panelBg[2], t.panelBg[3], t.panelBg[4])
panel:SetBackdropBorderColor(t.panelBorder[1], t.panelBorder[2], t.panelBorder[3], t.panelBorder[4])
-- Header
headerTitle = panel:CreateFontString(nil, "OVERLAY")
headerTitle:SetFont(SFrames:GetFont(), 13, "OUTLINE")
headerTitle:SetPoint("TOP", panel, "TOP", 0, -14)
headerTitle:SetTextColor(t.title[1], t.title[2], t.title[3])
-- Step dots row
local dotHolder = CreateFrame("Frame", nil, panel)
dotHolder:SetWidth(PANEL_W); dotHolder:SetHeight(16)
dotHolder:SetPoint("TOP", panel, "TOP", 0, -34)
-- Content scroll area
local contentHolder = CreateFrame("Frame", nil, panel)
contentHolder:SetWidth(CONTENT_W); contentHolder:SetHeight(CONTENT_H)
contentHolder:SetPoint("TOPLEFT", panel, "TOPLEFT", 20, -56)
contentScroll = CreateFrame("ScrollFrame", WN("Scr"), contentHolder)
contentScroll:SetAllPoints(contentHolder)
contentChild = CreateFrame("Frame", nil, contentScroll)
contentChild:SetWidth(CONTENT_W)
contentChild:SetHeight(CONTENT_H)
contentScroll:SetScrollChild(contentChild)
contentHolder:EnableMouseWheel(true)
contentHolder:SetScript("OnMouseWheel", function()
local cur = contentScroll:GetVerticalScroll()
local maxS = math.max(0, contentChild:GetHeight() - CONTENT_H)
local nv = cur - arg1 * 30
if nv < 0 then nv = 0 end
if nv > maxS then nv = maxS end
contentScroll:SetVerticalScroll(nv)
end)
-- Footer navigation
local footer = CreateFrame("Frame", nil, panel)
footer:SetWidth(PANEL_W - 40); footer:SetHeight(36)
footer:SetPoint("BOTTOM", panel, "BOTTOM", 0, 12)
btnPrev = MakeButton(footer, "< 上一步", 120, 28, function() SW:GoPrev() end)
btnPrev:SetPoint("LEFT", footer, "LEFT", 0, 0)
stepLabel = footer:CreateFontString(nil, "OVERLAY")
stepLabel:SetFont(SFrames:GetFont(), 10, "OUTLINE")
stepLabel:SetPoint("CENTER", footer, "CENTER", 0, 0)
stepLabel:SetTextColor(t.dimText[1], t.dimText[2], t.dimText[3])
btnNext = MakeButton(footer, "下一步 >", 120, 28, function() SW:GoNext() end)
btnNext:SetPoint("RIGHT", footer, "RIGHT", 0, 0)
btnSkip = CreateFrame("Button", WN("Skip"), footer)
btnSkip:SetWidth(60); btnSkip:SetHeight(16)
btnSkip:SetPoint("BOTTOMRIGHT", panel, "BOTTOMRIGHT", -14, 4)
local skipFs = btnSkip:CreateFontString(nil, "OVERLAY")
skipFs:SetFont(SFrames:GetFont(), 9, "OUTLINE")
skipFs:SetAllPoints(btnSkip)
skipFs:SetText("跳过全部")
skipFs:SetTextColor(0.55, 0.50, 0.54)
btnSkip:SetScript("OnClick", function() SW:DoSkip() end)
btnSkip:SetScript("OnEnter", function() skipFs:SetTextColor(0.85, 0.75, 0.80) end)
btnSkip:SetScript("OnLeave", function() skipFs:SetTextColor(0.55, 0.50, 0.54) end)
-- Store dot holder for later
panel._dotHolder = dotHolder
end
--------------------------------------------------------------------------------
-- Step list & dot creation
--------------------------------------------------------------------------------
local function BuildStepList()
stepList = {}
table.insert(stepList, { id = "welcome", title = "欢迎", builder = BuildWelcome })
table.insert(stepList, { id = "theme", title = "主题与皮肤", builder = BuildTheme })
table.insert(stepList, { id = "chat", title = "聊天与翻译", builder = BuildChat })
table.insert(stepList, { id = "framesbars", title = "框体与动作条", builder = BuildFramesAndBars })
table.insert(stepList, { id = "extras", title = "辅助功能", builder = BuildExtras })
-- Hardcore: only if player is hardcore
local isHC = false
if C_TurtleWoW and C_TurtleWoW.IsHardcore then
local ok, val = pcall(C_TurtleWoW.IsHardcore, "player")
if ok and val then isHC = true end
end
if isHC then
table.insert(stepList, { id = "hardcore", title = "硬核模式", builder = BuildHardcore })
end
table.insert(stepList, { id = "complete", title = "完成", builder = BuildComplete })
-- Create step pages (empty frames, lazily populated)
for _, pg in ipairs(stepPages) do if pg then pg:Hide() end end
stepPages = {}
for i = 1, table.getn(stepList) do
local pg = CreateFrame("Frame", nil, contentChild)
pg:SetWidth(CONTENT_W)
pg:SetHeight(CONTENT_H)
pg:SetPoint("TOPLEFT", contentChild, "TOPLEFT", 0, 0)
pg:Hide()
pg._built = false
stepPages[i] = pg
end
-- Create dots
for _, d in ipairs(stepDots) do if d then d:Hide() end end
stepDots = {}
local totalDots = table.getn(stepList)
local dotSpacing = 18
local startX = (PANEL_W - totalDots * dotSpacing) / 2
for i = 1, totalDots do
local dot = CreateFrame("Frame", nil, panel._dotHolder)
dot:SetWidth(8); dot:SetHeight(8)
dot:SetPoint("TOPLEFT", panel._dotHolder, "TOPLEFT", startX + (i-1)*dotSpacing, -4)
dot:SetBackdrop({
bgFile = "Interface\\Buttons\\WHITE8X8",
edgeFile = "Interface\\Buttons\\WHITE8X8",
tile = false, edgeSize = 1,
insets = { left = 0, right = 0, top = 0, bottom = 0 },
})
stepDots[i] = dot
end
end
--------------------------------------------------------------------------------
-- Navigation
--------------------------------------------------------------------------------
function SW:RefreshPanelColors()
if not panel then return end
local t = T()
panel:SetBackdropColor(t.panelBg[1], t.panelBg[2], t.panelBg[3], t.panelBg[4])
panel:SetBackdropBorderColor(t.panelBorder[1], t.panelBorder[2], t.panelBorder[3], t.panelBorder[4])
if headerTitle then headerTitle:SetTextColor(t.title[1], t.title[2], t.title[3]) end
end
function SW:GoTo(idx)
if idx < 1 or idx > table.getn(stepList) then return end
currentStep = idx
-- Lazily build
local pg = stepPages[idx]
if not pg._built then
pg._built = true
local ok, err = pcall(stepList[idx].builder, pg)
if not ok then
DEFAULT_CHAT_FRAME:AddMessage("|cffff4444[Nanami-UI] Wizard step error ("..stepList[idx].id.."): "..tostring(err).."|r")
end
end
-- Show/hide pages
for i = 1, table.getn(stepPages) do
if i == idx then stepPages[i]:Show() else stepPages[i]:Hide() end
end
-- Set scroll child height and reset scroll
contentChild:SetHeight(math.max(pg:GetHeight(), CONTENT_H))
contentScroll:SetVerticalScroll(0)
-- Update header
headerTitle:SetText(stepList[idx].title)
-- Update dots
local t = T()
for i = 1, table.getn(stepDots) do
local d = stepDots[i]
if i == idx then
d:SetBackdropColor(t.accentLight[1], t.accentLight[2], t.accentLight[3], 1)
d:SetBackdropBorderColor(t.accentLight[1], t.accentLight[2], t.accentLight[3], 1)
elseif i < idx then
d:SetBackdropColor(t.accent[1], t.accent[2], t.accent[3], 0.5)
d:SetBackdropBorderColor(t.accent[1], t.accent[2], t.accent[3], 0.5)
else
d:SetBackdropColor(0.3, 0.3, 0.3, 0.6)
d:SetBackdropBorderColor(0.3, 0.3, 0.3, 0.6)
end
end
-- Update step label
stepLabel:SetText(idx .. " / " .. table.getn(stepList))
-- Navigation buttons
local isFirst = (idx == 1)
local isLast = (idx == table.getn(stepList))
local isWelcome = (stepList[idx].id == "welcome")
local isDone = (stepList[idx].id == "complete")
if isWelcome then
btnPrev:Hide(); btnNext:Hide(); btnSkip:Hide(); stepLabel:SetText("")
elseif isDone then
btnPrev:Show(); btnNext:Hide(); btnSkip:Hide()
else
btnPrev:Show(); btnNext:Show(); btnSkip:Show()
if isFirst then btnPrev:Hide() end
end
end
function SW:GoNext()
if currentStep < table.getn(stepList) then
self:GoTo(currentStep + 1)
end
end
function SW:GoPrev()
if currentStep > 1 then
self:GoTo(currentStep - 1)
end
end
function SW:DoSkip()
if runMode == "rerun" then
self:Hide()
return
end
-- First-run: apply defaults
if not SFramesDB then SFramesDB = {} end
SFramesDB.setupComplete = true
self:Hide()
if completeCb then completeCb() end
end
function SW:DoComplete()
local ok, err = pcall(ApplyChoices)
if not ok then
DEFAULT_CHAT_FRAME:AddMessage("|cffff4444[Nanami-UI] Wizard apply error: "..tostring(err).."|r")
if not SFramesDB then SFramesDB = {} end
SFramesDB.setupComplete = true
end
self:Hide()
ReloadUI()
end
--------------------------------------------------------------------------------
-- Public API
--------------------------------------------------------------------------------
function SW:Show(callback, mode)
local ok, err = pcall(function()
runMode = mode or "firstrun"
completeCb = callback
if runMode == "rerun" then
choices = GetCurrentChoices()
else
choices = GetDefaultChoices()
end
EnsureWizardFrame()
BuildStepList()
overlay:Show()
overlay:Raise()
self:GoTo(1)
end)
if not ok then
DEFAULT_CHAT_FRAME:AddMessage("|cffff4444[Nanami-UI] Setup wizard failed: "..tostring(err).."|r")
if not SFramesDB then SFramesDB = {} end
SFramesDB.setupComplete = true
if callback then callback() end
end
end
function SW:Hide()
if overlay then overlay:Hide() end
end