1371 lines
56 KiB
Lua
1371 lines
56 KiB
Lua
--------------------------------------------------------------------------------
|
||
-- 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", sb, "CENTER", 0, 0)
|
||
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
|