-------------------------------------------------------------------------------- -- 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", 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 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 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) 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} }, function() return choices.minimapMapStyle end, function(v) choices.minimapMapStyle = v end) return 54 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