-------------------------------------------------------------------------------- -- S-Frames: In-Game Settings Panel (ConfigUI.lua) -- Pages: -- 1) UI Settings (scrollable) -- 2) Bag Settings -------------------------------------------------------------------------------- SFrames.ConfigUI = SFrames.ConfigUI or {} SFrames.ConfigUI.ready = true local PANEL_WIDTH = 700 local PANEL_HEIGHT = 560 local BAG_DEFAULTS = { enable = true, columns = 10, bagSpacing = 0, scale = 1, sellGrey = true, bankColumns = 12, bankSpacing = 0, bankScale = 1, } local widgetId = 0 local function NextWidgetName(prefix) widgetId = widgetId + 1 return "SFramesConfig" .. prefix .. tostring(widgetId) end local function Clamp(value, minValue, maxValue) if value < minValue then return minValue end if value > maxValue then return maxValue end return value end local SOFT_THEME = SFrames.ActiveTheme local function EnsureSoftBackdrop(frame) if not frame then return end if frame.sfSoftBackdrop then return end frame: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 }, }) frame.sfSoftBackdrop = true end -- Thin 1px border backdrop, suitable for small frames like checkbox boxes local function EnsureThinBackdrop(frame) if not frame then return end if frame.sfSoftBackdrop then return end if SFrames and SFrames.CreateBackdrop then SFrames:CreateBackdrop(frame) elseif frame.SetBackdrop then frame:SetBackdrop({ bgFile = "Interface\\Buttons\\WHITE8X8", edgeFile = "Interface\\Buttons\\WHITE8X8", tile = false, edgeSize = 1, insets = { left = 1, right = 1, top = 1, bottom = 1 }, }) end frame.sfSoftBackdrop = true end local function HideTexture(tex) if not tex then return end if tex.SetTexture then tex:SetTexture(nil) end tex:Hide() end local function StyleButton(btn) if not btn or btn.sfSoftStyled then return btn end btn.sfSoftStyled = true -- Apply ESC menu style round backdrop 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 }, }) HideTexture(btn.GetNormalTexture and btn:GetNormalTexture()) HideTexture(btn.GetPushedTexture and btn:GetPushedTexture()) HideTexture(btn.GetHighlightTexture and btn:GetHighlightTexture()) HideTexture(btn.GetDisabledTexture and btn:GetDisabledTexture()) local name = btn:GetName() or "" for _, suffix in ipairs({ "Left", "Right", "Middle" }) do local tex = _G[name .. suffix] if tex then tex:SetAlpha(0) tex:Hide() end end local function SetVisual(state) local bg = SOFT_THEME.buttonBg local border = SOFT_THEME.buttonBorder local text = SOFT_THEME.buttonText if btn.sfSoftActive then bg = SOFT_THEME.buttonActiveBg border = SOFT_THEME.buttonActiveBorder text = SOFT_THEME.buttonActiveText elseif state == "hover" then bg = SOFT_THEME.buttonHoverBg border = (SFrames.ActiveTheme and SFrames.ActiveTheme.btnHoverBd) or { 0.80, 0.48, 0.64, 0.98 } text = SOFT_THEME.buttonActiveText elseif state == "down" then bg = SOFT_THEME.buttonDownBg elseif state == "disabled" then bg = SOFT_THEME.buttonDisabledBg text = SOFT_THEME.buttonDisabledText end if btn.SetBackdropColor then btn:SetBackdropColor(bg[1], bg[2], bg[3], bg[4]) end if btn.SetBackdropBorderColor then btn:SetBackdropBorderColor(border[1], border[2], border[3], border[4]) end local fs = btn.GetFontString and btn:GetFontString() if fs then fs:SetTextColor(text[1], text[2], text[3]) end end btn.RefreshVisual = function() if btn.sfSoftActive then SetVisual("active") elseif btn.IsEnabled and not btn:IsEnabled() then SetVisual("disabled") else SetVisual("normal") end end local oldEnter = btn:GetScript("OnEnter") local oldLeave = btn:GetScript("OnLeave") local oldDown = btn:GetScript("OnMouseDown") local oldUp = btn:GetScript("OnMouseUp") btn:SetScript("OnEnter", function() if oldEnter then oldEnter() end if this.IsEnabled and this:IsEnabled() and not this.sfSoftActive then SetVisual("hover") end end) btn:SetScript("OnLeave", function() if oldLeave then oldLeave() end if this.IsEnabled and this:IsEnabled() and not this.sfSoftActive then SetVisual("normal") end end) btn:SetScript("OnMouseDown", function() if oldDown then oldDown() end if this.IsEnabled and this:IsEnabled() and not this.sfSoftActive then SetVisual("down") end end) btn:SetScript("OnMouseUp", function() if oldUp then oldUp() end if this.IsEnabled and this:IsEnabled() and not this.sfSoftActive then SetVisual("hover") end end) btn:RefreshVisual() return btn end local function AddBtnIcon(btn, iconKey, size, align) if not (SFrames and SFrames.CreateIcon) then return end local sz = size or 12 local gap = 3 local ico = SFrames:CreateIcon(btn, iconKey, sz) ico:SetDrawLayer("OVERLAY") btn.nanamiIcon = ico local fs = btn.GetFontString and btn:GetFontString() if fs then fs:ClearAllPoints() if align == "left" then ico:SetPoint("LEFT", btn, "LEFT", 8, 0) fs:SetPoint("LEFT", ico, "RIGHT", gap, 0) fs:SetPoint("RIGHT", btn, "RIGHT", -4, 0) fs:SetJustifyH("LEFT") else fs:SetPoint("CENTER", btn, "CENTER", (sz + gap) / 2, 0) ico:SetPoint("RIGHT", fs, "LEFT", -gap, 0) end else ico:SetPoint("CENTER", btn, "CENTER", 0, 0) end end local function StyleCheckBox(cb) if not cb or cb.sfSoftStyled then return cb end cb.sfSoftStyled = true -- Hide ALL default CheckButton textures including the checked texture HideTexture(cb.GetNormalTexture and cb:GetNormalTexture()) HideTexture(cb.GetPushedTexture and cb:GetPushedTexture()) HideTexture(cb.GetHighlightTexture and cb:GetHighlightTexture()) HideTexture(cb.GetDisabledTexture and cb:GetDisabledTexture()) if cb.SetCheckedTexture then cb:SetCheckedTexture("") end if cb.SetDisabledCheckedTexture then cb:SetDisabledCheckedTexture("") end -- Round-corner box (tooltip border = soft rounded edges) 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 }, }) cb.sfBox = box local function ApplyState(isChecked) if isChecked then box:SetBackdropColor( SOFT_THEME.checkFill[1], SOFT_THEME.checkFill[2], SOFT_THEME.checkFill[3], 0.88) box:SetBackdropBorderColor( SOFT_THEME.checkFill[1], SOFT_THEME.checkFill[2], SOFT_THEME.checkFill[3], 1) else box:SetBackdropColor( SOFT_THEME.checkBg[1], SOFT_THEME.checkBg[2], SOFT_THEME.checkBg[3], SOFT_THEME.checkBg[4]) box:SetBackdropBorderColor( SOFT_THEME.checkBorder[1], SOFT_THEME.checkBorder[2], SOFT_THEME.checkBorder[3], SOFT_THEME.checkBorder[4]) end end cb.sfApplyState = ApplyState -- Text label: push it away from the box with a left margin local label = _G[cb:GetName() .. "Text"] if label then label:ClearAllPoints() label:SetPoint("LEFT", cb, "RIGHT", 4, 0) label:SetFont(SFrames:GetFont(), 11, "OUTLINE") label:SetJustifyH("LEFT") label:SetTextColor(SOFT_THEME.text[1], SOFT_THEME.text[2], SOFT_THEME.text[3]) end -- Hover: brighten border when unchecked cb:SetScript("OnEnter", function() if not this.sfChecked then if this.sfBox then this.sfBox:SetBackdropBorderColor( SOFT_THEME.checkHoverBorder[1], SOFT_THEME.checkHoverBorder[2], SOFT_THEME.checkHoverBorder[3], SOFT_THEME.checkHoverBorder[4]) end end end) cb:SetScript("OnLeave", function() if not this.sfChecked then if this.sfBox then this.sfBox:SetBackdropBorderColor( SOFT_THEME.checkBorder[1], SOFT_THEME.checkBorder[2], SOFT_THEME.checkBorder[3], SOFT_THEME.checkBorder[4]) end end end) ApplyState(false) return cb end local function StyleSlider(slider, low, high, text) if not slider or slider.sfSoftStyled then return slider end slider.sfSoftStyled = true local regions = { slider:GetRegions() } for i = 1, table.getn(regions) do local region = regions[i] if region and region.GetObjectType and region:GetObjectType() == "Texture" then region:SetTexture(nil) end end local track = slider:CreateTexture(nil, "BACKGROUND") track:SetTexture("Interface\\Buttons\\WHITE8X8") track:SetPoint("LEFT", slider, "LEFT", 0, 0) track:SetPoint("RIGHT", slider, "RIGHT", 0, 0) track:SetHeight(4) track:SetVertexColor(SOFT_THEME.sliderTrack[1], SOFT_THEME.sliderTrack[2], SOFT_THEME.sliderTrack[3], SOFT_THEME.sliderTrack[4]) slider.sfTrack = track local fill = slider: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(SOFT_THEME.sliderFill[1], SOFT_THEME.sliderFill[2], SOFT_THEME.sliderFill[3], SOFT_THEME.sliderFill[4]) slider.sfFill = fill if slider.SetThumbTexture then slider:SetThumbTexture("Interface\\Buttons\\WHITE8X8") end local thumb = slider.GetThumbTexture and slider:GetThumbTexture() if thumb then thumb:SetWidth(8) thumb:SetHeight(16) thumb:SetVertexColor(SOFT_THEME.sliderThumb[1], SOFT_THEME.sliderThumb[2], SOFT_THEME.sliderThumb[3], SOFT_THEME.sliderThumb[4]) end local function UpdateFill() local minValue, maxValue = slider:GetMinMaxValues() local value = slider:GetValue() or minValue local pct = 0 if maxValue > minValue then pct = (value - minValue) / (maxValue - minValue) end pct = Clamp(pct, 0, 1) local width = math.floor((slider:GetWidth() or 1) * pct + 0.5) if width < 1 then width = 1 end slider.sfFill:SetWidth(width) end local oldChanged = slider:GetScript("OnValueChanged") slider:SetScript("OnValueChanged", function() if oldChanged then oldChanged() end UpdateFill() end) UpdateFill() if low then low:SetTextColor(SOFT_THEME.dimText[1], SOFT_THEME.dimText[2], SOFT_THEME.dimText[3]) low:ClearAllPoints() low:SetPoint("TOPLEFT", slider, "BOTTOMLEFT", 0, 0) end if high then high:SetTextColor(SOFT_THEME.dimText[1], SOFT_THEME.dimText[2], SOFT_THEME.dimText[3]) high:ClearAllPoints() high:SetPoint("TOPRIGHT", slider, "BOTTOMRIGHT", 0, 0) end if text then text:SetTextColor(SOFT_THEME.text[1], SOFT_THEME.text[2], SOFT_THEME.text[3]) text:ClearAllPoints() text:SetPoint("BOTTOM", slider, "TOP", 0, 2) end return slider end local function StyleScrollBar(slider, sliderName) if not slider or slider.sfSoftStyled then return slider end slider.sfSoftStyled = true slider:SetWidth(12) local regions = { slider:GetRegions() } for i = 1, table.getn(regions) do local region = regions[i] if region and region.GetObjectType and region:GetObjectType() == "Texture" then region:SetTexture(nil) end end local track = slider:CreateTexture(nil, "BACKGROUND") track:SetTexture("Interface\\Buttons\\WHITE8X8") track:SetPoint("TOPLEFT", slider, "TOPLEFT", 3, 0) track:SetPoint("BOTTOMRIGHT", slider, "BOTTOMRIGHT", -3, 0) track:SetVertexColor(0.18, 0.19, 0.22, 0.9) if slider.SetThumbTexture then slider:SetThumbTexture("Interface\\Buttons\\WHITE8X8") end local thumb = slider.GetThumbTexture and slider:GetThumbTexture() if thumb then thumb:SetWidth(8) thumb:SetHeight(20) thumb:SetVertexColor(SOFT_THEME.scrollThumb[1], SOFT_THEME.scrollThumb[2], SOFT_THEME.scrollThumb[3], SOFT_THEME.scrollThumb[4] or 0.95) end local upBtn = _G[sliderName .. "ScrollUpButton"] local downBtn = _G[sliderName .. "ScrollDownButton"] if upBtn then upBtn:Hide() end if downBtn then downBtn:Hide() end return slider end local function EnsureDB() if not SFramesDB then SFramesDB = {} end if SFramesDB.showLevel == nil then SFramesDB.showLevel = true end if SFramesDB.classColorHealth == nil then SFramesDB.classColorHealth = true end if SFramesDB.enableMerchant == nil then SFramesDB.enableMerchant = true end if SFramesDB.enableQuestUI == nil then SFramesDB.enableQuestUI = true end if SFramesDB.enableQuestLogSkin == nil then SFramesDB.enableQuestLogSkin = true end if SFramesDB.enableTrainer == nil then SFramesDB.enableTrainer = true end if SFramesDB.enableSpellBook == nil then SFramesDB.enableSpellBook = true end if SFramesDB.enableTradeSkill == nil then SFramesDB.enableTradeSkill = true end if SFramesDB.spellBookHighestOnly == nil then SFramesDB.spellBookHighestOnly = false end if SFramesDB.spellBookAutoReplace == nil then SFramesDB.spellBookAutoReplace = false end if SFramesDB.enableSocial == nil then SFramesDB.enableSocial = true end if SFramesDB.enableInspect == nil then SFramesDB.enableInspect = true end if SFramesDB.enableFlightMap == nil then SFramesDB.enableFlightMap = true end if SFramesDB.enablePetStable == nil then SFramesDB.enablePetStable = true end if SFramesDB.enableMail == nil then SFramesDB.enableMail = true end if type(SFramesDB.spellBookScale) ~= "number" then SFramesDB.spellBookScale = 1 end if type(SFramesDB.socialScale) ~= "number" then SFramesDB.socialScale = 1 end if type(SFramesDB.playerFrameScale) ~= "number" then SFramesDB.playerFrameScale = 1 end if type(SFramesDB.playerFrameWidth) ~= "number" then SFramesDB.playerFrameWidth = 220 end if type(SFramesDB.playerPortraitWidth) ~= "number" then SFramesDB.playerPortraitWidth = 50 end if type(SFramesDB.playerHealthHeight) ~= "number" then SFramesDB.playerHealthHeight = 38 end if type(SFramesDB.playerPowerHeight) ~= "number" then SFramesDB.playerPowerHeight = 9 end if SFramesDB.playerShowClass == nil then SFramesDB.playerShowClass = true end if SFramesDB.playerShowClassIcon == nil then SFramesDB.playerShowClassIcon = true end if type(SFramesDB.playerNameFontSize) ~= "number" then SFramesDB.playerNameFontSize = 10 end if type(SFramesDB.playerValueFontSize) ~= "number" then SFramesDB.playerValueFontSize = 10 end if type(SFramesDB.targetFrameScale) ~= "number" then SFramesDB.targetFrameScale = 1 end if type(SFramesDB.targetFrameWidth) ~= "number" then SFramesDB.targetFrameWidth = 220 end if type(SFramesDB.targetPortraitWidth) ~= "number" then SFramesDB.targetPortraitWidth = 50 end if type(SFramesDB.targetHealthHeight) ~= "number" then SFramesDB.targetHealthHeight = 38 end if type(SFramesDB.targetPowerHeight) ~= "number" then SFramesDB.targetPowerHeight = 9 end if SFramesDB.targetShowClass == nil then SFramesDB.targetShowClass = true end if SFramesDB.targetShowClassIcon == nil then SFramesDB.targetShowClassIcon = true end if type(SFramesDB.targetNameFontSize) ~= "number" then SFramesDB.targetNameFontSize = 10 end if type(SFramesDB.targetValueFontSize) ~= "number" then SFramesDB.targetValueFontSize = 10 end if SFramesDB.targetDistanceEnabled == nil then SFramesDB.targetDistanceEnabled = true end if type(SFramesDB.targetDistanceScale) ~= "number" then SFramesDB.targetDistanceScale = 1 end if SFramesDB.showPetFrame == nil then SFramesDB.showPetFrame = true end if type(SFramesDB.petFrameScale) ~= "number" then SFramesDB.petFrameScale = 1 end if SFramesDB.partyLayout ~= "horizontal" and SFramesDB.partyLayout ~= "vertical" then SFramesDB.partyLayout = "vertical" end if type(SFramesDB.partyFrameScale) ~= "number" then SFramesDB.partyFrameScale = 1 end if type(SFramesDB.partyFrameWidth) ~= "number" then SFramesDB.partyFrameWidth = 150 end if type(SFramesDB.partyFrameHeight) ~= "number" then SFramesDB.partyFrameHeight = 35 end if type(SFramesDB.partyPortraitWidth) ~= "number" then SFramesDB.partyPortraitWidth = 33 end if type(SFramesDB.partyHealthHeight) ~= "number" then SFramesDB.partyHealthHeight = 22 end if type(SFramesDB.partyPowerHeight) ~= "number" then SFramesDB.partyPowerHeight = 10 end if type(SFramesDB.partyHorizontalGap) ~= "number" then SFramesDB.partyHorizontalGap = 8 end if type(SFramesDB.partyVerticalGap) ~= "number" then SFramesDB.partyVerticalGap = 30 end if type(SFramesDB.partyNameFontSize) ~= "number" then SFramesDB.partyNameFontSize = 10 end if type(SFramesDB.partyValueFontSize) ~= "number" then SFramesDB.partyValueFontSize = 10 end if SFramesDB.partyShowBuffs == nil then SFramesDB.partyShowBuffs = true end if SFramesDB.partyShowDebuffs == nil then SFramesDB.partyShowDebuffs = true end if SFramesDB.raidLayout ~= "horizontal" and SFramesDB.raidLayout ~= "vertical" then SFramesDB.raidLayout = "horizontal" end if type(SFramesDB.raidFrameScale) ~= "number" then SFramesDB.raidFrameScale = 1 end if type(SFramesDB.raidFrameWidth) ~= "number" then SFramesDB.raidFrameWidth = 60 end if type(SFramesDB.raidFrameHeight) ~= "number" then SFramesDB.raidFrameHeight = 40 end if type(SFramesDB.raidHealthHeight) ~= "number" then SFramesDB.raidHealthHeight = 31 end if type(SFramesDB.raidHorizontalGap) ~= "number" then SFramesDB.raidHorizontalGap = 2 end if type(SFramesDB.raidVerticalGap) ~= "number" then SFramesDB.raidVerticalGap = 2 end if type(SFramesDB.raidGroupGap) ~= "number" then SFramesDB.raidGroupGap = 8 end if type(SFramesDB.raidNameFontSize) ~= "number" then SFramesDB.raidNameFontSize = 10 end if type(SFramesDB.raidValueFontSize) ~= "number" then SFramesDB.raidValueFontSize = 9 end if SFramesDB.raidShowPower == nil then SFramesDB.raidShowPower = true end if SFramesDB.enableRaidFrames == nil then SFramesDB.enableRaidFrames = true end if SFramesDB.raidHealthFormat == nil then SFramesDB.raidHealthFormat = "compact" end if SFramesDB.raidShowGroupLabel == nil then SFramesDB.raidShowGroupLabel = true end if SFramesDB.charPanelEnable == nil then SFramesDB.charPanelEnable = true end if SFramesDB.afkEnabled == nil then SFramesDB.afkEnabled = true end if type(SFramesDB.afkDelay) ~= "number" then SFramesDB.afkDelay = 5 end if SFramesDB.afkOutsideRest == nil then SFramesDB.afkOutsideRest = false end if SFramesDB.trainerReminder == nil then SFramesDB.trainerReminder = true end if SFramesDB.trainerCache == nil then SFramesDB.trainerCache = {} end if SFramesDB.smoothBars == nil then SFramesDB.smoothBars = true end if SFramesDB.mobRealHealth == nil then SFramesDB.mobRealHealth = true end if SFramesDB.showItemLevel == nil then SFramesDB.showItemLevel = true end if SFramesDB.showTooltipIDs == nil then SFramesDB.showTooltipIDs = true end if SFramesDB.itemCompare == nil then SFramesDB.itemCompare = true end if SFramesDB.gearScore == nil then SFramesDB.gearScore = true end if type(SFramesDB.Bags) ~= "table" then SFramesDB.Bags = {} end local defaults = BAG_DEFAULTS if SFrames and SFrames.Config and type(SFrames.Config.Bags) == "table" then defaults = SFrames.Config.Bags end for key, value in pairs(defaults) do if SFramesDB.Bags[key] == nil then SFramesDB.Bags[key] = value end end if type(SFramesDB.Minimap) ~= "table" then SFramesDB.Minimap = {} end if SFramesDB.Minimap.enabled == nil then SFramesDB.Minimap.enabled = true end if type(SFramesDB.Minimap.scale) ~= "number" then SFramesDB.Minimap.scale = 1.0 end if SFramesDB.Minimap.showClock == nil then SFramesDB.Minimap.showClock = true end if SFramesDB.Minimap.showCoords == nil then SFramesDB.Minimap.showCoords = true end if SFramesDB.Minimap.mapStyle == nil then SFramesDB.Minimap.mapStyle = "auto" end if type(SFramesDB.Minimap.posX) ~= "number" then SFramesDB.Minimap.posX = -5 end if type(SFramesDB.Minimap.posY) ~= "number" then SFramesDB.Minimap.posY = -5 end if type(SFramesDB.MapReveal) ~= "table" then SFramesDB.MapReveal = {} end if SFramesDB.MapReveal.enabled == nil then SFramesDB.MapReveal.enabled = true end if type(SFramesDB.MapReveal.unexploredAlpha) ~= "number" then SFramesDB.MapReveal.unexploredAlpha = 0.7 end if type(SFramesDB.Tweaks) ~= "table" then SFramesDB.Tweaks = {} end if SFramesDB.Tweaks.autoStance == nil then SFramesDB.Tweaks.autoStance = true end if SFramesDB.Tweaks.autoDismount == nil then SFramesDB.Tweaks.autoDismount = true end if SFramesDB.Tweaks.superWoW == nil then SFramesDB.Tweaks.superWoW = true end if SFramesDB.Tweaks.turtleCompat == nil then SFramesDB.Tweaks.turtleCompat = true end if SFramesDB.Tweaks.cooldownNumbers == nil then SFramesDB.Tweaks.cooldownNumbers = true end if SFramesDB.Tweaks.darkUI == nil then SFramesDB.Tweaks.darkUI = false end if SFramesDB.Tweaks.worldMapWindow == nil then SFramesDB.Tweaks.worldMapWindow = false end if type(SFramesDB.WorldMap) ~= "table" then SFramesDB.WorldMap = {} end if SFramesDB.WorldMap.enabled == nil then SFramesDB.WorldMap.enabled = true end if type(SFramesDB.WorldMap.scale) ~= "number" then SFramesDB.WorldMap.scale = 0.85 end if type(SFramesDB.WorldMap.nav) ~= "table" then SFramesDB.WorldMap.nav = { enabled = false, width = 350, alpha = 0.70, locked = false } end if type(SFramesDB.MinimapBuffs) ~= "table" then SFramesDB.MinimapBuffs = {} end if SFramesDB.MinimapBuffs.enabled == nil then SFramesDB.MinimapBuffs.enabled = true end if type(SFramesDB.MinimapBuffs.iconSize) ~= "number" then SFramesDB.MinimapBuffs.iconSize = 30 end if type(SFramesDB.MinimapBuffs.iconsPerRow) ~= "number" then SFramesDB.MinimapBuffs.iconsPerRow = 8 end if type(SFramesDB.MinimapBuffs.spacing) ~= "number" then SFramesDB.MinimapBuffs.spacing = 2 end if SFramesDB.MinimapBuffs.growDirection ~= "LEFT" and SFramesDB.MinimapBuffs.growDirection ~= "RIGHT" then SFramesDB.MinimapBuffs.growDirection = "LEFT" end if SFramesDB.MinimapBuffs.position ~= "TOPRIGHT" and SFramesDB.MinimapBuffs.position ~= "TOPLEFT" and SFramesDB.MinimapBuffs.position ~= "BOTTOMRIGHT" and SFramesDB.MinimapBuffs.position ~= "BOTTOMLEFT" then SFramesDB.MinimapBuffs.position = "TOPRIGHT" end if type(SFramesDB.MinimapBuffs.offsetX) ~= "number" then SFramesDB.MinimapBuffs.offsetX = 0 end if type(SFramesDB.MinimapBuffs.offsetY) ~= "number" then SFramesDB.MinimapBuffs.offsetY = 0 end if SFramesDB.MinimapBuffs.showTimer == nil then SFramesDB.MinimapBuffs.showTimer = true end if SFramesDB.MinimapBuffs.showDebuffs == nil then SFramesDB.MinimapBuffs.showDebuffs = true end if type(SFramesDB.MinimapBuffs.debuffIconSize) ~= "number" then SFramesDB.MinimapBuffs.debuffIconSize = 30 end if type(SFramesDB.ActionBars) ~= "table" then SFramesDB.ActionBars = {} end local abDef = { enable = true, buttonSize = 36, buttonGap = 2, smallBarSize = 27, scale = 1.0, barCount = 3, showHotkey = true, showMacroName = false, rangeColoring = true, showPetBar = true, showStanceBar = true, showRightBars = true, buttonRounded = false, buttonInnerShadow = false, } for k, v in pairs(abDef) do if SFramesDB.ActionBars[k] == nil then SFramesDB.ActionBars[k] = v end end if SFramesDB.enableUnitFrames == nil then SFramesDB.enableUnitFrames = true end if SFramesDB.enableChat == nil then SFramesDB.enableChat = true end if type(SFramesDB.Theme) ~= "table" then SFramesDB.Theme = {} end if SFramesDB.Theme.preset == nil then SFramesDB.Theme.preset = "pink" end if SFramesDB.Theme.useClassTheme == nil then SFramesDB.Theme.useClassTheme = false end if SFramesDB.Theme.iconSet == nil or SFramesDB.Theme.iconSet == "default" then SFramesDB.Theme.iconSet = "icon" end end local function CreateSection(parent, title, x, y, width, height, font) local section = CreateFrame("Frame", NextWidgetName("Section"), parent) section:SetWidth(width) section:SetHeight(height) section:SetPoint("TOPLEFT", parent, "TOPLEFT", x, y) EnsureSoftBackdrop(section) if section.SetBackdropColor then section:SetBackdropColor(SOFT_THEME.sectionBg[1], SOFT_THEME.sectionBg[2], SOFT_THEME.sectionBg[3], SOFT_THEME.sectionBg[4]) end if section.SetBackdropBorderColor then section:SetBackdropBorderColor(SOFT_THEME.sectionBorder[1], SOFT_THEME.sectionBorder[2], SOFT_THEME.sectionBorder[3], SOFT_THEME.sectionBorder[4]) end local titleFS = section:CreateFontString(nil, "OVERLAY") titleFS:SetFont(font, 11, "OUTLINE") titleFS:SetPoint("TOPLEFT", section, "TOPLEFT", 10, -9) titleFS:SetText(title) titleFS:SetTextColor(SOFT_THEME.title[1], SOFT_THEME.title[2], SOFT_THEME.title[3]) -- Divider line under title local div = section:CreateTexture(nil, "ARTWORK") div:SetTexture("Interface\\Buttons\\WHITE8X8") div:SetHeight(1) div:SetPoint("TOPLEFT", section, "TOPLEFT", 8, -24) div:SetPoint("TOPRIGHT", section, "TOPRIGHT", -8, -24) div:SetVertexColor(SOFT_THEME.sectionBorder[1], SOFT_THEME.sectionBorder[2], SOFT_THEME.sectionBorder[3], 0.6) return section end local function CreateLabel(parent, text, x, y, font, size, r, g, b) local fs = parent:CreateFontString(nil, "OVERLAY") fs:SetFont(font, 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 CreateDesc(parent, text, x, y, font, maxWidth) local fs = parent:CreateFontString(nil, "OVERLAY") fs:SetFont(font, 9, "OUTLINE") fs:SetPoint("TOPLEFT", parent, "TOPLEFT", x, y) if maxWidth then fs:SetWidth(maxWidth) fs:SetJustifyH("LEFT") end fs:SetText(text) fs:SetTextColor(0.65, 0.58, 0.62) return fs end local function CreateCheckBox(parent, label, x, y, getter, setter, onValueChanged) local cb = CreateFrame("CheckButton", NextWidgetName("Check"), parent, "UICheckButtonTemplate") cb:SetWidth(18) cb:SetHeight(18) cb:SetPoint("TOPLEFT", parent, "TOPLEFT", x, y) StyleCheckBox(cb) local text = _G[cb:GetName() .. "Text"] if text then text:SetText(label) local parentWidth = parent:GetWidth() or 520 local textWidth = parentWidth - x - 24 if textWidth > 240 then textWidth = 240 end text:SetWidth(textWidth) end local internalRefresh = false cb:SetScript("OnClick", function() if internalRefresh then return end local v = this:GetChecked() local checkedBool = (v == 1 or v == true) this.sfChecked = checkedBool if this.sfApplyState then this.sfApplyState(checkedBool) end setter(checkedBool) if onValueChanged then onValueChanged(checkedBool) end PlaySound(checkedBool and "igMainMenuOptionCheckBoxOn" or "igMainMenuOptionCheckBoxOff") end) cb.Refresh = function() internalRefresh = true local isChecked = getter() and true or false cb:SetChecked(isChecked and 1 or 0) cb.sfChecked = isChecked if cb.sfApplyState then cb.sfApplyState(isChecked) end internalRefresh = false end cb:Refresh() return cb end local function CreateSlider(parent, label, x, y, width, minValue, maxValue, step, getter, setter, formatter, onValueChanged) local sliderName = NextWidgetName("Slider") local slider = CreateFrame("Slider", sliderName, parent, "OptionsSliderTemplate") slider:SetWidth(width) slider:SetHeight(26) slider:SetPoint("TOPLEFT", parent, "TOPLEFT", x, y) slider:SetMinMaxValues(minValue, maxValue) slider:SetValueStep(step) if slider.SetObeyStepOnDrag then slider:SetObeyStepOnDrag(true) end local low = _G[sliderName .. "Low"] local high = _G[sliderName .. "High"] local text = _G[sliderName .. "Text"] if low then low:SetText(tostring(minValue)) end if high then high:SetText(tostring(maxValue)) end local internalRefresh = false local function UpdateLabel(value) local display = formatter and formatter(value) or value if text then text:SetText(label .. ": " .. tostring(display)) end end slider:SetScript("OnValueChanged", function() if internalRefresh then return end local raw = this:GetValue() or minValue or 0 local safeStep = step or 1 local value if safeStep >= 1 then value = math.floor(raw + 0.5) else if safeStep == 0 then safeStep = 1 end local scaled = raw / safeStep value = math.floor(scaled + 0.5) * safeStep end value = Clamp(value, minValue, maxValue) UpdateLabel(value) setter(value) if onValueChanged then onValueChanged(value) end end) slider.Refresh = function() local value = tonumber(getter()) or minValue value = Clamp(value, minValue, maxValue) internalRefresh = true slider:SetValue(value) internalRefresh = false UpdateLabel(value) end StyleSlider(slider, low, high, text) slider:Refresh() return slider end local function CreateButton(parent, text, x, y, width, height, onClick) local btn = CreateFrame("Button", NextWidgetName("Button"), parent, "UIPanelButtonTemplate") btn:SetWidth(width) btn:SetHeight(height) btn:SetPoint("TOPLEFT", parent, "TOPLEFT", x, y) btn:SetText(text) btn:SetScript("OnClick", onClick) StyleButton(btn) return btn end local function CreateScrollArea(parent, x, y, width, height, childHeight) local holder = CreateFrame("Frame", NextWidgetName("ScrollHolder"), parent) holder:SetWidth(width) holder:SetHeight(height) holder:SetPoint("TOPLEFT", parent, "TOPLEFT", x, y) local scroll = CreateFrame("ScrollFrame", NextWidgetName("ScrollFrame"), holder) scroll:SetPoint("TOPLEFT", holder, "TOPLEFT", 0, 0) scroll:SetPoint("BOTTOMRIGHT", holder, "BOTTOMRIGHT", -18, 0) local child = CreateFrame("Frame", NextWidgetName("ScrollChild"), scroll) child:SetWidth(width - 22) child:SetHeight(childHeight) scroll:SetScrollChild(child) local sliderName = NextWidgetName("ScrollBar") local slider = CreateFrame("Slider", sliderName, holder, "UIPanelScrollBarTemplate") slider:SetPoint("TOPRIGHT", holder, "TOPRIGHT", 0, -16) slider:SetPoint("BOTTOMRIGHT", holder, "BOTTOMRIGHT", 0, 16) slider:SetScript("OnValueChanged", function() if scroll and scroll.SetVerticalScroll then scroll:SetVerticalScroll(this:GetValue()) end end) slider:SetMinMaxValues(0, 0) slider:SetValueStep(20) slider:SetValue(0) local upBtn = _G[sliderName .. "ScrollUpButton"] if upBtn then upBtn:SetScript("OnClick", function() local value = slider:GetValue() - 24 if value < 0 then value = 0 end slider:SetValue(value) end) end local downBtn = _G[sliderName .. "ScrollDownButton"] if downBtn then downBtn:SetScript("OnClick", function() local _, maxValue = slider:GetMinMaxValues() local value = slider:GetValue() + 24 if value > maxValue then value = maxValue end slider:SetValue(value) end) end StyleScrollBar(slider, sliderName) local function UpdateRange() local maxScroll = child:GetHeight() - scroll:GetHeight() if maxScroll < 0 then maxScroll = 0 end slider:SetMinMaxValues(0, maxScroll) slider:SetValueStep(20) local current = slider:GetValue() if current > maxScroll then current = maxScroll slider:SetValue(current) end if scroll and scroll.SetVerticalScroll then scroll:SetVerticalScroll(current) end if maxScroll <= 0 then slider:Hide() else slider:Show() end end local function ScrollBy(delta) local _, maxValue = slider:GetMinMaxValues() local value = slider:GetValue() - delta * 28 if value < 0 then value = 0 end if value > maxValue then value = maxValue end slider:SetValue(value) end if scroll.EnableMouseWheel then scroll:EnableMouseWheel(true) scroll:SetScript("OnMouseWheel", function() ScrollBy(arg1) end) end if child.EnableMouseWheel then child:EnableMouseWheel(true) child:SetScript("OnMouseWheel", function() ScrollBy(arg1) end) end return { holder = holder, scroll = scroll, child = child, slider = slider, UpdateRange = UpdateRange, Reset = function() slider:SetValue(0) if scroll and scroll.SetVerticalScroll then scroll:SetVerticalScroll(0) end UpdateRange() end, } end function SFrames.ConfigUI:RefreshControls(controlList) if not controlList then return end for _, control in ipairs(controlList) do if control and control.Refresh then control:Refresh() end end end function SFrames.ConfigUI:BuildUIPage() local font = SFrames:GetFont() local page = self.uiPage local controls = {} local function RefreshPlayer() if SFrames.Player and SFrames.Player.ApplyConfig then SFrames.Player:ApplyConfig() return end if SFrames.Player and SFrames.Player.UpdateAll then SFrames.Player:UpdateAll() end end local function RefreshTarget() if SFrames.Target and SFrames.Target.ApplyConfig then SFrames.Target:ApplyConfig() return end if SFrames.Target and SFrames.Target.UpdateAll then SFrames.Target:UpdateAll() end end local function RefreshParty() if SFrames.Party and SFrames.Party.ApplyConfig then SFrames.Party:ApplyConfig() return end if SFrames.Party and SFrames.Party.UpdateAll then SFrames.Party:UpdateAll() end end -- Section 内容从 y=-32 开始(标题24 + 分隔线 + 8px 间距) -- 每个选项行占 26px,描述文字占 16px local uiScroll = CreateScrollArea(page, 4, -4, 548, 458, 830) local root = uiScroll.child -- ── 初始化向导 ────────────────────────────────────────────── local wizSection = CreateSection(root, "初始化向导", 8, -8, 520, 62, font) CreateDesc(wizSection, "重新打开首次配置向导,可重新选择各项功能开关", 14, -30, font, 380) CreateButton(wizSection, "重新运行初始化向导", 370, -30, 140, 24, function() if SFrames.ConfigUI.frame then SFrames.ConfigUI.frame:Hide() end if SFrames.SetupWizard and SFrames.SetupWizard.Show then SFrames.SetupWizard:Show(nil, "rerun") end end) -- ── 全局 ────────────────────────────────────────────────────── local globalSection = CreateSection(root, "全局", 8, -78, 520, 288, font) table.insert(controls, CreateCheckBox(globalSection, "显示玩家/目标等级文本", 14, -34, function() return SFramesDB.showLevel ~= false end, function(checked) SFramesDB.showLevel = checked end, function() RefreshPlayer() RefreshTarget() RefreshParty() end )) CreateDesc(globalSection, "在玩家和目标框体中显示角色等级数字", 36, -50, font) table.insert(controls, CreateCheckBox(globalSection, "玩家/目标生命条使用职业颜色", 14, -70, function() return SFramesDB.classColorHealth ~= false end, function(checked) SFramesDB.classColorHealth = checked end, function() RefreshPlayer() RefreshTarget() RefreshParty() end )) CreateDesc(globalSection, "血条颜色跟随职业(关闭则统一绿色)", 36, -86, font) table.insert(controls, CreateCheckBox(globalSection, "启用全新商人购买界面", 14, -106, function() return SFramesDB.enableMerchant ~= false end, function(checked) SFramesDB.enableMerchant = checked end )) CreateDesc(globalSection, "替换默认商人窗口为自定义界面", 36, -122, font) table.insert(controls, CreateCheckBox(globalSection, "启用全新任务/NPC对话界面", 14, -142, function() return SFramesDB.enableQuestUI ~= false end, function(checked) SFramesDB.enableQuestUI = checked end )) CreateDesc(globalSection, "替换默认任务和NPC对话窗口(需重载UI)", 36, -158, font) table.insert(controls, CreateCheckBox(globalSection, "启用任务日志美化皮肤", 14, -178, function() return SFramesDB.enableQuestLogSkin ~= false end, function(checked) SFramesDB.enableQuestLogSkin = checked end )) CreateDesc(globalSection, "美化任务日志界面,兼容 pfQuest(需重载UI)", 36, -194, font) table.insert(controls, CreateCheckBox(globalSection, "启用全新训练师界面", 270, -34, function() return SFramesDB.enableTrainer ~= false end, function(checked) SFramesDB.enableTrainer = checked end )) CreateDesc(globalSection, "替换默认职业/专业训练师窗口(需重载UI)", 292, -50, font) table.insert(controls, CreateCheckBox(globalSection, "启用全新法术书界面", 270, -70, function() return SFramesDB.enableSpellBook ~= false end, function(checked) SFramesDB.enableSpellBook = checked end )) CreateDesc(globalSection, "替换默认法术书窗口(需重载UI)", 292, -86, font) table.insert(controls, CreateCheckBox(globalSection, "启用全新社交界面", 270, -106, function() return SFramesDB.enableSocial ~= false end, function(checked) SFramesDB.enableSocial = checked end )) CreateDesc(globalSection, "替换好友/查询/工会/团队窗口(需重载UI)", 292, -122, font) table.insert(controls, CreateCheckBox(globalSection, "NPC单选项自动跳过", 270, -142, function() return SFramesDB.autoGossip ~= false end, function(checked) SFramesDB.autoGossip = checked end )) CreateDesc(globalSection, "只有一个对话选项时自动确认(按 Shift 临时禁用)", 292, -158, font) table.insert(controls, CreateCheckBox(globalSection, "启用兽栏皮肤", 270, -178, function() return SFramesDB.enablePetStable ~= false end, function(checked) SFramesDB.enablePetStable = checked end )) CreateDesc(globalSection, "美化宠物兽栏界面(需重载UI)", 292, -194, font) table.insert(controls, CreateCheckBox(globalSection, "启用全新专业技能界面", 14, -214, function() return SFramesDB.enableTradeSkill ~= false end, function(checked) SFramesDB.enableTradeSkill = checked end )) CreateDesc(globalSection, "替换默认专业技能窗口(需重载UI)", 36, -230, font) table.insert(controls, CreateCheckBox(globalSection, "启用全新飞行地图界面", 270, -214, function() return SFramesDB.enableFlightMap ~= false end, function(checked) SFramesDB.enableFlightMap = checked end )) CreateDesc(globalSection, "美化飞行管理员地图,增加目的地列表(需重载UI)", 292, -230, font) table.insert(controls, CreateCheckBox(globalSection, "启用全新邮箱界面", 14, -250, function() return SFramesDB.enableMail ~= false end, function(checked) SFramesDB.enableMail = checked end )) CreateDesc(globalSection, "替换默认邮箱窗口,支持批量收取和多物品发送(需重载UI)", 36, -266, font) -- ── 增强功能(Libs 库集成)────────────────────────────────── local enhSection = CreateSection(root, "增强功能(需安装 !Libs 插件)", 8, -376, 520, 204, font) table.insert(controls, CreateCheckBox(enhSection, "血条平滑动画(需 /reload)", 14, -34, function() return SFramesDB.smoothBars ~= false end, function(checked) SFramesDB.smoothBars = checked end )) CreateDesc(enhSection, "血量/法力变化时丝滑过渡,需要 LibSmoothStatusBar", 36, -50, font, 218) table.insert(controls, CreateCheckBox(enhSection, "目标真实血量", 270, -34, function() return SFramesDB.mobRealHealth ~= false end, function(checked) SFramesDB.mobRealHealth = checked end )) CreateDesc(enhSection, "怪物血条显示实际HP数值,需要 LibMobHealthCache", 292, -50, font, 218) table.insert(controls, CreateCheckBox(enhSection, "物品等级显示", 14, -80, function() return SFramesDB.showItemLevel ~= false end, function(checked) SFramesDB.showItemLevel = checked end, function() if SFrames.CharacterPanel and SFrames.CharacterPanel.UpdateCurrentTab then SFrames.CharacterPanel:UpdateCurrentTab() end end )) CreateDesc(enhSection, "装备栏/背包角标显示 iLvl + 面板平均装等,需要 LibItem-Level", 36, -96, font, 218) table.insert(controls, CreateCheckBox(enhSection, "装备属性对比", 270, -80, function() return SFramesDB.itemCompare ~= false end, function(checked) SFramesDB.itemCompare = checked end )) CreateDesc(enhSection, "背包物品 tooltip 显示与已装备的属性差异,需要 ItemBonusLib", 292, -96, font, 218) table.insert(controls, CreateCheckBox(enhSection, "装备评分 (EP)", 14, -126, function() return SFramesDB.gearScore ~= false end, function(checked) SFramesDB.gearScore = checked end )) CreateDesc(enhSection, "Tooltip 显示当前职业各天赋EP评分及硬核评分,需要 ItemBonusLib", 36, -142, font, 480) table.insert(controls, CreateCheckBox(enhSection, "显示物品/法术ID", 270, -126, function() return SFramesDB.showTooltipIDs ~= false end, function(checked) SFramesDB.showTooltipIDs = checked end )) CreateDesc(enhSection, "在 Tooltip 中显示物品ID和法术ID", 292, -142, font, 218) CreateLabel(enhSection, "提示:以上功能需要安装对应的 !Libs 库才能生效。", 14, -172, font, 10, 0.6, 0.6, 0.65) -- ── ShaguTweaks 功能移植 ────────────────────────────────────── local tweaksSection = CreateSection(root, "ShaguTweaks 功能移植(需 /reload 生效)", 8, -590, 520, 214, font) table.insert(controls, CreateCheckBox(tweaksSection, "自动切换姿态/形态", 14, -34, function() return SFramesDB.Tweaks.autoStance ~= false end, function(checked) SFramesDB.Tweaks.autoStance = checked end )) CreateDesc(tweaksSection, "施法需要特定姿态时自动切换(如人形按熊掌→自动变熊)", 36, -50, font, 218) table.insert(controls, CreateCheckBox(tweaksSection, "自动取消变形/下马", 270, -34, function() return SFramesDB.Tweaks.autoDismount ~= false end, function(checked) SFramesDB.Tweaks.autoDismount = checked end )) CreateDesc(tweaksSection, "变形/骑马时施法自动取消(如熊形按治疗→自动回人形)", 292, -50, font, 218) table.insert(controls, CreateCheckBox(tweaksSection, "乌龟服兼容修改", 14, -80, function() return SFramesDB.Tweaks.turtleCompat ~= false end, function(checked) SFramesDB.Tweaks.turtleCompat = checked end )) CreateDesc(tweaksSection, "隐藏乌龟服自带目标血量文字,修复地图窗口标题偏移", 36, -96, font, 218) table.insert(controls, CreateCheckBox(tweaksSection, "SuperWoW 客户端兼容", 270, -80, function() return SFramesDB.Tweaks.superWoW ~= false end, function(checked) SFramesDB.Tweaks.superWoW = checked end )) CreateDesc(tweaksSection, "为 SuperWoW 提供 GUID 施法数据支持(需安装 SuperWoW)", 292, -96, font, 218) table.insert(controls, CreateCheckBox(tweaksSection, "冷却倒计时", 14, -126, function() return SFramesDB.Tweaks.cooldownNumbers ~= false end, function(checked) SFramesDB.Tweaks.cooldownNumbers = checked end )) CreateDesc(tweaksSection, "在技能/物品冷却图标上显示剩余时间文字(>2秒)", 36, -142, font, 218) table.insert(controls, CreateCheckBox(tweaksSection, "暗色界面风格", 270, -126, function() return SFramesDB.Tweaks.darkUI == true end, function(checked) SFramesDB.Tweaks.darkUI = checked end )) CreateDesc(tweaksSection, "将整个游戏界面调暗为深色主题(默认关闭)", 292, -142, font, 218) CreateLabel(tweaksSection, "提示:以上所有选项修改后需要 /reload 才能生效。", 14, -172, font, 10, 0.6, 0.6, 0.65) uiScroll:UpdateRange() self.uiControls = controls self.uiScroll = uiScroll end function SFrames.ConfigUI:BuildPlayerPage() local font = SFrames:GetFont() local page = self.playerPage local controls = {} local function RefreshPlayer() if SFrames.Player and SFrames.Player.ApplyConfig then SFrames.Player:ApplyConfig() return end if SFrames.Player and SFrames.Player.UpdateAll then SFrames.Player:UpdateAll() end end local playerSection = CreateSection(page, "玩家框体", 8, -8, 520, 254, font) table.insert(controls, CreateSlider(playerSection, "缩放", 14, -46, 150, 0.7, 1.8, 0.05, function() return SFramesDB.playerFrameScale end, function(value) SFramesDB.playerFrameScale = value end, function(v) return string.format("%.2f", v) end, function() RefreshPlayer() end )) table.insert(controls, CreateSlider(playerSection, "框体宽度", 170, -46, 150, 170, 420, 1, function() return SFramesDB.playerFrameWidth end, function(value) SFramesDB.playerFrameWidth = value end, function(v) return tostring(math.floor(v + 0.5)) end, function() RefreshPlayer() end )) table.insert(controls, CreateSlider(playerSection, "头像宽度", 326, -46, 130, 32, 95, 1, function() return SFramesDB.playerPortraitWidth end, function(value) SFramesDB.playerPortraitWidth = value end, function(v) return tostring(math.floor(v + 0.5)) end, function() RefreshPlayer() end )) table.insert(controls, CreateSlider(playerSection, "生命条高度", 14, -108, 150, 14, 80, 1, function() return SFramesDB.playerHealthHeight end, function(value) SFramesDB.playerHealthHeight = value end, function(v) return tostring(math.floor(v + 0.5)) end, function() RefreshPlayer() end )) table.insert(controls, CreateSlider(playerSection, "能量条高度", 170, -108, 150, 6, 40, 1, function() return SFramesDB.playerPowerHeight end, function(value) SFramesDB.playerPowerHeight = value end, function(v) return tostring(math.floor(v + 0.5)) end, function() RefreshPlayer() end )) table.insert(controls, CreateCheckBox(playerSection, "玩家姓名显示职业", 326, -106, function() return SFramesDB.playerShowClass ~= false end, function(checked) SFramesDB.playerShowClass = checked end, function() RefreshPlayer() end )) table.insert(controls, CreateCheckBox(playerSection, "玩家显示职业图标", 326, -132, function() return SFramesDB.playerShowClassIcon ~= false end, function(checked) SFramesDB.playerShowClassIcon = checked end, function() RefreshPlayer() end )) table.insert(controls, CreateSlider(playerSection, "姓名字号", 14, -170, 150, 8, 18, 1, function() return SFramesDB.playerNameFontSize end, function(value) SFramesDB.playerNameFontSize = value end, function(v) return tostring(math.floor(v + 0.5)) end, function() RefreshPlayer() end )) table.insert(controls, CreateSlider(playerSection, "数值字号", 170, -170, 150, 8, 18, 1, function() return SFramesDB.playerValueFontSize end, function(value) SFramesDB.playerValueFontSize = value end, function(v) return tostring(math.floor(v + 0.5)) end, function() RefreshPlayer() end )) CreateLabel(playerSection, "提示:宽度/头像/血条/能量条可独立实时调节。", 14, -221, font, 10, 0.9, 0.9, 0.9) -- ── 宠物框体 ────────────────────────────────────────────────── local petSection = CreateSection(page, "宠物框体", 8, -270, 520, 98, font) table.insert(controls, CreateCheckBox(petSection, "显示宠物框体", 14, -34, function() return SFramesDB.showPetFrame ~= false end, function(checked) SFramesDB.showPetFrame = checked end, function() if SFrames.Pet and SFrames.Pet.UpdateAll then SFrames.Pet:UpdateAll() end end )) CreateDesc(petSection, "在玩家框体附近显示当前宠物的血量条", 36, -50, font) table.insert(controls, CreateSlider(petSection, "缩放", 270, -52, 200, 0.7, 1.8, 0.05, function() return SFramesDB.petFrameScale or 1 end, function(value) SFramesDB.petFrameScale = value end, function(v) return string.format("%.2f", v) end, function(value) if SFrames.Pet and SFrames.Pet.frame then SFrames.Pet.frame:SetScale(value) end end )) self.playerControls = controls end function SFrames.ConfigUI:BuildTargetPage() local font = SFrames:GetFont() local page = self.targetPage local controls = {} local function RefreshTarget() if SFrames.Target and SFrames.Target.ApplyConfig then SFrames.Target:ApplyConfig() return end if SFrames.Target and SFrames.Target.UpdateAll then SFrames.Target:UpdateAll() end end local targetSection = CreateSection(page, "目标框体", 8, -8, 520, 294, font) table.insert(controls, CreateSlider(targetSection, "缩放", 14, -46, 150, 0.7, 1.8, 0.05, function() return SFramesDB.targetFrameScale end, function(value) SFramesDB.targetFrameScale = value end, function(v) return string.format("%.2f", v) end, function() RefreshTarget() end )) table.insert(controls, CreateSlider(targetSection, "框体宽度", 170, -46, 150, 170, 420, 1, function() return SFramesDB.targetFrameWidth end, function(value) SFramesDB.targetFrameWidth = value end, function(v) return tostring(math.floor(v + 0.5)) end, function() RefreshTarget() end )) table.insert(controls, CreateSlider(targetSection, "头像宽度", 326, -46, 130, 32, 95, 1, function() return SFramesDB.targetPortraitWidth end, function(value) SFramesDB.targetPortraitWidth = value end, function(v) return tostring(math.floor(v + 0.5)) end, function() RefreshTarget() end )) table.insert(controls, CreateSlider(targetSection, "生命条高度", 14, -108, 150, 14, 80, 1, function() return SFramesDB.targetHealthHeight end, function(value) SFramesDB.targetHealthHeight = value end, function(v) return tostring(math.floor(v + 0.5)) end, function() RefreshTarget() end )) table.insert(controls, CreateSlider(targetSection, "能量条高度", 170, -108, 150, 6, 40, 1, function() return SFramesDB.targetPowerHeight end, function(value) SFramesDB.targetPowerHeight = value end, function(v) return tostring(math.floor(v + 0.5)) end, function() RefreshTarget() end )) table.insert(controls, CreateCheckBox(targetSection, "目标姓名显示职业", 326, -106, function() return SFramesDB.targetShowClass ~= false end, function(checked) SFramesDB.targetShowClass = checked end, function() RefreshTarget() end )) table.insert(controls, CreateCheckBox(targetSection, "目标显示职业图标", 326, -132, function() return SFramesDB.targetShowClassIcon ~= false end, function(checked) SFramesDB.targetShowClassIcon = checked end, function() RefreshTarget() end )) table.insert(controls, CreateSlider(targetSection, "姓名字号", 14, -170, 150, 8, 18, 1, function() return SFramesDB.targetNameFontSize end, function(value) SFramesDB.targetNameFontSize = value end, function(v) return tostring(math.floor(v + 0.5)) end, function() RefreshTarget() end )) table.insert(controls, CreateSlider(targetSection, "数值字号", 170, -170, 150, 8, 18, 1, function() return SFramesDB.targetValueFontSize end, function(value) SFramesDB.targetValueFontSize = value end, function(v) return tostring(math.floor(v + 0.5)) end, function() RefreshTarget() end )) table.insert(controls, CreateCheckBox(targetSection, "启用目标距离文本", 326, -168, function() return SFramesDB.targetDistanceEnabled ~= false end, function(checked) SFramesDB.targetDistanceEnabled = checked end, function(checked) if SFrames.Target and SFrames.Target.distanceFrame then if checked and UnitExists("target") then SFrames.Target.distanceFrame:Show() elseif not checked then SFrames.Target.distanceFrame:Hide() end end end )) table.insert(controls, CreateSlider(targetSection, "距离文本缩放", 14, -232, 150, 0.7, 1.8, 0.05, function() return SFramesDB.targetDistanceScale end, function(value) SFramesDB.targetDistanceScale = value end, function(v) return string.format("%.2f", v) end, function(value) if SFrames.Target and SFrames.Target.distanceFrame then SFrames.Target.distanceFrame:SetScale(value) end end )) self.targetControls = controls end function SFrames.ConfigUI:BuildPartyPage() local font = SFrames:GetFont() local page = self.partyPage local controls = {} local function RefreshParty() if SFrames.Party and SFrames.Party.ApplyConfig then SFrames.Party:ApplyConfig() end if SFrames.Party and SFrames.Party.UpdateAll then SFrames.Party:UpdateAll() end end local uiScroll = CreateScrollArea(page, 4, -4, 548, 458, 320) local root = uiScroll.child local partySection = CreateSection(root, "小队", 8, -8, 520, 356, font) CreateButton(partySection, "解锁小队框架", 14, -26, 130, 22, function() if SFrames and SFrames.UnlockFrames then SFrames:UnlockFrames() end end) CreateButton(partySection, "锁定小队框架", 154, -26, 130, 22, function() if SFrames and SFrames.LockFrames then SFrames:LockFrames() end end) table.insert(controls, CreateCheckBox(partySection, "横向布局(关闭为竖向)", 12, -60, function() return SFramesDB.partyLayout == "horizontal" end, function(checked) if checked then SFramesDB.partyLayout = "horizontal" else SFramesDB.partyLayout = "vertical" end end, function(checked) if SFrames.Party and SFrames.Party.SetLayout then if checked then SFrames.Party:SetLayout("horizontal") else SFrames.Party:SetLayout("vertical") end end RefreshParty() end )) table.insert(controls, CreateSlider(partySection, "缩放", 14, -108, 150, 0.7, 1.8, 0.05, function() return SFramesDB.partyFrameScale end, function(value) SFramesDB.partyFrameScale = value end, function(v) return string.format("%.2f", v) end, function() RefreshParty() end )) table.insert(controls, CreateSlider(partySection, "框体宽度", 170, -108, 150, 120, 320, 1, function() return SFramesDB.partyFrameWidth end, function(value) SFramesDB.partyFrameWidth = value end, function(v) return tostring(math.floor(v + 0.5)) end, function() RefreshParty() end )) table.insert(controls, CreateSlider(partySection, "框体高度", 326, -108, 130, 28, 80, 1, function() return SFramesDB.partyFrameHeight end, function(value) SFramesDB.partyFrameHeight = value end, function(v) return tostring(math.floor(v + 0.5)) end, function() RefreshParty() end )) table.insert(controls, CreateSlider(partySection, "头像宽度", 14, -170, 150, 24, 64, 1, function() return SFramesDB.partyPortraitWidth end, function(value) SFramesDB.partyPortraitWidth = value end, function(v) return tostring(math.floor(v + 0.5)) end, function() RefreshParty() end )) table.insert(controls, CreateSlider(partySection, "生命条高度", 170, -170, 150, 10, 60, 1, function() return SFramesDB.partyHealthHeight end, function(value) SFramesDB.partyHealthHeight = value end, function(v) return tostring(math.floor(v + 0.5)) end, function() RefreshParty() end )) table.insert(controls, CreateSlider(partySection, "能量条高度", 326, -170, 130, 6, 30, 1, function() return SFramesDB.partyPowerHeight end, function(value) SFramesDB.partyPowerHeight = value end, function(v) return tostring(math.floor(v + 0.5)) end, function() RefreshParty() end )) table.insert(controls, CreateSlider(partySection, "横向间距", 14, -232, 150, 0, 40, 1, function() return SFramesDB.partyHorizontalGap end, function(value) SFramesDB.partyHorizontalGap = value end, function(v) return tostring(math.floor(v + 0.5)) end, function() RefreshParty() end )) table.insert(controls, CreateSlider(partySection, "纵向间距", 170, -232, 150, 0, 80, 1, function() return SFramesDB.partyVerticalGap end, function(value) SFramesDB.partyVerticalGap = value end, function(v) return tostring(math.floor(v + 0.5)) end, function() RefreshParty() end )) table.insert(controls, CreateSlider(partySection, "姓名字号", 326, -232, 130, 8, 18, 1, function() return SFramesDB.partyNameFontSize end, function(value) SFramesDB.partyNameFontSize = value end, function(v) return tostring(math.floor(v + 0.5)) end, function() RefreshParty() end )) table.insert(controls, CreateSlider(partySection, "数值字号", 14, -294, 150, 8, 18, 1, function() return SFramesDB.partyValueFontSize end, function(value) SFramesDB.partyValueFontSize = value end, function(v) return tostring(math.floor(v + 0.5)) end, function() RefreshParty() end )) table.insert(controls, CreateCheckBox(partySection, "显示小队增益", 170, -292, function() return SFramesDB.partyShowBuffs ~= false end, function(checked) SFramesDB.partyShowBuffs = checked end, function() RefreshParty() end )) table.insert(controls, CreateCheckBox(partySection, "显示小队减益", 326, -292, function() return SFramesDB.partyShowDebuffs ~= false end, function(checked) SFramesDB.partyShowDebuffs = checked end, function() RefreshParty() end )) CreateButton(partySection, "小队测试模式", 326, -324, 130, 22, function() if SFrames.Party and SFrames.Party.TestMode then SFrames.Party:TestMode() end end) uiScroll:UpdateRange() self.partyControls = controls self.partyScroll = uiScroll end function SFrames.ConfigUI:BuildBagPage() local font = SFrames:GetFont() local page = self.bagPage local controls = {} local bagScroll = CreateScrollArea(page, 4, -4, 548, 458, 510) local root = bagScroll.child local bagSection = CreateSection(root, "背包", 8, -8, 502, 196, font) table.insert(controls, CreateCheckBox(bagSection, "启用 Nanami 背包(需 /reload)", 12, -28, function() return SFramesDB.Bags.enable ~= false end, function(checked) SFramesDB.Bags.enable = checked end )) table.insert(controls, CreateCheckBox(bagSection, "自动出售灰色物品", 12, -52, function() return SFramesDB.Bags.sellGrey ~= false end, function(checked) SFramesDB.Bags.sellGrey = checked end )) table.insert(controls, CreateSlider(bagSection, "背包列数", 16, -90, 220, 4, 20, 1, function() return SFramesDB.Bags.columns end, function(value) SFramesDB.Bags.columns = value end, function(value) return tostring(math.floor(value + 0.5)) end, function() if SFrames and SFrames.Bags and SFrames.Bags.Container and SFrames.Bags.Container.UpdateLayout then SFrames.Bags.Container:UpdateLayout() end end )) table.insert(controls, CreateSlider(bagSection, "背包间距", 258, -90, 220, 0, 12, 1, function() return SFramesDB.Bags.bagSpacing end, function(value) SFramesDB.Bags.bagSpacing = value end, function(value) return tostring(math.floor(value + 0.5)) end, function() if SFrames and SFrames.Bags and SFrames.Bags.Container and SFrames.Bags.Container.UpdateLayout then SFrames.Bags.Container:UpdateLayout() end end )) table.insert(controls, CreateSlider(bagSection, "背包缩放", 16, -150, 220, 0.50, 2.00, 0.05, function() return SFramesDB.Bags.scale end, function(value) SFramesDB.Bags.scale = value end, function(value) return string.format("%.2f", value) end, function(value) if SFramesBagFrame then SFramesBagFrame:SetScale(value) end end )) CreateButton(bagSection, "打开背包", 258, -150, 220, 24, function() if SFrames and SFrames.Bags and SFrames.Bags.Container and SFrames.Bags.Container.Open then SFrames.Bags.Container:Open() end end) local bankSection = CreateSection(root, "银行", 8, -210, 502, 155, font) table.insert(controls, CreateSlider(bankSection, "银行列数", 16, -50, 220, 4, 24, 1, function() return SFramesDB.Bags.bankColumns end, function(value) SFramesDB.Bags.bankColumns = value end, function(value) return tostring(math.floor(value + 0.5)) end, function() if SFrames and SFrames.Bags and SFrames.Bags.Bank and SFrames.Bags.Bank.UpdateLayout then SFrames.Bags.Bank:UpdateLayout() end end )) table.insert(controls, CreateSlider(bankSection, "银行间距", 258, -50, 220, 0, 12, 1, function() return SFramesDB.Bags.bankSpacing end, function(value) SFramesDB.Bags.bankSpacing = value end, function(value) return tostring(math.floor(value + 0.5)) end, function() if SFrames and SFrames.Bags and SFrames.Bags.Bank and SFrames.Bags.Bank.UpdateLayout then SFrames.Bags.Bank:UpdateLayout() end end )) table.insert(controls, CreateSlider(bankSection, "银行缩放", 16, -110, 220, 0.50, 2.00, 0.05, function() return SFramesDB.Bags.bankScale end, function(value) SFramesDB.Bags.bankScale = value end, function(value) return string.format("%.2f", value) end, function(value) if SFramesBankFrame then SFramesBankFrame:SetScale(value) end end )) CreateButton(bankSection, "打开离线银行", 258, -110, 220, 24, function() if SFrames and SFrames.Bags and SFrames.Bags.Bank and SFrames.Bags.Bank.OpenOffline then SFrames.Bags.Bank:OpenOffline() end end) --------------------------------------------------------------------------- -- Sell Price Database Section --------------------------------------------------------------------------- local priceSection = CreateSection(root, "售价数据库", 8, -371, 502, 126, font) local function CountCacheEntries() local n = 0 if SFramesGlobalDB and SFramesGlobalDB.sellPriceCache then for _ in pairs(SFramesGlobalDB.sellPriceCache) do n = n + 1 end end return n end local function CountDBEntries() local n = 0 if NanamiSellPriceDB then for _ in pairs(NanamiSellPriceDB) do n = n + 1 end end return n end local dbCount = CountDBEntries() local statusLabel = CreateLabel(priceSection, string.format("内置: %d 条 | 已学习: %d 条", dbCount, CountCacheEntries()), 14, -30, font, 10, 0.8, 0.8, 0.7) local scanResultLabel = CreateLabel(priceSection, "", 14, -104, font, 10, 0.45, 0.9, 0.45) CreateButton(priceSection, "扫描全部物品售价", 16, -48, 228, 24, function() if not SFramesGlobalDB then SFramesGlobalDB = {} end if not SFramesGlobalDB.sellPriceCache then SFramesGlobalDB.sellPriceCache = {} end local cache = SFramesGlobalDB.sellPriceCache local scanned, learned = 0, 0 local function TryScan(link) if not link then return end local _, _, sid = string.find(link, "item:(%d+)") if not sid then return end local id = tonumber(sid) if not id then return end scanned = scanned + 1 if cache[id] and cache[id] > 0 then return end local price if NanamiSellPriceDB and NanamiSellPriceDB[id] and NanamiSellPriceDB[id] > 0 then price = NanamiSellPriceDB[id] elseif ShaguTweaks and ShaguTweaks.SellValueDB and ShaguTweaks.SellValueDB[id] and ShaguTweaks.SellValueDB[id] > 0 then price = ShaguTweaks.SellValueDB[id] else local _,_,_,_,_,_,_,_,_,_,sp = GetItemInfo(link) if sp and type(sp) == "number" and sp > 0 then price = sp end end if price then cache[id] = price learned = learned + 1 end end for bag = 0, 4 do for slot = 1, (GetContainerNumSlots(bag) or 0) do TryScan(GetContainerItemLink(bag, slot)) end end for bag = 5, 10 do pcall(function() for slot = 1, (GetContainerNumSlots(bag) or 0) do TryScan(GetContainerItemLink(bag, slot)) end end) end pcall(function() for slot = 1, (GetContainerNumSlots(-1) or 0) do TryScan(GetContainerItemLink(-1, slot)) end end) for slot = 1, 19 do TryScan(GetInventoryItemLink("player", slot)) end statusLabel:SetText(string.format("内置: %d 条 | 已学习: %d 条", dbCount, CountCacheEntries())) scanResultLabel:SetText(string.format("完成! 检查 %d 件, 新增 %d 条售价", scanned, learned)) end) CreateButton(priceSection, "清空学习缓存", 258, -48, 228, 24, function() if SFramesGlobalDB then SFramesGlobalDB.sellPriceCache = {} end statusLabel:SetText(string.format("内置: %d 条 | 已学习: 0 条", dbCount)) scanResultLabel:SetText("学习缓存已清空") end) CreateDesc(priceSection, "扫描背包/银行/装备中所有物品, 缓存售价到离线数据库 (随存档保存)", 14, -76, font, 480) CreateDesc(priceSection, "多次游玩后数据自动丰富, 其他角色也能共享离线数据", 14, -90, font, 480) bagScroll:UpdateRange() self.bagScroll = bagScroll self.bagControls = controls end function SFrames.ConfigUI:BuildRaidPage() local font = SFrames:GetFont() local page = self.raidPage local controls = {} local function RefreshRaid() if SFrames.Raid and SFrames.Raid.ApplyLayout then if not SFrames.Raid._framesBuilt then return end SFrames.Raid:ApplyLayout() SFrames.Raid:UpdateAll() for i = 1, 40 do local f = SFrames.Raid.frames[i] and SFrames.Raid.frames[i].frame if f then SFrames.Raid:ApplyFrameStyle(f, SFrames.Raid:GetMetrics()) end end end end local uiScroll = CreateScrollArea(page, 4, -4, 548, 458, 600) local root = uiScroll.child local raidSection = CreateSection(root, "团队框架设置", 8, -8, 520, 570, font) table.insert(controls, CreateCheckBox(raidSection, "启用 Nanami 团队框架(默认启用,需 /reload)", 12, -28, function() return SFramesDB.enableRaidFrames ~= false end, function(checked) SFramesDB.enableRaidFrames = checked end )) CreateButton(raidSection, "解锁团队框架", 14, -56, 130, 22, function() if SFrames and SFrames.UnlockFrames then SFrames:UnlockFrames() end end) CreateButton(raidSection, "锁定团队框架", 154, -56, 130, 22, function() if SFrames and SFrames.LockFrames then SFrames:LockFrames() end end) table.insert(controls, CreateCheckBox(raidSection, "横向小队排列(关闭为竖向叠放)", 12, -88, function() return SFramesDB.raidLayout == "horizontal" end, function(checked) SFramesDB.raidLayout = checked and "horizontal" or "vertical" end, function() RefreshRaid() end )) table.insert(controls, CreateSlider(raidSection, "缩放", 14, -150, 150, 0.5, 2.0, 0.05, function() return SFramesDB.raidFrameScale end, function(value) SFramesDB.raidFrameScale = value end, function(v) return string.format("%.2f", v) end, function(val) if SFrames.Raid and SFrames.Raid.parent then SFrames.Raid.parent:SetScale(val) end end )) table.insert(controls, CreateSlider(raidSection, "框体宽度", 170, -150, 150, 30, 150, 1, function() return SFramesDB.raidFrameWidth end, function(value) SFramesDB.raidFrameWidth = value end, function(v) return tostring(math.floor(v + 0.5)) end, function() RefreshRaid() end )) table.insert(controls, CreateSlider(raidSection, "框体高度", 326, -150, 130, 20, 80, 1, function() return SFramesDB.raidFrameHeight end, function(value) SFramesDB.raidFrameHeight = value end, function(v) return tostring(math.floor(v + 0.5)) end, function() RefreshRaid() end )) table.insert(controls, CreateSlider(raidSection, "生命条高度", 14, -212, 150, 10, 78, 1, function() return SFramesDB.raidHealthHeight end, function(value) SFramesDB.raidHealthHeight = value end, function(v) return tostring(math.floor(v + 0.5)) end, function() RefreshRaid() end )) table.insert(controls, CreateCheckBox(raidSection, "显示能量条", 170, -210, function() return SFramesDB.raidShowPower ~= false end, function(checked) SFramesDB.raidShowPower = checked end, function() RefreshRaid() end )) table.insert(controls, CreateCheckBox(raidSection, "显示小队标题(横向=顶部,竖向=左侧)", 14, -244, function() return SFramesDB.raidShowGroupLabel ~= false end, function(checked) SFramesDB.raidShowGroupLabel = checked end, function() RefreshRaid() end )) table.insert(controls, CreateSlider(raidSection, "横向间距", 14, -274, 150, 0, 40, 1, function() return SFramesDB.raidHorizontalGap end, function(value) SFramesDB.raidHorizontalGap = value end, function(v) return tostring(math.floor(v + 0.5)) end, function() RefreshRaid() end )) table.insert(controls, CreateSlider(raidSection, "纵向间距", 170, -274, 150, 0, 40, 1, function() return SFramesDB.raidVerticalGap end, function(value) SFramesDB.raidVerticalGap = value end, function(v) return tostring(math.floor(v + 0.5)) end, function() RefreshRaid() end )) table.insert(controls, CreateSlider(raidSection, "小队间隔", 326, -274, 130, 0, 60, 1, function() return SFramesDB.raidGroupGap end, function(value) SFramesDB.raidGroupGap = value end, function(v) return tostring(math.floor(v + 0.5)) end, function() RefreshRaid() end )) table.insert(controls, CreateSlider(raidSection, "姓名字号", 14, -336, 150, 8, 18, 1, function() return SFramesDB.raidNameFontSize end, function(value) SFramesDB.raidNameFontSize = value end, function(v) return tostring(math.floor(v + 0.5)) end, function() RefreshRaid() end )) table.insert(controls, CreateSlider(raidSection, "数值字号", 170, -336, 150, 8, 18, 1, function() return SFramesDB.raidValueFontSize end, function(value) SFramesDB.raidValueFontSize = value end, function(v) return tostring(math.floor(v + 0.5)) end, function() RefreshRaid() end )) table.insert(controls, CreateCheckBox(raidSection, "血量百分比模式 (关=紧凑数值/-缺口)", 14, -390, function() return SFramesDB.raidHealthFormat == "percent" end, function(checked) SFramesDB.raidHealthFormat = checked and "percent" or "compact" end, function() RefreshRaid() end )) CreateButton(raidSection, "测试模式(假团队)", 14, -450, 150, 24, function() if SFrames.Raid then if SFrames.Raid.testing then SFrames.Raid.testing = false SFrames.Raid:UpdateAll() else SFrames.Raid.testing = true SFrames.Raid:EnsureFrames() SFrames.Raid.parent:Show() for i = 1, 40 do local f = SFrames.Raid.frames[i].frame f:Show() f.nameText:SetText("玩家"..i) f.healthText:SetText("100%") f.health:SetMinMaxValues(0, 100) f.health:SetValue(100) f.health:SetStatusBarColor(0, 1, 0) end end end end) uiScroll:UpdateRange() self.raidControls = controls self.raidScroll = uiScroll end function SFrames.ConfigUI:BuildCharPage() local font = SFrames:GetFont() local page = self.charPage local controls = {} local uiScroll = CreateScrollArea(page, 4, -4, 552, PANEL_HEIGHT - 110, 720) local root = uiScroll.child local charSection = CreateSection(root, "人物面板", 8, -8, 524, 430, font) table.insert(controls, CreateCheckBox(charSection, "启用 Nanami 人物面板(需 /reload)", 12, -34, function() return SFramesDB.charPanelEnable ~= false end, function(checked) SFramesDB.charPanelEnable = checked end )) CreateDesc(charSection, "关闭后按 C 键将打开原版人物面板", 36, -50, font) table.insert(controls, CreateSlider(charSection, "面板缩放", 14, -72, 240, 0.6, 1.5, 0.05, function() return SFramesDB.charPanelScale or 1.0 end, function(v) SFramesDB.charPanelScale = v end, function(v) return string.format("%.0f%%", v * 100) end, function() if SFramesCharacterPanel then SFramesCharacterPanel:SetScale(SFramesDB.charPanelScale or 1.0) end end )) table.insert(controls, CreateCheckBox(charSection, "显示PVP称号", 12, -118, function() return SFramesDB.charShowTitle ~= false end, function(checked) SFramesDB.charShowTitle = checked end, function() if SFrames.CharacterPanel and SFrames.CharacterPanel.UpdateCurrentTab then SFrames.CharacterPanel:UpdateCurrentTab() end end )) table.insert(controls, CreateCheckBox(charSection, "显示公会名", 12, -146, function() return SFramesDB.charShowGuild ~= false end, function(checked) SFramesDB.charShowGuild = checked end, function() if SFrames.CharacterPanel and SFrames.CharacterPanel.UpdateCurrentTab then SFrames.CharacterPanel:UpdateCurrentTab() end end )) table.insert(controls, CreateCheckBox(charSection, "显示装备品质光晕", 12, -174, function() return SFramesDB.charShowQualityGlow ~= false end, function(checked) SFramesDB.charShowQualityGlow = checked end, function() if SFrames.CharacterPanel and SFrames.CharacterPanel.UpdateCurrentTab then SFrames.CharacterPanel:UpdateCurrentTab() end end )) table.insert(controls, CreateCheckBox(charSection, "显示3D角色模型", 12, -202, function() return SFramesDB.charShowModel ~= false end, function(checked) SFramesDB.charShowModel = checked end, function() if SFrames.CharacterPanel and SFrames.CharacterPanel.UpdateCurrentTab then SFrames.CharacterPanel:UpdateCurrentTab() end end )) table.insert(controls, CreateCheckBox(charSection, "启用 Nanami 观察面板(需 /reload)", 12, -230, function() return SFramesDB.enableInspect ~= false end, function(checked) SFramesDB.enableInspect = checked end )) CreateDesc(charSection, "关闭后观察他人将使用原版界面", 36, -246, font) CreateLabel(charSection, "提示: 面板使用 C 键打开/关闭,按 ESC 关闭。", 14, -274, font, 10, 0.6, 0.6, 0.65) CreateLabel(charSection, "装备页下方可滚动查看全部角色属性。", 14, -292, font, 10, 0.6, 0.6, 0.65) -- Spell Book section local sbSection = CreateSection(root, "法术书", 8, -446, 524, 200, font) table.insert(controls, CreateSlider(sbSection, "法术书缩放", 14, -34, 240, 0.6, 1.5, 0.05, function() return SFramesDB.spellBookScale or 1.0 end, function(v) SFramesDB.spellBookScale = v end, function(v) return string.format("%.0f%%", v * 100) end, function() if SFramesSpellBookUI then SFramesSpellBookUI:SetScale(SFramesDB.spellBookScale or 1.0) end end )) table.insert(controls, CreateCheckBox(sbSection, "只显示最高等级法术", 14, -80, function() return SFramesDB.spellBookHighestOnly == true end, function(checked) SFramesDB.spellBookHighestOnly = checked end )) CreateDesc(sbSection, "隐藏同名法术的低等级版本,只保留最高等级", 36, -96, font) table.insert(controls, CreateCheckBox(sbSection, "学习新等级后自动替换动作条", 14, -116, function() return SFramesDB.spellBookAutoReplace == true end, function(checked) SFramesDB.spellBookAutoReplace = checked end )) CreateDesc(sbSection, "学习高等级法术时,自动替换动作条上的低等级版本", 36, -132, font) CreateLabel(sbSection, "提示: 法术书使用 P 键打开/关闭,支持鼠标滚轮翻页。", 14, -160, font, 10, 0.6, 0.6, 0.65) -- Social section local socialSection = CreateSection(root, "社交面板", 8, -660, 524, 120, font) table.insert(controls, CreateSlider(socialSection, "社交面板缩放", 14, -34, 240, 0.6, 1.5, 0.05, function() return SFramesDB.socialScale or 1.0 end, function(v) SFramesDB.socialScale = v end, function(v) return string.format("%.0f%%", v * 100) end, function() if SFramesSocialUI then SFramesSocialUI:SetScale(SFramesDB.socialScale or 1.0) end end )) CreateLabel(socialSection, "提示: 社交面板使用 O 键打开/关闭。", 14, -80, font, 10, 0.6, 0.6, 0.65) uiScroll:UpdateRange() self.charControls = controls self.charScroll = uiScroll end function SFrames.ConfigUI:BuildActionBarPage() local font = SFrames:GetFont() local page = self.actionBarPage local controls = {} local function RefreshAB() if SFrames.ActionBars and SFrames.ActionBars.ApplyConfig then SFrames.ActionBars:ApplyConfig() end end local uiScroll = CreateScrollArea(page, 4, -4, 548, 458, 978) local root = uiScroll.child local abSection = CreateSection(root, "动作条", 8, -8, 520, 948, font) table.insert(controls, CreateCheckBox(abSection, "启用动作条接管(需 /reload 生效)", 12, -28, function() return SFramesDB.ActionBars.enable ~= false end, function(checked) SFramesDB.ActionBars.enable = checked end )) CreateLabel(abSection, "按键绑定:", 14, -56, font, 11, 0.85, 0.75, 0.80) CreateButton(abSection, "进入按键绑定模式", 14, -76, 160, 24, function() if SFrames.ActionBars and SFrames.ActionBars.EnterKeyBindMode then if self.frame then self.frame:Hide() end SFrames.ActionBars:EnterKeyBindMode() end end) CreateDesc(abSection, "悬停动作条/姿态栏/宠物栏按钮,按下键盘键/鼠标键(3-5)/滚轮绑定。右键清除。ESC 退出。也可用 /nui bind", 14, -100, font, 480) table.insert(controls, CreateSlider(abSection, "按钮大小", 14, -130, 150, 24, 48, 1, function() return SFramesDB.ActionBars.buttonSize end, function(value) SFramesDB.ActionBars.buttonSize = value end, function(v) return tostring(math.floor(v + 0.5)) end, function() RefreshAB() end )) table.insert(controls, CreateSlider(abSection, "按钮间距", 180, -130, 150, 0, 8, 1, function() return SFramesDB.ActionBars.buttonGap end, function(value) SFramesDB.ActionBars.buttonGap = value end, function(v) return tostring(math.floor(v + 0.5)) end, function() RefreshAB() end )) table.insert(controls, CreateSlider(abSection, "整体缩放", 346, -130, 150, 0.5, 2.0, 0.05, function() return SFramesDB.ActionBars.scale end, function(value) SFramesDB.ActionBars.scale = value end, function(v) return string.format("%.2f", v) end, function() RefreshAB() end )) table.insert(controls, CreateSlider(abSection, "显示行数", 14, -192, 150, 1, 3, 1, function() return SFramesDB.ActionBars.barCount end, function(value) SFramesDB.ActionBars.barCount = value end, function(v) return tostring(math.floor(v + 0.5)) end, function() RefreshAB() end )) table.insert(controls, CreateSlider(abSection, "姿态/宠物栏按钮大小", 180, -192, 150, 16, 40, 1, function() return SFramesDB.ActionBars.smallBarSize end, function(value) SFramesDB.ActionBars.smallBarSize = value end, function(v) return tostring(math.floor(v + 0.5)) end, function() RefreshAB() end )) table.insert(controls, CreateCheckBox(abSection, "显示快捷键文字", 12, -242, function() return SFramesDB.ActionBars.showHotkey ~= false end, function(checked) SFramesDB.ActionBars.showHotkey = checked end, function() RefreshAB() end )) table.insert(controls, CreateCheckBox(abSection, "显示宏名称", 200, -242, function() return SFramesDB.ActionBars.showMacroName == true end, function(checked) SFramesDB.ActionBars.showMacroName = checked end, function() RefreshAB() end )) table.insert(controls, CreateCheckBox(abSection, "超距红色着色(技能超出射程时按钮变红)", 12, -270, function() return SFramesDB.ActionBars.rangeColoring ~= false end, function(checked) SFramesDB.ActionBars.rangeColoring = checked end )) table.insert(controls, CreateCheckBox(abSection, "显示宠物动作条", 12, -298, function() return SFramesDB.ActionBars.showPetBar ~= false end, function(checked) SFramesDB.ActionBars.showPetBar = checked end, function() RefreshAB() end )) table.insert(controls, CreateCheckBox(abSection, "显示姿态栏", 200, -298, function() return SFramesDB.ActionBars.showStanceBar ~= false end, function(checked) SFramesDB.ActionBars.showStanceBar = checked end, function() RefreshAB() end )) table.insert(controls, CreateCheckBox(abSection, "显示右侧动作条(两列竖向栏)", 12, -326, function() return SFramesDB.ActionBars.showRightBars ~= false end, function(checked) SFramesDB.ActionBars.showRightBars = checked end, function() RefreshAB() end )) table.insert(controls, CreateCheckBox(abSection, "始终显示动作条(空格子也显示背景框)", 12, -354, function() return SFramesDB.ActionBars.alwaysShowGrid == true end, function(checked) SFramesDB.ActionBars.alwaysShowGrid = checked end, function() RefreshAB() end )) table.insert(controls, CreateCheckBox(abSection, "按钮圆角", 12, -382, function() return SFramesDB.ActionBars.buttonRounded == true end, function(checked) SFramesDB.ActionBars.buttonRounded = checked end, function() RefreshAB() end )) table.insert(controls, CreateCheckBox(abSection, "按钮内阴影", 200, -382, function() return SFramesDB.ActionBars.buttonInnerShadow == true end, function(checked) SFramesDB.ActionBars.buttonInnerShadow = checked end, function() RefreshAB() end )) table.insert(controls, CreateCheckBox(abSection, "显示动作条狮鹫(在底部动作条两侧显示装饰狮鹫)", 12, -410, function() return SFramesDB.ActionBars.hideGryphon == false end, function(checked) SFramesDB.ActionBars.hideGryphon = not checked end, function() RefreshAB() end )) table.insert(controls, CreateCheckBox(abSection, "狮鹫置于动作条之上(否则在动作条之下)", 12, -438, function() return SFramesDB.ActionBars.gryphonOnTop == true end, function(checked) SFramesDB.ActionBars.gryphonOnTop = checked end, function() RefreshAB() end )) table.insert(controls, CreateSlider(abSection, "狮鹫宽度", 14, -492, 150, 24, 200, 2, function() return SFramesDB.ActionBars.gryphonWidth or 64 end, function(value) SFramesDB.ActionBars.gryphonWidth = value end, function(v) return tostring(math.floor(v + 0.5)) end, function() RefreshAB() end )) table.insert(controls, CreateSlider(abSection, "狮鹫高度", 180, -492, 150, 24, 200, 2, function() return SFramesDB.ActionBars.gryphonHeight or 64 end, function(value) SFramesDB.ActionBars.gryphonHeight = value end, function(v) return tostring(math.floor(v + 0.5)) end, function() RefreshAB() end )) table.insert(controls, CreateSlider(abSection, "水平偏移(向内重叠)", 14, -554, 150, -50, 100, 1, function() return SFramesDB.ActionBars.gryphonOffsetX or 30 end, function(value) SFramesDB.ActionBars.gryphonOffsetX = value end, function(v) return tostring(math.floor(v + 0.5)) end, function() RefreshAB() end )) table.insert(controls, CreateSlider(abSection, "垂直偏移", 180, -554, 150, -100, 100, 1, function() return SFramesDB.ActionBars.gryphonOffsetY or 0 end, function(value) SFramesDB.ActionBars.gryphonOffsetY = value end, function(v) return tostring(math.floor(v + 0.5)) end, function() RefreshAB() end )) -- 狮鹫样式选择器(带图例预览) CreateLabel(abSection, "狮鹫样式:", 14, -606, font, 11, 0.85, 0.75, 0.80) local GRYPHON_STYLES_UI = { { key = "dragonflight", label = "巨龙时代", tex = "Interface\\AddOns\\Nanami-UI\\img\\df-gryphon" }, { key = "dragonflight_beta", label = "巨龙时代(Beta)", tex = "Interface\\AddOns\\Nanami-UI\\img\\df-gryphon-beta" }, { key = "classic", label = "经典", tex = "Interface\\MainMenuBar\\UI-MainMenuBar-EndCap-Human" }, { key = "cat", label = "猫", tex = "Interface\\AddOns\\Nanami-UI\\img\\cat" }, } local styleBorders = {} local styleStartX = 14 local styleY = -626 for idx, style in ipairs(GRYPHON_STYLES_UI) do local xOff = styleStartX + (idx - 1) * 125 -- 预览框 local preview = CreateFrame("Frame", nil, abSection) preview:SetWidth(64) preview:SetHeight(64) preview:SetPoint("TOPLEFT", abSection, "TOPLEFT", xOff, styleY) -- 预览背景 local bg = preview:CreateTexture(nil, "BACKGROUND") bg:SetAllPoints() bg:SetTexture(0, 0, 0, 0.4) -- 纹理预览 local tex = preview:CreateTexture(nil, "ARTWORK") tex:SetAllPoints() tex:SetTexture(style.tex) -- 选中边框 local border = CreateFrame("Frame", nil, preview) border:SetPoint("TOPLEFT", preview, "TOPLEFT", -2, 2) border:SetPoint("BOTTOMRIGHT", preview, "BOTTOMRIGHT", 2, -2) border:SetBackdrop({ edgeFile = "Interface\\Buttons\\WHITE8X8", edgeSize = 2, }) border:Hide() styleBorders[idx] = border -- 样式名标签 local label = preview:CreateFontString(nil, "OVERLAY") label:SetFont(font, 10, "OUTLINE") label:SetPoint("TOP", preview, "BOTTOM", 0, -4) label:SetText(style.label) label:SetTextColor(0.7, 0.65, 0.7) -- 点击选择 preview:EnableMouse(true) preview._sfStyleKey = style.key preview._sfIdx = idx preview:SetScript("OnMouseUp", function() SFramesDB.ActionBars.gryphonStyle = this._sfStyleKey for i, bd in ipairs(styleBorders) do if i == this._sfIdx then bd:Show() bd:SetBackdropBorderColor(1, 0.78, 0.2, 1) else bd:Hide() end end RefreshAB() end) -- 悬停高亮 preview:SetScript("OnEnter", function() local bd = styleBorders[this._sfIdx] if bd and not bd:IsShown() then bd:Show() bd:SetBackdropBorderColor(SOFT_THEME.panelBorder[1], SOFT_THEME.panelBorder[2], SOFT_THEME.panelBorder[3], 0.8) end end) preview:SetScript("OnLeave", function() local bd = styleBorders[this._sfIdx] local current = SFramesDB.ActionBars.gryphonStyle or "dragonflight" if bd and this._sfStyleKey ~= current then bd:Hide() end end) end -- 初始化选中边框 local currentStyle = SFramesDB.ActionBars.gryphonStyle or "dragonflight" for idx, style in ipairs(GRYPHON_STYLES_UI) do if style.key == currentStyle then styleBorders[idx]:Show() styleBorders[idx]:SetBackdropBorderColor(1, 0.78, 0.2, 1) end end CreateLabel(abSection, "底部动作条位置:", 14, -720, font, 11, 0.85, 0.75, 0.80) table.insert(controls, CreateSlider(abSection, "底部 X 偏移", 14, -756, 220, -500, 500, 1, function() return SFramesDB.ActionBars.bottomOffsetX or 0 end, function(value) SFramesDB.ActionBars.bottomOffsetX = value end, function(v) return tostring(math.floor(v + 0.5)) end, function() RefreshAB() end )) table.insert(controls, CreateSlider(abSection, "底部 Y 偏移", 270, -756, 220, 0, 500, 1, function() return SFramesDB.ActionBars.bottomOffsetY or 2 end, function(value) SFramesDB.ActionBars.bottomOffsetY = value end, function(v) return tostring(math.floor(v + 0.5)) end, function() RefreshAB() end )) CreateLabel(abSection, "右侧动作条位置:", 14, -812, font, 11, 0.85, 0.75, 0.80) table.insert(controls, CreateSlider(abSection, "右侧 X 偏移", 14, -848, 220, -500, 0, 1, function() return SFramesDB.ActionBars.rightOffsetX or -4 end, function(value) SFramesDB.ActionBars.rightOffsetX = value end, function(v) return tostring(math.floor(v + 0.5)) end, function() RefreshAB() end )) table.insert(controls, CreateSlider(abSection, "右侧 Y 偏移", 270, -848, 220, -500, 500, 1, function() return SFramesDB.ActionBars.rightOffsetY or -80 end, function(value) SFramesDB.ActionBars.rightOffsetY = value end, function(v) return tostring(math.floor(v + 0.5)) end, function() RefreshAB() end )) CreateLabel(abSection, "提示:启用/禁用动作条接管需要 /reload 才能生效。", 14, -904, font, 10, 1, 0.92, 0.38) uiScroll:UpdateRange() self.actionBarControls = controls self.actionBarScroll = uiScroll end function SFrames.ConfigUI:BuildMinimapPage() local font = SFrames:GetFont() local page = self.minimapPage local controls = {} local function RefreshMinimap() if SFrames.Minimap and SFrames.Minimap.Refresh then SFrames.Minimap:Refresh() end end local uiScroll = CreateScrollArea(page, 4, -4, 548, 458, 782) local root = uiScroll.child local mmSection = CreateSection(root, "小地图", 8, -8, 520, 540, font) table.insert(controls, CreateCheckBox(mmSection, "启用 Nanami 小地图皮肤", 14, -36, function() return SFramesDB.Minimap.enabled ~= false end, function(checked) SFramesDB.Minimap.enabled = checked end )) CreateDesc(mmSection, "关闭后恢复默认小地图,需要 /reload 生效", 36, -52, font) table.insert(controls, CreateSlider(mmSection, "缩放", 14, -80, 220, 0.6, 2.0, 0.05, function() return SFramesDB.Minimap.scale or 1.0 end, function(value) SFramesDB.Minimap.scale = value end, function(v) return string.format("%.0f%%", v * 100) end, function() RefreshMinimap() end )) table.insert(controls, CreateCheckBox(mmSection, "显示游戏时钟", 14, -134, function() return SFramesDB.Minimap.showClock ~= false end, function(checked) SFramesDB.Minimap.showClock = checked end )) CreateDesc(mmSection, "在小地图下方显示服务器时间", 36, -150, font) table.insert(controls, CreateCheckBox(mmSection, "显示玩家坐标", 270, -134, function() return SFramesDB.Minimap.showCoords ~= false end, function(checked) SFramesDB.Minimap.showCoords = checked end )) CreateDesc(mmSection, "在小地图圆内底部显示当前坐标", 292, -150, font) local function ApplyPos() if SFrames.Minimap and SFrames.Minimap.ApplyPosition then SFrames.Minimap.ApplyPosition() end end table.insert(controls, CreateSlider(mmSection, "水平位置 (X)", 14, -182, 220, -800, 0, 1, function() return SFramesDB.Minimap.posX or -5 end, function(value) SFramesDB.Minimap.posX = value end, function(v) return string.format("%.0f", v) end, function() ApplyPos() end )) table.insert(controls, CreateSlider(mmSection, "垂直位置 (Y)", 270, -182, 220, -800, 0, 1, function() return SFramesDB.Minimap.posY or -5 end, function(value) SFramesDB.Minimap.posY = value end, function(v) return string.format("%.0f", v) end, function() ApplyPos() end )) CreateButton(mmSection, "重置位置", 14, -226, 130, 22, function() SFramesDB.Minimap.posX = -5 SFramesDB.Minimap.posY = -5 ApplyPos() end) -- Map frame style selector with texture previews (5 columns x 2 rows) CreateLabel(mmSection, "边框样式:", 14, -256, font, 11, 0.85, 0.75, 0.80) local styles = SFrames.Minimap and SFrames.Minimap.MAP_STYLES or {} -- "Auto (match class)" button local autoBtn = CreateFrame("Button", nil, mmSection) autoBtn:SetWidth(100) autoBtn:SetHeight(18) autoBtn:SetPoint("TOPLEFT", mmSection, "TOPLEFT", 130, -256) autoBtn: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 }, }) local autoLabel = autoBtn:CreateFontString(nil, "OVERLAY") autoLabel:SetFont(font, 10, "OUTLINE") autoLabel:SetPoint("CENTER", autoBtn, "CENTER", 0, 0) autoLabel:SetText("自动(匹配职业)") local function UpdateAutoBtn(isAuto) if isAuto then autoBtn:SetBackdropColor(0.2, 0.4, 0.2, 0.9) autoBtn:SetBackdropBorderColor(0.4, 0.8, 0.3, 1) autoLabel:SetTextColor(0.5, 1, 0.5) else autoBtn:SetBackdropColor(0.15, 0.15, 0.15, 0.7) autoBtn:SetBackdropBorderColor(0.4, 0.4, 0.4, 0.6) autoLabel:SetTextColor(0.6, 0.6, 0.6) end end local styleBorders = {} local cols = 5 local cellW = 56 local cellH = 56 local gapX = 10 local gapY = 18 local styleStartX = 14 local styleY = -280 local function HighlightSelection() local current = SFramesDB.Minimap.mapStyle or "auto" local isAuto = (current == "auto") UpdateAutoBtn(isAuto) for i, bd in ipairs(styleBorders) do if (not isAuto) and styles[i] and styles[i].key == current then bd:Show() bd:SetBackdropBorderColor(1, 0.78, 0.2, 1) else bd:Hide() end end end for idx, style in ipairs(styles) do local col = math.mod(idx - 1, cols) local row = math.floor((idx - 1) / cols) local xOff = styleStartX + col * (cellW + gapX) local yOff = styleY - row * (cellH + gapY) local preview = CreateFrame("Frame", nil, mmSection) preview:SetWidth(cellW) preview:SetHeight(cellH) preview:SetPoint("TOPLEFT", mmSection, "TOPLEFT", xOff, yOff) local bg = preview:CreateTexture(nil, "BACKGROUND") bg:SetAllPoints() bg:SetTexture(0, 0, 0, 0.4) local ptex = preview:CreateTexture(nil, "ARTWORK") ptex:SetAllPoints() ptex:SetTexture(style.tex) local border = CreateFrame("Frame", nil, preview) border:SetPoint("TOPLEFT", preview, "TOPLEFT", -2, 2) border:SetPoint("BOTTOMRIGHT", preview, "BOTTOMRIGHT", 2, -2) border:SetBackdrop({ edgeFile = "Interface\\Buttons\\WHITE8X8", edgeSize = 2 }) border:Hide() styleBorders[idx] = border local label = preview:CreateFontString(nil, "OVERLAY") label:SetFont(font, 9, "OUTLINE") label:SetPoint("TOP", preview, "BOTTOM", 0, -2) label:SetText(style.label) label:SetTextColor(0.7, 0.65, 0.7) preview:EnableMouse(true) preview._sfStyleKey = style.key preview._sfIdx = idx preview:SetScript("OnMouseUp", function() SFramesDB.Minimap.mapStyle = this._sfStyleKey HighlightSelection() RefreshMinimap() end) preview:SetScript("OnEnter", function() local bd = styleBorders[this._sfIdx] if bd and not bd:IsShown() then bd:Show() bd:SetBackdropBorderColor(SOFT_THEME.panelBorder[1], SOFT_THEME.panelBorder[2], SOFT_THEME.panelBorder[3], 0.8) end end) preview:SetScript("OnLeave", function() local bd = styleBorders[this._sfIdx] local current = SFramesDB.Minimap.mapStyle or "auto" if bd and this._sfStyleKey ~= current then bd:Hide() end end) end autoBtn:SetScript("OnClick", function() SFramesDB.Minimap.mapStyle = "auto" HighlightSelection() RefreshMinimap() end) HighlightSelection() local totalRows = math.ceil(table.getn(styles) / cols) local tipY = styleY - totalRows * (cellH + gapY) - 10 CreateLabel(mmSection, "提示: 缩放、位置和样式修改后实时生效。", 14, tipY, font, 10, 0.6, 0.6, 0.65) -- ── 世界地图 ────────────────────────────────────────────────── local wmSection = CreateSection(root, "世界地图", 8, -558, 520, 130, font) table.insert(controls, CreateCheckBox(wmSection, "启用全新世界地图界面", 14, -34, function() return SFramesDB.WorldMap.enabled == true end, function(checked) SFramesDB.WorldMap.enabled = checked if checked then SFramesDB.Tweaks.worldMapWindow = false end end )) CreateDesc(wmSection, "Nanami主题窗口化地图,隐藏原生装饰(需重载UI)", 36, -50, font) table.insert(controls, CreateCheckBox(wmSection, "启用导航地图", 14, -68, function() return SFramesDB.WorldMap.nav and SFramesDB.WorldMap.nav.enabled == true end, function(checked) if type(SFramesDB.WorldMap.nav) ~= "table" then SFramesDB.WorldMap.nav = { enabled = false, width = 350, alpha = 0.70, locked = false } end SFramesDB.WorldMap.nav.enabled = checked if SFrames.WorldMap and SFrames.WorldMap.ToggleNav then if (checked and not (NanamiNavMap and NanamiNavMap:IsVisible())) or (not checked and NanamiNavMap and NanamiNavMap:IsVisible()) then SFrames.WorldMap:ToggleNav() end end end )) CreateDesc(wmSection, "以玩家为中心的实时导航地图,半透明覆盖,可拖动", 36, -84, font) CreateDesc(wmSection, "命令: /nui nav | 滚轮缩放 | Ctrl+滚轮透明度", 36, -96, font) -- ── 地图迷雾揭示 ─────────────────────────────────────────── local mapRevealSection = CreateSection(root, "世界地图迷雾揭示", 8, -698, 520, 112, font) table.insert(controls, CreateCheckBox(mapRevealSection, "启用地图迷雾揭示", 14, -34, function() return SFramesDB.MapReveal.enabled ~= false end, function(checked) SFramesDB.MapReveal.enabled = checked if SFrames.MapReveal and SFrames.MapReveal.Refresh then SFrames.MapReveal:Refresh() end end )) CreateDesc(mapRevealSection, "在世界地图上显示未探索区域(变暗显示)", 36, -50, font) CreateDesc(mapRevealSection, "需要 LibMapOverlayData 库支持", 36, -62, font) table.insert(controls, CreateSlider(mapRevealSection, "未探索区域亮度", 270, -64, 200, 0.2, 1.0, 0.05, function() return SFramesDB.MapReveal.unexploredAlpha or 0.7 end, function(value) SFramesDB.MapReveal.unexploredAlpha = value end, function(v) return string.format("%.0f%%", v * 100) end, function(value) if SFrames.MapReveal and SFrames.MapReveal.SetAlpha then SFrames.MapReveal:SetAlpha(value) end end )) uiScroll:UpdateRange() self.minimapControls = controls self.minimapScroll = uiScroll end -------------------------------------------------------------------------------- -- Buff / Debuff Page (standalone tab) -------------------------------------------------------------------------------- function SFrames.ConfigUI:BuildBuffPage() local font = SFrames:GetFont() local page = self.buffPage local controls = {} local function RefreshBuffs() if SFrames.MinimapBuffs and SFrames.MinimapBuffs.Refresh then SFrames.MinimapBuffs:Refresh() end end local uiScroll = CreateScrollArea(page, 4, -4, 548, 458, 520) local root = uiScroll.child local buffSection = CreateSection(root, "Buff / Debuff 栏", 8, -8, 520, 490, font) -- Row 1: enable + show timer table.insert(controls, CreateCheckBox(buffSection, "启用 Buff / Debuff 栏", 14, -36, function() return SFramesDB.MinimapBuffs.enabled ~= false end, function(checked) SFramesDB.MinimapBuffs.enabled = checked end )) CreateDesc(buffSection, "替代暴雪默认 Buff 栏显示(需 /reload 生效)", 36, -52, font) table.insert(controls, CreateCheckBox(buffSection, "显示时间文本", 270, -36, function() return SFramesDB.MinimapBuffs.showTimer ~= false end, function(checked) SFramesDB.MinimapBuffs.showTimer = checked RefreshBuffs() end )) CreateDesc(buffSection, "图标下方显示剩余时间,永久 Buff 显示 N/A", 292, -52, font) -- Row 2: show debuffs table.insert(controls, CreateCheckBox(buffSection, "显示 Debuff", 14, -72, function() return SFramesDB.MinimapBuffs.showDebuffs ~= false end, function(checked) SFramesDB.MinimapBuffs.showDebuffs = checked RefreshBuffs() end )) CreateDesc(buffSection, "在 Buff 栏下方显示 Debuff(边框按类型着色)", 36, -88, font) -- Row 3: sliders - icon sizes table.insert(controls, CreateSlider(buffSection, "Buff 图标大小", 14, -114, 220, 20, 50, 1, function() return SFramesDB.MinimapBuffs.iconSize or 30 end, function(value) SFramesDB.MinimapBuffs.iconSize = value end, function(v) return tostring(math.floor(v + 0.5)) end, function() RefreshBuffs() end )) table.insert(controls, CreateSlider(buffSection, "Debuff 图标大小", 270, -114, 220, 20, 50, 1, function() return SFramesDB.MinimapBuffs.debuffIconSize or 30 end, function(value) SFramesDB.MinimapBuffs.debuffIconSize = value end, function(v) return tostring(math.floor(v + 0.5)) end, function() RefreshBuffs() end )) -- Row 4: per-row + spacing table.insert(controls, CreateSlider(buffSection, "每行图标数", 14, -176, 220, 4, 16, 1, function() return SFramesDB.MinimapBuffs.iconsPerRow or 8 end, function(value) SFramesDB.MinimapBuffs.iconsPerRow = value end, function(v) return tostring(math.floor(v + 0.5)) end, function() RefreshBuffs() end )) table.insert(controls, CreateSlider(buffSection, "间距", 270, -176, 220, 0, 8, 1, function() return SFramesDB.MinimapBuffs.spacing or 2 end, function(value) SFramesDB.MinimapBuffs.spacing = value end, function(v) return tostring(math.floor(v + 0.5)) end, function() RefreshBuffs() end )) -- Row 5: grow direction + anchor CreateLabel(buffSection, "增长方向:", 14, -240, font, 11, 0.85, 0.75, 0.80) CreateButton(buffSection, "向左", 100, -240, 70, 22, function() SFramesDB.MinimapBuffs.growDirection = "LEFT" RefreshBuffs() end) CreateButton(buffSection, "向右", 176, -240, 70, 22, function() SFramesDB.MinimapBuffs.growDirection = "RIGHT" RefreshBuffs() end) CreateLabel(buffSection, "锚点位置:", 270, -240, font, 11, 0.85, 0.75, 0.80) local posLabels = { TOPRIGHT = "右上", TOPLEFT = "左上", BOTTOMRIGHT = "右下", BOTTOMLEFT = "左下" } local posKeys = { "TOPRIGHT", "TOPLEFT", "BOTTOMRIGHT", "BOTTOMLEFT" } local posStartX = 356 for _, key in ipairs(posKeys) do CreateButton(buffSection, posLabels[key], posStartX, -240, 40, 22, function() SFramesDB.MinimapBuffs.position = key RefreshBuffs() end) posStartX = posStartX + 42 end -- Row 6: X/Y offset sliders table.insert(controls, CreateSlider(buffSection, "水平偏移 (X)", 14, -286, 220, -800, 800, 1, function() return SFramesDB.MinimapBuffs.offsetX or 0 end, function(value) SFramesDB.MinimapBuffs.offsetX = value end, function(v) return string.format("%.0f", v) end, function() RefreshBuffs() end )) table.insert(controls, CreateSlider(buffSection, "垂直偏移 (Y)", 270, -286, 220, -800, 800, 1, function() return SFramesDB.MinimapBuffs.offsetY or 0 end, function(value) SFramesDB.MinimapBuffs.offsetY = value end, function(v) return string.format("%.0f", v) end, function() RefreshBuffs() end )) -- Row 7: action buttons CreateButton(buffSection, "重置默认位置", 14, -340, 120, 24, function() SFramesDB.MinimapBuffs.offsetX = 0 SFramesDB.MinimapBuffs.offsetY = 0 SFramesDB.MinimapBuffs.position = "TOPRIGHT" SFramesDB.MinimapBuffs.growDirection = "LEFT" RefreshBuffs() end) local simBtn simBtn = CreateButton(buffSection, "模拟预览", 142, -340, 100, 24, function() if not SFrames.MinimapBuffs then return end if SFrames.MinimapBuffs._simulating then SFrames.MinimapBuffs:StopSimulation() simBtn:SetText("模拟预览") else SFrames.MinimapBuffs:SimulateBuffs() simBtn:SetText("停止模拟") end end) -- Row 8: tips CreateLabel(buffSection, "模拟预览:显示假 Buff / Debuff 以预览布局效果,不影响实际状态。", 14, -374, font, 9, 0.65, 0.58, 0.62) CreateLabel(buffSection, "Debuff 边框颜色: |cff3399ff魔法|r |cff9900ff诅咒|r |cff996600疾病|r |cff009900毒药|r |cffcc0000物理|r", 14, -390, font, 9, 0.65, 0.58, 0.62) CreateLabel(buffSection, "提示:启用/禁用 Buff 栏需要 /reload 才能生效。其他调整实时生效。", 14, -414, font, 10, 0.6, 0.6, 0.65) uiScroll:UpdateRange() self.buffControls = controls self.buffScroll = uiScroll end function SFrames.ConfigUI:ShowPage(mode) self.activePage = mode self.uiPage:Hide() self.playerPage:Hide() self.targetPage:Hide() self.bagPage:Hide() self.raidPage:Hide() self.partyPage:Hide() self.charPage:Hide() self.actionBarPage:Hide() self.minimapPage:Hide() self.buffPage:Hide() self.personalizePage:Hide() self.themePage:Hide() local allTabs = { self.uiTab, self.playerTab, self.targetTab, self.partyTab, self.raidTab, self.bagTab, self.charTab, self.actionBarTab, self.minimapTab, self.buffTab, self.personalizeTab, self.themeTab } for _, tab in ipairs(allTabs) do tab.sfSoftActive = false tab:Enable() tab:RefreshVisual() end self:EnsurePage(mode or "ui") if mode == "player" then self.playerPage:Show() self.playerTab.sfSoftActive = true self.playerTab:Disable() self.playerTab:RefreshVisual() self.title:SetText("Nanami-UI 设置 - 玩家框体") self:RefreshControls(self.playerControls) elseif mode == "target" then self.targetPage:Show() self.targetTab.sfSoftActive = true self.targetTab:Disable() self.targetTab:RefreshVisual() self.title:SetText("Nanami-UI 设置 - 目标框体") self:RefreshControls(self.targetControls) elseif mode == "bags" then self.bagPage:Show() self.bagTab.sfSoftActive = true self.bagTab:Disable() self.bagTab:RefreshVisual() self.title:SetText("Nanami-UI 设置 - 背包") self:RefreshControls(self.bagControls) if self.bagScroll and self.bagScroll.Reset then self.bagScroll:Reset() end elseif mode == "raid" then self.raidPage:Show() self.raidTab.sfSoftActive = true self.raidTab:Disable() self.raidTab:RefreshVisual() self.title:SetText("Nanami-UI 设置 - 团队") self:RefreshControls(self.raidControls) if self.raidScroll and self.raidScroll.Reset then self.raidScroll:Reset() end elseif mode == "party" then self.partyPage:Show() self.partyTab.sfSoftActive = true self.partyTab:Disable() self.partyTab:RefreshVisual() self.title:SetText("Nanami-UI 设置 - 小队") self:RefreshControls(self.partyControls) if self.partyScroll and self.partyScroll.Reset then self.partyScroll:Reset() end elseif mode == "char" then self.charPage:Show() self.charTab.sfSoftActive = true self.charTab:Disable() self.charTab:RefreshVisual() self.title:SetText("Nanami-UI 设置 - 人物面板") self:RefreshControls(self.charControls) if self.charScroll and self.charScroll.Reset then self.charScroll:Reset() end elseif mode == "actionbar" then self.actionBarPage:Show() self.actionBarTab.sfSoftActive = true self.actionBarTab:Disable() self.actionBarTab:RefreshVisual() self.title:SetText("Nanami-UI 设置 - 动作条") self:RefreshControls(self.actionBarControls) if self.actionBarScroll and self.actionBarScroll.Reset then self.actionBarScroll:Reset() end elseif mode == "minimap" then self.minimapPage:Show() self.minimapTab.sfSoftActive = true self.minimapTab:Disable() self.minimapTab:RefreshVisual() self.title:SetText("Nanami-UI 设置 - 地图设置") self:RefreshControls(self.minimapControls) if self.minimapScroll and self.minimapScroll.Reset then self.minimapScroll:Reset() end elseif mode == "buff" then self.buffPage:Show() self.buffTab.sfSoftActive = true self.buffTab:Disable() self.buffTab:RefreshVisual() self.title:SetText("Nanami-UI 设置 - Buff / Debuff") self:RefreshControls(self.buffControls) if self.buffScroll and self.buffScroll.Reset then self.buffScroll:Reset() end elseif mode == "personalize" then self.personalizePage:Show() self.personalizeTab.sfSoftActive = true self.personalizeTab:Disable() self.personalizeTab:RefreshVisual() self.title:SetText("Nanami-UI 设置 - 个性化") self:RefreshControls(self.personalizeControls) if self.personalizeScroll and self.personalizeScroll.Reset then self.personalizeScroll:Reset() end elseif mode == "theme" then self.themePage:Show() self.themeTab.sfSoftActive = true self.themeTab:Disable() self.themeTab:RefreshVisual() self.title:SetText("Nanami-UI 设置 - 主题") self:RefreshControls(self.themeControls) if self.themeScroll and self.themeScroll.Reset then self.themeScroll:Reset() end else self.uiPage:Show() self.uiTab.sfSoftActive = true self.uiTab:Disable() self.uiTab:RefreshVisual() self.title:SetText("Nanami-UI 设置 - 界面") self:RefreshControls(self.uiControls) if self.uiScroll and self.uiScroll.Reset then self.uiScroll:Reset() end end end function SFrames.ConfigUI:EnsureFrame() if self.frame then return end local font = SFrames:GetFont() local panel = CreateFrame("Frame", "SFramesConfigPanel", UIParent) panel:SetWidth(PANEL_WIDTH) panel:SetHeight(PANEL_HEIGHT) panel:SetPoint("CENTER", UIParent, "CENTER", 0, 0) panel:SetMovable(true) panel:EnableMouse(true) panel:SetClampedToScreen(true) panel:SetToplevel(true) panel:SetFrameStrata("TOOLTIP") panel:SetFrameLevel(100) panel:RegisterForDrag("LeftButton") panel:SetScript("OnDragStart", function() this:StartMoving() end) panel:SetScript("OnDragStop", function() this:StopMovingOrSizing() end) tinsert(UISpecialFrames, "SFramesConfigPanel") -- Apply the rounded "Roll UI" textured backdrop to the main settings panel if SFrames and SFrames.CreateRoundBackdrop then SFrames:CreateRoundBackdrop(panel) panel.sfSoftBackdrop = true else EnsureSoftBackdrop(panel) end if panel.SetBackdropColor then panel:SetBackdropColor(SOFT_THEME.panelBg[1], SOFT_THEME.panelBg[2], SOFT_THEME.panelBg[3], SOFT_THEME.panelBg[4]) end if panel.SetBackdropBorderColor then panel:SetBackdropBorderColor(SOFT_THEME.panelBorder[1], SOFT_THEME.panelBorder[2], SOFT_THEME.panelBorder[3], SOFT_THEME.panelBorder[4]) end local titleIco = SFrames:CreateIcon(panel, "logo", 18) titleIco:SetDrawLayer("OVERLAY") titleIco:SetPoint("TOP", panel, "TOP", -52, -14) titleIco:SetVertexColor(SOFT_THEME.title[1], SOFT_THEME.title[2], SOFT_THEME.title[3]) local title = panel:CreateFontString(nil, "OVERLAY") title:SetFont(font, 14, "OUTLINE") title:SetPoint("LEFT", titleIco, "RIGHT", 5, 0) title:SetTextColor(SOFT_THEME.title[1], SOFT_THEME.title[2], SOFT_THEME.title[3]) title:SetText("Nanami-UI 设置") local divider = panel:CreateTexture(nil, "ARTWORK") divider:SetWidth(1) divider:SetPoint("TOPLEFT", panel, "TOPLEFT", 120, -40) divider:SetPoint("BOTTOMLEFT", panel, "BOTTOMLEFT", 120, 10) divider:SetTexture(0.44, 0.42, 0.5, 0.9) local closeBtn = CreateFrame("Button", "SFramesConfigCloseBtn", panel) closeBtn:SetWidth(20) closeBtn:SetHeight(20) closeBtn:SetPoint("TOPRIGHT", panel, "TOPRIGHT", -8, -8) closeBtn: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 _cA = SOFT_THEME closeBtn:SetBackdropColor(_cA.buttonDownBg[1], _cA.buttonDownBg[2], _cA.buttonDownBg[3], _cA.buttonDownBg[4] or 0.85) closeBtn:SetBackdropBorderColor(_cA.btnBorder[1], _cA.btnBorder[2], _cA.btnBorder[3], _cA.btnBorder[4] or 0.7) local closeIco = SFrames:CreateIcon(closeBtn, "close", 14) closeIco:SetDrawLayer("OVERLAY") closeIco:SetPoint("CENTER", closeBtn, "CENTER", 0, 0) closeIco:SetVertexColor(1, 0.7, 0.7) closeBtn:SetScript("OnClick", function() SFrames.ConfigUI.frame:Hide() end) closeBtn:SetScript("OnEnter", function() this:SetBackdropColor(_cA.btnHoverBg[1], _cA.btnHoverBg[2], _cA.btnHoverBg[3], _cA.btnHoverBg[4] or 0.95) this:SetBackdropBorderColor(_cA.btnHoverBd[1], _cA.btnHoverBd[2], _cA.btnHoverBd[3], _cA.btnHoverBd[4] or 0.9) end) closeBtn:SetScript("OnLeave", function() this:SetBackdropColor(_cA.buttonDownBg[1], _cA.buttonDownBg[2], _cA.buttonDownBg[3], _cA.buttonDownBg[4] or 0.85) this:SetBackdropBorderColor(_cA.btnBorder[1], _cA.btnBorder[2], _cA.btnBorder[3], _cA.btnBorder[4] or 0.7) end) local tabUI = CreateFrame("Button", "SFramesConfigTabUI", panel, "UIPanelButtonTemplate") tabUI:SetWidth(100) tabUI:SetHeight(28) tabUI:SetPoint("TOPLEFT", panel, "TOPLEFT", 10, -50) tabUI:SetText("界面设置") tabUI:SetScript("OnClick", function() SFrames.ConfigUI:ShowPage("ui") end) StyleButton(tabUI) AddBtnIcon(tabUI, "settings", nil, "left") local tabPlayer = CreateFrame("Button", "SFramesConfigTabPlayer", panel, "UIPanelButtonTemplate") tabPlayer:SetWidth(100) tabPlayer:SetHeight(28) tabPlayer:SetPoint("TOP", tabUI, "BOTTOM", 0, -4) tabPlayer:SetText("玩家框体") tabPlayer:SetScript("OnClick", function() SFrames.ConfigUI:ShowPage("player") end) StyleButton(tabPlayer) AddBtnIcon(tabPlayer, "character", nil, "left") local tabTarget = CreateFrame("Button", "SFramesConfigTabTarget", panel, "UIPanelButtonTemplate") tabTarget:SetWidth(100) tabTarget:SetHeight(28) tabTarget:SetPoint("TOP", tabPlayer, "BOTTOM", 0, -4) tabTarget:SetText("目标框体") tabTarget:SetScript("OnClick", function() SFrames.ConfigUI:ShowPage("target") end) StyleButton(tabTarget) AddBtnIcon(tabTarget, "search", nil, "left") local tabParty = CreateFrame("Button", "SFramesConfigTabParty", panel, "UIPanelButtonTemplate") tabParty:SetWidth(100) tabParty:SetHeight(28) tabParty:SetPoint("TOP", tabTarget, "BOTTOM", 0, -4) tabParty:SetText("小队框架") tabParty:SetScript("OnClick", function() SFrames.ConfigUI:ShowPage("party") end) StyleButton(tabParty) AddBtnIcon(tabParty, "friends", nil, "left") local tabRaid = CreateFrame("Button", "SFramesConfigTabRaid", panel, "UIPanelButtonTemplate") tabRaid:SetWidth(100) tabRaid:SetHeight(28) tabRaid:SetPoint("TOP", tabParty, "BOTTOM", 0, -4) tabRaid:SetText("团队框架") tabRaid:SetScript("OnClick", function() SFrames.ConfigUI:ShowPage("raid") end) StyleButton(tabRaid) AddBtnIcon(tabRaid, "party", nil, "left") local tabBags = CreateFrame("Button", "SFramesConfigTabBags", panel, "UIPanelButtonTemplate") tabBags:SetWidth(100) tabBags:SetHeight(28) tabBags:SetPoint("TOP", tabRaid, "BOTTOM", 0, -4) tabBags:SetText("背包设置") tabBags:SetScript("OnClick", function() SFrames.ConfigUI:ShowPage("bags") end) StyleButton(tabBags) AddBtnIcon(tabBags, "backpack", nil, "left") local tabChar = CreateFrame("Button", "SFramesConfigTabChar", panel, "UIPanelButtonTemplate") tabChar:SetWidth(100) tabChar:SetHeight(28) tabChar:SetPoint("TOP", tabBags, "BOTTOM", 0, -4) tabChar:SetText("人物面板") tabChar:SetScript("OnClick", function() SFrames.ConfigUI:ShowPage("char") end) StyleButton(tabChar) AddBtnIcon(tabChar, "charsheet", nil, "left") local tabActionBar = CreateFrame("Button", "SFramesConfigTabActionBar", panel, "UIPanelButtonTemplate") tabActionBar:SetWidth(100) tabActionBar:SetHeight(28) tabActionBar:SetPoint("TOP", tabChar, "BOTTOM", 0, -4) tabActionBar:SetText("动作条") tabActionBar:SetScript("OnClick", function() SFrames.ConfigUI:ShowPage("actionbar") end) StyleButton(tabActionBar) AddBtnIcon(tabActionBar, "attack", nil, "left") local tabMinimap = CreateFrame("Button", "SFramesConfigTabMinimap", panel, "UIPanelButtonTemplate") tabMinimap:SetWidth(100) tabMinimap:SetHeight(28) tabMinimap:SetPoint("TOP", tabActionBar, "BOTTOM", 0, -4) tabMinimap:SetText("地图设置") tabMinimap:SetScript("OnClick", function() SFrames.ConfigUI:ShowPage("minimap") end) StyleButton(tabMinimap) AddBtnIcon(tabMinimap, "worldmap", nil, "left") local tabBuff = CreateFrame("Button", "SFramesConfigTabBuff", panel, "UIPanelButtonTemplate") tabBuff:SetWidth(100) tabBuff:SetHeight(28) tabBuff:SetPoint("TOP", tabMinimap, "BOTTOM", 0, -4) tabBuff:SetText("Buff栏") tabBuff:SetScript("OnClick", function() SFrames.ConfigUI:ShowPage("buff") end) StyleButton(tabBuff) AddBtnIcon(tabBuff, "buff", nil, "left") local tabPersonalize = CreateFrame("Button", "SFramesConfigTabPersonalize", panel, "UIPanelButtonTemplate") tabPersonalize:SetWidth(100) tabPersonalize:SetHeight(28) tabPersonalize:SetPoint("TOP", tabBuff, "BOTTOM", 0, -4) tabPersonalize:SetText("个性化") tabPersonalize:SetScript("OnClick", function() SFrames.ConfigUI:ShowPage("personalize") end) StyleButton(tabPersonalize) AddBtnIcon(tabPersonalize, "potion", nil, "left") local tabTheme = CreateFrame("Button", "SFramesConfigTabTheme", panel, "UIPanelButtonTemplate") tabTheme:SetWidth(100) tabTheme:SetHeight(28) tabTheme:SetPoint("TOP", tabPersonalize, "BOTTOM", 0, -4) tabTheme:SetText("主题设置") tabTheme:SetScript("OnClick", function() SFrames.ConfigUI:ShowPage("theme") end) StyleButton(tabTheme) AddBtnIcon(tabTheme, "star", nil, "left") local content = CreateFrame("Frame", "SFramesConfigContentMain", panel) content:SetWidth(PANEL_WIDTH - 140) content:SetHeight(PANEL_HEIGHT - 60) content:SetPoint("TOPLEFT", panel, "TOPLEFT", 130, -50) local uiPage = CreateFrame("Frame", "SFramesConfigUIPage", content) uiPage:SetAllPoints(content) local playerPage = CreateFrame("Frame", "SFramesConfigPlayerPage", content) playerPage:SetAllPoints(content) local targetPage = CreateFrame("Frame", "SFramesConfigTargetPage", content) targetPage:SetAllPoints(content) local bagPage = CreateFrame("Frame", "SFramesConfigBagPage", content) bagPage:SetAllPoints(content) local raidPage = CreateFrame("Frame", "SFramesConfigRaidPage", content) raidPage:SetAllPoints(content) local partyPage = CreateFrame("Frame", "SFramesConfigPartyPage", content) partyPage:SetAllPoints(content) local charPage = CreateFrame("Frame", "SFramesConfigCharPage", content) charPage:SetAllPoints(content) local actionBarPage = CreateFrame("Frame", "SFramesConfigActionBarPage", content) actionBarPage:SetAllPoints(content) local minimapPage = CreateFrame("Frame", "SFramesConfigMinimapPage", content) minimapPage:SetAllPoints(content) local buffPage = CreateFrame("Frame", "SFramesConfigBuffPage", content) buffPage:SetAllPoints(content) local personalizePage = CreateFrame("Frame", "SFramesConfigPersonalizePage", content) personalizePage:SetAllPoints(content) local themePage = CreateFrame("Frame", "SFramesConfigThemePage", content) themePage:SetAllPoints(content) self.frame = panel self.title = title self.uiTab = tabUI self.playerTab = tabPlayer self.targetTab = tabTarget self.bagTab = tabBags self.raidTab = tabRaid self.partyTab = tabParty self.charTab = tabChar self.actionBarTab = tabActionBar self.minimapTab = tabMinimap self.buffTab = tabBuff self.personalizeTab = tabPersonalize self.themeTab = tabTheme self.content = content self.uiPage = uiPage self.playerPage = playerPage self.targetPage = targetPage self.bagPage = bagPage self.raidPage = raidPage self.partyPage = partyPage self.charPage = charPage self.actionBarPage = actionBarPage self.minimapPage = minimapPage self.buffPage = buffPage self.personalizePage = personalizePage self.themePage = themePage local btnSaveReload = CreateFrame("Button", "SFramesConfigSaveReloadBtn", panel, "UIPanelButtonTemplate") btnSaveReload:SetWidth(100) btnSaveReload:SetHeight(26) btnSaveReload:SetPoint("BOTTOMRIGHT", panel, "BOTTOMRIGHT", -15, 12) btnSaveReload:SetText("保存并重载") btnSaveReload:SetScript("OnClick", function() ReloadUI() end) StyleButton(btnSaveReload) AddBtnIcon(btnSaveReload, "save") local btnConfirm = CreateFrame("Button", "SFramesConfigConfirmBtn", panel, "UIPanelButtonTemplate") btnConfirm:SetWidth(80) btnConfirm:SetHeight(26) btnConfirm:SetPoint("RIGHT", btnSaveReload, "LEFT", -6, 0) btnConfirm:SetText("确认") btnConfirm:SetScript("OnClick", function() SFrames.ConfigUI.frame:Hide() end) StyleButton(btnConfirm) AddBtnIcon(btnConfirm, "close") self._pageBuilt = {} end function SFrames.ConfigUI:BuildPersonalizePage() local font = SFrames:GetFont() local page = self.personalizePage local controls = {} local personalizeScroll = CreateScrollArea(page, 4, -4, 548, 458, 620) local root = personalizeScroll.child -- ── 升级训练师提醒 ────────────────────────────────────────── local trainerSection = CreateSection(root, "升级技能提醒", 8, -8, 520, 100, font) table.insert(controls, CreateCheckBox(trainerSection, "升级时提醒可学习的新技能", 14, -34, function() return SFramesDB.trainerReminder ~= false end, function(checked) SFramesDB.trainerReminder = checked end )) CreateDesc(trainerSection, "升级后如果有新的职业技能可学习,将在屏幕和聊天窗口显示技能列表提醒", 36, -50, font, 340) CreateButton(trainerSection, "模拟提醒", 370, -34, 120, 22, function() if SFrames.Player and SFrames.Player.ShowTrainerReminder then local testLevel = UnitLevel("player") if mod(testLevel, 2) ~= 0 then testLevel = testLevel + 1 end local _, classEn = UnitClass("player") local cachedSkills = SFramesDB and SFramesDB.trainerCache and SFramesDB.trainerCache[classEn] local staticSkills = SFrames.ClassSkillData and SFrames.ClassSkillData[classEn] local function hasData(lv) if cachedSkills then if cachedSkills[lv] then return true end if lv > 1 and cachedSkills[lv - 1] then return true end end if staticSkills and staticSkills[lv] then return true end return false end if not hasData(testLevel) then while testLevel <= 60 and not hasData(testLevel) do testLevel = testLevel + 2 end if testLevel > 60 then testLevel = 60 end end SFrames.Player:ShowTrainerReminder(testLevel) end end) CreateDesc(trainerSection, "点击按钮预览升级提醒效果", 392, -55, font, 100) -- ── 鼠标提示框 ──────────────────────────────────────────────── local tooltipSection = CreateSection(root, "鼠标提示框", 8, -118, 520, 120, font) CreateDesc(tooltipSection, "选择游戏提示框的显示位置方式(三选一)", 14, -28, font) local function RefreshTooltipMode() if SFrames.FloatingTooltip and SFrames.FloatingTooltip.ToggleAnchor then SFrames.FloatingTooltip:ToggleAnchor(SFramesDB.tooltipMode == "CUSTOM") end if SFrames.ConfigUI.tooltipCBs then for _, cb in ipairs(SFrames.ConfigUI.tooltipCBs) do cb:Refresh() end end end SFrames.ConfigUI.tooltipCBs = {} local cbDefault = CreateCheckBox(tooltipSection, "系统默认位置", 14, -46, function() return (SFramesDB.tooltipMode == "DEFAULT" or SFramesDB.tooltipMode == nil) end, function(checked) if checked then SFramesDB.tooltipMode = "DEFAULT" RefreshTooltipMode() end end ) table.insert(SFrames.ConfigUI.tooltipCBs, cbDefault) table.insert(controls, cbDefault) local cbCursor = CreateCheckBox(tooltipSection, "跟随鼠标(鼠标右下方)", 14, -72, function() return SFramesDB.tooltipMode == "CURSOR" end, function(checked) if checked then SFramesDB.tooltipMode = "CURSOR" RefreshTooltipMode() end end ) table.insert(SFrames.ConfigUI.tooltipCBs, cbCursor) table.insert(controls, cbCursor) local cbCustom = CreateCheckBox(tooltipSection, "自定义固定位置", 270, -46, function() return SFramesDB.tooltipMode == "CUSTOM" end, function(checked) if checked then SFramesDB.tooltipMode = "CUSTOM" RefreshTooltipMode() end end ) table.insert(SFrames.ConfigUI.tooltipCBs, cbCustom) table.insert(controls, cbCustom) CreateDesc(tooltipSection, "启用后屏幕上会出现绿色锚点,拖动到目标位置后锁定", 292, -62, font) -- ── AFK 待机动画 ────────────────────────────────────────────── local afkSection = CreateSection(root, "AFK 待机动画", 8, -248, 520, 146, font) table.insert(controls, CreateCheckBox(afkSection, "启用 AFK 待机画面", 14, -34, function() return SFramesDB.afkEnabled ~= false end, function(checked) SFramesDB.afkEnabled = checked end )) CreateDesc(afkSection, "进入暂离状态后显示全屏待机画面(角色跳舞 + 信息面板)", 36, -50, font) table.insert(controls, CreateCheckBox(afkSection, "在非休息区也启用", 14, -70, function() return SFramesDB.afkOutsideRest == true end, function(checked) SFramesDB.afkOutsideRest = checked end )) CreateDesc(afkSection, "默认仅在旅店/主城等休息区触发,勾选后在任何区域都会触发", 36, -86, font) table.insert(controls, CreateSlider(afkSection, "AFK 延迟(分钟)", 14, -116, 200, 0, 15, 1, function() return SFramesDB.afkDelay or 5 end, function(v) SFramesDB.afkDelay = v end, function(v) if v == 0 then return "立即" end return v .. " 分钟" end )) CreateDesc(afkSection, "进入暂离后等待多久才显示待机画面", 240, -116, font) CreateButton(afkSection, "预览待机画面", 370, -34, 120, 22, function() if SFrames.AFKScreen and SFrames.AFKScreen.Toggle then SFrames.AFKScreen._manualTrigger = true SFrames.AFKScreen:Show() end end) personalizeScroll:UpdateRange() self.personalizeControls = controls self.personalizeScroll = personalizeScroll end function SFrames.ConfigUI:BuildThemePage() local font = SFrames:GetFont() local page = self.themePage local controls = {} local themeScroll = CreateScrollArea(page, 4, -4, 548, 458, 680) local root = themeScroll.child --------------------------------------------------------------------------- -- Section 1: Theme color selection (regular + class presets) --------------------------------------------------------------------------- local sec1 = CreateSection(root, "主题色", 8, -8, 520, 340, font) CreateDesc(sec1, "选择主题色后界面全局配色将跟随变化,重载 UI 后完全生效。", 14, -34, font, 480) local SWATCH_SIZE = 32 local SWATCH_GAP = 8 local SWATCH_ROW = SWATCH_SIZE + SWATCH_GAP + 14 local presets = SFrames.Theme.Presets local order = SFrames.Theme.PresetOrder local swatches = {} local function CreateSwatchRow(parent, presetList, startY) for idx = 1, table.getn(presetList) do local key = presetList[idx] local preset = presets[key] if preset then local col = (idx - 1) - math.floor((idx - 1) / 5) * 5 local row = math.floor((idx - 1) / 5) local sx = 14 + col * (SWATCH_SIZE + SWATCH_GAP) local sy = startY + row * -SWATCH_ROW local swatch = CreateFrame("Button", NextWidgetName("Swatch"), parent) swatch:SetWidth(SWATCH_SIZE) swatch:SetHeight(SWATCH_SIZE) swatch:SetPoint("TOPLEFT", parent, "TOPLEFT", sx, sy) swatch:SetBackdrop({ bgFile = "Interface\\Buttons\\WHITE8X8", edgeFile = "Interface\\Buttons\\WHITE8X8", tile = false, edgeSize = 2, insets = { left = 2, right = 2, top = 2, bottom = 2 }, }) local r, g, b if preset.swatchRGB then r, g, b = preset.swatchRGB[1], preset.swatchRGB[2], preset.swatchRGB[3] else r, g, b = SFrames.Theme.HSVtoRGB(preset.hue, 0.50 * (preset.satMul or 1), 0.75) end swatch:SetBackdropColor(r, g, b, 1) swatch:SetBackdropBorderColor(0.25, 0.25, 0.25, 1) local nameFs = swatch:CreateFontString(nil, "OVERLAY") nameFs:SetFont(font, 8, "OUTLINE") nameFs:SetPoint("TOP", swatch, "BOTTOM", 0, -1) nameFs:SetText(preset.name) nameFs:SetTextColor(0.75, 0.75, 0.75) swatch.presetKey = key swatch.selectFrame = CreateFrame("Frame", nil, swatch) swatch.selectFrame:SetPoint("TOPLEFT", swatch, "TOPLEFT", -4, 4) swatch.selectFrame:SetPoint("BOTTOMRIGHT", swatch, "BOTTOMRIGHT", 4, -4) swatch.selectFrame:SetBackdrop({ edgeFile = "Interface\\Buttons\\WHITE8X8", edgeSize = 2, }) swatch.selectFrame:SetBackdropBorderColor(1, 0.85, 0, 1) swatch.selectFrame:Hide() swatch.selectGlow = swatch.selectFrame:CreateTexture(nil, "BACKGROUND") swatch.selectGlow:SetTexture("Interface\\Buttons\\WHITE8X8") swatch.selectGlow:SetAllPoints(swatch.selectFrame) swatch.selectGlow:SetVertexColor(1, 0.85, 0, 0.15) swatch:SetScript("OnClick", function() EnsureDB() SFramesDB.Theme.preset = this.presetKey SFramesDB.Theme.useClassTheme = false SFrames.Theme:Apply(this.presetKey) SFrames.ConfigUI:RefreshThemeSwatches() SFrames.ConfigUI:RefreshThemePreview() PlaySound("igMainMenuOptionCheckBoxOn") end) swatch:SetScript("OnEnter", function() this:SetBackdropBorderColor(0.8, 0.8, 0.8, 1) end) swatch:SetScript("OnLeave", function() local active = SFramesDB and SFramesDB.Theme and SFramesDB.Theme.preset local useClass = SFramesDB and SFramesDB.Theme and SFramesDB.Theme.useClassTheme if useClass then active = SFrames.Theme:GetCurrentPreset() end if this.presetKey == active then this:SetBackdropBorderColor(1, 0.85, 0, 1) else this:SetBackdropBorderColor(0.25, 0.25, 0.25, 1) end end) table.insert(swatches, swatch) end end end CreateSwatchRow(sec1, order, -52) local classLabel = sec1:CreateFontString(nil, "OVERLAY") classLabel:SetFont(font, 10, "OUTLINE") classLabel:SetPoint("TOPLEFT", sec1, "TOPLEFT", 14, -164) classLabel:SetText("职业色") classLabel:SetTextColor(0.85, 0.85, 0.85) local divLine = sec1:CreateTexture(nil, "ARTWORK") divLine:SetTexture("Interface\\Buttons\\WHITE8X8") divLine:SetHeight(1) divLine:SetPoint("TOPLEFT", classLabel, "BOTTOMLEFT", 0, -3) divLine:SetPoint("RIGHT", sec1, "RIGHT", -14, 0) divLine:SetVertexColor(0.35, 0.35, 0.40, 0.6) CreateSwatchRow(sec1, SFrames.Theme.ClassPresetOrder, -182) local classCheck = CreateCheckBox(sec1, "跟随当前职业自动切换", 14, -296, function() return SFramesDB and SFramesDB.Theme and SFramesDB.Theme.useClassTheme end, function(checked) EnsureDB() SFramesDB.Theme.useClassTheme = checked if checked then SFrames.Theme:Apply(SFrames.Theme:GetCurrentPreset()) else SFrames.Theme:Apply(SFramesDB.Theme.preset or "pink") end SFrames.ConfigUI:RefreshThemeSwatches() SFrames.ConfigUI:RefreshThemePreview() end ) table.insert(controls, classCheck) self._themeSwatches = swatches --------------------------------------------------------------------------- -- Section 3: Icon set picker --------------------------------------------------------------------------- local sec3 = CreateSection(root, "图标风格", 8, -356, 520, 110, font) CreateDesc(sec3, "选择图标风格后需重载 UI 以完全生效。", 14, -34, font, 480) local ICON_SETS = { "icon", "icon2", "icon3", "icon4", "icon5", "icon6", "icon7", "icon8" } local ICON_SET_SIZE = 48 local ICON_SET_GAP = 10 local faction = UnitFactionGroup and UnitFactionGroup("player") or "Alliance" local factionKey = (faction == "Horde") and "horde" or "alliance" local factionCoords = SFrames.ICON_TCOORDS and SFrames.ICON_TCOORDS[factionKey] if not factionCoords then factionCoords = (factionKey == "horde") and { 0.75, 0.875, 0, 0.125 } or { 0.625, 0.75, 0, 0.125 } end local iconSetBtns = {} for idx = 1, table.getn(ICON_SETS) do local setKey = ICON_SETS[idx] local texPath = "Interface\\AddOns\\Nanami-UI\\img\\" .. setKey local sx = 14 + (idx - 1) * (ICON_SET_SIZE + ICON_SET_GAP) local btn = CreateFrame("Button", NextWidgetName("IcoSet"), sec3) btn:SetWidth(ICON_SET_SIZE) btn:SetHeight(ICON_SET_SIZE) btn:SetPoint("TOPLEFT", sec3, "TOPLEFT", sx, -50) btn: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 }, }) btn:SetBackdropColor(0.08, 0.08, 0.1, 0.85) btn:SetBackdropBorderColor(0.3, 0.3, 0.35, 1) local preview = btn:CreateTexture(nil, "ARTWORK") preview:SetTexture(texPath) preview:SetTexCoord(factionCoords[1], factionCoords[2], factionCoords[3], factionCoords[4]) preview:SetPoint("CENTER", btn, "CENTER", 0, 0) preview:SetWidth(ICON_SET_SIZE - 8) preview:SetHeight(ICON_SET_SIZE - 8) btn.setKey = setKey btn.selectBorder = CreateFrame("Frame", nil, btn) btn.selectBorder:SetPoint("TOPLEFT", btn, "TOPLEFT", -2, 2) btn.selectBorder:SetPoint("BOTTOMRIGHT", btn, "BOTTOMRIGHT", 2, -2) btn.selectBorder:SetBackdrop({ edgeFile = "Interface\\Buttons\\WHITE8X8", edgeSize = 2 }) btn.selectBorder:SetBackdropBorderColor(1, 1, 1, 1) btn.selectBorder:Hide() btn:SetScript("OnClick", function() EnsureDB() SFramesDB.Theme.iconSet = this.setKey SFrames.ConfigUI:RefreshIconSetButtons() if SFrames.MinimapButton and SFrames.MinimapButton.Refresh then SFrames.MinimapButton:Refresh() end PlaySound("igMainMenuOptionCheckBoxOn") end) btn: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:AddLine("点击选择,重载 UI 后生效", 0.7, 0.7, 0.7) GameTooltip:Show() GameTooltip:SetFrameStrata("TOOLTIP") GameTooltip:Raise() end) btn:SetScript("OnLeave", function() local cur = SFramesDB and SFramesDB.Theme and SFramesDB.Theme.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) table.insert(iconSetBtns, btn) end self._iconSetBtns = iconSetBtns self:RefreshIconSetButtons() --------------------------------------------------------------------------- -- Section 4: Preview --------------------------------------------------------------------------- local sec4 = CreateSection(root, "主题预览", 8, -478, 520, 180, font) local previewPanel = CreateFrame("Frame", NextWidgetName("Preview"), sec4) previewPanel:SetWidth(490) previewPanel:SetHeight(50) previewPanel:SetPoint("TOPLEFT", sec4, "TOPLEFT", 14, -34) EnsureSoftBackdrop(previewPanel) local previewTitle = previewPanel:CreateFontString(nil, "OVERLAY") previewTitle:SetFont(font, 12, "OUTLINE") previewTitle:SetPoint("TOPLEFT", previewPanel, "TOPLEFT", 10, -8) previewTitle:SetText("面板标题示例") local previewText = previewPanel:CreateFontString(nil, "OVERLAY") previewText:SetFont(font, 10, "OUTLINE") previewText:SetPoint("TOPLEFT", previewPanel, "TOPLEFT", 10, -26) previewText:SetText("正文文本 / Body text") local previewDim = previewPanel:CreateFontString(nil, "OVERLAY") previewDim:SetFont(font, 9, "OUTLINE") previewDim:SetPoint("TOPLEFT", previewPanel, "TOPLEFT", 10, -40) previewDim:SetText("辅助文字 / Dim text") local previewBtn = CreateFrame("Button", NextWidgetName("PreviewBtn"), sec4, "UIPanelButtonTemplate") previewBtn:SetWidth(90) previewBtn:SetHeight(24) previewBtn:SetPoint("TOPLEFT", sec4, "TOPLEFT", 14, -92) previewBtn:SetText("按钮示例") StyleButton(previewBtn) local previewAccent = sec4:CreateTexture(nil, "ARTWORK") previewAccent:SetTexture("Interface\\Buttons\\WHITE8X8") previewAccent:SetWidth(490) previewAccent:SetHeight(4) previewAccent:SetPoint("TOPLEFT", sec4, "TOPLEFT", 14, -122) local previewSlot = CreateFrame("Frame", NextWidgetName("PreviewSlot"), sec4) previewSlot:SetWidth(80) previewSlot:SetHeight(24) previewSlot:SetPoint("TOPLEFT", sec4, "TOPLEFT", 14, -134) previewSlot:SetBackdrop({ bgFile = "Interface\\Buttons\\WHITE8X8", edgeFile = "Interface\\Buttons\\WHITE8X8", tile = false, edgeSize = 1, insets = { left = 1, right = 1, top = 1, bottom = 1 }, }) local previewSlotLabel = previewSlot:CreateFontString(nil, "OVERLAY") previewSlotLabel:SetFont(font, 9, "OUTLINE") previewSlotLabel:SetPoint("CENTER", previewSlot, "CENTER", 0, 0) previewSlotLabel:SetText("色块") self._themePreview = { panel = previewPanel, title = previewTitle, text = previewText, dim = previewDim, btn = previewBtn, accent = previewAccent, slot = previewSlot, slotLabel = previewSlotLabel, } --------------------------------------------------------------------------- -- Hint --------------------------------------------------------------------------- CreateDesc(root, "提示: 更换主题后建议点击右下方 [保存并重载] 以确保所有界面生效。", 14, -650, font, 500) --------------------------------------------------------------------------- -- Finalize --------------------------------------------------------------------------- themeScroll:UpdateRange() self.themeControls = controls self.themeScroll = themeScroll self:RefreshThemeSwatches() self:RefreshThemePreview() end function SFrames.ConfigUI:RefreshThemeSwatches() if not self._themeSwatches then return end local active = SFramesDB and SFramesDB.Theme and SFramesDB.Theme.preset or "pink" local useClass = SFramesDB and SFramesDB.Theme and SFramesDB.Theme.useClassTheme if useClass then active = SFrames.Theme:GetCurrentPreset() end for _, sw in ipairs(self._themeSwatches) do if sw.presetKey == active then sw:SetBackdropBorderColor(1, 0.85, 0, 1) if sw.selectFrame then sw.selectFrame:Show() end else sw:SetBackdropBorderColor(0.25, 0.25, 0.25, 1) if sw.selectFrame then sw.selectFrame:Hide() end end end end function SFrames.ConfigUI:RefreshThemePreview() local p = self._themePreview if not p then return end local A = SFrames.ActiveTheme if p.panel and p.panel.SetBackdropColor then p.panel:SetBackdropColor(A.panelBg[1], A.panelBg[2], A.panelBg[3], A.panelBg[4] or 0.95) p.panel:SetBackdropBorderColor(A.panelBorder[1], A.panelBorder[2], A.panelBorder[3], A.panelBorder[4] or 0.9) end if p.title then p.title:SetTextColor(A.title[1], A.title[2], A.title[3]) end if p.text then p.text:SetTextColor(A.text[1], A.text[2], A.text[3]) end if p.dim then p.dim:SetTextColor(A.dimText[1], A.dimText[2], A.dimText[3]) end if p.accent then p.accent:SetVertexColor(A.accent[1], A.accent[2], A.accent[3], A.accent[4] or 1) end if p.slot and p.slot.SetBackdropColor then p.slot:SetBackdropColor(A.slotBg[1], A.slotBg[2], A.slotBg[3], A.slotBg[4] or 0.9) p.slot:SetBackdropBorderColor(A.slotSelected[1], A.slotSelected[2], A.slotSelected[3], A.slotSelected[4] or 1) end if p.slotLabel then p.slotLabel:SetTextColor(A.nameText[1], A.nameText[2], A.nameText[3]) end end function SFrames.ConfigUI:RefreshIconSetButtons() if not self._iconSetBtns then return end local cur = SFramesDB and SFramesDB.Theme and SFramesDB.Theme.iconSet or "icon" for _, btn in ipairs(self._iconSetBtns) do if btn.setKey == cur then btn:SetBackdropBorderColor(1, 0.85, 0.6, 1) if btn.selectBorder then btn.selectBorder:Show() end else btn:SetBackdropBorderColor(0.3, 0.3, 0.35, 1) if btn.selectBorder then btn.selectBorder:Hide() end end end end function SFrames.ConfigUI:EnsurePage(mode) if self._pageBuilt[mode] then return end self._pageBuilt[mode] = true if mode == "ui" then self:BuildUIPage() elseif mode == "player" then self:BuildPlayerPage() elseif mode == "target" then self:BuildTargetPage() elseif mode == "party" then self:BuildPartyPage() elseif mode == "bags" then self:BuildBagPage() elseif mode == "raid" then self:BuildRaidPage() elseif mode == "char" then self:BuildCharPage() elseif mode == "actionbar" then self:BuildActionBarPage() elseif mode == "minimap" then self:BuildMinimapPage() elseif mode == "buff" then self:BuildBuffPage() elseif mode == "personalize" then self:BuildPersonalizePage() elseif mode == "theme" then self:BuildThemePage() end end function SFrames.ConfigUI:Build(mode) EnsureDB() self:EnsureFrame() local page = string.lower(mode or "ui") local validPages = { ui = true, player = true, target = true, bags = true, char = true, party = true, raid = true, actionbar = true, minimap = true, buff = true, personalize = true, theme = true } if not validPages[page] then page = "ui" end if self.frame:IsShown() and self.activePage == page then self.frame:Hide() return end self:ShowPage(page) self.frame:Show() self.frame:Raise() end function SFrames.ConfigUI:OpenUI() self:Build("ui") end function SFrames.ConfigUI:OpenBags() self:Build("bags") end