7008 lines
306 KiB
Lua
7008 lines
306 KiB
Lua
--------------------------------------------------------------------------------
|
||
-- 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 function EnsureNumberDefault(tbl, key, value)
|
||
if type(tbl[key]) ~= "number" then tbl[key] = value end
|
||
end
|
||
|
||
local function EnsureStringDefault(tbl, key, value)
|
||
if type(tbl[key]) ~= "string" or tbl[key] == "" then tbl[key] = value end
|
||
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()
|
||
|
||
local font = (SFrames.GetFont and SFrames:GetFont()) or "Fonts\\ARIALN.TTF"
|
||
if low then
|
||
low:SetFont(font, 8, "OUTLINE")
|
||
low:SetTextColor(SOFT_THEME.dimText[1], SOFT_THEME.dimText[2], SOFT_THEME.dimText[3])
|
||
low:ClearAllPoints()
|
||
low:SetWidth(40)
|
||
low:SetJustifyH("LEFT")
|
||
low:SetPoint("TOPLEFT", slider, "BOTTOMLEFT", 10, 0)
|
||
end
|
||
if high then
|
||
high:SetFont(font, 8, "OUTLINE")
|
||
high:SetTextColor(SOFT_THEME.dimText[1], SOFT_THEME.dimText[2], SOFT_THEME.dimText[3])
|
||
high:ClearAllPoints()
|
||
high:SetWidth(40)
|
||
high:SetJustifyH("RIGHT")
|
||
high:SetPoint("TOPRIGHT", slider, "BOTTOMRIGHT", -10, 0)
|
||
end
|
||
if text then
|
||
text:SetFont(font, 9, "OUTLINE")
|
||
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 = false end
|
||
if SFramesDB.playerShowClassIcon == nil then SFramesDB.playerShowClassIcon = true end
|
||
if SFramesDB.playerShowPortrait == nil then SFramesDB.playerShowPortrait = true end
|
||
if type(SFramesDB.playerFrameAlpha) ~= "number" then SFramesDB.playerFrameAlpha = 1 end
|
||
if type(SFramesDB.playerBgAlpha) ~= "number" then SFramesDB.playerBgAlpha = 0.9 end
|
||
if type(SFramesDB.playerNameFontSize) ~= "number" then SFramesDB.playerNameFontSize = 10 end
|
||
if type(SFramesDB.playerValueFontSize) ~= "number" then SFramesDB.playerValueFontSize = 10 end
|
||
local defaultPlayerPowerWidth = math.max(60, SFramesDB.playerFrameWidth - ((SFramesDB.playerShowPortrait ~= false) and SFramesDB.playerPortraitWidth or 0) - 2)
|
||
if type(SFramesDB.playerPowerWidth) ~= "number"
|
||
or math.abs(SFramesDB.playerPowerWidth - SFramesDB.playerFrameWidth) < 0.5
|
||
or math.abs(SFramesDB.playerPowerWidth - 168) < 0.5 then
|
||
SFramesDB.playerPowerWidth = defaultPlayerPowerWidth
|
||
end
|
||
if type(SFramesDB.playerPowerOffsetX) ~= "number" then SFramesDB.playerPowerOffsetX = 0 end
|
||
if type(SFramesDB.playerPowerOffsetY) ~= "number" then SFramesDB.playerPowerOffsetY = 0 end
|
||
if SFramesDB.playerPowerOnTop == nil then SFramesDB.playerPowerOnTop = false end
|
||
if SFramesDB.playerShowBorder == nil then SFramesDB.playerShowBorder = false end
|
||
if type(SFramesDB.playerCornerRadius) ~= "number" then SFramesDB.playerCornerRadius = 0 end
|
||
if type(SFramesDB.playerPortraitBgAlpha) ~= "number" then SFramesDB.playerPortraitBgAlpha = SFramesDB.playerBgAlpha end
|
||
if SFramesDB.playerHealthTexture == nil then SFramesDB.playerHealthTexture = "" end
|
||
if SFramesDB.playerPowerTexture == nil then SFramesDB.playerPowerTexture = "" end
|
||
if SFramesDB.playerNameFontKey == nil then SFramesDB.playerNameFontKey = "" end
|
||
if SFramesDB.playerHealthFontKey == nil then SFramesDB.playerHealthFontKey = "" end
|
||
if SFramesDB.playerPowerFontKey == nil then SFramesDB.playerPowerFontKey = "" end
|
||
if type(SFramesDB.playerHealthFontSize) ~= "number" then SFramesDB.playerHealthFontSize = SFramesDB.playerValueFontSize end
|
||
if type(SFramesDB.playerPowerFontSize) ~= "number" then SFramesDB.playerPowerFontSize = SFramesDB.playerValueFontSize 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 = false end
|
||
if SFramesDB.targetShowClassIcon == nil then SFramesDB.targetShowClassIcon = true end
|
||
if SFramesDB.targetShowPortrait == nil then SFramesDB.targetShowPortrait = true end
|
||
if type(SFramesDB.targetFrameAlpha) ~= "number" then SFramesDB.targetFrameAlpha = 1 end
|
||
if type(SFramesDB.targetBgAlpha) ~= "number" then SFramesDB.targetBgAlpha = 0.9 end
|
||
if type(SFramesDB.targetNameFontSize) ~= "number" then SFramesDB.targetNameFontSize = 10 end
|
||
if type(SFramesDB.targetValueFontSize) ~= "number" then SFramesDB.targetValueFontSize = 10 end
|
||
local defaultTargetPowerWidth = math.max(60, SFramesDB.targetFrameWidth - ((SFramesDB.targetShowPortrait ~= false) and SFramesDB.targetPortraitWidth or 0) - 2)
|
||
if type(SFramesDB.targetPowerWidth) ~= "number"
|
||
or math.abs(SFramesDB.targetPowerWidth - SFramesDB.targetFrameWidth) < 0.5 then
|
||
SFramesDB.targetPowerWidth = defaultTargetPowerWidth
|
||
end
|
||
if type(SFramesDB.targetPowerOffsetX) ~= "number" then SFramesDB.targetPowerOffsetX = 0 end
|
||
if type(SFramesDB.targetPowerOffsetY) ~= "number" then SFramesDB.targetPowerOffsetY = 0 end
|
||
if SFramesDB.targetPowerOnTop == nil then SFramesDB.targetPowerOnTop = false end
|
||
if SFramesDB.targetShowBorder == nil then SFramesDB.targetShowBorder = false end
|
||
if type(SFramesDB.targetCornerRadius) ~= "number" then SFramesDB.targetCornerRadius = 0 end
|
||
if type(SFramesDB.targetPortraitBgAlpha) ~= "number" then SFramesDB.targetPortraitBgAlpha = SFramesDB.targetBgAlpha end
|
||
if SFramesDB.targetHealthTexture == nil then SFramesDB.targetHealthTexture = "" end
|
||
if SFramesDB.targetPowerTexture == nil then SFramesDB.targetPowerTexture = "" end
|
||
if SFramesDB.targetNameFontKey == nil then SFramesDB.targetNameFontKey = "" end
|
||
if SFramesDB.targetHealthFontKey == nil then SFramesDB.targetHealthFontKey = "" end
|
||
if SFramesDB.targetPowerFontKey == nil then SFramesDB.targetPowerFontKey = "" end
|
||
if type(SFramesDB.targetHealthFontSize) ~= "number" then SFramesDB.targetHealthFontSize = SFramesDB.targetValueFontSize end
|
||
if type(SFramesDB.targetPowerFontSize) ~= "number" then SFramesDB.targetPowerFontSize = SFramesDB.targetValueFontSize end
|
||
if SFramesDB.targetDistanceEnabled == nil then SFramesDB.targetDistanceEnabled = true end
|
||
if type(SFramesDB.targetDistanceScale) ~= "number" then SFramesDB.targetDistanceScale = 1 end
|
||
if type(SFramesDB.targetDistanceFontSize) ~= "number" then SFramesDB.targetDistanceFontSize = 14 end
|
||
if SFramesDB.targetDistanceFontKey == nil then SFramesDB.targetDistanceFontKey = "" end
|
||
if type(SFramesDB.fontKey) ~= "string" then SFramesDB.fontKey = "default" end
|
||
|
||
-- Focus frame defaults
|
||
if SFramesDB.focusEnabled == nil then SFramesDB.focusEnabled = true end
|
||
if type(SFramesDB.focusFrameScale) ~= "number" then SFramesDB.focusFrameScale = 0.9 end
|
||
if type(SFramesDB.focusFrameWidth) ~= "number" then SFramesDB.focusFrameWidth = 200 end
|
||
if type(SFramesDB.focusPortraitWidth) ~= "number" then SFramesDB.focusPortraitWidth = 45 end
|
||
if type(SFramesDB.focusHealthHeight) ~= "number" then SFramesDB.focusHealthHeight = 32 end
|
||
if type(SFramesDB.focusPowerHeight) ~= "number" then SFramesDB.focusPowerHeight = 10 end
|
||
if SFramesDB.focusShowPortrait == nil then SFramesDB.focusShowPortrait = true end
|
||
if SFramesDB.focusShowCastBar == nil then SFramesDB.focusShowCastBar = true end
|
||
if SFramesDB.focusShowAuras == nil then SFramesDB.focusShowAuras = true end
|
||
if type(SFramesDB.focusNameFontSize) ~= "number" then SFramesDB.focusNameFontSize = 11 end
|
||
if type(SFramesDB.focusValueFontSize) ~= "number" then SFramesDB.focusValueFontSize = 10 end
|
||
if type(SFramesDB.focusBgAlpha) ~= "number" then SFramesDB.focusBgAlpha = 0.9 end
|
||
local defaultFocusPowerWidth = math.max(60, SFramesDB.focusFrameWidth - ((SFramesDB.focusShowPortrait ~= false) and SFramesDB.focusPortraitWidth or 0) - 2)
|
||
if type(SFramesDB.focusPowerWidth) ~= "number"
|
||
or math.abs(SFramesDB.focusPowerWidth - SFramesDB.focusFrameWidth) < 0.5 then
|
||
SFramesDB.focusPowerWidth = defaultFocusPowerWidth
|
||
end
|
||
if type(SFramesDB.focusPowerOffsetX) ~= "number" then SFramesDB.focusPowerOffsetX = 0 end
|
||
if type(SFramesDB.focusPowerOffsetY) ~= "number" then SFramesDB.focusPowerOffsetY = 0 end
|
||
if SFramesDB.focusPowerOnTop == nil then SFramesDB.focusPowerOnTop = false end
|
||
if SFramesDB.focusShowBorder == nil then SFramesDB.focusShowBorder = false end
|
||
if type(SFramesDB.focusCornerRadius) ~= "number" then SFramesDB.focusCornerRadius = 0 end
|
||
if type(SFramesDB.focusPortraitBgAlpha) ~= "number" then SFramesDB.focusPortraitBgAlpha = SFramesDB.focusBgAlpha end
|
||
if SFramesDB.focusHealthTexture == nil then SFramesDB.focusHealthTexture = "" end
|
||
if SFramesDB.focusPowerTexture == nil then SFramesDB.focusPowerTexture = "" end
|
||
if SFramesDB.focusNameFontKey == nil then SFramesDB.focusNameFontKey = "" end
|
||
if SFramesDB.focusHealthFontKey == nil then SFramesDB.focusHealthFontKey = "" end
|
||
if SFramesDB.focusPowerFontKey == nil then SFramesDB.focusPowerFontKey = "" end
|
||
if SFramesDB.focusCastFontKey == nil then SFramesDB.focusCastFontKey = "" end
|
||
if SFramesDB.focusDistanceFontKey == nil then SFramesDB.focusDistanceFontKey = "" end
|
||
if type(SFramesDB.focusHealthFontSize) ~= "number" then SFramesDB.focusHealthFontSize = SFramesDB.focusValueFontSize end
|
||
if type(SFramesDB.focusPowerFontSize) ~= "number" then SFramesDB.focusPowerFontSize = SFramesDB.focusValueFontSize end
|
||
if type(SFramesDB.focusCastFontSize) ~= "number" then SFramesDB.focusCastFontSize = 10 end
|
||
if type(SFramesDB.focusDistanceFontSize) ~= "number" then SFramesDB.focusDistanceFontSize = 14 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
|
||
local defaultPartyPowerWidth = math.max(40, SFramesDB.partyFrameWidth - SFramesDB.partyPortraitWidth - 5)
|
||
if type(SFramesDB.partyPowerWidth) ~= "number"
|
||
or math.abs(SFramesDB.partyPowerWidth - SFramesDB.partyFrameWidth) < 0.5 then
|
||
SFramesDB.partyPowerWidth = defaultPartyPowerWidth
|
||
end
|
||
if type(SFramesDB.partyPowerOffsetX) ~= "number" then SFramesDB.partyPowerOffsetX = 0 end
|
||
if type(SFramesDB.partyPowerOffsetY) ~= "number" then SFramesDB.partyPowerOffsetY = 0 end
|
||
if SFramesDB.partyPowerOnTop == nil then SFramesDB.partyPowerOnTop = false end
|
||
if SFramesDB.partyShowBorder == nil then SFramesDB.partyShowBorder = false end
|
||
if type(SFramesDB.partyCornerRadius) ~= "number" then SFramesDB.partyCornerRadius = 0 end
|
||
if type(SFramesDB.partyPortraitBgAlpha) ~= "number" then SFramesDB.partyPortraitBgAlpha = SFramesDB.partyBgAlpha end
|
||
if SFramesDB.partyHealthTexture == nil then SFramesDB.partyHealthTexture = "" end
|
||
if SFramesDB.partyPowerTexture == nil then SFramesDB.partyPowerTexture = "" end
|
||
if SFramesDB.partyNameFontKey == nil then SFramesDB.partyNameFontKey = "" end
|
||
if SFramesDB.partyHealthFontKey == nil then SFramesDB.partyHealthFontKey = "" end
|
||
if SFramesDB.partyPowerFontKey == nil then SFramesDB.partyPowerFontKey = "" end
|
||
if type(SFramesDB.partyHealthFontSize) ~= "number" then SFramesDB.partyHealthFontSize = SFramesDB.partyValueFontSize end
|
||
if type(SFramesDB.partyPowerFontSize) ~= "number" then SFramesDB.partyPowerFontSize = SFramesDB.partyValueFontSize end
|
||
if SFramesDB.partyShowBuffs == nil then SFramesDB.partyShowBuffs = true end
|
||
if SFramesDB.partyShowDebuffs == nil then SFramesDB.partyShowDebuffs = true end
|
||
if SFramesDB.partyPortrait3D == nil then SFramesDB.partyPortrait3D = true end
|
||
if type(SFramesDB.partyBgAlpha) ~= "number" then SFramesDB.partyBgAlpha = 0.9 end
|
||
|
||
if SFramesDB.totHealthTexture == nil then SFramesDB.totHealthTexture = "" end
|
||
if SFramesDB.petHealthTexture == nil then SFramesDB.petHealthTexture = "" end
|
||
if SFramesDB.petPowerTexture == nil then SFramesDB.petPowerTexture = "" 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
|
||
local defaultRaidPowerWidth = math.max(20, SFramesDB.raidFrameWidth - 2)
|
||
if type(SFramesDB.raidPowerWidth) ~= "number"
|
||
or math.abs(SFramesDB.raidPowerWidth - SFramesDB.raidFrameWidth) < 0.5 then
|
||
SFramesDB.raidPowerWidth = defaultRaidPowerWidth
|
||
end
|
||
if type(SFramesDB.raidPowerOffsetX) ~= "number" then SFramesDB.raidPowerOffsetX = 0 end
|
||
if type(SFramesDB.raidPowerOffsetY) ~= "number" then SFramesDB.raidPowerOffsetY = 0 end
|
||
if SFramesDB.raidPowerOnTop == nil then SFramesDB.raidPowerOnTop = false end
|
||
if SFramesDB.raidShowBorder == nil then SFramesDB.raidShowBorder = false end
|
||
if type(SFramesDB.raidCornerRadius) ~= "number" then SFramesDB.raidCornerRadius = 0 end
|
||
if SFramesDB.raidHealthTexture == nil then SFramesDB.raidHealthTexture = "" end
|
||
if SFramesDB.raidPowerTexture == nil then SFramesDB.raidPowerTexture = "" end
|
||
if SFramesDB.raidNameFontKey == nil then SFramesDB.raidNameFontKey = "" end
|
||
if SFramesDB.raidHealthFontKey == nil then SFramesDB.raidHealthFontKey = "" end
|
||
if SFramesDB.raidPowerFontKey == nil then SFramesDB.raidPowerFontKey = "" end
|
||
if type(SFramesDB.raidHealthFontSize) ~= "number" then SFramesDB.raidHealthFontSize = SFramesDB.raidValueFontSize end
|
||
if type(SFramesDB.raidPowerFontSize) ~= "number" then SFramesDB.raidPowerFontSize = SFramesDB.raidValueFontSize 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 type(SFramesDB.raidBgAlpha) ~= "number" then SFramesDB.raidBgAlpha = 0.9 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.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.Bags.alpha) ~= "number" then SFramesDB.Bags.alpha = 1 end
|
||
if type(SFramesDB.Bags.bankAlpha) ~= "number" then SFramesDB.Bags.bankAlpha = 1 end
|
||
if type(SFramesDB.Bags.bgAlpha) ~= "number" then SFramesDB.Bags.bgAlpha = 0.95 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 SFramesDB.Minimap.mapShape == nil then SFramesDB.Minimap.mapShape = "square1" 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.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.combatNotify == nil then SFramesDB.Tweaks.combatNotify = 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 SFramesDB.Tweaks.mouseoverCast == nil then SFramesDB.Tweaks.mouseoverCast = 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.MinimapBuffs.bgAlpha) ~= "number" then SFramesDB.MinimapBuffs.bgAlpha = 0.92 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,
|
||
alpha = 1.0,
|
||
bgAlpha = 0.9,
|
||
rightBar1PerRow = 1,
|
||
rightBar2PerRow = 1,
|
||
bottomBar1PerRow = 12,
|
||
bottomBar2PerRow = 12,
|
||
bottomBar3PerRow = 12,
|
||
}
|
||
for k, v in pairs(abDef) do
|
||
if SFramesDB.ActionBars[k] == nil then SFramesDB.ActionBars[k] = v end
|
||
end
|
||
|
||
if type(SFramesDB.ExtraBar) ~= "table" then SFramesDB.ExtraBar = {} end
|
||
local ebDef = {
|
||
enable = false, buttonCount = 12, perRow = 12, buttonSize = 36,
|
||
buttonGap = 2, align = "center", startSlot = 73, alpha = 1.0,
|
||
showHotkey = true, showCount = true,
|
||
buttonRounded = false, buttonInnerShadow = false,
|
||
}
|
||
for k, v in pairs(ebDef) do
|
||
if SFramesDB.ExtraBar[k] == nil then SFramesDB.ExtraBar[k] = v end
|
||
end
|
||
|
||
if SFramesDB.enableUnitFrames == nil then SFramesDB.enableUnitFrames = true end
|
||
if SFramesDB.enablePlayerFrame == nil then SFramesDB.enablePlayerFrame = true end
|
||
if SFramesDB.enableTargetFrame == nil then SFramesDB.enableTargetFrame = true end
|
||
if SFramesDB.enablePartyFrame == nil then SFramesDB.enablePartyFrame = true end
|
||
if SFramesDB.enableChat == nil then SFramesDB.enableChat = true end
|
||
if type(SFramesDB.chatBgAlpha) ~= "number" then SFramesDB.chatBgAlpha = 0.9 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 ApplyBgAlphaToFrame(frame, alpha)
|
||
if not frame or not frame.SetBackdropColor then return end
|
||
local A = SFrames.ActiveTheme
|
||
if A and A.panelBg then
|
||
frame:SetBackdropColor(A.panelBg[1], A.panelBg[2], A.panelBg[3], alpha)
|
||
else
|
||
frame:SetBackdropColor(0.1, 0.1, 0.1, alpha)
|
||
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 CreateOptionGrid(parent, title, x, y, width, options, getter, setter, previewKind, previewSize, onValueChanged)
|
||
local font = SFrames:GetFont()
|
||
local controls = {}
|
||
local cols = previewKind == "font" and 3 or 5
|
||
local btnW = previewKind == "font" and 150 or 96
|
||
local btnH = previewKind == "font" and 22 or 48
|
||
local gapX = previewKind == "font" and 8 or 6
|
||
local gapY = 6
|
||
|
||
CreateLabel(parent, title, x, y, font, 10, 0.85, 0.75, 0.80)
|
||
|
||
local buttons = {}
|
||
for idx, entry in ipairs(options) do
|
||
local col = math.mod(idx - 1, cols)
|
||
local row = math.floor((idx - 1) / cols)
|
||
local bx = x + col * (btnW + gapX)
|
||
local by = y - 18 - row * (btnH + gapY)
|
||
local btn = CreateFrame("Button", NextWidgetName("OptionGrid"), parent)
|
||
btn:SetWidth(btnW)
|
||
btn:SetHeight(btnH)
|
||
btn:SetPoint("TOPLEFT", parent, "TOPLEFT", bx, by)
|
||
btn:SetBackdrop({
|
||
bgFile = "Interface\\Buttons\\WHITE8X8",
|
||
edgeFile = "Interface\\Buttons\\WHITE8X8",
|
||
tile = false, edgeSize = 1,
|
||
insets = { left = 1, right = 1, top = 1, bottom = 1 },
|
||
})
|
||
btn:SetBackdropColor(0.08, 0.08, 0.10, 0.9)
|
||
btn:SetBackdropBorderColor(0.3, 0.3, 0.35, 1)
|
||
btn.optionKey = entry.key
|
||
btn.optionLabel = entry.label
|
||
|
||
if previewKind == "bar" then
|
||
local preview = CreateFrame("StatusBar", NextWidgetName("OptionGridBar"), btn)
|
||
preview:SetPoint("TOPLEFT", btn, "TOPLEFT", 2, -2)
|
||
preview:SetPoint("BOTTOMRIGHT", btn, "BOTTOMRIGHT", -2, 14)
|
||
preview:SetStatusBarTexture(entry.path)
|
||
preview:SetStatusBarColor(0.2, 0.85, 0.3, 1)
|
||
preview:SetMinMaxValues(0, 1)
|
||
preview:SetValue(1)
|
||
|
||
local label = btn:CreateFontString(nil, "OVERLAY")
|
||
label:SetFont(font, 9, "OUTLINE")
|
||
label:SetPoint("BOTTOM", btn, "BOTTOM", 0, 3)
|
||
label:SetText(entry.label)
|
||
label:SetTextColor(0.8, 0.8, 0.8)
|
||
else
|
||
local label = btn:CreateFontString(nil, "OVERLAY")
|
||
local ok = pcall(label.SetFont, label, entry.path, previewSize or 11, "OUTLINE")
|
||
if not ok then
|
||
label:SetFont(font, previewSize or 11, "OUTLINE")
|
||
end
|
||
label:SetPoint("CENTER", btn, "CENTER", 0, 0)
|
||
label:SetText(entry.label)
|
||
label:SetTextColor(0.9, 0.9, 0.9)
|
||
end
|
||
|
||
local selectBorder = btn:CreateTexture(nil, "OVERLAY")
|
||
selectBorder:SetTexture("Interface\\Buttons\\WHITE8X8")
|
||
selectBorder:SetPoint("TOPLEFT", btn, "TOPLEFT", -1, 1)
|
||
selectBorder:SetPoint("BOTTOMRIGHT", btn, "BOTTOMRIGHT", 1, -1)
|
||
selectBorder:SetVertexColor(1, 0.85, 0.4, 0.8)
|
||
selectBorder:Hide()
|
||
btn.selectBorder = selectBorder
|
||
|
||
btn:SetScript("OnClick", function()
|
||
setter(this.optionKey)
|
||
for _, b in ipairs(buttons) do
|
||
local active = b.optionKey == this.optionKey
|
||
b:SetBackdropBorderColor(active and 1 or 0.3, active and 0.85 or 0.3, active and 0.6 or 0.35, 1)
|
||
if b.selectBorder then
|
||
if active then b.selectBorder:Show() else b.selectBorder:Hide() end
|
||
end
|
||
end
|
||
if onValueChanged then onValueChanged(this.optionKey) end
|
||
end)
|
||
btn:SetScript("OnEnter", function()
|
||
if this.optionKey ~= getter() then
|
||
this:SetBackdropBorderColor(0.7, 0.7, 0.8, 1)
|
||
end
|
||
GameTooltip:SetOwner(this, "ANCHOR_TOP")
|
||
GameTooltip:SetText(this.optionLabel)
|
||
GameTooltip:Show()
|
||
end)
|
||
btn:SetScript("OnLeave", function()
|
||
local active = this.optionKey == getter()
|
||
this:SetBackdropBorderColor(active and 1 or 0.3, active and 0.85 or 0.3, active and 0.6 or 0.35, 1)
|
||
GameTooltip:Hide()
|
||
end)
|
||
|
||
btn.Refresh = function()
|
||
local active = btn.optionKey == getter()
|
||
btn:SetBackdropBorderColor(active and 1 or 0.3, active and 0.85 or 0.3, active and 0.6 or 0.35, 1)
|
||
if btn.selectBorder then
|
||
if active then btn.selectBorder:Show() else btn.selectBorder:Hide() end
|
||
end
|
||
end
|
||
btn:Refresh()
|
||
|
||
table.insert(buttons, btn)
|
||
table.insert(controls, btn)
|
||
end
|
||
|
||
local rows = math.floor((table.getn(options) + cols - 1) / cols)
|
||
return controls, (rows * btnH) + ((rows - 1) * gapY) + 18
|
||
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, 1090)
|
||
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 layoutSection = CreateSection(root, "布局模式", 8, -78, 520, 62, font)
|
||
CreateDesc(layoutSection, "进入布局模式,拖拽调整所有 UI 元素的位置", 14, -30, font, 380)
|
||
CreateButton(layoutSection, "进入布局模式", 370, -30, 140, 24, function()
|
||
if SFrames.ConfigUI.frame then SFrames.ConfigUI.frame:Hide() end
|
||
if SFrames.Movers and SFrames.Movers.ToggleLayoutMode then
|
||
SFrames.Movers:ToggleLayoutMode()
|
||
end
|
||
end)
|
||
|
||
-- ── UI 缩放 ──────────────────────────────────────────────────
|
||
local scaleSection = CreateSection(root, "UI 缩放", 8, -148, 520, 82, font)
|
||
|
||
local function ApplyUIScaleKeepPos(newScale)
|
||
UIParent:SetScale(newScale)
|
||
local panel = SFrames.ConfigUI.frame
|
||
if panel and panel:IsShown() then
|
||
panel:ClearAllPoints()
|
||
panel:SetPoint("CENTER", UIParent, "CENTER", 0, 0)
|
||
end
|
||
end
|
||
|
||
table.insert(controls, CreateCheckBox(scaleSection,
|
||
"启用自定义 UI 缩放", 14, -34,
|
||
function() return GetCVar("useUiScale") == "1" end,
|
||
function(checked)
|
||
SetCVar("useUiScale", checked and "1" or "0")
|
||
if checked then
|
||
local s = tonumber(GetCVar("uiScale")) or 1.0
|
||
ApplyUIScaleKeepPos(s)
|
||
else
|
||
ApplyUIScaleKeepPos(1.0)
|
||
end
|
||
end
|
||
))
|
||
CreateDesc(scaleSection, "调用系统 UI 缩放,对所有界面元素生效", 36, -50, font)
|
||
|
||
local uiScaleSlider = CreateSlider(scaleSection, "缩放比例", 270, -34, 200, 0.64, 1.00, 0.01,
|
||
function() return tonumber(GetCVar("uiScale")) or 1.0 end,
|
||
function(value)
|
||
SetCVar("uiScale", tostring(value))
|
||
end,
|
||
function(v) return string.format("%.0f%%", v * 100) end
|
||
)
|
||
table.insert(controls, uiScaleSlider)
|
||
|
||
uiScaleSlider:SetScript("OnMouseUp", function()
|
||
if GetCVar("useUiScale") == "1" then
|
||
local s = tonumber(GetCVar("uiScale")) or 1.0
|
||
ApplyUIScaleKeepPos(s)
|
||
end
|
||
end)
|
||
|
||
-- ── 全局 ──────────────────────────────────────────────────────
|
||
local globalSection = CreateSection(root, "全局", 8, -238, 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)
|
||
|
||
table.insert(controls, CreateCheckBox(globalSection,
|
||
"启用自定义拾取界面", 270, -250,
|
||
function()
|
||
if type(SFramesDB.lootDisplay) ~= "table" then return true end
|
||
return SFramesDB.lootDisplay.enable ~= false
|
||
end,
|
||
function(checked)
|
||
if type(SFramesDB.lootDisplay) ~= "table" then SFramesDB.lootDisplay = {} end
|
||
SFramesDB.lootDisplay.enable = checked
|
||
end
|
||
))
|
||
CreateDesc(globalSection, "替换原生拾取窗口,品质染色+拾取提示(需重载UI)", 292, -266, font)
|
||
|
||
-- ── 增强功能(Libs 库集成)──────────────────────────────────
|
||
local enhSection = CreateSection(root, "增强功能(需安装 !Libs 插件)", 8, -536, 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, -750, 520, 260, 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.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)
|
||
|
||
table.insert(controls, CreateCheckBox(tweaksSection,
|
||
"进战斗后台通知", 14, -172,
|
||
function() return SFramesDB.Tweaks.combatNotify ~= false end,
|
||
function(checked) SFramesDB.Tweaks.combatNotify = checked end
|
||
))
|
||
CreateDesc(tweaksSection, "进入战斗时闪烁任务栏图标(需安装 UnitXP SP3)", 36, -188, font, 218)
|
||
|
||
table.insert(controls, CreateCheckBox(tweaksSection,
|
||
"鼠标指向施法", 14, -218,
|
||
function() return SFramesDB.Tweaks.mouseoverCast == true end,
|
||
function(checked) SFramesDB.Tweaks.mouseoverCast = checked end
|
||
))
|
||
CreateDesc(tweaksSection, "鼠标悬停单位框体时施法直接作用于该单位,不切换目标(需 SuperWoW)", 36, -234, font, 480)
|
||
|
||
CreateLabel(tweaksSection, "提示:以上所有选项修改后需要 /reload 才能生效。", 14, -264, font, 10, 0.6, 0.6, 0.65)
|
||
|
||
-- ── 聊天背景透明度 ──────────────────────────────────────
|
||
local chatBgSection = CreateSection(root, "聊天窗口", 8, -1018, 520, 120, font)
|
||
|
||
table.insert(controls, CreateSlider(chatBgSection, "背景透明度", 14, -38, 220, 0, 1.0, 0.05,
|
||
function()
|
||
local chatDB = SFramesDB and SFramesDB.Chat
|
||
return (chatDB and type(chatDB.bgAlpha) == "number") and chatDB.bgAlpha or 0.45
|
||
end,
|
||
function(value)
|
||
if not SFramesDB.Chat then SFramesDB.Chat = {} end
|
||
SFramesDB.Chat.bgAlpha = value
|
||
end,
|
||
function(v) return string.format("%.0f%%", v * 100) end,
|
||
function()
|
||
if SFrames.Chat and SFrames.Chat.ApplyConfig then
|
||
SFrames.Chat:ApplyConfig()
|
||
end
|
||
end
|
||
))
|
||
table.insert(controls, CreateCheckBox(chatBgSection,
|
||
"悬停显示背景", 260, -36,
|
||
function()
|
||
local chatDB = SFramesDB and SFramesDB.Chat
|
||
return not chatDB or chatDB.hoverTransparent ~= false
|
||
end,
|
||
function(checked)
|
||
if not SFramesDB.Chat then SFramesDB.Chat = {} end
|
||
SFramesDB.Chat.hoverTransparent = (checked == true)
|
||
if SFrames.Chat and SFrames.Chat.ApplyConfig then
|
||
SFrames.Chat:ApplyConfig()
|
||
end
|
||
end
|
||
))
|
||
CreateDesc(chatBgSection, "调整聊天窗口背景的透明度(仅影响背景,不影响文字)。勾选[悬停显示背景]后,鼠标离开时背景自动透明。", 14, -68, font, 480)
|
||
|
||
uiScroll:UpdateRange()
|
||
self.uiControls = controls
|
||
self.uiScroll = uiScroll
|
||
end
|
||
|
||
local function AddFrameAdvancedSections(root, controls, spec)
|
||
local font = SFrames:GetFont()
|
||
local y = spec.startY
|
||
local apply = spec.apply
|
||
local prefix = spec.prefix
|
||
local showPowerHint = spec.showPowerHint
|
||
local powerSectionHeight = showPowerHint and 154 or 132
|
||
|
||
local powerSection = CreateSection(root, spec.powerTitle or "能量条", 8, y, 520, powerSectionHeight, font)
|
||
table.insert(controls, CreateSlider(powerSection, "宽度", 14, -42, 150, 40, 420, 1,
|
||
function() return SFramesDB[prefix .. "PowerWidth"] end,
|
||
function(value) SFramesDB[prefix .. "PowerWidth"] = value end,
|
||
function(v) return tostring(math.floor(v + 0.5)) end,
|
||
apply
|
||
))
|
||
table.insert(controls, CreateSlider(powerSection, "高度", 170, -42, 150, 4, 40, 1,
|
||
function() return SFramesDB[prefix .. "PowerHeight"] end,
|
||
function(value) SFramesDB[prefix .. "PowerHeight"] = value end,
|
||
function(v) return tostring(math.floor(v + 0.5)) end,
|
||
apply
|
||
))
|
||
table.insert(controls, CreateSlider(powerSection, "X 偏移", 326, -42, 130, -120, 120, 1,
|
||
function() return SFramesDB[prefix .. "PowerOffsetX"] end,
|
||
function(value) SFramesDB[prefix .. "PowerOffsetX"] = value end,
|
||
function(v) return tostring(math.floor(v + 0.5)) end,
|
||
apply
|
||
))
|
||
table.insert(controls, CreateSlider(powerSection, "Y 偏移", 14, -94, 150, -80, 80, 1,
|
||
function() return SFramesDB[prefix .. "PowerOffsetY"] end,
|
||
function(value) SFramesDB[prefix .. "PowerOffsetY"] = value end,
|
||
function(v) return tostring(math.floor(v + 0.5)) end,
|
||
apply
|
||
))
|
||
table.insert(controls, CreateCheckBox(powerSection,
|
||
"能量条在上层", 170, -92,
|
||
function() return SFramesDB[prefix .. "PowerOnTop"] == true end,
|
||
function(checked) SFramesDB[prefix .. "PowerOnTop"] = checked end,
|
||
apply
|
||
))
|
||
if showPowerHint then
|
||
CreateDesc(powerSection, showPowerHint, 14, -118, font, 470)
|
||
end
|
||
|
||
y = y - (powerSectionHeight + 12)
|
||
local textureSection = CreateSection(root, "条材质", 8, y, 520, 182, font)
|
||
local texControls, texHeight = CreateOptionGrid(textureSection, "血条材质", 14, -34, 490, SFrames.BarTextures or {},
|
||
function() return SFramesDB[prefix .. "HealthTexture"] or "" end,
|
||
function(value) SFramesDB[prefix .. "HealthTexture"] = value end,
|
||
"bar", 0, apply)
|
||
for _, control in ipairs(texControls) do table.insert(controls, control) end
|
||
local texControls2 = CreateOptionGrid(textureSection, "能量条材质", 14, -(34 + texHeight + 12), 490, SFrames.BarTextures or {},
|
||
function() return SFramesDB[prefix .. "PowerTexture"] or "" end,
|
||
function(value) SFramesDB[prefix .. "PowerTexture"] = value end,
|
||
"bar", 0, apply)
|
||
for _, control in ipairs(texControls2) do table.insert(controls, control) end
|
||
|
||
y = y - 194
|
||
local fontSection = CreateSection(root, "字体", 8, y, 520, spec.fontSectionHeight or 286, font)
|
||
local blockY = -34
|
||
local function AddFontBlock(title, keyName, sizeKey)
|
||
local optControls, optHeight = CreateOptionGrid(fontSection, title, 14, blockY, 490, SFrames.FontChoices or {},
|
||
function() return SFramesDB[keyName] or "" end,
|
||
function(value) SFramesDB[keyName] = value end,
|
||
"font", 11, apply)
|
||
for _, control in ipairs(optControls) do table.insert(controls, control) end
|
||
table.insert(controls, CreateSlider(fontSection, title .. "字号", 14, blockY - optHeight - 26, 180, 8, 24, 1,
|
||
function() return SFramesDB[sizeKey] end,
|
||
function(value) SFramesDB[sizeKey] = value end,
|
||
function(v) return tostring(math.floor(v + 0.5)) end,
|
||
apply
|
||
))
|
||
blockY = blockY - optHeight - 74
|
||
end
|
||
|
||
AddFontBlock("姓名字体", prefix .. "NameFontKey", prefix .. "NameFontSize")
|
||
AddFontBlock("生命值字体", prefix .. "HealthFontKey", prefix .. "HealthFontSize")
|
||
AddFontBlock("能量值字体", prefix .. "PowerFontKey", prefix .. "PowerFontSize")
|
||
|
||
if spec.extraFontBlocks then
|
||
for _, extra in ipairs(spec.extraFontBlocks) do
|
||
AddFontBlock(extra.title, extra.keyName, extra.sizeKey)
|
||
end
|
||
end
|
||
|
||
return y - (spec.fontSectionHeight or 286) - 12
|
||
end
|
||
|
||
local FRAME_DEFAULT_BUILDERS = {
|
||
player = function()
|
||
local portraitHeight = (SFrames.Config and SFrames.Config.height or 45) - 2
|
||
return {
|
||
enablePlayerFrame = true, playerFrameScale = 1, playerFrameWidth = 220,
|
||
playerPortraitWidth = 50, playerPortraitHeight = portraitHeight, playerPortraitOffsetX = 0, playerPortraitOffsetY = 0, playerPortraitMode = "head",
|
||
playerHealthHeight = 38, playerPowerHeight = 9, playerPowerWidth = 166, playerPowerOffsetX = 0, playerPowerOffsetY = 0, playerPowerOnTop = false,
|
||
playerShowClass = false, playerShowClassIcon = true, playerShowPortrait = true,
|
||
playerFrameAlpha = 1, playerBgAlpha = 0.9, playerPortraitBgAlpha = 0.9, playerShowBorder = false, playerCornerRadius = 0,
|
||
playerNameFontSize = 10, playerValueFontSize = 10, playerHealthFontSize = 10, playerPowerFontSize = 10,
|
||
playerHealthTexture = "", playerPowerTexture = "", playerNameFontKey = "", playerHealthFontKey = "", playerPowerFontKey = "",
|
||
castbarStandalone = true, castbarRainbow = true, castbarWidth = 280, castbarHeight = 20, castbarAlpha = 1, powerRainbow = false, showPetFrame = true, petFrameScale = 1,
|
||
playerLeaderIconOffsetX = 0, playerLeaderIconOffsetY = 0, playerRaidIconOffsetX = 0, playerRaidIconOffsetY = 0,
|
||
petHealthTexture = "", petPowerTexture = "",
|
||
}
|
||
end,
|
||
target = function()
|
||
local portraitHeight = (SFrames.Config and SFrames.Config.height or 45) - 2
|
||
return {
|
||
enableTargetFrame = true, targetFrameScale = 1, targetFrameWidth = 220,
|
||
targetPortraitWidth = 50, targetPortraitHeight = portraitHeight, targetPortraitOffsetX = 0, targetPortraitOffsetY = 0, targetPortraitMode = "head",
|
||
targetHealthHeight = 38, targetPowerHeight = 9, targetPowerWidth = 168, targetPowerOffsetX = 0, targetPowerOffsetY = 0, targetPowerOnTop = false,
|
||
targetShowClass = false, targetShowClassIcon = true, targetShowPortrait = true,
|
||
targetFrameAlpha = 1, targetBgAlpha = 0.9, targetPortraitBgAlpha = 0.9, targetShowBorder = false, targetCornerRadius = 0,
|
||
targetNameFontSize = 10, targetValueFontSize = 10, targetHealthFontSize = 10, targetPowerFontSize = 10,
|
||
targetHealthTexture = "", targetPowerTexture = "", targetNameFontKey = "", targetHealthFontKey = "", targetPowerFontKey = "",
|
||
targetDistanceEnabled = true, targetDistanceOnFrame = true, targetDistanceScale = 1, targetDistanceFontSize = 14, targetDistanceFontKey = "",
|
||
totHealthTexture = "",
|
||
}
|
||
end,
|
||
focus = function()
|
||
return {
|
||
focusEnabled = true, focusFrameScale = 0.9, focusFrameWidth = 200,
|
||
focusPortraitWidth = 45, focusPortraitHeight = 43, focusPortraitOffsetX = 0, focusPortraitOffsetY = 0, focusPortraitMode = "head",
|
||
focusHealthHeight = 32, focusPowerHeight = 10, focusPowerWidth = 153, focusPowerOffsetX = 0, focusPowerOffsetY = 0, focusPowerOnTop = false,
|
||
focusShowPortrait = true, focusShowCastBar = true, focusShowAuras = true,
|
||
focusBgAlpha = 0.9, focusPortraitBgAlpha = 0.9, focusShowBorder = false, focusCornerRadius = 0,
|
||
focusNameFontSize = 11, focusValueFontSize = 10, focusHealthFontSize = 10, focusPowerFontSize = 10, focusCastFontSize = 10, focusDistanceFontSize = 14,
|
||
focusHealthTexture = "", focusPowerTexture = "", focusNameFontKey = "", focusHealthFontKey = "", focusPowerFontKey = "", focusCastFontKey = "", focusDistanceFontKey = "",
|
||
}
|
||
end,
|
||
party = function()
|
||
return {
|
||
enablePartyFrame = true, partyLayout = "vertical", partyFrameScale = 1, partyFrameWidth = 150, partyFrameHeight = 35,
|
||
partyPortraitWidth = 33, partyPortraitHeight = 33, partyPortraitOffsetX = 0, partyPortraitOffsetY = 0, partyPortraitMode = "head",
|
||
partyHealthHeight = 22, partyPowerHeight = 10, partyPowerWidth = 112, partyPowerOffsetX = 0, partyPowerOffsetY = 0, partyPowerOnTop = false,
|
||
partyHorizontalGap = 8, partyVerticalGap = 30, partyBgAlpha = 0.9, partyPortraitBgAlpha = 0.9, partyShowBorder = false, partyCornerRadius = 0,
|
||
partyNameFontSize = 10, partyValueFontSize = 10, partyHealthFontSize = 10, partyPowerFontSize = 10,
|
||
partyHealthTexture = "", partyPowerTexture = "", partyNameFontKey = "", partyHealthFontKey = "", partyPowerFontKey = "",
|
||
partyShowBuffs = true, partyShowDebuffs = true, partyPortrait3D = true,
|
||
}
|
||
end,
|
||
raid = function()
|
||
return {
|
||
enableRaidFrames = true, raidLayout = "horizontal", raidFrameScale = 1, raidFrameWidth = 60, raidFrameHeight = 40,
|
||
raidHealthHeight = 31, raidPowerHeight = 6, raidPowerWidth = 58, raidPowerOffsetX = 0, raidPowerOffsetY = 0, raidPowerOnTop = false,
|
||
raidHorizontalGap = 2, raidVerticalGap = 2, raidGroupGap = 8, raidBgAlpha = 0.9, raidShowBorder = false, raidCornerRadius = 0,
|
||
raidNameFontSize = 10, raidValueFontSize = 9, raidHealthFontSize = 9, raidPowerFontSize = 9,
|
||
raidHealthTexture = "", raidPowerTexture = "", raidNameFontKey = "", raidHealthFontKey = "", raidPowerFontKey = "",
|
||
raidShowPower = true, raidHealthFormat = "compact", raidShowGroupLabel = true,
|
||
}
|
||
end,
|
||
}
|
||
|
||
local activePreviewDropdown
|
||
|
||
local function FindOptionEntry(options, key)
|
||
if type(options) ~= "table" then return nil end
|
||
for _, entry in ipairs(options) do
|
||
if entry.key == key then return entry end
|
||
end
|
||
return options[1]
|
||
end
|
||
|
||
local function ClosePreviewDropdown()
|
||
if activePreviewDropdown and activePreviewDropdown.Hide then activePreviewDropdown:Hide() end
|
||
activePreviewDropdown = nil
|
||
end
|
||
|
||
local function CreatePreviewDropdown(parent, label, x, y, width, options, getter, setter, previewKind, previewSize, onValueChanged)
|
||
local font = SFrames:GetFont()
|
||
CreateLabel(parent, label, x, y, font, 10, 0.85, 0.75, 0.80)
|
||
|
||
local button = CreateFrame("Button", NextWidgetName("PreviewDropdown"), parent)
|
||
button:SetWidth(width)
|
||
button:SetHeight(24)
|
||
button:SetPoint("TOPLEFT", parent, "TOPLEFT", x, y - 16)
|
||
button:SetBackdrop({ bgFile = "Interface\\Buttons\\WHITE8X8", edgeFile = "Interface\\Buttons\\WHITE8X8", tile = false, edgeSize = 1, insets = { left = 1, right = 1, top = 1, bottom = 1 } })
|
||
button:SetBackdropColor(0.08, 0.08, 0.10, 0.92)
|
||
button:SetBackdropBorderColor(0.3, 0.3, 0.35, 1)
|
||
|
||
local preview = button:CreateFontString(nil, "OVERLAY")
|
||
preview:SetPoint("LEFT", button, "LEFT", 8, 0)
|
||
preview:SetPoint("RIGHT", button, "RIGHT", -24, 0)
|
||
preview:SetJustifyH("LEFT")
|
||
preview:SetTextColor(0.92, 0.92, 0.92)
|
||
|
||
local arrow = button:CreateFontString(nil, "OVERLAY")
|
||
arrow:SetFont(font, 10, "OUTLINE")
|
||
arrow:SetPoint("RIGHT", button, "RIGHT", -8, 0)
|
||
arrow:SetText("▼")
|
||
arrow:SetTextColor(0.8, 0.75, 0.8)
|
||
|
||
local menu = CreateFrame("Frame", NextWidgetName("PreviewDropdownMenu"), UIParent)
|
||
menu:SetWidth(width)
|
||
menu:SetHeight((table.getn(options or {}) * 22) + 8)
|
||
menu:SetFrameStrata("DIALOG")
|
||
menu:SetClampedToScreen(true)
|
||
EnsureSoftBackdrop(menu)
|
||
menu:SetBackdropColor(0.05, 0.05, 0.07, 0.96)
|
||
menu:SetBackdropBorderColor(0.45, 0.38, 0.46, 1)
|
||
menu:Hide()
|
||
button.menu = menu
|
||
|
||
for index, entry in ipairs(options or {}) do
|
||
local item = CreateFrame("Button", NextWidgetName("PreviewDropdownItem"), menu)
|
||
item:SetWidth(width - 10)
|
||
item:SetHeight(20)
|
||
item:SetPoint("TOPLEFT", menu, "TOPLEFT", 5, -5 - ((index - 1) * 22))
|
||
item.optionKey = entry.key
|
||
|
||
local itemText = item:CreateFontString(nil, "OVERLAY")
|
||
itemText:SetPoint("LEFT", item, "LEFT", 6, 0)
|
||
itemText:SetPoint("RIGHT", item, "RIGHT", -6, 0)
|
||
itemText:SetJustifyH("LEFT")
|
||
itemText:SetText(entry.label)
|
||
if previewKind == "font" then
|
||
local ok = pcall(itemText.SetFont, itemText, entry.path, previewSize or 11, "OUTLINE")
|
||
if not ok then itemText:SetFont(font, previewSize or 11, "OUTLINE") end
|
||
else
|
||
itemText:SetFont(font, 10, "OUTLINE")
|
||
end
|
||
itemText:SetTextColor(0.9, 0.9, 0.9)
|
||
item.text = itemText
|
||
|
||
item:SetScript("OnEnter", function() this.text:SetTextColor(1, 0.88, 0.55) end)
|
||
item:SetScript("OnLeave", function() this.text:SetTextColor(0.9, 0.9, 0.9) end)
|
||
item:SetScript("OnClick", function()
|
||
setter(this.optionKey)
|
||
ClosePreviewDropdown()
|
||
if button.Refresh then button:Refresh() end
|
||
if onValueChanged then onValueChanged(this.optionKey) end
|
||
end)
|
||
end
|
||
|
||
button:SetScript("OnHide", function() if this.menu then this.menu:Hide() end end)
|
||
button:SetScript("OnClick", function()
|
||
if this.menu:IsShown() then
|
||
ClosePreviewDropdown()
|
||
return
|
||
end
|
||
ClosePreviewDropdown()
|
||
this.menu:ClearAllPoints()
|
||
this.menu:SetPoint("TOPLEFT", this, "BOTTOMLEFT", 0, -2)
|
||
this.menu:Show()
|
||
activePreviewDropdown = this.menu
|
||
end)
|
||
|
||
button.Refresh = function()
|
||
local entry = FindOptionEntry(options, getter() or "")
|
||
if not entry then return end
|
||
preview:SetText(entry.label)
|
||
if previewKind == "font" then
|
||
local ok = pcall(preview.SetFont, preview, entry.path, previewSize or 11, "OUTLINE")
|
||
if not ok then preview:SetFont(font, previewSize or 11, "OUTLINE") end
|
||
else
|
||
preview:SetFont(font, 10, "OUTLINE")
|
||
end
|
||
end
|
||
|
||
button:Refresh()
|
||
return button
|
||
end
|
||
|
||
local function ResetFrameDefaults(kind)
|
||
local builder = FRAME_DEFAULT_BUILDERS[kind]
|
||
if not builder then return end
|
||
for key, value in pairs(builder()) do
|
||
SFramesDB[key] = value
|
||
end
|
||
end
|
||
|
||
local function CreateResetButton(parent, x, y, frameKind, refreshFn, controls)
|
||
return CreateButton(parent, "恢复默认", x, y, 100, 22, function()
|
||
ClosePreviewDropdown()
|
||
ResetFrameDefaults(frameKind)
|
||
EnsureDB()
|
||
if controls then SFrames.ConfigUI:RefreshControls(controls) end
|
||
if refreshFn then refreshFn() end
|
||
end)
|
||
end
|
||
|
||
local function AddFontControl(parent, controls, label, keyName, sizeKey, x, y, apply, sizeMin, sizeMax)
|
||
table.insert(controls, CreatePreviewDropdown(parent, label .. "字体", x, y, 220, SFrames.FontChoices or {},
|
||
function() return SFramesDB[keyName] or "" end,
|
||
function(value) SFramesDB[keyName] = value end,
|
||
"font", 11, apply))
|
||
table.insert(controls, CreateSlider(parent, label .. "字号", x + 244, y - 4, 180, sizeMin or 8, sizeMax or 24, 1,
|
||
function() return SFramesDB[sizeKey] end,
|
||
function(value) SFramesDB[sizeKey] = value end,
|
||
function(v) return tostring(math.floor(v + 0.5)) end,
|
||
apply))
|
||
end
|
||
|
||
local function AddFontControl(parent, controls, label, keyName, sizeKey, x, y, apply, sizeMin, sizeMax)
|
||
CreateLabel(parent, label .. "字体", x, y, SFrames:GetFont(), 10, 0.85, 0.75, 0.80)
|
||
|
||
local choices = SFrames.FontChoices or {}
|
||
local previewBtn = CreateFrame("Button", NextWidgetName("FontPickerSafe"), parent)
|
||
previewBtn:SetWidth(220)
|
||
previewBtn:SetHeight(24)
|
||
previewBtn:SetPoint("TOPLEFT", parent, "TOPLEFT", x, y - 16)
|
||
previewBtn:SetBackdrop({
|
||
bgFile = "Interface\\Buttons\\WHITE8X8",
|
||
edgeFile = "Interface\\Buttons\\WHITE8X8",
|
||
tile = false, edgeSize = 1,
|
||
insets = { left = 1, right = 1, top = 1, bottom = 1 },
|
||
})
|
||
previewBtn:SetBackdropColor(0.08, 0.08, 0.10, 0.92)
|
||
previewBtn:SetBackdropBorderColor(0.3, 0.3, 0.35, 1)
|
||
|
||
local previewText = previewBtn:CreateFontString(nil, "OVERLAY")
|
||
previewText:SetPoint("LEFT", previewBtn, "LEFT", 8, 0)
|
||
previewText:SetPoint("RIGHT", previewBtn, "RIGHT", -8, 0)
|
||
previewText:SetJustifyH("LEFT")
|
||
previewText:SetTextColor(0.92, 0.92, 0.92)
|
||
|
||
local function FindFontIndex()
|
||
local current = SFramesDB[keyName] or ""
|
||
for idx, entry in ipairs(choices) do
|
||
if entry.key == current then
|
||
return idx
|
||
end
|
||
end
|
||
return 1
|
||
end
|
||
|
||
local function ApplyFontIndex(index)
|
||
local entry = choices[index] or choices[1]
|
||
if not entry then return end
|
||
SFramesDB[keyName] = entry.key
|
||
previewText:SetText(entry.label)
|
||
local ok = pcall(previewText.SetFont, previewText, entry.path, 11, "OUTLINE")
|
||
if not ok then
|
||
previewText:SetFont(SFrames:GetFont(), 11, "OUTLINE")
|
||
end
|
||
if apply then apply() end
|
||
end
|
||
|
||
previewBtn.Refresh = function()
|
||
ApplyFontIndex(FindFontIndex())
|
||
end
|
||
previewBtn:SetScript("OnClick", function()
|
||
local idx = FindFontIndex() + 1
|
||
if idx > table.getn(choices) then idx = 1 end
|
||
ApplyFontIndex(idx)
|
||
end)
|
||
previewBtn:Refresh()
|
||
table.insert(controls, previewBtn)
|
||
|
||
table.insert(controls, CreateButton(parent, "<", x + 224, y - 16, 24, 24, function()
|
||
local idx = FindFontIndex() - 1
|
||
if idx < 1 then idx = table.getn(choices) end
|
||
ApplyFontIndex(idx)
|
||
end))
|
||
|
||
table.insert(controls, CreateButton(parent, ">", x + 252, y - 16, 24, 24, function()
|
||
local idx = FindFontIndex() + 1
|
||
if idx > table.getn(choices) then idx = 1 end
|
||
ApplyFontIndex(idx)
|
||
end))
|
||
|
||
table.insert(controls, CreateSlider(parent, label .. "字号", x + 284, y - 4, 140, sizeMin or 8, sizeMax or 24, 1,
|
||
function() return SFramesDB[sizeKey] end,
|
||
function(value) SFramesDB[sizeKey] = value end,
|
||
function(v) return tostring(math.floor(v + 0.5)) end,
|
||
apply))
|
||
end
|
||
|
||
function SFrames.ConfigUI:BuildPlayerPage()
|
||
local font = SFrames:GetFont()
|
||
local page = self.playerPage
|
||
local controls = {}
|
||
|
||
local playerScroll = CreateScrollArea(page, 4, -4, 552, 420, 1960)
|
||
local root = playerScroll.child
|
||
|
||
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(root, "玩家框体", 8, -8, 520, 402, font)
|
||
|
||
table.insert(controls, CreateCheckBox(playerSection,
|
||
"启用 Nanami 玩家框体(需 /reload)", 12, -28,
|
||
function() return SFramesDB.enablePlayerFrame ~= false end,
|
||
function(checked) SFramesDB.enablePlayerFrame = checked end
|
||
))
|
||
table.insert(controls, CreateResetButton(playerSection, 356, -24, "player", RefreshPlayer, controls))
|
||
|
||
table.insert(controls, CreateSlider(playerSection, "缩放", 14, -72, 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, -72, 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, -72, 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, -134, 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, -134, 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, -132,
|
||
function() return SFramesDB.playerShowClass ~= false end,
|
||
function(checked) SFramesDB.playerShowClass = checked end,
|
||
function() RefreshPlayer() end
|
||
))
|
||
|
||
table.insert(controls, CreateCheckBox(playerSection,
|
||
"玩家显示职业图标", 326, -158,
|
||
function() return SFramesDB.playerShowClassIcon ~= false end,
|
||
function(checked) SFramesDB.playerShowClassIcon = checked end,
|
||
function() RefreshPlayer() end
|
||
))
|
||
|
||
table.insert(controls, CreateCheckBox(playerSection,
|
||
"启用3D头像", 12, -188,
|
||
function() return SFramesDB.playerShowPortrait ~= false end,
|
||
function(checked) SFramesDB.playerShowPortrait = checked end,
|
||
function() RefreshPlayer() end
|
||
))
|
||
|
||
table.insert(controls, CreateSlider(playerSection, "姓名字号", 14, -226, 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, -226, 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
|
||
))
|
||
|
||
table.insert(controls, CreateSlider(playerSection, "透明度", 326, -226, 130, 0.1, 1.0, 0.05,
|
||
function() return SFramesDB.playerFrameAlpha end,
|
||
function(value) SFramesDB.playerFrameAlpha = value end,
|
||
function(v) return string.format("%.0f%%", v * 100) end,
|
||
function(value)
|
||
if SFrames.Player and SFrames.Player.frame then
|
||
SFrames.Player.frame:SetAlpha(value)
|
||
end
|
||
end
|
||
))
|
||
|
||
table.insert(controls, CreateSlider(playerSection, "背景透明度", 14, -288, 150, 0, 1.0, 0.05,
|
||
function() return SFramesDB.playerBgAlpha end,
|
||
function(value) SFramesDB.playerBgAlpha = value end,
|
||
function(v) return string.format("%.0f%%", v * 100) end,
|
||
function(value)
|
||
if SFrames.Player and SFrames.Player.frame then
|
||
local f = SFrames.Player.frame
|
||
ApplyBgAlphaToFrame(f, value)
|
||
if f.healthBGFrame then ApplyBgAlphaToFrame(f.healthBGFrame, value) end
|
||
if f.powerBGFrame then ApplyBgAlphaToFrame(f.powerBGFrame, value) end
|
||
if f.portraitBG then ApplyBgAlphaToFrame(f.portraitBG, value) end
|
||
end
|
||
end
|
||
))
|
||
|
||
CreateLabel(playerSection,
|
||
"提示:关闭头像后职业图标移到框体最左侧,框体仅显示血条。",
|
||
14, -355, font, 10, 0.9, 0.9, 0.9)
|
||
|
||
-- ── 施法条 ──────────────────────────────────────────────────
|
||
local cbSection = CreateSection(root, "施法条", 8, -418, 520, 250, font)
|
||
|
||
table.insert(controls, CreateCheckBox(cbSection,
|
||
"独立施法条(显示在屏幕下方)", 14, -34,
|
||
function() return SFramesDB.castbarStandalone == true end,
|
||
function(checked) SFramesDB.castbarStandalone = checked end,
|
||
function()
|
||
if SFrames.Player and SFrames.Player.ApplyCastbarPosition then
|
||
SFrames.Player:ApplyCastbarPosition()
|
||
end
|
||
end
|
||
))
|
||
CreateDesc(cbSection, "关闭时施法条依附玩家框体上方,开启后独立显示在屏幕下方中央", 36, -50, font)
|
||
|
||
table.insert(controls, CreateCheckBox(cbSection,
|
||
"启用彩虹色施法条", 14, -74,
|
||
function() return SFramesDB.castbarRainbow == true end,
|
||
function(checked) SFramesDB.castbarRainbow = checked end
|
||
))
|
||
CreateDesc(cbSection, "施法时条颜色会随时间流转变化", 36, -90, font)
|
||
|
||
table.insert(controls, CreateSlider(cbSection, "独立施法条宽度", 14, -120, 200, 100, 500, 5,
|
||
function() return SFramesDB.castbarWidth or 280 end,
|
||
function(value) SFramesDB.castbarWidth = value end,
|
||
function(v) return tostring(math.floor(v + 0.5)) end,
|
||
function()
|
||
if SFrames.Player and SFrames.Player.ApplyCastbarPosition then
|
||
SFrames.Player:ApplyCastbarPosition()
|
||
end
|
||
end
|
||
))
|
||
|
||
table.insert(controls, CreateSlider(cbSection, "独立施法条高度", 270, -120, 200, 10, 60, 1,
|
||
function() return SFramesDB.castbarHeight or 20 end,
|
||
function(value) SFramesDB.castbarHeight = value end,
|
||
function(v) return tostring(math.floor(v + 0.5)) end,
|
||
function()
|
||
if SFrames.Player and SFrames.Player.ApplyCastbarPosition then
|
||
SFrames.Player:ApplyCastbarPosition()
|
||
end
|
||
end
|
||
))
|
||
|
||
table.insert(controls, CreateSlider(cbSection, "施法条透明度", 14, -160, 200, 0.1, 1.0, 0.05,
|
||
function() return SFramesDB.castbarAlpha or 1 end,
|
||
function(value) SFramesDB.castbarAlpha = value end,
|
||
function(v) return string.format("%.0f%%", v * 100) end,
|
||
function(value)
|
||
local cb = SFrames.Player and SFrames.Player.frame and SFrames.Player.frame.castbar
|
||
if cb and cb:IsShown() then
|
||
cb:SetAlpha(value)
|
||
if cb.cbbg then cb.cbbg:SetAlpha(value) end
|
||
end
|
||
end
|
||
))
|
||
|
||
-- ── 宠物框体 ──────────────────────────────────────────────────
|
||
local petSection = CreateSection(root, "宠物框体", 8, -676, 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
|
||
))
|
||
|
||
-- ── 图标位置 ──────────────────────────────────────────────────
|
||
local iconSection = CreateSection(root, "图标位置", 8, -754, 520, 142, font)
|
||
table.insert(controls, CreateSlider(iconSection, "队长图标 X", 14, -44, 150, -80, 80, 1,
|
||
function() return SFramesDB.playerLeaderIconOffsetX or 0 end,
|
||
function(value) SFramesDB.playerLeaderIconOffsetX = value end,
|
||
function(v) return tostring(math.floor(v + 0.5)) end,
|
||
RefreshPlayer
|
||
))
|
||
table.insert(controls, CreateSlider(iconSection, "队长图标 Y", 170, -44, 150, -80, 80, 1,
|
||
function() return SFramesDB.playerLeaderIconOffsetY or 0 end,
|
||
function(value) SFramesDB.playerLeaderIconOffsetY = value end,
|
||
function(v) return tostring(math.floor(v + 0.5)) end,
|
||
RefreshPlayer
|
||
))
|
||
table.insert(controls, CreateSlider(iconSection, "战斗标记 X", 14, -104, 150, -120, 120, 1,
|
||
function() return SFramesDB.playerRaidIconOffsetX or 0 end,
|
||
function(value) SFramesDB.playerRaidIconOffsetX = value end,
|
||
function(v) return tostring(math.floor(v + 0.5)) end,
|
||
RefreshPlayer
|
||
))
|
||
table.insert(controls, CreateSlider(iconSection, "战斗标记 Y", 170, -104, 150, -80, 80, 1,
|
||
function() return SFramesDB.playerRaidIconOffsetY or 0 end,
|
||
function(value) SFramesDB.playerRaidIconOffsetY = value end,
|
||
function(v) return tostring(math.floor(v + 0.5)) end,
|
||
RefreshPlayer
|
||
))
|
||
|
||
AddFrameAdvancedSections(root, controls, {
|
||
prefix = "player",
|
||
startY = -944,
|
||
apply = RefreshPlayer,
|
||
})
|
||
|
||
local function RefreshPet()
|
||
if SFrames.Pet and SFrames.Pet.ApplyConfig then SFrames.Pet:ApplyConfig() end
|
||
if SFrames.Pet and SFrames.Pet.UpdateAll then SFrames.Pet:UpdateAll() end
|
||
end
|
||
|
||
local petTexSection = CreateSection(root, "宠物框体 条材质", 8, -1800, 520, 182, font)
|
||
local petTexControls, petTexH = CreateOptionGrid(petTexSection, "血条材质", 14, -34, 490, SFrames.BarTextures or {},
|
||
function() return SFramesDB.petHealthTexture or "" end,
|
||
function(value) SFramesDB.petHealthTexture = value end,
|
||
"bar", 0, RefreshPet)
|
||
for _, c in ipairs(petTexControls) do table.insert(controls, c) end
|
||
local petTexControls2 = CreateOptionGrid(petTexSection, "能量条材质", 14, -(34 + petTexH + 12), 490, SFrames.BarTextures or {},
|
||
function() return SFramesDB.petPowerTexture or "" end,
|
||
function(value) SFramesDB.petPowerTexture = value end,
|
||
"bar", 0, RefreshPet)
|
||
for _, c in ipairs(petTexControls2) do table.insert(controls, c) end
|
||
|
||
playerScroll:UpdateRange()
|
||
self.playerControls = controls
|
||
self.playerScroll = playerScroll
|
||
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 targetScroll = CreateScrollArea(page, 4, -4, 548, 420, 1780)
|
||
local targetRoot = targetScroll.child
|
||
|
||
local targetSection = CreateSection(targetRoot, "目标框体", 8, -8, 520, 460, font)
|
||
|
||
table.insert(controls, CreateCheckBox(targetSection,
|
||
"启用 Nanami 目标框体(需 /reload)", 12, -28,
|
||
function() return SFramesDB.enableTargetFrame ~= false end,
|
||
function(checked) SFramesDB.enableTargetFrame = checked end
|
||
))
|
||
table.insert(controls, CreateResetButton(targetSection, 356, -24, "target", RefreshTarget, controls))
|
||
|
||
table.insert(controls, CreateSlider(targetSection, "缩放", 14, -72, 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, -72, 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, -72, 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, -134, 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, -134, 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, -132,
|
||
function() return SFramesDB.targetShowClass ~= false end,
|
||
function(checked) SFramesDB.targetShowClass = checked end,
|
||
function() RefreshTarget() end
|
||
))
|
||
|
||
table.insert(controls, CreateCheckBox(targetSection,
|
||
"目标显示职业图标", 326, -158,
|
||
function() return SFramesDB.targetShowClassIcon ~= false end,
|
||
function(checked) SFramesDB.targetShowClassIcon = checked end,
|
||
function() RefreshTarget() end
|
||
))
|
||
|
||
table.insert(controls, CreateCheckBox(targetSection,
|
||
"启用3D头像", 12, -188,
|
||
function() return SFramesDB.targetShowPortrait ~= false end,
|
||
function(checked) SFramesDB.targetShowPortrait = checked end,
|
||
function() RefreshTarget() end
|
||
))
|
||
|
||
table.insert(controls, CreateSlider(targetSection, "姓名字号", 14, -226, 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, -226, 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, CreateSlider(targetSection, "透明度", 326, -226, 130, 0.1, 1.0, 0.05,
|
||
function() return SFramesDB.targetFrameAlpha end,
|
||
function(value) SFramesDB.targetFrameAlpha = value end,
|
||
function(v) return string.format("%.0f%%", v * 100) end,
|
||
function(value)
|
||
if SFrames.Target and SFrames.Target.frame then
|
||
SFrames.Target.frame:SetAlpha(value)
|
||
end
|
||
end
|
||
))
|
||
|
||
table.insert(controls, CreateSlider(targetSection, "背景透明度", 170, -288, 150, 0, 1.0, 0.05,
|
||
function() return SFramesDB.targetBgAlpha end,
|
||
function(value) SFramesDB.targetBgAlpha = value end,
|
||
function(v) return string.format("%.0f%%", v * 100) end,
|
||
function(value)
|
||
if SFrames.Target and SFrames.Target.frame then
|
||
local f = SFrames.Target.frame
|
||
ApplyBgAlphaToFrame(f, value)
|
||
if f.healthBGFrame then ApplyBgAlphaToFrame(f.healthBGFrame, value) end
|
||
if f.powerBGFrame then ApplyBgAlphaToFrame(f.powerBGFrame, value) end
|
||
if f.portraitBG then ApplyBgAlphaToFrame(f.portraitBG, value) end
|
||
end
|
||
end
|
||
))
|
||
|
||
table.insert(controls, CreateCheckBox(targetSection,
|
||
"启用目标距离文本", 326, -326,
|
||
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
|
||
if SFrames.Target and SFrames.Target.frame and SFrames.Target.frame.distText then
|
||
if not checked then SFrames.Target.frame.distText:Hide() end
|
||
end
|
||
end
|
||
))
|
||
|
||
table.insert(controls, CreateCheckBox(targetSection,
|
||
"距离显示在框体上", 326, -354,
|
||
function() return SFramesDB.targetDistanceOnFrame ~= false end,
|
||
function(checked) SFramesDB.targetDistanceOnFrame = checked end,
|
||
function()
|
||
if SFrames.Target then SFrames.Target:OnTargetChanged() end
|
||
end
|
||
))
|
||
|
||
table.insert(controls, CreateSlider(targetSection, "距离文本缩放(独立框体)", 14, -406, 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.ApplyDistanceScale then
|
||
SFrames.Target:ApplyDistanceScale(value)
|
||
end
|
||
end
|
||
))
|
||
|
||
table.insert(controls, CreateSlider(targetSection, "距离文字大小", 200, -406, 150, 8, 24, 1,
|
||
function() return SFramesDB.targetDistanceFontSize or 14 end,
|
||
function(value) SFramesDB.targetDistanceFontSize = value end,
|
||
function(v) return tostring(math.floor(v + 0.5)) end,
|
||
function(value)
|
||
if SFrames.Target and SFrames.Target.ApplyDistanceScale then
|
||
SFrames.Target:ApplyDistanceScale(SFramesDB.targetDistanceScale or 1)
|
||
end
|
||
if SFrames.Target and SFrames.Target.ApplyConfig then
|
||
SFrames.Target:ApplyConfig()
|
||
end
|
||
end
|
||
))
|
||
|
||
AddFrameAdvancedSections(targetRoot, controls, {
|
||
prefix = "target",
|
||
startY = -516,
|
||
apply = RefreshTarget,
|
||
extraFontBlocks = {
|
||
{ title = "距离文字字体", keyName = "targetDistanceFontKey", sizeKey = "targetDistanceFontSize" },
|
||
},
|
||
fontSectionHeight = 372,
|
||
})
|
||
|
||
local function RefreshToT()
|
||
if SFrames.ToT and SFrames.ToT.ApplyConfig then SFrames.ToT:ApplyConfig() end
|
||
end
|
||
|
||
local totTexSection = CreateSection(targetRoot, "目标的目标 条材质", 8, -1560, 520, 100, font)
|
||
local totTexControls, totTexH = CreateOptionGrid(totTexSection, "血条材质", 14, -34, 490, SFrames.BarTextures or {},
|
||
function() return SFramesDB.totHealthTexture or "" end,
|
||
function(value) SFramesDB.totHealthTexture = value end,
|
||
"bar", 0, RefreshToT)
|
||
for _, c in ipairs(totTexControls) do table.insert(controls, c) end
|
||
|
||
targetScroll:UpdateRange()
|
||
self.targetControls = controls
|
||
self.targetScroll = targetScroll
|
||
end
|
||
|
||
function SFrames.ConfigUI:BuildFocusPage()
|
||
local font = SFrames:GetFont()
|
||
local page = self.focusPage
|
||
local controls = {}
|
||
|
||
local focusScroll = CreateScrollArea(page, 4, -4, 548, 420, 1860)
|
||
local root = focusScroll.child
|
||
local focusSection = CreateSection(root, "焦点框体", 8, -8, 520, 390, font)
|
||
|
||
local function FocusApply()
|
||
if SFrames.Focus and SFrames.Focus.ApplySettings then
|
||
SFrames.Focus:ApplySettings()
|
||
end
|
||
end
|
||
|
||
table.insert(controls, CreateCheckBox(focusSection,
|
||
"启用焦点框体(需 /reload)", 12, -28,
|
||
function() return SFramesDB.focusEnabled ~= false end,
|
||
function(checked) SFramesDB.focusEnabled = checked end
|
||
))
|
||
table.insert(controls, CreateResetButton(focusSection, 356, -24, "focus", FocusApply, controls))
|
||
|
||
table.insert(controls, CreateSlider(focusSection, "缩放", 14, -72, 150, 0.5, 1.8, 0.05,
|
||
function() return SFramesDB.focusFrameScale end,
|
||
function(v) SFramesDB.focusFrameScale = v; FocusApply() end,
|
||
function(v) return string.format("%.0f%%", v * 100) end
|
||
))
|
||
|
||
table.insert(controls, CreateSlider(focusSection, "宽度", 280, -72, 150, 140, 320, 5,
|
||
function() return SFramesDB.focusFrameWidth end,
|
||
function(v) SFramesDB.focusFrameWidth = v; FocusApply() end,
|
||
function(v) return string.format("%.0f", v) end
|
||
))
|
||
|
||
table.insert(controls, CreateSlider(focusSection, "血条高度", 14, -126, 150, 16, 60, 1,
|
||
function() return SFramesDB.focusHealthHeight end,
|
||
function(v) SFramesDB.focusHealthHeight = v; FocusApply() end,
|
||
function(v) return string.format("%.0f", v) end
|
||
))
|
||
|
||
table.insert(controls, CreateSlider(focusSection, "蓝条高度", 280, -126, 150, 4, 20, 1,
|
||
function() return SFramesDB.focusPowerHeight end,
|
||
function(v) SFramesDB.focusPowerHeight = v; FocusApply() end,
|
||
function(v) return string.format("%.0f", v) end
|
||
))
|
||
|
||
table.insert(controls, CreateSlider(focusSection, "头像宽度", 14, -180, 150, 0, 80, 1,
|
||
function() return SFramesDB.focusPortraitWidth end,
|
||
function(v) SFramesDB.focusPortraitWidth = v; FocusApply() end,
|
||
function(v) return string.format("%.0f", v) end
|
||
))
|
||
|
||
table.insert(controls, CreateSlider(focusSection, "背景透明度", 280, -180, 150, 0, 1, 0.05,
|
||
function() return SFramesDB.focusBgAlpha end,
|
||
function(v) SFramesDB.focusBgAlpha = v; FocusApply() end,
|
||
function(v) return string.format("%.0f%%", v * 100) end
|
||
))
|
||
|
||
table.insert(controls, CreateCheckBox(focusSection,
|
||
"显示 3D 头像", 12, -230,
|
||
function() return SFramesDB.focusShowPortrait ~= false end,
|
||
function(checked) SFramesDB.focusShowPortrait = checked; FocusApply() end
|
||
))
|
||
|
||
table.insert(controls, CreateCheckBox(focusSection,
|
||
"显示施法条", 270, -230,
|
||
function() return SFramesDB.focusShowCastBar ~= false end,
|
||
function(checked) SFramesDB.focusShowCastBar = checked; FocusApply() end
|
||
))
|
||
|
||
table.insert(controls, CreateCheckBox(focusSection,
|
||
"显示 Buff/Debuff", 12, -260,
|
||
function() return SFramesDB.focusShowAuras ~= false end,
|
||
function(checked) SFramesDB.focusShowAuras = checked; FocusApply() end
|
||
))
|
||
|
||
table.insert(controls, CreateSlider(focusSection, "姓名字号", 14, -304, 150, 8, 16, 1,
|
||
function() return SFramesDB.focusNameFontSize end,
|
||
function(v) SFramesDB.focusNameFontSize = v; FocusApply() end,
|
||
function(v) return string.format("%.0f", v) end
|
||
))
|
||
|
||
table.insert(controls, CreateSlider(focusSection, "数值字号", 280, -304, 150, 7, 14, 1,
|
||
function() return SFramesDB.focusValueFontSize end,
|
||
function(v) SFramesDB.focusValueFontSize = v; FocusApply() end,
|
||
function(v) return string.format("%.0f", v) end
|
||
))
|
||
|
||
CreateLabel(focusSection, "操作: Shift+左键目标框体 = 设为焦点 | 右键目标框体菜单 = 设为/取消焦点", 14, -348, font, 9, 0.65, 0.58, 0.62)
|
||
CreateLabel(focusSection, "命令: /nui focus (设焦点) | /nui unfocus (取消) | /nui focustarget (选中焦点)", 14, -364, font, 9, 0.65, 0.58, 0.62)
|
||
CreateLabel(focusSection, "提示: 启用/禁用焦点框体需要 /reload,其余设置实时生效。", 14, -384, font, 10, 0.6, 0.6, 0.65)
|
||
|
||
AddFrameAdvancedSections(root, controls, {
|
||
prefix = "focus",
|
||
startY = -418,
|
||
apply = FocusApply,
|
||
extraFontBlocks = {
|
||
{ title = "施法条字体", keyName = "focusCastFontKey", sizeKey = "focusCastFontSize" },
|
||
{ title = "距离文字字体", keyName = "focusDistanceFontKey", sizeKey = "focusDistanceFontSize" },
|
||
},
|
||
fontSectionHeight = 458,
|
||
})
|
||
|
||
focusScroll:UpdateRange()
|
||
self.focusControls = controls
|
||
self.focusScroll = focusScroll
|
||
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, 420, 1720)
|
||
local root = uiScroll.child
|
||
|
||
local partySection = CreateSection(root, "小队", 8, -8, 520, 460, font)
|
||
|
||
table.insert(controls, CreateCheckBox(partySection,
|
||
"启用 Nanami 小队框体(需 /reload)", 12, -28,
|
||
function() return SFramesDB.enablePartyFrame ~= false end,
|
||
function(checked) SFramesDB.enablePartyFrame = checked end
|
||
))
|
||
table.insert(controls, CreateResetButton(partySection, 356, -24, "party", RefreshParty, controls))
|
||
|
||
CreateButton(partySection, "解锁小队框架", 14, -52, 130, 22, function()
|
||
if SFrames and SFrames.UnlockFrames then SFrames:UnlockFrames() end
|
||
end)
|
||
|
||
CreateButton(partySection, "锁定小队框架", 154, -52, 130, 22, function()
|
||
if SFrames and SFrames.LockFrames then SFrames:LockFrames() end
|
||
end)
|
||
|
||
table.insert(controls, CreateCheckBox(partySection,
|
||
"横向布局(关闭为竖向)", 12, -86,
|
||
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, -134, 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, -134, 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, -134, 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, -196, 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, -196, 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, -196, 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, -258, 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, -258, 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, -258, 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, -320, 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, CreateSlider(partySection, "背景透明度", 170, -320, 150, 0, 1.0, 0.05,
|
||
function() return SFramesDB.partyBgAlpha end,
|
||
function(value) SFramesDB.partyBgAlpha = value end,
|
||
function(v) return string.format("%.0f%%", v * 100) end,
|
||
function(value)
|
||
if SFrames.Party and SFrames.Party.frames then
|
||
for _, f in ipairs(SFrames.Party.frames) do
|
||
if f then
|
||
if f.pbg then ApplyBgAlphaToFrame(f.pbg, value) end
|
||
if f.healthBGFrame then ApplyBgAlphaToFrame(f.healthBGFrame, value) end
|
||
end
|
||
end
|
||
end
|
||
end
|
||
))
|
||
|
||
table.insert(controls, CreateCheckBox(partySection,
|
||
"显示小队增益", 14, -380,
|
||
function() return SFramesDB.partyShowBuffs ~= false end,
|
||
function(checked) SFramesDB.partyShowBuffs = checked end,
|
||
function() RefreshParty() end
|
||
))
|
||
|
||
table.insert(controls, CreateCheckBox(partySection,
|
||
"显示小队减益", 170, -380,
|
||
function() return SFramesDB.partyShowDebuffs ~= false end,
|
||
function(checked) SFramesDB.partyShowDebuffs = checked end,
|
||
function() RefreshParty() end
|
||
))
|
||
|
||
table.insert(controls, CreateCheckBox(partySection,
|
||
"启用3D头像", 326, -380,
|
||
function() return SFramesDB.partyPortrait3D ~= false end,
|
||
function(checked) SFramesDB.partyPortrait3D = checked end,
|
||
function() RefreshParty() end
|
||
))
|
||
|
||
CreateButton(partySection, "小队测试模式", 326, -410, 130, 22, function()
|
||
if SFrames.Party and SFrames.Party.TestMode then SFrames.Party:TestMode() end
|
||
end)
|
||
AddFrameAdvancedSections(root, controls, {
|
||
prefix = "party",
|
||
startY = -486,
|
||
apply = RefreshParty,
|
||
})
|
||
|
||
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, 700)
|
||
local root = bagScroll.child
|
||
|
||
local bagSection = CreateSection(root, "背包", 8, -8, 502, 300, 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
|
||
))
|
||
|
||
table.insert(controls, CreateSlider(bagSection, "背包透明度", 258, -150, 220, 0.1, 1.0, 0.05,
|
||
function() return SFramesDB.Bags.alpha or 1 end,
|
||
function(value) SFramesDB.Bags.alpha = value end,
|
||
function(value) return string.format("%.0f%%", value * 100) end,
|
||
function(value)
|
||
if SFramesBagFrame then SFramesBagFrame:SetAlpha(value) end
|
||
end
|
||
))
|
||
|
||
table.insert(controls, CreateSlider(bagSection, "背包背景透明度", 16, -196, 220, 0, 1.0, 0.05,
|
||
function() return SFramesDB.Bags.bgAlpha or 0.95 end,
|
||
function(value) SFramesDB.Bags.bgAlpha = value end,
|
||
function(value) return string.format("%.0f%%", value * 100) end,
|
||
function(value)
|
||
if SFramesBagFrame then ApplyBgAlphaToFrame(SFramesBagFrame, value) end
|
||
end
|
||
))
|
||
|
||
CreateButton(bagSection, "打开背包", 258, -196, 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, -316, 502, 215, 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
|
||
))
|
||
|
||
table.insert(controls, CreateSlider(bankSection, "银行透明度", 258, -110, 220, 0.1, 1.0, 0.05,
|
||
function() return SFramesDB.Bags.bankAlpha or 1 end,
|
||
function(value) SFramesDB.Bags.bankAlpha = value end,
|
||
function(value) return string.format("%.0f%%", value * 100) end,
|
||
function(value)
|
||
if SFramesBankFrame then SFramesBankFrame:SetAlpha(value) end
|
||
end
|
||
))
|
||
|
||
CreateButton(bankSection, "打开离线银行", 16, -170, 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, -481, 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()
|
||
return 0 -- No longer using embedded DB file
|
||
end
|
||
|
||
local dbCount = CountDBEntries()
|
||
local statusLabel = CreateLabel(priceSection,
|
||
string.format("已学习: %d 条 (API模式, 无内置数据库)", 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 ShaguTweaks and ShaguTweaks.SellValueDB and ShaguTweaks.SellValueDB[id] and ShaguTweaks.SellValueDB[id] > 0 then
|
||
price = ShaguTweaks.SellValueDB[id]
|
||
elseif aux and aux.account_data and aux.account_data.merchant_sell and aux.account_data.merchant_sell[id] and aux.account_data.merchant_sell[id] > 0 then
|
||
price = aux.account_data.merchant_sell[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 条 (API模式)", 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("已学习: 0 条 (API模式)")
|
||
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, 420, 1620)
|
||
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
|
||
))
|
||
table.insert(controls, CreateResetButton(raidSection, 356, -24, "raid", RefreshRaid, controls))
|
||
|
||
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, CreateSlider(raidSection, "背景透明度", 326, -336, 130, 0, 1.0, 0.05,
|
||
function() return SFramesDB.raidBgAlpha end,
|
||
function(value) SFramesDB.raidBgAlpha = value end,
|
||
function(v) return string.format("%.0f%%", v * 100) end,
|
||
function(value)
|
||
if SFrames.Raid and SFrames.Raid.frames then
|
||
for i = 1, 40 do
|
||
local entry = SFrames.Raid.frames[i]
|
||
local f = entry and entry.frame
|
||
if f then
|
||
if f.healthBGFrame then ApplyBgAlphaToFrame(f.healthBGFrame, value) end
|
||
if f.powerBGFrame then ApplyBgAlphaToFrame(f.powerBGFrame, value) end
|
||
end
|
||
end
|
||
end
|
||
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)
|
||
|
||
AddFrameAdvancedSections(root, controls, {
|
||
prefix = "raid",
|
||
startY = -592,
|
||
apply = RefreshRaid,
|
||
hasPortraitBg = false,
|
||
showPowerHint = "关闭团队能量条后,这些设置会保留。",
|
||
})
|
||
|
||
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, 1400)
|
||
local root = uiScroll.child
|
||
|
||
local abSection = CreateSection(root, "动作条", 8, -8, 520, 1040, 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, CreateSlider(abSection, "透明度", 14, -242, 150, 0.1, 1.0, 0.05,
|
||
function() return SFramesDB.ActionBars.alpha or 1 end,
|
||
function(value) SFramesDB.ActionBars.alpha = value end,
|
||
function(v) return string.format("%.0f%%", v * 100) end,
|
||
function(value)
|
||
if SFrames.ActionBars then
|
||
if SFrames.ActionBars.anchor then SFrames.ActionBars.anchor:SetAlpha(value) end
|
||
if SFrames.ActionBars.row2Anchor then SFrames.ActionBars.row2Anchor:SetAlpha(value) end
|
||
if SFrames.ActionBars.row3Anchor then SFrames.ActionBars.row3Anchor:SetAlpha(value) end
|
||
if SFrames.ActionBars.rightBar1Holder then SFrames.ActionBars.rightBar1Holder:SetAlpha(value) end
|
||
if SFrames.ActionBars.rightBar2Holder then SFrames.ActionBars.rightBar2Holder:SetAlpha(value) end
|
||
if SFrames.ActionBars.stanceHolder then SFrames.ActionBars.stanceHolder:SetAlpha(value) end
|
||
if SFrames.ActionBars.petHolder then SFrames.ActionBars.petHolder:SetAlpha(value) end
|
||
end
|
||
end
|
||
))
|
||
|
||
table.insert(controls, CreateSlider(abSection, "背景透明度", 170, -242, 150, 0, 1.0, 0.05,
|
||
function() return SFramesDB.ActionBars.bgAlpha or 0.9 end,
|
||
function(value) SFramesDB.ActionBars.bgAlpha = value end,
|
||
function(v) return string.format("%.0f%%", v * 100) end,
|
||
function(value)
|
||
if SFrames.ActionBars then
|
||
if SFrames.ActionBars.anchor then ApplyBgAlphaToFrame(SFrames.ActionBars.anchor, value) end
|
||
if SFrames.ActionBars.row2Anchor then ApplyBgAlphaToFrame(SFrames.ActionBars.row2Anchor, value) end
|
||
if SFrames.ActionBars.row3Anchor then ApplyBgAlphaToFrame(SFrames.ActionBars.row3Anchor, value) end
|
||
if SFrames.ActionBars.rightBar1Holder then ApplyBgAlphaToFrame(SFrames.ActionBars.rightBar1Holder, value) end
|
||
if SFrames.ActionBars.rightBar2Holder then ApplyBgAlphaToFrame(SFrames.ActionBars.rightBar2Holder, value) end
|
||
if SFrames.ActionBars.stanceHolder then ApplyBgAlphaToFrame(SFrames.ActionBars.stanceHolder, value) end
|
||
if SFrames.ActionBars.petHolder then ApplyBgAlphaToFrame(SFrames.ActionBars.petHolder, value) end
|
||
end
|
||
end
|
||
))
|
||
|
||
table.insert(controls, CreateCheckBox(abSection,
|
||
"显示快捷键文字", 12, -300,
|
||
function() return SFramesDB.ActionBars.showHotkey ~= false end,
|
||
function(checked) SFramesDB.ActionBars.showHotkey = checked end,
|
||
function() RefreshAB() end
|
||
))
|
||
|
||
table.insert(controls, CreateCheckBox(abSection,
|
||
"显示宏名称", 200, -300,
|
||
function() return SFramesDB.ActionBars.showMacroName == true end,
|
||
function(checked) SFramesDB.ActionBars.showMacroName = checked end,
|
||
function() RefreshAB() end
|
||
))
|
||
|
||
table.insert(controls, CreateCheckBox(abSection,
|
||
"超距红色着色(技能超出射程时按钮变红)", 12, -328,
|
||
function() return SFramesDB.ActionBars.rangeColoring ~= false end,
|
||
function(checked) SFramesDB.ActionBars.rangeColoring = checked end
|
||
))
|
||
|
||
table.insert(controls, CreateCheckBox(abSection,
|
||
"显示宠物动作条", 12, -356,
|
||
function() return SFramesDB.ActionBars.showPetBar ~= false end,
|
||
function(checked) SFramesDB.ActionBars.showPetBar = checked end,
|
||
function() RefreshAB() end
|
||
))
|
||
|
||
table.insert(controls, CreateCheckBox(abSection,
|
||
"显示姿态栏", 200, -356,
|
||
function() return SFramesDB.ActionBars.showStanceBar ~= false end,
|
||
function(checked) SFramesDB.ActionBars.showStanceBar = checked end,
|
||
function() RefreshAB() end
|
||
))
|
||
|
||
table.insert(controls, CreateCheckBox(abSection,
|
||
"显示右侧动作条", 12, -384,
|
||
function() return SFramesDB.ActionBars.showRightBars ~= false end,
|
||
function(checked) SFramesDB.ActionBars.showRightBars = checked end,
|
||
function() RefreshAB() end
|
||
))
|
||
|
||
table.insert(controls, CreateSlider(abSection, "右侧栏1每行按钮数", 14, -416, 150, 1, 12, 1,
|
||
function() return SFramesDB.ActionBars.rightBar1PerRow or 1 end,
|
||
function(value) SFramesDB.ActionBars.rightBar1PerRow = value end,
|
||
function(v) local n = math.floor(v + 0.5); if n == 1 then return "1 (竖)" elseif n == 12 then return "12 (横)" else return tostring(n) end end,
|
||
function() RefreshAB() end
|
||
))
|
||
|
||
table.insert(controls, CreateSlider(abSection, "右侧栏2每行按钮数", 180, -416, 150, 1, 12, 1,
|
||
function() return SFramesDB.ActionBars.rightBar2PerRow or 1 end,
|
||
function(value) SFramesDB.ActionBars.rightBar2PerRow = value end,
|
||
function(v) local n = math.floor(v + 0.5); if n == 1 then return "1 (竖)" elseif n == 12 then return "12 (横)" else return tostring(n) end end,
|
||
function() RefreshAB() end
|
||
))
|
||
|
||
table.insert(controls, CreateSlider(abSection, "底部栏1每行", 12, -456, 110, 1, 12, 1,
|
||
function() return SFramesDB.ActionBars.bottomBar1PerRow or 12 end,
|
||
function(value) SFramesDB.ActionBars.bottomBar1PerRow = value end,
|
||
function(v) local n = math.floor(v + 0.5); if n == 12 then return "12 (满)" else return tostring(n) end end,
|
||
function() RefreshAB() end
|
||
))
|
||
|
||
table.insert(controls, CreateSlider(abSection, "底部栏2每行", 134, -456, 110, 1, 12, 1,
|
||
function() return SFramesDB.ActionBars.bottomBar2PerRow or 12 end,
|
||
function(value) SFramesDB.ActionBars.bottomBar2PerRow = value end,
|
||
function(v) local n = math.floor(v + 0.5); if n == 12 then return "12 (满)" else return tostring(n) end end,
|
||
function() RefreshAB() end
|
||
))
|
||
|
||
table.insert(controls, CreateSlider(abSection, "底部栏3每行", 256, -456, 110, 1, 12, 1,
|
||
function() return SFramesDB.ActionBars.bottomBar3PerRow or 12 end,
|
||
function(value) SFramesDB.ActionBars.bottomBar3PerRow = value end,
|
||
function(v) local n = math.floor(v + 0.5); if n == 12 then return "12 (满)" else return tostring(n) end end,
|
||
function() RefreshAB() end
|
||
))
|
||
|
||
table.insert(controls, CreateCheckBox(abSection,
|
||
"始终显示动作条(空格子也显示背景框)", 12, -514,
|
||
function() return SFramesDB.ActionBars.alwaysShowGrid == true end,
|
||
function(checked) SFramesDB.ActionBars.alwaysShowGrid = checked end,
|
||
function() RefreshAB() end
|
||
))
|
||
|
||
table.insert(controls, CreateCheckBox(abSection,
|
||
"按钮圆角", 12, -542,
|
||
function() return SFramesDB.ActionBars.buttonRounded == true end,
|
||
function(checked) SFramesDB.ActionBars.buttonRounded = checked end,
|
||
function() RefreshAB() end
|
||
))
|
||
|
||
table.insert(controls, CreateCheckBox(abSection,
|
||
"按钮内阴影", 200, -542,
|
||
function() return SFramesDB.ActionBars.buttonInnerShadow == true end,
|
||
function(checked) SFramesDB.ActionBars.buttonInnerShadow = checked end,
|
||
function() RefreshAB() end
|
||
))
|
||
|
||
table.insert(controls, CreateCheckBox(abSection,
|
||
"显示动作条狮鹫(在底部动作条两侧显示装饰狮鹫)", 12, -570,
|
||
function() return SFramesDB.ActionBars.hideGryphon == false end,
|
||
function(checked) SFramesDB.ActionBars.hideGryphon = not checked end,
|
||
function() RefreshAB() end
|
||
))
|
||
|
||
table.insert(controls, CreateCheckBox(abSection,
|
||
"狮鹫置于动作条之上(否则在动作条之下)", 12, -598,
|
||
function() return SFramesDB.ActionBars.gryphonOnTop == true end,
|
||
function(checked) SFramesDB.ActionBars.gryphonOnTop = checked end,
|
||
function() RefreshAB() end
|
||
))
|
||
|
||
table.insert(controls, CreateSlider(abSection, "狮鹫宽度", 14, -652, 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, -652, 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
|
||
))
|
||
|
||
CreateDesc(abSection, "使用布局模式 (/nui layout) 调整狮鹫位置", 14, -714, font, 480)
|
||
|
||
-- 狮鹫样式选择器(带图例预览)
|
||
CreateLabel(abSection, "狮鹫样式:", 14, -766, 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 = -786
|
||
|
||
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
|
||
|
||
-- Layout presets
|
||
CreateLabel(abSection, "布局预设:", 14, -908, font, 11, 0.85, 0.75, 0.80)
|
||
CreateDesc(abSection,
|
||
"快速切换动作条排列方式。选择预设会重置动作条位置并应用对应的排列。",
|
||
14, -926, font, 480)
|
||
|
||
local presetDefs = (SFrames.ActionBars and SFrames.ActionBars.PRESETS) or {
|
||
{ id = 1, name = "经典", desc = "底部堆叠 + 右侧竖栏" },
|
||
{ id = 2, name = "宽屏", desc = "左4x3 + 底部堆叠 + 右4x3" },
|
||
{ id = 3, name = "堆叠", desc = "全部堆叠于底部中央" },
|
||
}
|
||
|
||
local presetBtnW = 155
|
||
local presetBtnGap = 8
|
||
for pi = 1, 3 do
|
||
local pdef = presetDefs[pi]
|
||
local pLabel = pdef and pdef.name or ("方案" .. pi)
|
||
local pId = pi
|
||
local px = 14 + (pi - 1) * (presetBtnW + presetBtnGap)
|
||
CreateButton(abSection, pLabel, px, -946, presetBtnW, 24, function()
|
||
if SFrames.ActionBars and SFrames.ActionBars.ApplyPreset then
|
||
SFrames.ActionBars:ApplyPreset(pId)
|
||
RefreshAB()
|
||
end
|
||
end)
|
||
end
|
||
|
||
for pi = 1, 3 do
|
||
local pdef = presetDefs[pi]
|
||
local pdesc = pdef and pdef.desc or ""
|
||
local px = 14 + (pi - 1) * (presetBtnW + presetBtnGap)
|
||
CreateDesc(abSection, pdesc, px, -976, font, presetBtnW)
|
||
end
|
||
|
||
CreateLabel(abSection, "动作条位置:", 14, -1000, font, 11, 0.85, 0.75, 0.80)
|
||
CreateDesc(abSection, "使用 /nui layout 或右键聊天框 Nanami 标题进入布局模式调整动作条位置", 14, -1018, font, 480)
|
||
|
||
CreateLabel(abSection,
|
||
"提示:启用/禁用动作条接管需要 /reload 才能生效。",
|
||
14, -1038, font, 10, 1, 0.92, 0.38)
|
||
|
||
-- ── 额外动作条 ──────────────────────────────────────────────
|
||
local function RefreshEB()
|
||
if SFrames.ExtraBar then
|
||
local edb = SFrames.ExtraBar:GetDB()
|
||
if edb.enable then
|
||
if not SFrames.ExtraBar.holder then
|
||
SFrames.ExtraBar:Enable()
|
||
else
|
||
SFrames.ExtraBar:ApplyConfig()
|
||
end
|
||
else
|
||
SFrames.ExtraBar:Disable()
|
||
end
|
||
end
|
||
end
|
||
|
||
local ebSection = CreateSection(root, "额外动作条", 8, -1056, 520, 310, font)
|
||
|
||
table.insert(controls, CreateCheckBox(ebSection,
|
||
"启用额外动作条(独立面板,使用空闲动作槽位)", 12, -30,
|
||
function() return SFramesDB.ExtraBar.enable == true end,
|
||
function(checked)
|
||
SFramesDB.ExtraBar.enable = checked
|
||
if checked then
|
||
if SFrames.ExtraBar then SFrames.ExtraBar:Enable() end
|
||
else
|
||
if SFrames.ExtraBar then SFrames.ExtraBar:Disable() end
|
||
end
|
||
end,
|
||
function() end
|
||
))
|
||
|
||
table.insert(controls, CreateSlider(ebSection, "按钮总数", 14, -62, 150, 1, 48, 1,
|
||
function() return SFramesDB.ExtraBar.buttonCount or 12 end,
|
||
function(value) SFramesDB.ExtraBar.buttonCount = value end,
|
||
function(v) return tostring(math.floor(v + 0.5)) end,
|
||
function() RefreshEB() end
|
||
))
|
||
|
||
table.insert(controls, CreateSlider(ebSection, "每行按钮数", 180, -62, 150, 1, 12, 1,
|
||
function() return SFramesDB.ExtraBar.perRow or 12 end,
|
||
function(value) SFramesDB.ExtraBar.perRow = value end,
|
||
function(v) local n = math.floor(v + 0.5); if n == 12 then return "12 (满)" else return tostring(n) end end,
|
||
function() RefreshEB() end
|
||
))
|
||
|
||
table.insert(controls, CreateSlider(ebSection, "按钮尺寸", 14, -102, 150, 20, 60, 1,
|
||
function() return SFramesDB.ExtraBar.buttonSize or 36 end,
|
||
function(value) SFramesDB.ExtraBar.buttonSize = value end,
|
||
function(v) return tostring(math.floor(v + 0.5)) end,
|
||
function() RefreshEB() end
|
||
))
|
||
|
||
table.insert(controls, CreateSlider(ebSection, "按钮间距", 180, -102, 150, 0, 10, 1,
|
||
function() return SFramesDB.ExtraBar.buttonGap or 2 end,
|
||
function(value) SFramesDB.ExtraBar.buttonGap = value end,
|
||
function(v) return tostring(math.floor(v + 0.5)) end,
|
||
function() RefreshEB() end
|
||
))
|
||
|
||
table.insert(controls, CreateSlider(ebSection, "对齐方式", 14, -142, 150, 1, 3, 1,
|
||
function()
|
||
local a = SFramesDB.ExtraBar.align or "center"
|
||
if a == "left" then return 1 elseif a == "right" then return 3 else return 2 end
|
||
end,
|
||
function(value)
|
||
local n = math.floor(value + 0.5)
|
||
if n == 1 then SFramesDB.ExtraBar.align = "left"
|
||
elseif n == 3 then SFramesDB.ExtraBar.align = "right"
|
||
else SFramesDB.ExtraBar.align = "center" end
|
||
end,
|
||
function(v) local n = math.floor(v + 0.5); if n == 1 then return "左对齐" elseif n == 3 then return "右对齐" else return "居中" end end,
|
||
function() RefreshEB() end
|
||
))
|
||
|
||
table.insert(controls, CreateSlider(ebSection, "透明度", 180, -142, 150, 0.1, 1.0, 0.05,
|
||
function() return SFramesDB.ExtraBar.alpha or 1.0 end,
|
||
function(value) SFramesDB.ExtraBar.alpha = value end,
|
||
function(v) return string.format("%.0f%%", v * 100) end,
|
||
function() RefreshEB() end
|
||
))
|
||
|
||
table.insert(controls, CreateSlider(ebSection, "起始槽位(页7=73)", 14, -182, 320, 73, 109, 1,
|
||
function() return SFramesDB.ExtraBar.startSlot or 73 end,
|
||
function(value) SFramesDB.ExtraBar.startSlot = value end,
|
||
function(v) local n = math.floor(v + 0.5); local pg = math.floor((n - 1) / 12) + 1; local idx = math.fmod(n - 1, 12) + 1; return n .. " (页" .. pg .. "-" .. idx .. ")" end,
|
||
function() RefreshEB() end
|
||
))
|
||
|
||
table.insert(controls, CreateCheckBox(ebSection,
|
||
"显示快捷键", 12, -220,
|
||
function() return SFramesDB.ExtraBar.showHotkey ~= false end,
|
||
function(checked) SFramesDB.ExtraBar.showHotkey = checked end,
|
||
function() RefreshEB() end
|
||
))
|
||
|
||
table.insert(controls, CreateCheckBox(ebSection,
|
||
"显示堆叠数", 200, -220,
|
||
function() return SFramesDB.ExtraBar.showCount ~= false end,
|
||
function(checked) SFramesDB.ExtraBar.showCount = checked end,
|
||
function() RefreshEB() end
|
||
))
|
||
|
||
table.insert(controls, CreateCheckBox(ebSection,
|
||
"按钮圆角", 12, -248,
|
||
function() return SFramesDB.ExtraBar.buttonRounded == true end,
|
||
function(checked) SFramesDB.ExtraBar.buttonRounded = checked end,
|
||
function() RefreshEB() end
|
||
))
|
||
|
||
table.insert(controls, CreateCheckBox(ebSection,
|
||
"按钮内阴影", 200, -248,
|
||
function() return SFramesDB.ExtraBar.buttonInnerShadow == true end,
|
||
function(checked) SFramesDB.ExtraBar.buttonInnerShadow = checked end,
|
||
function() RefreshEB() end
|
||
))
|
||
|
||
CreateDesc(ebSection,
|
||
"使用布局模式 (/nui layout) 调整额外动作条位置。默认槽位 73-84 (页7) 对所有职业安全。",
|
||
14, -280, font, 480)
|
||
|
||
uiScroll:UpdateRange()
|
||
self.actionBarControls = controls
|
||
self.actionBarScroll = uiScroll
|
||
end
|
||
|
||
function SFrames.ConfigUI:BuildKeybindsPage()
|
||
local font = SFrames:GetFont()
|
||
local page = self.keybindsPage
|
||
local controls = {}
|
||
|
||
local uiScroll = CreateScrollArea(page, 4, -4, 548, 458, 660)
|
||
local root = uiScroll.child
|
||
|
||
-- Quick keybind mode entry
|
||
local bindSection = CreateSection(root, "动作条按键绑定", 8, -8, 520, 80, font)
|
||
|
||
CreateButton(bindSection, "进入按键绑定模式", 14, -30, 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(bindSection,
|
||
"悬停动作条/姿态栏/宠物栏按钮,按下键盘键/鼠标键(3-5)/滚轮绑定。右键清除。ESC 退出。",
|
||
184, -32, font, 320)
|
||
|
||
-- Profile management
|
||
local kbSection = CreateSection(root, "按键绑定方案管理", 8, -98, 520, 520, font)
|
||
|
||
CreateDesc(kbSection,
|
||
"保存/加载所有按键绑定(包含动作条、移动、聊天、目标选择、菜单等全部设定),可在角色间共享方案。",
|
||
14, -28, font, 490)
|
||
|
||
-- Profile list container
|
||
local listHolder = CreateFrame("Frame", "SFramesKBMListHolder", kbSection)
|
||
listHolder:SetWidth(496)
|
||
listHolder:SetHeight(192)
|
||
listHolder:SetPoint("TOPLEFT", kbSection, "TOPLEFT", 12, -56)
|
||
listHolder:SetBackdrop({
|
||
bgFile = "Interface\\Tooltips\\UI-Tooltip-Background",
|
||
edgeFile = "Interface\\Tooltips\\UI-Tooltip-Border",
|
||
tile = true, tileSize = 16, edgeSize = 10,
|
||
insets = { left = 3, right = 3, top = 3, bottom = 3 },
|
||
})
|
||
listHolder:SetBackdropColor(0.08, 0.06, 0.1, 0.85)
|
||
listHolder:SetBackdropBorderColor(0.4, 0.35, 0.45, 0.8)
|
||
|
||
local ROW_HEIGHT = 24
|
||
local MAX_VISIBLE = 8
|
||
local selectedProfile = nil
|
||
local profileRows = {}
|
||
|
||
local listScroll = CreateFrame("ScrollFrame", "SFramesKBMListScroll", listHolder)
|
||
listScroll:SetPoint("TOPLEFT", listHolder, "TOPLEFT", 4, -4)
|
||
listScroll:SetPoint("BOTTOMRIGHT", listHolder, "BOTTOMRIGHT", -4, 4)
|
||
|
||
local listChild = CreateFrame("Frame", "SFramesKBMListChild", listScroll)
|
||
listChild:SetWidth(488)
|
||
listChild:SetHeight(ROW_HEIGHT * MAX_VISIBLE)
|
||
listScroll:SetScrollChild(listChild)
|
||
|
||
listScroll:EnableMouseWheel(true)
|
||
listScroll:SetScript("OnMouseWheel", function()
|
||
local cur = listScroll:GetVerticalScroll() or 0
|
||
local maxS = listChild:GetHeight() - listScroll:GetHeight()
|
||
if maxS < 0 then maxS = 0 end
|
||
local newVal = cur - arg1 * ROW_HEIGHT
|
||
if newVal < 0 then newVal = 0 end
|
||
if newVal > maxS then newVal = maxS end
|
||
listScroll:SetVerticalScroll(newVal)
|
||
end)
|
||
|
||
local emptyLabel = listChild:CreateFontString(nil, "OVERLAY")
|
||
emptyLabel:SetFont(font, 11, "OUTLINE")
|
||
emptyLabel:SetPoint("CENTER", listChild, "CENTER", 0, 0)
|
||
emptyLabel:SetText("暂无保存的方案")
|
||
emptyLabel:SetTextColor(0.5, 0.5, 0.5)
|
||
|
||
local function RefreshProfileList()
|
||
local KBM = SFrames.KeyBindManager
|
||
if not KBM then return end
|
||
local list = KBM:GetProfileList()
|
||
|
||
for _, row in ipairs(profileRows) do
|
||
row:Hide()
|
||
end
|
||
|
||
if table.getn(list) == 0 then
|
||
emptyLabel:Show()
|
||
listChild:SetHeight(ROW_HEIGHT * MAX_VISIBLE)
|
||
return
|
||
end
|
||
emptyLabel:Hide()
|
||
|
||
local totalH = table.getn(list) * ROW_HEIGHT
|
||
if totalH < ROW_HEIGHT * MAX_VISIBLE then totalH = ROW_HEIGHT * MAX_VISIBLE end
|
||
listChild:SetHeight(totalH)
|
||
|
||
for idx, name in ipairs(list) do
|
||
local row = profileRows[idx]
|
||
if not row then
|
||
row = CreateFrame("Button", nil, listChild)
|
||
row:SetWidth(480)
|
||
row:SetHeight(ROW_HEIGHT)
|
||
row:EnableMouse(true)
|
||
|
||
row.bg = row:CreateTexture(nil, "BACKGROUND")
|
||
row.bg:SetAllPoints()
|
||
row.bg:SetTexture("Interface\\Buttons\\WHITE8X8")
|
||
row.bg:SetVertexColor(0.15, 0.12, 0.18, 0)
|
||
|
||
row.nameText = row:CreateFontString(nil, "OVERLAY")
|
||
row.nameText:SetFont(font, 11, "OUTLINE")
|
||
row.nameText:SetPoint("LEFT", row, "LEFT", 8, 0)
|
||
row.nameText:SetWidth(200)
|
||
row.nameText:SetJustifyH("LEFT")
|
||
|
||
row.infoText = row:CreateFontString(nil, "OVERLAY")
|
||
row.infoText:SetFont(font, 9, "OUTLINE")
|
||
row.infoText:SetPoint("RIGHT", row, "RIGHT", -8, 0)
|
||
row.infoText:SetWidth(240)
|
||
row.infoText:SetJustifyH("RIGHT")
|
||
row.infoText:SetTextColor(0.6, 0.6, 0.65)
|
||
|
||
row.highlight = row:CreateTexture(nil, "HIGHLIGHT")
|
||
row.highlight:SetAllPoints()
|
||
row.highlight:SetTexture("Interface\\Buttons\\WHITE8X8")
|
||
row.highlight:SetVertexColor(0.4, 0.35, 0.5, 0.2)
|
||
|
||
row:SetScript("OnClick", function()
|
||
selectedProfile = this.profileName
|
||
RefreshProfileList()
|
||
end)
|
||
|
||
profileRows[idx] = row
|
||
end
|
||
|
||
row:SetPoint("TOPLEFT", listChild, "TOPLEFT", 0, -(idx - 1) * ROW_HEIGHT)
|
||
row.profileName = name
|
||
|
||
local info = KBM:GetProfileInfo(name)
|
||
row.nameText:SetText(name)
|
||
|
||
local infoStr = ""
|
||
if info then
|
||
infoStr = info.charName .. " | " .. info.count .. " 条绑定"
|
||
if info.timestamp and info.timestamp > 0 then
|
||
local d = date and date("%m/%d %H:%M", info.timestamp) or ""
|
||
if d ~= "" then infoStr = infoStr .. " | " .. d end
|
||
end
|
||
end
|
||
row.infoText:SetText(infoStr)
|
||
|
||
if selectedProfile == name then
|
||
row.bg:SetVertexColor(0.3, 0.25, 0.45, 0.6)
|
||
row.nameText:SetTextColor(1, 0.85, 0.4)
|
||
else
|
||
row.bg:SetVertexColor(0.15, 0.12, 0.18, (math.mod(idx, 2) == 0) and 0.25 or 0)
|
||
row.nameText:SetTextColor(0.9, 0.88, 0.92)
|
||
end
|
||
|
||
row:Show()
|
||
end
|
||
end
|
||
|
||
-- Buttons row 1: Save / Load / Delete / Rename
|
||
local btnY1 = -256
|
||
CreateButton(kbSection, "保存当前绑定", 12, btnY1, 120, 24, function()
|
||
local KBM = SFrames.KeyBindManager
|
||
if not KBM then return end
|
||
KBM.ShowInputDialog("|cffffcc00保存按键绑定方案|r", "", function(name)
|
||
KBM:SaveProfile(name)
|
||
RefreshProfileList()
|
||
end)
|
||
end)
|
||
|
||
CreateButton(kbSection, "加载方案", 142, btnY1, 100, 24, function()
|
||
local KBM = SFrames.KeyBindManager
|
||
if not KBM or not selectedProfile then
|
||
SFrames:Print("请先在列表中选择一个方案")
|
||
return
|
||
end
|
||
KBM.ShowConfirmDialog(
|
||
"确定要加载方案 |cffffd100" .. selectedProfile .. "|r 吗?\n当前所有按键绑定将被替换。",
|
||
function()
|
||
KBM:LoadProfile(selectedProfile)
|
||
RefreshProfileList()
|
||
end)
|
||
end)
|
||
|
||
CreateButton(kbSection, "删除", 252, btnY1, 80, 24, function()
|
||
local KBM = SFrames.KeyBindManager
|
||
if not KBM or not selectedProfile then
|
||
SFrames:Print("请先在列表中选择一个方案")
|
||
return
|
||
end
|
||
KBM.ShowConfirmDialog(
|
||
"确定要删除方案 |cffffd100" .. selectedProfile .. "|r 吗?",
|
||
function()
|
||
KBM:DeleteProfile(selectedProfile)
|
||
selectedProfile = nil
|
||
RefreshProfileList()
|
||
end)
|
||
end)
|
||
|
||
CreateButton(kbSection, "重命名", 342, btnY1, 90, 24, function()
|
||
local KBM = SFrames.KeyBindManager
|
||
if not KBM or not selectedProfile then
|
||
SFrames:Print("请先在列表中选择一个方案")
|
||
return
|
||
end
|
||
local old = selectedProfile
|
||
KBM.ShowInputDialog("|cffffcc00重命名方案|r", old, function(newName)
|
||
local ok, err = KBM:RenameProfile(old, newName)
|
||
if ok then
|
||
selectedProfile = newName
|
||
else
|
||
SFrames:Print("|cffff4444重命名失败: " .. (err or "") .. "|r")
|
||
end
|
||
RefreshProfileList()
|
||
end)
|
||
end)
|
||
|
||
-- Buttons row 2: Export / Import
|
||
local btnY2 = -288
|
||
CreateButton(kbSection, "导出当前绑定", 12, btnY2, 130, 24, function()
|
||
local KBM = SFrames.KeyBindManager
|
||
if KBM then KBM:ShowExportDialog() end
|
||
end)
|
||
|
||
CreateButton(kbSection, "导入绑定", 152, btnY2, 130, 24, function()
|
||
local KBM = SFrames.KeyBindManager
|
||
if KBM then KBM:ShowImportDialog() end
|
||
end)
|
||
|
||
CreateButton(kbSection, "导出选中方案", 292, btnY2, 130, 24, function()
|
||
local KBM = SFrames.KeyBindManager
|
||
if not KBM or not selectedProfile then
|
||
SFrames:Print("请先在列表中选择一个方案")
|
||
return
|
||
end
|
||
if not SFramesGlobalDB then SFramesGlobalDB = {} end
|
||
if not SFramesGlobalDB.KeyBindProfiles then return end
|
||
local profile = SFramesGlobalDB.KeyBindProfiles[selectedProfile]
|
||
if profile and profile.bindings then
|
||
local text = KBM:SerializeBindings(profile.bindings)
|
||
KBM:ShowExportDialog()
|
||
local ef = _G["SFramesKBMExport"]
|
||
if ef and ef.edit then
|
||
ef.edit:SetText(text)
|
||
ef.edit:HighlightText()
|
||
end
|
||
end
|
||
end)
|
||
|
||
-- Info section
|
||
CreateLabel(kbSection, "涵盖范围:", 14, -326, font, 10, 0.85, 0.75, 0.80)
|
||
CreateDesc(kbSection,
|
||
"移动按键 | 聊天按键 | 动作条快捷键 | 目标选择 | 界面面板 |\n宠物/姿态栏 | 镜头控制 | 多动作条 | 所有系统按键绑定",
|
||
14, -340, font, 490)
|
||
|
||
CreateLabel(kbSection, "使用说明:", 14, -380, font, 10, 0.85, 0.75, 0.80)
|
||
CreateDesc(kbSection,
|
||
"1. 点击「保存当前绑定」将当前所有按键设定存为方案\n2. 在列表中选中方案后点击「加载方案」恢复\n3. 导出会将绑定编码为字符串,可安全复制分享给其他玩家\n4. 方案存储在全局变量中,所有角色共享",
|
||
14, -394, font, 490)
|
||
|
||
CreateLabel(kbSection, "命令行:", 14, -452, font, 10, 0.85, 0.75, 0.80)
|
||
CreateDesc(kbSection,
|
||
"/nui keybinds save <名称> | /nui keybinds load <名称> | /nui keybinds list",
|
||
14, -466, font, 490)
|
||
|
||
RefreshProfileList()
|
||
self.RefreshKBMList = RefreshProfileList
|
||
|
||
uiScroll:UpdateRange()
|
||
self.keybindsControls = controls
|
||
self.keybindsScroll = 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, 800)
|
||
local root = uiScroll.child
|
||
|
||
-- ══════════════════════════════════════════════════════════════
|
||
-- 世界地图 & 迷雾揭示 (合并 · 置顶)
|
||
-- ══════════════════════════════════════════════════════════════
|
||
local wmSection = CreateSection(root, "世界地图", 8, -8, 520, 260, 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,
|
||
"启用导航地图", 270, -34,
|
||
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, "实时导航地图,/nui nav | 滚轮缩放 | Ctrl+滚轮透明度", 292, -50, font)
|
||
|
||
-- 迷雾揭示 (合并在同一 section)
|
||
CreateLabel(wmSection, "── 迷雾揭示 ──", 14, -76, font, 11, 0.70, 0.60, 0.65)
|
||
|
||
table.insert(controls, CreateCheckBox(wmSection,
|
||
"启用地图迷雾揭示", 14, -96,
|
||
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(wmSection, "在世界地图上显示未探索区域(变暗显示),需要 LibMapOverlayData", 36, -112, font)
|
||
|
||
table.insert(controls, CreateSlider(wmSection, "未探索区域亮度", 14, -140, 220, 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
|
||
))
|
||
|
||
CreateButton(wmSection, "扫描所有地图", 270, -148, 120, 24, function()
|
||
if SFrames.MapReveal and SFrames.MapReveal.ScanAllMaps then
|
||
SFrames.MapReveal:ScanAllMaps()
|
||
else
|
||
SFrames:Print("MapReveal 模块不可用")
|
||
end
|
||
end)
|
||
CreateDesc(wmSection, "遍历所有大陆区域,发现新地图并自动补充迷雾数据", 292, -174, font)
|
||
|
||
CreateButton(wmSection, "查看统计", 400, -148, 80, 24, function()
|
||
if SFrames.MapReveal and SFrames.MapReveal.ShowStats then
|
||
SFrames.MapReveal:ShowStats()
|
||
else
|
||
SFrames:Print("MapReveal 模块不可用")
|
||
end
|
||
end)
|
||
|
||
CreateButton(wmSection, "导出数据", 488, -148, 80, 24, function()
|
||
if SFrames.MapReveal and SFrames.MapReveal.ExportScannedData then
|
||
SFrames.MapReveal:ExportScannedData()
|
||
else
|
||
SFrames:Print("MapReveal 模块不可用")
|
||
end
|
||
end)
|
||
|
||
CreateLabel(wmSection, "浏览世界地图时自动发现并持久化覆盖层数据,扫描可批量发现已探索区域。", 14, -200, font, 10, 0.6, 0.6, 0.65)
|
||
CreateDesc(wmSection, "命令: /nui mapscan | /nui mapstats | /nui mapreveal 切换", 14, -216, font)
|
||
|
||
-- ══════════════════════════════════════════════════════════════
|
||
-- 小地图 (移至下方)
|
||
-- ══════════════════════════════════════════════════════════════
|
||
local mmSection = CreateSection(root, "小地图", 8, -278, 520, 480, 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)
|
||
|
||
CreateDesc(mmSection, "使用 /nui layout 或右键聊天框 Nanami 标题进入布局模式调整位置", 14, -182, font, 480)
|
||
|
||
-- ── Shape selector (方形 / 圆形) ──────────────────────────────
|
||
CreateLabel(mmSection, "地图形状:", 14, -210, font, 11, 0.85, 0.75, 0.80)
|
||
|
||
local shapeKeys = {"square1", "square2", "circle"}
|
||
local shapeNames = {"方形·金", "方形·暗", "圆形"}
|
||
local shapeBtns = {}
|
||
local shapeLbls = {}
|
||
|
||
for i = 1, 3 do
|
||
local btn = CreateFrame("Button", nil, mmSection)
|
||
btn:SetWidth(72)
|
||
btn:SetHeight(22)
|
||
btn:SetPoint("TOPLEFT", mmSection, "TOPLEFT", 100 + (i - 1) * 80, -208)
|
||
btn: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 lbl = btn:CreateFontString(nil, "OVERLAY")
|
||
lbl:SetFont(font, 10, "OUTLINE")
|
||
lbl:SetPoint("CENTER", btn, "CENTER", 0, 0)
|
||
lbl:SetText(shapeNames[i])
|
||
btn._sfShapeKey = shapeKeys[i]
|
||
shapeBtns[i] = btn
|
||
shapeLbls[i] = lbl
|
||
end
|
||
|
||
CreateLabel(mmSection, "提示: 缩放、位置和样式修改后实时生效。", 14, -238, font, 10, 0.6, 0.6, 0.65)
|
||
|
||
-- ── Circular style container (only visible when shape == "circle") ──
|
||
local circleStyleFrame = CreateFrame("Frame", nil, mmSection)
|
||
circleStyleFrame:SetPoint("TOPLEFT", mmSection, "TOPLEFT", 0, -256)
|
||
circleStyleFrame:SetWidth(520)
|
||
circleStyleFrame:SetHeight(220)
|
||
|
||
CreateLabel(circleStyleFrame, "圆形边框样式:", 14, -4, font, 11, 0.85, 0.75, 0.80)
|
||
|
||
local styles = SFrames.Minimap and SFrames.Minimap.MAP_STYLES or {}
|
||
|
||
local autoBtn = CreateFrame("Button", nil, circleStyleFrame)
|
||
autoBtn:SetWidth(100)
|
||
autoBtn:SetHeight(18)
|
||
autoBtn:SetPoint("TOPLEFT", circleStyleFrame, "TOPLEFT", 130, -2)
|
||
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 = -26
|
||
|
||
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, circleStyleFrame)
|
||
preview:SetWidth(cellW)
|
||
preview:SetHeight(cellH)
|
||
preview:SetPoint("TOPLEFT", circleStyleFrame, "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()
|
||
|
||
-- Shape UI update: highlight active shape button, toggle circular style visibility
|
||
local function UpdateShapeUI()
|
||
local current = SFramesDB.Minimap.mapShape or "square1"
|
||
for i = 1, 3 do
|
||
if shapeKeys[i] == current then
|
||
shapeBtns[i]:SetBackdropColor(0.2, 0.4, 0.2, 0.9)
|
||
shapeBtns[i]:SetBackdropBorderColor(0.4, 0.8, 0.3, 1)
|
||
shapeLbls[i]:SetTextColor(0.5, 1, 0.5)
|
||
else
|
||
shapeBtns[i]:SetBackdropColor(0.15, 0.15, 0.15, 0.7)
|
||
shapeBtns[i]:SetBackdropBorderColor(0.4, 0.4, 0.4, 0.6)
|
||
shapeLbls[i]:SetTextColor(0.6, 0.6, 0.6)
|
||
end
|
||
end
|
||
if current == "circle" then
|
||
circleStyleFrame:Show()
|
||
else
|
||
circleStyleFrame:Hide()
|
||
end
|
||
end
|
||
|
||
for i = 1, 3 do
|
||
shapeBtns[i]:SetScript("OnClick", function()
|
||
SFramesDB.Minimap.mapShape = this._sfShapeKey
|
||
UpdateShapeUI()
|
||
RefreshMinimap()
|
||
end)
|
||
end
|
||
|
||
UpdateShapeUI()
|
||
|
||
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, 580)
|
||
local root = uiScroll.child
|
||
|
||
local buffSection = CreateSection(root, "Buff / Debuff 栏", 8, -8, 520, 480, 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
|
||
))
|
||
|
||
table.insert(controls, CreateSlider(buffSection, "背景透明度", 14, -238, 220, 0, 1.0, 0.05,
|
||
function() return SFramesDB.MinimapBuffs.bgAlpha or 0.92 end,
|
||
function(value)
|
||
SFramesDB.MinimapBuffs.bgAlpha = value
|
||
RefreshBuffs()
|
||
end,
|
||
function(v) return string.format("%.0f%%", v * 100) end
|
||
))
|
||
|
||
-- Row 5: grow direction + anchor
|
||
CreateLabel(buffSection, "增长方向:", 14, -300, font, 11, 0.85, 0.75, 0.80)
|
||
CreateButton(buffSection, "向左", 100, -300, 70, 22, function()
|
||
SFramesDB.MinimapBuffs.growDirection = "LEFT"
|
||
RefreshBuffs()
|
||
end)
|
||
CreateButton(buffSection, "向右", 176, -300, 70, 22, function()
|
||
SFramesDB.MinimapBuffs.growDirection = "RIGHT"
|
||
RefreshBuffs()
|
||
end)
|
||
|
||
CreateLabel(buffSection, "锚点位置:", 270, -300, 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, -300, 40, 22, function()
|
||
SFramesDB.MinimapBuffs.position = key
|
||
RefreshBuffs()
|
||
end)
|
||
posStartX = posStartX + 42
|
||
end
|
||
|
||
-- Row 6: position hint
|
||
CreateDesc(buffSection, "使用 /nui layout 进入布局模式调整 Buff 栏位置", 14, -346, font, 480)
|
||
|
||
-- Row 7: action buttons
|
||
CreateButton(buffSection, "重置默认位置", 14, -370, 120, 24, function()
|
||
SFramesDB.MinimapBuffs.offsetX = 0
|
||
SFramesDB.MinimapBuffs.offsetY = 0
|
||
SFramesDB.MinimapBuffs.position = "TOPRIGHT"
|
||
SFramesDB.MinimapBuffs.growDirection = "LEFT"
|
||
if SFramesDB.Positions then SFramesDB.Positions["MinimapBuffs"] = nil end
|
||
if SFramesDB.Positions then SFramesDB.Positions["MinimapDebuffs"] = nil end
|
||
RefreshBuffs()
|
||
end)
|
||
|
||
local simBtn
|
||
simBtn = CreateButton(buffSection, "模拟预览", 142, -370, 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, -404, font, 9, 0.65, 0.58, 0.62)
|
||
CreateLabel(buffSection, "Debuff 边框颜色: |cff3399ff魔法|r |cff9900ff诅咒|r |cff996600疾病|r |cff009900毒药|r |cffcc0000物理|r", 14, -420, font, 9, 0.65, 0.58, 0.62)
|
||
|
||
CreateLabel(buffSection, "提示:启用/禁用 Buff 栏需要 /reload 才能生效。其他调整实时生效。", 14, -444, font, 10, 0.6, 0.6, 0.65)
|
||
|
||
uiScroll:UpdateRange()
|
||
self.buffControls = controls
|
||
self.buffScroll = uiScroll
|
||
end
|
||
|
||
function SFrames.ConfigUI:ShowFramesSubPage(mode)
|
||
local subMode = mode
|
||
if subMode ~= "player" and subMode ~= "target" and subMode ~= "focus" and subMode ~= "party" and subMode ~= "raid" then
|
||
subMode = self.activeFrameSubPage or "player"
|
||
end
|
||
|
||
self.activeFrameSubPage = subMode
|
||
self:EnsurePage(subMode)
|
||
|
||
self.playerPage:Hide()
|
||
self.targetPage:Hide()
|
||
self.focusPage:Hide()
|
||
self.partyPage:Hide()
|
||
self.raidPage:Hide()
|
||
|
||
local allSubTabs = { self.framesPlayerTab, self.framesTargetTab, self.framesFocusTab, self.framesPartyTab, self.framesRaidTab }
|
||
for _, tab in ipairs(allSubTabs) do
|
||
if tab then
|
||
tab.sfSoftActive = false
|
||
tab:Enable()
|
||
tab:RefreshVisual()
|
||
end
|
||
end
|
||
|
||
local activeTab, controls, scroll, titleSuffix
|
||
if subMode == "target" then
|
||
self.targetPage:Show()
|
||
activeTab = self.framesTargetTab
|
||
controls = self.targetControls
|
||
scroll = self.targetScroll
|
||
titleSuffix = "目标框体"
|
||
elseif subMode == "focus" then
|
||
self.focusPage:Show()
|
||
activeTab = self.framesFocusTab
|
||
controls = self.focusControls
|
||
scroll = self.focusScroll
|
||
titleSuffix = "焦点框体"
|
||
elseif subMode == "party" then
|
||
self.partyPage:Show()
|
||
activeTab = self.framesPartyTab
|
||
controls = self.partyControls
|
||
scroll = self.partyScroll
|
||
titleSuffix = "小队框体"
|
||
elseif subMode == "raid" then
|
||
self.raidPage:Show()
|
||
activeTab = self.framesRaidTab
|
||
controls = self.raidControls
|
||
scroll = self.raidScroll
|
||
titleSuffix = "团队框体"
|
||
else
|
||
self.playerPage:Show()
|
||
activeTab = self.framesPlayerTab
|
||
controls = self.playerControls
|
||
scroll = self.playerScroll
|
||
titleSuffix = "玩家框体"
|
||
end
|
||
|
||
if activeTab then
|
||
activeTab.sfSoftActive = true
|
||
activeTab:Disable()
|
||
activeTab:RefreshVisual()
|
||
end
|
||
|
||
self.title:SetText("Nanami-UI 设置 - 框体设置 / " .. titleSuffix)
|
||
self:RefreshControls(controls)
|
||
if scroll and scroll.Reset then scroll:Reset() end
|
||
end
|
||
|
||
function SFrames.ConfigUI:ShowPage(mode)
|
||
local requestedMode = mode or "ui"
|
||
local pageMode = requestedMode
|
||
if requestedMode == "player" or requestedMode == "target" or requestedMode == "focus" or requestedMode == "party" or requestedMode == "raid" then
|
||
pageMode = "frames"
|
||
end
|
||
|
||
if requestedMode == "frames" then
|
||
requestedMode = self.activeFrameSubPage or (SFramesDB and SFramesDB.configLastFrameSubPage) or "player"
|
||
end
|
||
|
||
if requestedMode == "player" or requestedMode == "target" or requestedMode == "focus" or requestedMode == "party" or requestedMode == "raid" then
|
||
if SFramesDB then
|
||
SFramesDB.configLastFrameSubPage = requestedMode
|
||
SFramesDB.configLastPage = requestedMode
|
||
end
|
||
elseif SFramesDB then
|
||
SFramesDB.configLastPage = pageMode
|
||
end
|
||
|
||
self.activePage = pageMode
|
||
|
||
self.uiPage:Hide()
|
||
self.framesPage:Hide()
|
||
self.playerPage:Hide()
|
||
self.targetPage:Hide()
|
||
self.focusPage:Hide()
|
||
self.bagPage:Hide()
|
||
self.raidPage:Hide()
|
||
self.partyPage:Hide()
|
||
self.charPage:Hide()
|
||
self.actionBarPage:Hide()
|
||
self.keybindsPage:Hide()
|
||
self.minimapPage:Hide()
|
||
self.buffPage:Hide()
|
||
self.personalizePage:Hide()
|
||
self.themePage:Hide()
|
||
self.profilePage:Hide()
|
||
|
||
local allTabs = { self.uiTab, self.framesTab, self.bagTab, self.charTab, self.actionBarTab, self.keybindsTab, self.minimapTab, self.buffTab, self.personalizeTab, self.themeTab, self.profileTab }
|
||
for _, tab in ipairs(allTabs) do
|
||
tab.sfSoftActive = false
|
||
tab:Enable()
|
||
tab:RefreshVisual()
|
||
end
|
||
|
||
self:EnsurePage(pageMode)
|
||
mode = pageMode
|
||
|
||
if mode == "frames" then
|
||
self.framesPage:Show()
|
||
self.framesTab.sfSoftActive = true
|
||
self.framesTab:Disable()
|
||
self.framesTab:RefreshVisual()
|
||
self:ShowFramesSubPage(requestedMode)
|
||
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 == "focus" then
|
||
self.focusPage:Show()
|
||
self.focusTab.sfSoftActive = true
|
||
self.focusTab:Disable()
|
||
self.focusTab:RefreshVisual()
|
||
self.title:SetText("Nanami-UI 设置 - 焦点框体")
|
||
self:RefreshControls(self.focusControls)
|
||
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 == "keybinds" then
|
||
self.keybindsPage:Show()
|
||
self.keybindsTab.sfSoftActive = true
|
||
self.keybindsTab:Disable()
|
||
self.keybindsTab:RefreshVisual()
|
||
self.title:SetText("Nanami-UI 设置 - 按键绑定方案")
|
||
self:RefreshControls(self.keybindsControls)
|
||
if self.keybindsScroll and self.keybindsScroll.Reset then self.keybindsScroll:Reset() end
|
||
if self.RefreshKBMList then self.RefreshKBMList() 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
|
||
elseif mode == "profile" then
|
||
self.profilePage:Show()
|
||
self.profileTab.sfSoftActive = true
|
||
self.profileTab:Disable()
|
||
self.profileTab:RefreshVisual()
|
||
self.title:SetText("Nanami-UI 设置 - 配置管理")
|
||
if self.profileScroll and self.profileScroll.Reset then self.profileScroll:Reset() end
|
||
if self.RefreshProfileSummary then self:RefreshProfileSummary() 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 title = panel:CreateFontString(nil, "OVERLAY")
|
||
title:SetFont(font, 14, "OUTLINE")
|
||
title:SetPoint("TOP", panel, "TOP", 0, -14)
|
||
title:SetJustifyH("CENTER")
|
||
title:SetTextColor(SOFT_THEME.title[1], SOFT_THEME.title[2], SOFT_THEME.title[3])
|
||
title:SetText("Nanami-UI 设置")
|
||
|
||
local titleIco = SFrames:CreateIcon(panel, "logo", 18)
|
||
titleIco:SetDrawLayer("OVERLAY")
|
||
titleIco:SetPoint("RIGHT", title, "LEFT", -5, 0)
|
||
titleIco:SetVertexColor(SOFT_THEME.title[1], SOFT_THEME.title[2], SOFT_THEME.title[3])
|
||
|
||
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 tabFrames = CreateFrame("Button", "SFramesConfigTabFrames", panel, "UIPanelButtonTemplate")
|
||
tabFrames:SetWidth(100)
|
||
tabFrames:SetHeight(28)
|
||
tabFrames:SetPoint("TOP", tabUI, "BOTTOM", 0, -4)
|
||
tabFrames:SetText("框体设置")
|
||
tabFrames:SetScript("OnClick", function() SFrames.ConfigUI:ShowPage("frames") end)
|
||
StyleButton(tabFrames)
|
||
AddBtnIcon(tabFrames, "character", nil, "left")
|
||
|
||
local tabPlayer = CreateFrame("Button", "SFramesConfigTabPlayer", panel, "UIPanelButtonTemplate")
|
||
tabPlayer:SetWidth(100)
|
||
tabPlayer:SetHeight(28)
|
||
tabPlayer:SetPoint("TOP", tabFrames, "BOTTOM", 0, -4)
|
||
tabPlayer:SetText("玩家框体")
|
||
tabPlayer:SetScript("OnClick", function() SFrames.ConfigUI:ShowPage("player") end)
|
||
StyleButton(tabPlayer)
|
||
AddBtnIcon(tabPlayer, "character", nil, "left")
|
||
tabPlayer:Hide()
|
||
|
||
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")
|
||
tabTarget:Hide()
|
||
|
||
local tabFocus = CreateFrame("Button", "SFramesConfigTabFocus", panel, "UIPanelButtonTemplate")
|
||
tabFocus:SetWidth(100)
|
||
tabFocus:SetHeight(28)
|
||
tabFocus:SetPoint("TOP", tabFrames, "BOTTOM", 0, -4)
|
||
tabFocus:SetText("焦点框体")
|
||
tabFocus:SetScript("OnClick", function() SFrames.ConfigUI:ShowPage("focus") end)
|
||
StyleButton(tabFocus)
|
||
AddBtnIcon(tabFocus, "search", nil, "left")
|
||
tabFocus:Hide()
|
||
|
||
local tabParty = CreateFrame("Button", "SFramesConfigTabParty", panel, "UIPanelButtonTemplate")
|
||
tabParty:SetWidth(100)
|
||
tabParty:SetHeight(28)
|
||
tabParty:SetPoint("TOP", tabFocus, "BOTTOM", 0, -4)
|
||
tabParty:SetText("小队框架")
|
||
tabParty:SetScript("OnClick", function() SFrames.ConfigUI:ShowPage("party") end)
|
||
StyleButton(tabParty)
|
||
AddBtnIcon(tabParty, "friends", nil, "left")
|
||
tabParty:Hide()
|
||
|
||
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")
|
||
tabRaid:Hide()
|
||
|
||
local tabBags = CreateFrame("Button", "SFramesConfigTabBags", panel, "UIPanelButtonTemplate")
|
||
tabBags:SetWidth(100)
|
||
tabBags:SetHeight(28)
|
||
tabBags:SetPoint("TOP", tabFrames, "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 tabKeybinds = CreateFrame("Button", "SFramesConfigTabKeybinds", panel, "UIPanelButtonTemplate")
|
||
tabKeybinds:SetWidth(100)
|
||
tabKeybinds:SetHeight(28)
|
||
tabKeybinds:SetPoint("TOP", tabActionBar, "BOTTOM", 0, -4)
|
||
tabKeybinds:SetText("按键方案")
|
||
tabKeybinds:SetScript("OnClick", function() SFrames.ConfigUI:ShowPage("keybinds") end)
|
||
StyleButton(tabKeybinds)
|
||
AddBtnIcon(tabKeybinds, "key", nil, "left")
|
||
|
||
local tabMinimap = CreateFrame("Button", "SFramesConfigTabMinimap", panel, "UIPanelButtonTemplate")
|
||
tabMinimap:SetWidth(100)
|
||
tabMinimap:SetHeight(28)
|
||
tabMinimap:SetPoint("TOP", tabKeybinds, "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 tabProfile = CreateFrame("Button", "SFramesConfigTabProfile", panel, "UIPanelButtonTemplate")
|
||
tabProfile:SetWidth(100)
|
||
tabProfile:SetHeight(28)
|
||
tabProfile:SetPoint("TOP", tabTheme, "BOTTOM", 0, -4)
|
||
tabProfile:SetText("配置")
|
||
tabProfile:SetScript("OnClick", function() SFrames.ConfigUI:ShowPage("profile") end)
|
||
StyleButton(tabProfile)
|
||
AddBtnIcon(tabProfile, "save", 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 framesPage = CreateFrame("Frame", "SFramesConfigFramesPage", content)
|
||
framesPage:SetAllPoints(content)
|
||
|
||
local framesTabs = CreateFrame("Frame", "SFramesConfigFramesTabs", framesPage)
|
||
framesTabs:SetWidth(548)
|
||
framesTabs:SetHeight(30)
|
||
framesTabs:SetPoint("TOPLEFT", framesPage, "TOPLEFT", 4, -4)
|
||
|
||
local framesPlayerTab = CreateFrame("Button", "SFramesConfigFramesPlayerTab", framesTabs, "UIPanelButtonTemplate")
|
||
framesPlayerTab:SetWidth(96)
|
||
framesPlayerTab:SetHeight(24)
|
||
framesPlayerTab:SetPoint("TOPLEFT", framesTabs, "TOPLEFT", 0, 0)
|
||
framesPlayerTab:SetText("玩家")
|
||
framesPlayerTab:SetScript("OnClick", function() SFrames.ConfigUI:ShowFramesSubPage("player") end)
|
||
StyleButton(framesPlayerTab)
|
||
|
||
local framesTargetTab = CreateFrame("Button", "SFramesConfigFramesTargetTab", framesTabs, "UIPanelButtonTemplate")
|
||
framesTargetTab:SetWidth(96)
|
||
framesTargetTab:SetHeight(24)
|
||
framesTargetTab:SetPoint("LEFT", framesPlayerTab, "RIGHT", 6, 0)
|
||
framesTargetTab:SetText("目标")
|
||
framesTargetTab:SetScript("OnClick", function() SFrames.ConfigUI:ShowFramesSubPage("target") end)
|
||
StyleButton(framesTargetTab)
|
||
|
||
local framesFocusTab = CreateFrame("Button", "SFramesConfigFramesFocusTab", framesTabs, "UIPanelButtonTemplate")
|
||
framesFocusTab:SetWidth(96)
|
||
framesFocusTab:SetHeight(24)
|
||
framesFocusTab:SetPoint("LEFT", framesTargetTab, "RIGHT", 6, 0)
|
||
framesFocusTab:SetText("焦点")
|
||
framesFocusTab:SetScript("OnClick", function() SFrames.ConfigUI:ShowFramesSubPage("focus") end)
|
||
StyleButton(framesFocusTab)
|
||
|
||
local framesPartyTab = CreateFrame("Button", "SFramesConfigFramesPartyTab", framesTabs, "UIPanelButtonTemplate")
|
||
framesPartyTab:SetWidth(96)
|
||
framesPartyTab:SetHeight(24)
|
||
framesPartyTab:SetPoint("LEFT", framesFocusTab, "RIGHT", 6, 0)
|
||
framesPartyTab:SetText("小队")
|
||
framesPartyTab:SetScript("OnClick", function() SFrames.ConfigUI:ShowFramesSubPage("party") end)
|
||
StyleButton(framesPartyTab)
|
||
|
||
local framesRaidTab = CreateFrame("Button", "SFramesConfigFramesRaidTab", framesTabs, "UIPanelButtonTemplate")
|
||
framesRaidTab:SetWidth(96)
|
||
framesRaidTab:SetHeight(24)
|
||
framesRaidTab:SetPoint("LEFT", framesPartyTab, "RIGHT", 6, 0)
|
||
framesRaidTab:SetText("团队")
|
||
framesRaidTab:SetScript("OnClick", function() SFrames.ConfigUI:ShowFramesSubPage("raid") end)
|
||
StyleButton(framesRaidTab)
|
||
|
||
local framesContent = CreateFrame("Frame", "SFramesConfigFramesContent", framesPage)
|
||
framesContent:SetPoint("TOPLEFT", framesTabs, "BOTTOMLEFT", 0, -6)
|
||
framesContent:SetPoint("BOTTOMRIGHT", framesPage, "BOTTOMRIGHT", 0, 0)
|
||
|
||
local playerPage = CreateFrame("Frame", "SFramesConfigPlayerPage", framesContent)
|
||
playerPage:SetAllPoints(framesContent)
|
||
|
||
local targetPage = CreateFrame("Frame", "SFramesConfigTargetPage", framesContent)
|
||
targetPage:SetAllPoints(framesContent)
|
||
|
||
local focusPage = CreateFrame("Frame", "SFramesConfigFocusPage", framesContent)
|
||
focusPage:SetAllPoints(framesContent)
|
||
|
||
local bagPage = CreateFrame("Frame", "SFramesConfigBagPage", content)
|
||
bagPage:SetAllPoints(content)
|
||
|
||
local raidPage = CreateFrame("Frame", "SFramesConfigRaidPage", framesContent)
|
||
raidPage:SetAllPoints(framesContent)
|
||
|
||
local partyPage = CreateFrame("Frame", "SFramesConfigPartyPage", framesContent)
|
||
partyPage:SetAllPoints(framesContent)
|
||
|
||
local charPage = CreateFrame("Frame", "SFramesConfigCharPage", content)
|
||
charPage:SetAllPoints(content)
|
||
|
||
local actionBarPage = CreateFrame("Frame", "SFramesConfigActionBarPage", content)
|
||
actionBarPage:SetAllPoints(content)
|
||
|
||
local keybindsPage = CreateFrame("Frame", "SFramesConfigKeybindsPage", content)
|
||
keybindsPage: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)
|
||
|
||
local profilePage = CreateFrame("Frame", "SFramesConfigProfilePage", content)
|
||
profilePage:SetAllPoints(content)
|
||
|
||
self.frame = panel
|
||
self.title = title
|
||
self.uiTab = tabUI
|
||
self.framesTab = tabFrames
|
||
self.playerTab = tabPlayer
|
||
self.targetTab = tabTarget
|
||
self.focusTab = tabFocus
|
||
self.bagTab = tabBags
|
||
self.raidTab = tabRaid
|
||
self.partyTab = tabParty
|
||
self.charTab = tabChar
|
||
self.actionBarTab = tabActionBar
|
||
self.keybindsTab = tabKeybinds
|
||
self.minimapTab = tabMinimap
|
||
self.buffTab = tabBuff
|
||
self.personalizeTab = tabPersonalize
|
||
self.themeTab = tabTheme
|
||
self.profileTab = tabProfile
|
||
self.content = content
|
||
self.uiPage = uiPage
|
||
self.framesPage = framesPage
|
||
self.playerPage = playerPage
|
||
self.targetPage = targetPage
|
||
self.focusPage = focusPage
|
||
self.bagPage = bagPage
|
||
self.raidPage = raidPage
|
||
self.partyPage = partyPage
|
||
self.charPage = charPage
|
||
self.actionBarPage = actionBarPage
|
||
self.keybindsPage = keybindsPage
|
||
self.minimapPage = minimapPage
|
||
self.buffPage = buffPage
|
||
self.personalizePage = personalizePage
|
||
self.themePage = themePage
|
||
self.profilePage = profilePage
|
||
self.framesPlayerTab = framesPlayerTab
|
||
self.framesTargetTab = framesTargetTab
|
||
self.framesFocusTab = framesFocusTab
|
||
self.framesPartyTab = framesPartyTab
|
||
self.framesRaidTab = framesRaidTab
|
||
self.activeFrameSubPage = "player"
|
||
|
||
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, 950)
|
||
local root = personalizeScroll.child
|
||
|
||
-- ── 拾取窗口 ────────────────────────────────────────────────
|
||
local lootOptSection = CreateSection(root, "拾取窗口", 8, -8, 520, 56, font)
|
||
|
||
table.insert(controls, CreateCheckBox(lootOptSection,
|
||
"拾取窗口跟随鼠标", 14, -30,
|
||
function()
|
||
if type(SFramesDB.lootDisplay) ~= "table" then return false end
|
||
return SFramesDB.lootDisplay.followCursor == true
|
||
end,
|
||
function(checked)
|
||
if type(SFramesDB.lootDisplay) ~= "table" then SFramesDB.lootDisplay = {} end
|
||
SFramesDB.lootDisplay.followCursor = checked
|
||
end
|
||
))
|
||
CreateDesc(lootOptSection, "勾选后拾取窗口显示在鼠标附近,否则显示在固定位置", 36, -46, font)
|
||
|
||
-- ── 鼠标提示框 ────────────────────────────────────────────────
|
||
local tooltipSection = CreateSection(root, "鼠标提示框", 8, -74, 520, 180, 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)
|
||
|
||
table.insert(controls, CreateSlider(tooltipSection,
|
||
"提示框缩放", 14, -116, 200, 0.5, 2.0, 0.05,
|
||
function() return SFramesDB.tooltipScale or 1.0 end,
|
||
function(value)
|
||
SFramesDB.tooltipScale = value
|
||
if SFrames.FloatingTooltip and SFrames.FloatingTooltip.ApplyScale then
|
||
SFrames.FloatingTooltip:ApplyScale()
|
||
end
|
||
end,
|
||
function(v) return string.format("%.0f%%", v * 100) end
|
||
))
|
||
|
||
-- ── AFK 待机动画 ──────────────────────────────────────────────
|
||
-- ── 血条材质选择 ──────────────────────────────────────────
|
||
local barTexSection = CreateSection(root, "血条材质", 8, -264, 520, 120, font)
|
||
|
||
CreateDesc(barTexSection, "选择单位框架中血条和能量条使用的材质。修改后需要 /reload 生效。", 14, -30, font, 490)
|
||
|
||
local texBtnStartX = 14
|
||
local texBtnStartY = -50
|
||
local texBtnW = 96
|
||
local texBtnH = 48
|
||
local texBtnGap = 6
|
||
local textures = SFrames.BarTextures or {}
|
||
local texBtns = {}
|
||
|
||
for idx, entry in ipairs(textures) do
|
||
local eKey = entry.key
|
||
local eLabel = entry.label
|
||
local ePath = entry.path
|
||
|
||
local x = texBtnStartX + (idx - 1) * (texBtnW + texBtnGap)
|
||
local btn = CreateFrame("Button", NextWidgetName("BarTex"), barTexSection)
|
||
btn:SetWidth(texBtnW)
|
||
btn:SetHeight(texBtnH)
|
||
btn:SetPoint("TOPLEFT", barTexSection, "TOPLEFT", x, texBtnStartY)
|
||
|
||
btn:SetBackdrop({
|
||
bgFile = "Interface\\Buttons\\WHITE8X8",
|
||
edgeFile = "Interface\\Buttons\\WHITE8X8",
|
||
tile = false, edgeSize = 1,
|
||
insets = { left = 1, right = 1, top = 1, bottom = 1 },
|
||
})
|
||
btn:SetBackdropColor(0.08, 0.08, 0.10, 0.9)
|
||
btn:SetBackdropBorderColor(0.3, 0.3, 0.35, 1)
|
||
|
||
local preview = CreateFrame("StatusBar", NextWidgetName("BarTexPrev"), btn)
|
||
preview:SetPoint("TOPLEFT", btn, "TOPLEFT", 2, -2)
|
||
preview:SetPoint("BOTTOMRIGHT", btn, "BOTTOMRIGHT", -2, 14)
|
||
preview:SetStatusBarTexture(ePath)
|
||
preview:SetStatusBarColor(0.2, 0.85, 0.3, 1)
|
||
preview:SetMinMaxValues(0, 1)
|
||
preview:SetValue(1)
|
||
|
||
local label = btn:CreateFontString(nil, "OVERLAY")
|
||
label:SetFont(font, 9, "OUTLINE")
|
||
label:SetPoint("BOTTOM", btn, "BOTTOM", 0, 3)
|
||
label:SetText(eLabel)
|
||
label:SetTextColor(0.8, 0.8, 0.8)
|
||
|
||
btn.texKey = eKey
|
||
btn.texLabel = eLabel
|
||
|
||
local selectBorder = btn:CreateTexture(nil, "OVERLAY")
|
||
selectBorder:SetTexture("Interface\\Buttons\\WHITE8X8")
|
||
selectBorder:SetPoint("TOPLEFT", btn, "TOPLEFT", -1, 1)
|
||
selectBorder:SetPoint("BOTTOMRIGHT", btn, "BOTTOMRIGHT", 1, -1)
|
||
selectBorder:SetVertexColor(1, 0.85, 0.4, 0.8)
|
||
selectBorder:Hide()
|
||
btn.selectBorder = selectBorder
|
||
|
||
btn:SetScript("OnClick", function()
|
||
SFramesDB.barTexture = this.texKey
|
||
for _, b in ipairs(texBtns) do
|
||
if b.texKey == this.texKey then
|
||
b:SetBackdropBorderColor(1, 0.85, 0.6, 1)
|
||
if b.selectBorder then b.selectBorder:Show() end
|
||
else
|
||
b:SetBackdropBorderColor(0.3, 0.3, 0.35, 1)
|
||
if b.selectBorder then b.selectBorder:Hide() end
|
||
end
|
||
end
|
||
SFrames:Print("血条材质已切换为: " .. this.texLabel .. " (/reload 生效)")
|
||
end)
|
||
btn:SetScript("OnEnter", function()
|
||
this:SetBackdropBorderColor(0.7, 0.7, 0.8, 1)
|
||
GameTooltip:SetOwner(this, "ANCHOR_TOP")
|
||
GameTooltip:SetText(this.texLabel)
|
||
GameTooltip:AddLine("点击选择此材质", 0.7, 0.7, 0.7)
|
||
GameTooltip:Show()
|
||
end)
|
||
btn:SetScript("OnLeave", function()
|
||
local cur = SFramesDB.barTexture or "default"
|
||
if this.texKey == 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(texBtns, btn)
|
||
end
|
||
|
||
local curTex = SFramesDB.barTexture or "default"
|
||
for _, b in ipairs(texBtns) do
|
||
if b.texKey == curTex then
|
||
b:SetBackdropBorderColor(1, 0.85, 0.6, 1)
|
||
if b.selectBorder then b.selectBorder:Show() end
|
||
end
|
||
end
|
||
|
||
-- ── 框体样式预设 ──────────────────────────────────────────
|
||
local styleSection = CreateSection(root, "框体样式", 8, -394, 520, 90, font)
|
||
|
||
CreateDesc(styleSection, "切换单位框体的整体外观风格。即时生效,无需 /reload。", 14, -30, font, 490)
|
||
|
||
local STYLE_PRESETS = {
|
||
{ key = "classic", label = "经典(默认)" },
|
||
{ key = "gradient", label = "简约渐变" },
|
||
}
|
||
|
||
local styleBtnW = 120
|
||
local styleBtnH = 28
|
||
local styleBtnGap = 8
|
||
local styleBtnStartX = 14
|
||
local styleBtnStartY = -52
|
||
local styleBtns = {}
|
||
|
||
for idx, preset in ipairs(STYLE_PRESETS) do
|
||
local x = styleBtnStartX + (idx - 1) * (styleBtnW + styleBtnGap)
|
||
local btn = CreateFrame("Button", NextWidgetName("StylePreset"), styleSection)
|
||
btn:SetWidth(styleBtnW)
|
||
btn:SetHeight(styleBtnH)
|
||
btn:SetPoint("TOPLEFT", styleSection, "TOPLEFT", x, styleBtnStartY)
|
||
|
||
btn:SetBackdrop({
|
||
bgFile = "Interface\\Buttons\\WHITE8X8",
|
||
edgeFile = "Interface\\Buttons\\WHITE8X8",
|
||
tile = false, edgeSize = 1,
|
||
insets = { left = 1, right = 1, top = 1, bottom = 1 },
|
||
})
|
||
btn:SetBackdropColor(0.08, 0.08, 0.10, 0.9)
|
||
btn:SetBackdropBorderColor(0.3, 0.3, 0.35, 1)
|
||
|
||
local label = btn:CreateFontString(nil, "OVERLAY")
|
||
label:SetFont(font, 10, "OUTLINE")
|
||
label:SetPoint("CENTER", btn, "CENTER", 0, 0)
|
||
label:SetText(preset.label)
|
||
label:SetTextColor(0.85, 0.85, 0.85)
|
||
|
||
btn.styleKey = preset.key
|
||
btn.styleLabel = preset.label
|
||
|
||
btn:SetScript("OnClick", function()
|
||
if not SFramesDB then SFramesDB = {} end
|
||
SFramesDB.frameStylePreset = this.styleKey
|
||
-- Gradient mode: default enable rainbow bar
|
||
if this.styleKey == "gradient" then
|
||
SFramesDB.powerRainbow = true
|
||
end
|
||
-- Update button highlights
|
||
for _, b in ipairs(styleBtns) do
|
||
if b.styleKey == this.styleKey then
|
||
b:SetBackdropBorderColor(1, 0.85, 0.6, 1)
|
||
else
|
||
b:SetBackdropBorderColor(0.3, 0.3, 0.35, 1)
|
||
end
|
||
end
|
||
-- Apply to all unit frames immediately
|
||
if SFrames.Player and SFrames.Player.ApplyConfig then
|
||
local ok, err = pcall(function() SFrames.Player:ApplyConfig() end)
|
||
if not ok then DEFAULT_CHAT_FRAME:AddMessage("|cffff4444[Nanami] Player style error: " .. tostring(err) .. "|r") end
|
||
end
|
||
if SFrames.Target and SFrames.Target.ApplyConfig then
|
||
local ok, err = pcall(function() SFrames.Target:ApplyConfig() end)
|
||
if not ok then DEFAULT_CHAT_FRAME:AddMessage("|cffff4444[Nanami] Target style error: " .. tostring(err) .. "|r") end
|
||
end
|
||
if SFrames.Party and SFrames.Party.ApplyConfig then
|
||
local ok, err = pcall(function() SFrames.Party:ApplyConfig() end)
|
||
if not ok then DEFAULT_CHAT_FRAME:AddMessage("|cffff4444[Nanami] Party style error: " .. tostring(err) .. "|r") end
|
||
end
|
||
if SFrames.Pet and SFrames.Pet.ApplyConfig then
|
||
local ok, err = pcall(function() SFrames.Pet:ApplyConfig() end)
|
||
if not ok then DEFAULT_CHAT_FRAME:AddMessage("|cffff4444[Nanami] Pet style error: " .. tostring(err) .. "|r") end
|
||
end
|
||
if SFrames.Raid and SFrames.Raid.ApplyConfig then
|
||
local ok, err = pcall(function() SFrames.Raid:ApplyConfig() end)
|
||
if not ok then DEFAULT_CHAT_FRAME:AddMessage("|cffff4444[Nanami] Raid style error: " .. tostring(err) .. "|r") end
|
||
end
|
||
if SFrames.ToT and SFrames.ToT.ApplyConfig then
|
||
local ok, err = pcall(function() SFrames.ToT:ApplyConfig() end)
|
||
if not ok then DEFAULT_CHAT_FRAME:AddMessage("|cffff4444[Nanami] ToT style error: " .. tostring(err) .. "|r") end
|
||
end
|
||
if SFrames.Focus and SFrames.Focus.ApplySettings then
|
||
local ok, err = pcall(function() SFrames.Focus:ApplySettings() end)
|
||
if not ok then DEFAULT_CHAT_FRAME:AddMessage("|cffff4444[Nanami] Focus style error: " .. tostring(err) .. "|r") end
|
||
end
|
||
SFrames:Print("框体样式已切换为: " .. this.styleLabel)
|
||
end)
|
||
btn:SetScript("OnEnter", function()
|
||
this:SetBackdropColor(0.15, 0.12, 0.18, 0.95)
|
||
end)
|
||
btn:SetScript("OnLeave", function()
|
||
this:SetBackdropColor(0.08, 0.08, 0.10, 0.9)
|
||
local cur = (SFramesDB and SFramesDB.frameStylePreset) or "classic"
|
||
if this.styleKey == cur then
|
||
this:SetBackdropBorderColor(1, 0.85, 0.6, 1)
|
||
else
|
||
this:SetBackdropBorderColor(0.3, 0.3, 0.35, 1)
|
||
end
|
||
end)
|
||
|
||
table.insert(styleBtns, btn)
|
||
end
|
||
|
||
local curStyle = (SFramesDB and SFramesDB.frameStylePreset) or "classic"
|
||
for _, b in ipairs(styleBtns) do
|
||
if b.styleKey == curStyle then
|
||
b:SetBackdropBorderColor(1, 0.85, 0.6, 1)
|
||
end
|
||
end
|
||
|
||
local afkSection = CreateSection(root, "AFK 待机动画", 8, -494, 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)
|
||
|
||
-- ── 字体选择 ──────────────────────────────────────────────────
|
||
local fontSection = CreateSection(root, "全局字体", 8, -650, 520, 160, font)
|
||
CreateDesc(fontSection, "选择 UI 全局使用的字体(聊天、框体、系统文字等),需 /reload 完全生效", 14, -28, font, 480)
|
||
|
||
local FONT_BTN_W = 150
|
||
local FONT_BTN_H = 22
|
||
local FONT_COLS = 3
|
||
local FONT_X0 = 14
|
||
local FONT_Y0 = -48
|
||
local FONT_GAP_X = 8
|
||
local FONT_GAP_Y = 6
|
||
local fontBtns = {}
|
||
|
||
for idx, entry in ipairs(SFrames.FontChoices) do
|
||
local col = math.mod(idx - 1, FONT_COLS)
|
||
local row = math.floor((idx - 1) / FONT_COLS)
|
||
local bx = FONT_X0 + col * (FONT_BTN_W + FONT_GAP_X)
|
||
local by = FONT_Y0 - row * (FONT_BTN_H + FONT_GAP_Y)
|
||
|
||
local btn = CreateFrame("Button", "SFramesFontBtn" .. idx, fontSection)
|
||
btn:SetWidth(FONT_BTN_W)
|
||
btn:SetHeight(FONT_BTN_H)
|
||
btn:SetPoint("TOPLEFT", fontSection, "TOPLEFT", bx, by)
|
||
btn:SetBackdrop({ bgFile = "Interface\\Buttons\\WHITE8X8", edgeFile = "Interface\\Buttons\\WHITE8X8", edgeSize = 1 })
|
||
btn:SetBackdropColor(0.12, 0.12, 0.15, 0.9)
|
||
btn:SetBackdropBorderColor(0.3, 0.3, 0.35, 1)
|
||
btn.fontKey = entry.key
|
||
|
||
local label = btn:CreateFontString(nil, "OVERLAY")
|
||
-- Try to use the actual font for preview; fallback to default if unavailable
|
||
local previewOk = pcall(label.SetFont, label, entry.path, 11, "OUTLINE")
|
||
if not previewOk then
|
||
label:SetFont(font, 11, "OUTLINE")
|
||
end
|
||
label:SetPoint("CENTER", btn, "CENTER", 0, 0)
|
||
label:SetText(entry.label)
|
||
label:SetTextColor(0.9, 0.9, 0.9)
|
||
|
||
btn:SetScript("OnEnter", function()
|
||
if btn.fontKey ~= (SFramesDB.fontKey or "default") then
|
||
this:SetBackdropBorderColor(0.8, 0.7, 0.5, 1)
|
||
end
|
||
end)
|
||
btn:SetScript("OnLeave", function()
|
||
if btn.fontKey ~= (SFramesDB.fontKey or "default") then
|
||
this:SetBackdropBorderColor(0.3, 0.3, 0.35, 1)
|
||
end
|
||
end)
|
||
btn:SetScript("OnClick", function()
|
||
SFramesDB.fontKey = btn.fontKey
|
||
-- Update highlight
|
||
for _, b in ipairs(fontBtns) do
|
||
if b.fontKey == btn.fontKey then
|
||
b:SetBackdropBorderColor(1, 0.85, 0.6, 1)
|
||
else
|
||
b:SetBackdropBorderColor(0.3, 0.3, 0.35, 1)
|
||
end
|
||
end
|
||
end)
|
||
|
||
table.insert(fontBtns, btn)
|
||
end
|
||
|
||
-- Highlight current selection
|
||
local curFont = SFramesDB.fontKey or "default"
|
||
for _, b in ipairs(fontBtns) do
|
||
if b.fontKey == curFont then
|
||
b:SetBackdropBorderColor(1, 0.85, 0.6, 1)
|
||
end
|
||
end
|
||
|
||
CreateLabel(fontSection, "提示:切换字体后需要 /reload 才能完全生效。", 14, -136, font, 10, 0.6, 0.6, 0.65)
|
||
|
||
-- ── 彩虹能量条 ──────────────────────────────────────────────────
|
||
local rainbowSection = CreateSection(root, "彩虹能量条", 8, -820, 520, 70, font)
|
||
table.insert(controls, CreateCheckBox(rainbowSection,
|
||
"启用彩虹色能量条", 14, -30,
|
||
function() return SFramesDB.powerRainbow == true end,
|
||
function(checked)
|
||
SFramesDB.powerRainbow = checked
|
||
if SFrames.Player then SFrames.Player:UpdatePower() end
|
||
if SFrames.Target then SFrames.Target:UpdatePower() end
|
||
if SFrames.Focus then SFrames.Focus:UpdatePower() end
|
||
if SFrames.Party then SFrames.Party:UpdateAll() end
|
||
if SFrames.Raid then SFrames.Raid:UpdateAll() end
|
||
end
|
||
))
|
||
CreateDesc(rainbowSection, "所有框体(玩家/目标/焦点/队伍/团队)的能量条均使用彩虹材质", 36, -46, font)
|
||
|
||
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
|
||
|
||
--------------------------------------------------------------------------------
|
||
-- Profile page: table serializer / deserializer (Lua 5.0 compatible)
|
||
--------------------------------------------------------------------------------
|
||
|
||
local PROFILE_PREFIX = "!NUI1!"
|
||
local PROFILE_SEP = "\30"
|
||
|
||
local P64 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"
|
||
|
||
local function P64Encode(src)
|
||
if not src or src == "" then return "" end
|
||
local out = {}
|
||
local len = string.len(src)
|
||
local i = 1
|
||
while i <= len do
|
||
local a = string.byte(src, i)
|
||
local b = (i + 1 <= len) and string.byte(src, i + 1) or 0
|
||
local c = (i + 2 <= len) and string.byte(src, i + 2) or 0
|
||
local remaining = len - i + 1
|
||
local n = a * 65536 + b * 256 + c
|
||
table.insert(out, string.sub(P64, math.floor(n / 262144) + 1, math.floor(n / 262144) + 1))
|
||
table.insert(out, string.sub(P64, math.floor(math.mod(n, 262144) / 4096) + 1, math.floor(math.mod(n, 262144) / 4096) + 1))
|
||
if remaining >= 2 then
|
||
table.insert(out, string.sub(P64, math.floor(math.mod(n, 4096) / 64) + 1, math.floor(math.mod(n, 4096) / 64) + 1))
|
||
else
|
||
table.insert(out, "=")
|
||
end
|
||
if remaining >= 3 then
|
||
table.insert(out, string.sub(P64, math.mod(n, 64) + 1, math.mod(n, 64) + 1))
|
||
else
|
||
table.insert(out, "=")
|
||
end
|
||
i = i + 3
|
||
end
|
||
return table.concat(out)
|
||
end
|
||
|
||
local P64_INV = {}
|
||
for i = 1, 64 do
|
||
P64_INV[string.sub(P64, i, i)] = i - 1
|
||
end
|
||
|
||
local function P64Decode(src)
|
||
if not src or src == "" then return "" end
|
||
src = string.gsub(src, "%s+", "")
|
||
local out = {}
|
||
local len = string.len(src)
|
||
local i = 1
|
||
while i <= len do
|
||
local v1 = P64_INV[string.sub(src, i, i)] or 0
|
||
local v2 = P64_INV[string.sub(src, i + 1, i + 1)] or 0
|
||
local v3 = P64_INV[string.sub(src, i + 2, i + 2)]
|
||
local v4 = P64_INV[string.sub(src, i + 3, i + 3)]
|
||
local n = v1 * 262144 + v2 * 4096 + (v3 or 0) * 64 + (v4 or 0)
|
||
table.insert(out, string.char(math.floor(n / 65536)))
|
||
if v3 then
|
||
table.insert(out, string.char(math.floor(math.mod(n, 65536) / 256)))
|
||
end
|
||
if v4 then
|
||
table.insert(out, string.char(math.mod(n, 256)))
|
||
end
|
||
i = i + 4
|
||
end
|
||
return table.concat(out)
|
||
end
|
||
|
||
local function SerializeValue(val, depth)
|
||
if depth > 20 then return "nil" end
|
||
local t = type(val)
|
||
if t == "string" then
|
||
local escaped = string.gsub(val, "\\", "\\\\")
|
||
escaped = string.gsub(escaped, "\"", "\\\"")
|
||
escaped = string.gsub(escaped, "\n", "\\n")
|
||
escaped = string.gsub(escaped, "\r", "\\r")
|
||
return "\"" .. escaped .. "\""
|
||
elseif t == "number" then
|
||
return tostring(val)
|
||
elseif t == "boolean" then
|
||
return val and "true" or "false"
|
||
elseif t == "table" then
|
||
local parts = {}
|
||
local arrayLen = 0
|
||
for i = 1, 2048 do
|
||
if val[i] ~= nil then
|
||
arrayLen = i
|
||
else
|
||
break
|
||
end
|
||
end
|
||
for i = 1, arrayLen do
|
||
table.insert(parts, SerializeValue(val[i], depth + 1))
|
||
end
|
||
local sorted = {}
|
||
for k, v in pairs(val) do
|
||
if type(k) == "number" and k >= 1 and k <= arrayLen and math.floor(k) == k then
|
||
-- already handled
|
||
else
|
||
table.insert(sorted, k)
|
||
end
|
||
end
|
||
table.sort(sorted, function(a, b)
|
||
local ta, tb = type(a), type(b)
|
||
if ta ~= tb then return ta < tb end
|
||
return a < b
|
||
end)
|
||
for _, k in ipairs(sorted) do
|
||
local ks
|
||
if type(k) == "string" then
|
||
ks = "[\"" .. k .. "\"]"
|
||
else
|
||
ks = "[" .. tostring(k) .. "]"
|
||
end
|
||
table.insert(parts, ks .. "=" .. SerializeValue(val[k], depth + 1))
|
||
end
|
||
return "{" .. table.concat(parts, ",") .. "}"
|
||
else
|
||
return "nil"
|
||
end
|
||
end
|
||
|
||
local function DeserializeTable(str)
|
||
if not str or str == "" then return nil, "空数据" end
|
||
local safeStr = string.gsub(str, "[^%w%s%p]", "")
|
||
local dangerPatterns = { "function", "loadstring", "dofile", "pcall", "getfenv", "setfenv", "rawget", "rawset", "getmetatable", "setmetatable", "io%.", "os%.", "debug%." }
|
||
for _, pat in ipairs(dangerPatterns) do
|
||
if string.find(safeStr, pat) then
|
||
return nil, "不安全的数据"
|
||
end
|
||
end
|
||
local fn
|
||
local loadFunc = loadstring or load
|
||
if not loadFunc then return nil, "环境不支持" end
|
||
fn = loadFunc("return " .. str)
|
||
if not fn then return nil, "解析失败" end
|
||
local ok, result = pcall(fn)
|
||
if not ok then return nil, "执行失败" end
|
||
if type(result) ~= "table" then return nil, "数据格式错误" end
|
||
return result
|
||
end
|
||
|
||
local function DeepCopy(orig)
|
||
if type(orig) ~= "table" then return orig end
|
||
local copy = {}
|
||
for k, v in pairs(orig) do
|
||
copy[k] = DeepCopy(v)
|
||
end
|
||
return copy
|
||
end
|
||
|
||
local function CollectExportData()
|
||
local data = {}
|
||
|
||
if SFramesDB then
|
||
data.db = DeepCopy(SFramesDB)
|
||
end
|
||
|
||
if SFramesGlobalDB then
|
||
data.global = {}
|
||
if SFramesGlobalDB.KeyBindProfiles then
|
||
data.global.KeyBindProfiles = DeepCopy(SFramesGlobalDB.KeyBindProfiles)
|
||
end
|
||
end
|
||
|
||
if SFrames.KeyBindManager and SFrames.KeyBindManager.CollectAllBindings then
|
||
data.currentBindings = SFrames.KeyBindManager:CollectAllBindings()
|
||
end
|
||
|
||
-- Nanami-QT
|
||
if QuickToolboxDB then
|
||
data.qtDB = DeepCopy(QuickToolboxDB)
|
||
end
|
||
if QuickToolboxSharedDB then
|
||
data.qtSharedDB = DeepCopy(QuickToolboxSharedDB)
|
||
end
|
||
|
||
-- Nanami-Plates
|
||
if NanamiPlatesDB then
|
||
data.platesDB = DeepCopy(NanamiPlatesDB)
|
||
end
|
||
|
||
-- Nanami-DPS
|
||
if NanamiDPS_DB then
|
||
data.dpsDB = DeepCopy(NanamiDPS_DB)
|
||
end
|
||
|
||
data.exportTime = time and time() or 0
|
||
data.charName = UnitName("player") or "Unknown"
|
||
data.version = GetAddOnMetadata and GetAddOnMetadata("Nanami-UI", "Version") or "unknown"
|
||
return data
|
||
end
|
||
|
||
local function ExportProfileString()
|
||
local data = CollectExportData()
|
||
local raw = SerializeValue(data, 0)
|
||
return PROFILE_PREFIX .. P64Encode(raw)
|
||
end
|
||
|
||
local function ImportProfileString(text)
|
||
if not text or text == "" then return nil, "文本为空" end
|
||
text = string.gsub(text, "^%s+", "")
|
||
text = string.gsub(text, "%s+$", "")
|
||
|
||
local prefixLen = string.len(PROFILE_PREFIX)
|
||
if string.sub(text, 1, prefixLen) ~= PROFILE_PREFIX then
|
||
return nil, "无效的配置字符串(前缀不匹配)"
|
||
end
|
||
|
||
local encoded = string.sub(text, prefixLen + 1)
|
||
local raw = P64Decode(encoded)
|
||
if not raw or raw == "" then return nil, "Base64 解码失败" end
|
||
|
||
local data, err = DeserializeTable(raw)
|
||
if not data then return nil, err end
|
||
|
||
return data
|
||
end
|
||
|
||
local function ApplyImportData(data)
|
||
if not data then return false, "无数据" end
|
||
local count = 0
|
||
|
||
if data.db and type(data.db) == "table" then
|
||
for k, v in pairs(data.db) do
|
||
SFramesDB[k] = DeepCopy(v)
|
||
count = count + 1
|
||
end
|
||
end
|
||
|
||
if data.global and type(data.global) == "table" then
|
||
if not SFramesGlobalDB then SFramesGlobalDB = {} end
|
||
if data.global.KeyBindProfiles then
|
||
SFramesGlobalDB.KeyBindProfiles = DeepCopy(data.global.KeyBindProfiles)
|
||
end
|
||
end
|
||
|
||
if data.keyProfiles and type(data.keyProfiles) == "table" then
|
||
if not SFramesGlobalDB then SFramesGlobalDB = {} end
|
||
SFramesGlobalDB.KeyBindProfiles = DeepCopy(data.keyProfiles)
|
||
end
|
||
|
||
if data.currentBindings and type(data.currentBindings) == "table" then
|
||
if SFrames.KeyBindManager and SFrames.KeyBindManager.ApplyBindings then
|
||
SFrames.KeyBindManager:ApplyBindings(data.currentBindings)
|
||
end
|
||
end
|
||
|
||
-- Nanami-QT
|
||
if data.qtDB and type(data.qtDB) == "table" then
|
||
if not QuickToolboxDB then QuickToolboxDB = {} end
|
||
for k, v in pairs(data.qtDB) do
|
||
QuickToolboxDB[k] = DeepCopy(v)
|
||
end
|
||
count = count + 1
|
||
end
|
||
if data.qtSharedDB and type(data.qtSharedDB) == "table" then
|
||
if not QuickToolboxSharedDB then QuickToolboxSharedDB = {} end
|
||
for k, v in pairs(data.qtSharedDB) do
|
||
QuickToolboxSharedDB[k] = DeepCopy(v)
|
||
end
|
||
count = count + 1
|
||
end
|
||
|
||
-- Nanami-Plates
|
||
if data.platesDB and type(data.platesDB) == "table" then
|
||
if not NanamiPlatesDB then NanamiPlatesDB = {} end
|
||
for k, v in pairs(data.platesDB) do
|
||
NanamiPlatesDB[k] = DeepCopy(v)
|
||
end
|
||
count = count + 1
|
||
end
|
||
|
||
-- Nanami-DPS
|
||
if data.dpsDB and type(data.dpsDB) == "table" then
|
||
if not NanamiDPS_DB then NanamiDPS_DB = {} end
|
||
for k, v in pairs(data.dpsDB) do
|
||
NanamiDPS_DB[k] = DeepCopy(v)
|
||
end
|
||
count = count + 1
|
||
end
|
||
|
||
return true, count
|
||
end
|
||
|
||
local function BoolStr(v, trueStr, falseStr)
|
||
if v then return "|cff44ff44" .. (trueStr or "开") .. "|r" end
|
||
return "|cffff4444" .. (falseStr or "关") .. "|r"
|
||
end
|
||
|
||
local function CountTableKeys(t)
|
||
if type(t) ~= "table" then return 0 end
|
||
local n = 0
|
||
for _ in pairs(t) do n = n + 1 end
|
||
return n
|
||
end
|
||
|
||
local function BuildSummaryText()
|
||
local lines = {}
|
||
local db = SFramesDB or {}
|
||
|
||
table.insert(lines, "|cffffcc00角色:|r " .. (UnitName("player") or "Unknown"))
|
||
local ver = GetAddOnMetadata and GetAddOnMetadata("Nanami-UI", "Version") or "N/A"
|
||
table.insert(lines, "|cffffcc00版本:|r " .. ver)
|
||
table.insert(lines, "|cffffcc00布局模式:|r " .. tostring(db.layoutMode or "default"))
|
||
table.insert(lines, "")
|
||
|
||
table.insert(lines, "|cffffcc00== 功能模块 ==|r")
|
||
table.insert(lines, "单位框架: " .. BoolStr(db.enableUnitFrames ~= false))
|
||
table.insert(lines, "玩家框体: " .. BoolStr(db.enablePlayerFrame ~= false))
|
||
table.insert(lines, "目标框体: " .. BoolStr(db.enableTargetFrame ~= false))
|
||
table.insert(lines, "背包: " .. BoolStr(db.Bags and db.Bags.enable ~= false))
|
||
table.insert(lines, "动作条: " .. BoolStr(db.ActionBars and db.ActionBars.enable ~= false))
|
||
table.insert(lines, "商人: " .. BoolStr(db.enableMerchant ~= false))
|
||
table.insert(lines, "任务UI: " .. BoolStr(db.enableQuestUI ~= false))
|
||
table.insert(lines, "任务日志: " .. BoolStr(db.enableQuestLogSkin ~= false))
|
||
table.insert(lines, "训练师: " .. BoolStr(db.enableTrainer ~= false))
|
||
table.insert(lines, "法术书: " .. BoolStr(db.enableSpellBook ~= false))
|
||
table.insert(lines, "专业技能: " .. BoolStr(db.enableTradeSkill ~= false))
|
||
table.insert(lines, "社交: " .. BoolStr(db.enableSocial ~= false))
|
||
table.insert(lines, "观察: " .. BoolStr(db.enableInspect ~= false))
|
||
table.insert(lines, "飞行地图: " .. BoolStr(db.enableFlightMap ~= false))
|
||
table.insert(lines, "宠物栏: " .. BoolStr(db.enablePetStable ~= false))
|
||
table.insert(lines, "邮件: " .. BoolStr(db.enableMail ~= false))
|
||
table.insert(lines, "")
|
||
|
||
table.insert(lines, "|cffffcc00== 玩家框体 ==|r")
|
||
table.insert(lines, "缩放: " .. tostring(db.playerFrameScale or 1))
|
||
table.insert(lines, "宽度: " .. tostring(db.playerFrameWidth or 220))
|
||
table.insert(lines, "头像宽: " .. tostring(db.playerPortraitWidth or 50))
|
||
table.insert(lines, "血条高: " .. tostring(db.playerHealthHeight or 38))
|
||
table.insert(lines, "能量条高: " .. tostring(db.playerPowerHeight or 9))
|
||
table.insert(lines, "头像: " .. BoolStr(db.playerShowPortrait ~= false))
|
||
table.insert(lines, "职业图标: " .. BoolStr(db.playerShowClassIcon ~= false))
|
||
table.insert(lines, "透明度: " .. tostring(db.playerFrameAlpha or 1))
|
||
table.insert(lines, "")
|
||
|
||
table.insert(lines, "|cffffcc00== 目标框体 ==|r")
|
||
table.insert(lines, "缩放: " .. tostring(db.targetFrameScale or 1))
|
||
table.insert(lines, "宽度: " .. tostring(db.targetFrameWidth or 220))
|
||
table.insert(lines, "头像宽: " .. tostring(db.targetPortraitWidth or 50))
|
||
table.insert(lines, "血条高: " .. tostring(db.targetHealthHeight or 38))
|
||
table.insert(lines, "距离显示: " .. BoolStr(db.targetDistanceEnabled ~= false))
|
||
table.insert(lines, "距离文字大小: " .. tostring(db.targetDistanceFontSize or 14))
|
||
table.insert(lines, "透明度: " .. tostring(db.targetFrameAlpha or 1))
|
||
table.insert(lines, "全局字体: " .. tostring(db.fontKey or "default"))
|
||
table.insert(lines, "")
|
||
|
||
table.insert(lines, "|cffffcc00== 小队框架 ==|r")
|
||
table.insert(lines, "布局: " .. tostring(db.partyLayout or "vertical"))
|
||
table.insert(lines, "缩放: " .. tostring(db.partyFrameScale or 1))
|
||
table.insert(lines, "宽度: " .. tostring(db.partyFrameWidth or 150))
|
||
table.insert(lines, "高度: " .. tostring(db.partyFrameHeight or 35))
|
||
table.insert(lines, "")
|
||
|
||
table.insert(lines, "|cffffcc00== 团队框架 ==|r")
|
||
if db.Raid then
|
||
table.insert(lines, "缩放: " .. tostring(db.Raid.scale or 1))
|
||
table.insert(lines, "列数: " .. tostring(db.Raid.columns or 5))
|
||
else
|
||
table.insert(lines, "使用默认设置")
|
||
end
|
||
table.insert(lines, "")
|
||
|
||
table.insert(lines, "|cffffcc00== 背包设置 ==|r")
|
||
local bags = db.Bags or {}
|
||
table.insert(lines, "列数: " .. tostring(bags.columns or 10))
|
||
table.insert(lines, "缩放: " .. tostring(bags.scale or 1))
|
||
table.insert(lines, "间距: " .. tostring(bags.bagSpacing or 0))
|
||
table.insert(lines, "自动卖灰: " .. BoolStr(bags.sellGrey ~= false))
|
||
table.insert(lines, "银行列数: " .. tostring(bags.bankColumns or 12))
|
||
table.insert(lines, "")
|
||
|
||
table.insert(lines, "|cffffcc00== 动作条 ==|r")
|
||
if db.ActionBars then
|
||
table.insert(lines, "启用: " .. BoolStr(db.ActionBars.enable ~= false))
|
||
table.insert(lines, "缩放: " .. tostring(db.ActionBars.scale or 1))
|
||
else
|
||
table.insert(lines, "使用默认设置")
|
||
end
|
||
table.insert(lines, "")
|
||
|
||
table.insert(lines, "|cffffcc00== 聊天设置 ==|r")
|
||
local chat = db.Chat or {}
|
||
table.insert(lines, "启用: " .. BoolStr(chat.enable ~= false))
|
||
table.insert(lines, "配置项数: " .. CountTableKeys(chat))
|
||
if chat.fontSize then table.insert(lines, "字体大小: " .. tostring(chat.fontSize)) end
|
||
table.insert(lines, "")
|
||
|
||
table.insert(lines, "|cffffcc00 -- 窗口 --|r")
|
||
table.insert(lines, "宽度: " .. tostring(chat.width or "N/A"))
|
||
table.insert(lines, "高度: " .. tostring(chat.height or "N/A"))
|
||
table.insert(lines, "缩放: " .. tostring(chat.scale or 1))
|
||
table.insert(lines, "背景透明度: " .. tostring(chat.bgAlpha or 0.45))
|
||
table.insert(lines, "悬停显示背景: " .. BoolStr(chat.hoverTransparent ~= false))
|
||
table.insert(lines, "边框: " .. BoolStr(chat.showBorder))
|
||
table.insert(lines, "职业色边框: " .. BoolStr(chat.borderClassColor))
|
||
table.insert(lines, "显示玩家等级: " .. BoolStr(chat.showPlayerLevel ~= false))
|
||
table.insert(lines, "输入框位置: " .. tostring(chat.editBoxPosition or "bottom"))
|
||
table.insert(lines, "")
|
||
|
||
table.insert(lines, "|cffffcc00 -- 标签/过滤 --|r")
|
||
local tabCount = (chat.tabs and table.getn(chat.tabs)) or 0
|
||
table.insert(lines, "标签数: " .. tostring(tabCount))
|
||
if chat.tabs then
|
||
for i = 1, table.getn(chat.tabs) do
|
||
local t = chat.tabs[i]
|
||
if t then
|
||
local filterCount = t.filters and CountTableKeys(t.filters) or 0
|
||
local chanCount = t.channelFilters and CountTableKeys(t.channelFilters) or 0
|
||
table.insert(lines, " [" .. tostring(i) .. "] " .. tostring(t.name or "?") .. " (过滤:" .. filterCount .. " 频道:" .. chanCount .. ")")
|
||
end
|
||
end
|
||
end
|
||
table.insert(lines, "")
|
||
|
||
table.insert(lines, "|cffffcc00 -- AI翻译 --|r")
|
||
table.insert(lines, "翻译引擎: " .. BoolStr(chat.translateEnabled ~= false))
|
||
table.insert(lines, "消息监控: " .. BoolStr(chat.chatMonitorEnabled ~= false))
|
||
if chat.tabs then
|
||
for i = 1, table.getn(chat.tabs) do
|
||
local t = chat.tabs[i]
|
||
if t and t.translateFilters then
|
||
local enabledKeys = {}
|
||
for k, v in pairs(t.translateFilters) do
|
||
if v == true then table.insert(enabledKeys, k) end
|
||
end
|
||
if table.getn(enabledKeys) > 0 then
|
||
table.insert(lines, " [" .. tostring(t.name or i) .. "] 翻译: " .. table.concat(enabledKeys, ","))
|
||
end
|
||
end
|
||
end
|
||
end
|
||
table.insert(lines, "")
|
||
|
||
table.insert(lines, "|cffffcc00 -- 硬核设置 --|r")
|
||
table.insert(lines, "全局关闭硬核频道: " .. BoolStr(chat.hcGlobalDisable))
|
||
table.insert(lines, "屏蔽死亡/满级信息: " .. BoolStr(chat.hcDeathDisable))
|
||
table.insert(lines, "最低死亡通报等级: " .. tostring(chat.hcDeathLevelMin or 10))
|
||
table.insert(lines, "")
|
||
|
||
table.insert(lines, "|cffffcc00== 地图设置 ==|r")
|
||
local mm = db.Minimap or {}
|
||
table.insert(lines, "小地图配置项: " .. CountTableKeys(mm))
|
||
if db.WorldMap then
|
||
table.insert(lines, "世界地图配置项: " .. CountTableKeys(db.WorldMap))
|
||
end
|
||
if db.MapReveal ~= nil then
|
||
table.insert(lines, "地图全亮: " .. BoolStr(db.MapReveal ~= false))
|
||
end
|
||
table.insert(lines, "")
|
||
|
||
table.insert(lines, "|cffffcc00== Buff 栏 ==|r")
|
||
if db.MinimapBuffs then
|
||
table.insert(lines, "配置项数: " .. CountTableKeys(db.MinimapBuffs))
|
||
end
|
||
if db.hiddenBuffs then
|
||
table.insert(lines, "隐藏的 Buff 数: " .. CountTableKeys(db.hiddenBuffs))
|
||
end
|
||
table.insert(lines, "")
|
||
|
||
table.insert(lines, "|cffffcc00== 主题 ==|r")
|
||
local theme = db.Theme or {}
|
||
table.insert(lines, "预设: " .. tostring(theme.preset or "default"))
|
||
table.insert(lines, "职业主题: " .. BoolStr(theme.classTheme))
|
||
table.insert(lines, "图标集: " .. tostring(theme.iconSet or "icon"))
|
||
table.insert(lines, "")
|
||
|
||
table.insert(lines, "|cffffcc00== 框架定位 ==|r")
|
||
if db.Positions then
|
||
local posCount = CountTableKeys(db.Positions)
|
||
table.insert(lines, "已保存的框架位置: " .. posCount .. " 个")
|
||
for name, _ in pairs(db.Positions) do
|
||
table.insert(lines, " - " .. tostring(name))
|
||
end
|
||
else
|
||
table.insert(lines, "无自定义位置")
|
||
end
|
||
table.insert(lines, "")
|
||
|
||
table.insert(lines, "|cffffcc00== 按键绑定 ==|r")
|
||
if SFramesGlobalDB and SFramesGlobalDB.KeyBindProfiles then
|
||
local profCount = 0
|
||
for name, _ in pairs(SFramesGlobalDB.KeyBindProfiles) do
|
||
profCount = profCount + 1
|
||
table.insert(lines, " 方案: " .. tostring(name))
|
||
end
|
||
table.insert(lines, "已保存方案: " .. profCount .. " 个")
|
||
else
|
||
table.insert(lines, "已保存方案: 0 个")
|
||
end
|
||
if SFrames.KeyBindManager and SFrames.KeyBindManager.CollectAllBindings then
|
||
local binds = SFrames.KeyBindManager:CollectAllBindings()
|
||
table.insert(lines, "当前绑定: " .. (binds and table.getn(binds) or 0) .. " 个")
|
||
end
|
||
table.insert(lines, "")
|
||
|
||
table.insert(lines, "|cffffcc00== 个性化 ==|r")
|
||
if db.Tweaks then
|
||
table.insert(lines, "微调项数: " .. CountTableKeys(db.Tweaks))
|
||
end
|
||
table.insert(lines, "")
|
||
|
||
table.insert(lines, "|cffffcc00== Nanami-QT (工具箱) ==|r")
|
||
if QuickToolboxDB then
|
||
table.insert(lines, "角色配置项: " .. CountTableKeys(QuickToolboxDB))
|
||
else
|
||
table.insert(lines, "角色配置: |cff888888未加载|r")
|
||
end
|
||
if QuickToolboxSharedDB then
|
||
table.insert(lines, "全局配置项: " .. CountTableKeys(QuickToolboxSharedDB))
|
||
else
|
||
table.insert(lines, "全局配置: |cff888888未加载|r")
|
||
end
|
||
table.insert(lines, "")
|
||
|
||
table.insert(lines, "|cffffcc00== Nanami-Plates (姓名板) ==|r")
|
||
if NanamiPlatesDB then
|
||
table.insert(lines, "配置项数: " .. CountTableKeys(NanamiPlatesDB))
|
||
else
|
||
table.insert(lines, "配置: |cff888888未加载|r")
|
||
end
|
||
table.insert(lines, "")
|
||
|
||
table.insert(lines, "|cffffcc00== Nanami-DPS (伤害统计) ==|r")
|
||
if NanamiDPS_DB then
|
||
table.insert(lines, "配置项数: " .. CountTableKeys(NanamiDPS_DB))
|
||
else
|
||
table.insert(lines, "配置: |cff888888未加载|r")
|
||
end
|
||
|
||
return table.concat(lines, "\n")
|
||
end
|
||
|
||
--------------------------------------------------------------------------------
|
||
-- Profile page: export / import dialogs
|
||
--------------------------------------------------------------------------------
|
||
|
||
local profileExportFrame, profileImportFrame
|
||
|
||
local function ShowProfileExportDialog()
|
||
local text = ExportProfileString()
|
||
|
||
if not profileExportFrame then
|
||
local font = SFrames:GetFont()
|
||
local f = CreateFrame("Frame", "SFramesProfileExport", UIParent)
|
||
f:SetWidth(520)
|
||
f:SetHeight(380)
|
||
f:SetPoint("CENTER", UIParent, "CENTER", 0, 0)
|
||
f:SetMovable(true)
|
||
f:EnableMouse(true)
|
||
f:SetClampedToScreen(true)
|
||
f:SetToplevel(true)
|
||
f:SetFrameStrata("TOOLTIP")
|
||
f:SetFrameLevel(200)
|
||
f:RegisterForDrag("LeftButton")
|
||
f:SetScript("OnDragStart", function() this:StartMoving() end)
|
||
f:SetScript("OnDragStop", function() this:StopMovingOrSizing() end)
|
||
if SFrames.CreateRoundBackdrop then
|
||
SFrames:CreateRoundBackdrop(f)
|
||
else
|
||
EnsureSoftBackdrop(f)
|
||
end
|
||
tinsert(UISpecialFrames, "SFramesProfileExport")
|
||
|
||
local title = f:CreateFontString(nil, "OVERLAY")
|
||
title:SetFont(font, 12, "OUTLINE")
|
||
title:SetPoint("TOP", f, "TOP", 0, -12)
|
||
title:SetText("|cffffcc00导出全部配置|r")
|
||
f.title = title
|
||
|
||
local desc = f:CreateFontString(nil, "OVERLAY")
|
||
desc:SetFont(font, 10, "OUTLINE")
|
||
desc:SetPoint("TOP", title, "BOTTOM", 0, -4)
|
||
desc:SetText("Ctrl+A 全选,Ctrl+C 复制。将字符串分享给其他人即可导入")
|
||
desc:SetTextColor(0.7, 0.7, 0.7)
|
||
|
||
local sf = CreateFrame("ScrollFrame", "SFramesProfileExportScroll", f, "UIPanelScrollFrameTemplate")
|
||
sf:SetPoint("TOPLEFT", f, "TOPLEFT", 14, -52)
|
||
sf:SetPoint("BOTTOMRIGHT", f, "BOTTOMRIGHT", -34, 44)
|
||
|
||
local edit = CreateFrame("EditBox", "SFramesProfileExportEdit", sf)
|
||
edit:SetWidth(450)
|
||
edit:SetHeight(240)
|
||
edit:SetMultiLine(true)
|
||
edit:SetFont(font, 9, "OUTLINE")
|
||
edit:SetAutoFocus(false)
|
||
edit:SetScript("OnEscapePressed", function() f:Hide() end)
|
||
sf:SetScrollChild(edit)
|
||
f.edit = edit
|
||
|
||
local closeBtn = CreateFrame("Button", "SFramesProfileExportClose", f, "UIPanelButtonTemplate")
|
||
closeBtn:SetWidth(100)
|
||
closeBtn:SetHeight(26)
|
||
closeBtn:SetPoint("BOTTOM", f, "BOTTOM", 0, 12)
|
||
closeBtn:SetText("关闭")
|
||
StyleButton(closeBtn)
|
||
closeBtn:SetScript("OnClick", function() f:Hide() end)
|
||
|
||
profileExportFrame = f
|
||
end
|
||
|
||
profileExportFrame.edit:SetText(text)
|
||
profileExportFrame:Show()
|
||
profileExportFrame:Raise()
|
||
profileExportFrame.edit:HighlightText()
|
||
profileExportFrame.edit:SetFocus()
|
||
end
|
||
|
||
local function ShowProfileImportDialog()
|
||
if not profileImportFrame then
|
||
local font = SFrames:GetFont()
|
||
local f = CreateFrame("Frame", "SFramesProfileImport", UIParent)
|
||
f:SetWidth(520)
|
||
f:SetHeight(420)
|
||
f:SetPoint("CENTER", UIParent, "CENTER", 0, 0)
|
||
f:SetMovable(true)
|
||
f:EnableMouse(true)
|
||
f:SetClampedToScreen(true)
|
||
f:SetToplevel(true)
|
||
f:SetFrameStrata("TOOLTIP")
|
||
f:SetFrameLevel(200)
|
||
f:RegisterForDrag("LeftButton")
|
||
f:SetScript("OnDragStart", function() this:StartMoving() end)
|
||
f:SetScript("OnDragStop", function() this:StopMovingOrSizing() end)
|
||
if SFrames.CreateRoundBackdrop then
|
||
SFrames:CreateRoundBackdrop(f)
|
||
else
|
||
EnsureSoftBackdrop(f)
|
||
end
|
||
tinsert(UISpecialFrames, "SFramesProfileImport")
|
||
|
||
local title = f:CreateFontString(nil, "OVERLAY")
|
||
title:SetFont(font, 12, "OUTLINE")
|
||
title:SetPoint("TOP", f, "TOP", 0, -12)
|
||
title:SetText("|cffffcc00导入配置|r")
|
||
f.title = title
|
||
|
||
local desc = f:CreateFontString(nil, "OVERLAY")
|
||
desc:SetFont(font, 10, "OUTLINE")
|
||
desc:SetPoint("TOP", title, "BOTTOM", 0, -4)
|
||
desc:SetText("将配置字符串粘贴到下方 (Ctrl+V),然后点击导入")
|
||
desc:SetTextColor(0.7, 0.7, 0.7)
|
||
|
||
local sf = CreateFrame("ScrollFrame", "SFramesProfileImportScroll", f, "UIPanelScrollFrameTemplate")
|
||
sf:SetPoint("TOPLEFT", f, "TOPLEFT", 14, -52)
|
||
sf:SetPoint("BOTTOMRIGHT", f, "BOTTOMRIGHT", -34, 78)
|
||
|
||
local edit = CreateFrame("EditBox", "SFramesProfileImportEdit", sf)
|
||
edit:SetWidth(450)
|
||
edit:SetHeight(240)
|
||
edit:SetMultiLine(true)
|
||
edit:SetFont(font, 9, "OUTLINE")
|
||
edit:SetAutoFocus(false)
|
||
edit:SetScript("OnEscapePressed", function() f:Hide() end)
|
||
sf:SetScrollChild(edit)
|
||
f.edit = edit
|
||
|
||
local statusLabel = f:CreateFontString(nil, "OVERLAY")
|
||
statusLabel:SetFont(font, 10, "OUTLINE")
|
||
statusLabel:SetPoint("BOTTOMLEFT", f, "BOTTOMLEFT", 16, 50)
|
||
statusLabel:SetWidth(340)
|
||
statusLabel:SetJustifyH("LEFT")
|
||
statusLabel:SetText("")
|
||
f.statusLabel = statusLabel
|
||
|
||
local importBtn = CreateFrame("Button", "SFramesProfileImportBtn", f, "UIPanelButtonTemplate")
|
||
importBtn:SetWidth(120)
|
||
importBtn:SetHeight(26)
|
||
importBtn:SetPoint("BOTTOMLEFT", f, "BOTTOMLEFT", 130, 14)
|
||
importBtn:SetText("导入并应用")
|
||
StyleButton(importBtn)
|
||
importBtn:SetScript("OnClick", function()
|
||
local inputText = f.edit:GetText()
|
||
if not inputText or inputText == "" then
|
||
f.statusLabel:SetText("|cffff4444请先粘贴配置数据|r")
|
||
return
|
||
end
|
||
local data, err = ImportProfileString(inputText)
|
||
if not data then
|
||
f.statusLabel:SetText("|cffff4444错误: " .. (err or "未知") .. "|r")
|
||
return
|
||
end
|
||
local ok, count = ApplyImportData(data)
|
||
if ok then
|
||
local src = data.charName or "未知"
|
||
local ver = data.version or "未知"
|
||
f.statusLabel:SetText("|cff44ff44导入成功! 来源: " .. src .. " (v" .. ver .. ")|r")
|
||
SFrames:Print("已导入配置 (来源: " .. src .. "),需要 /reload 生效")
|
||
else
|
||
f.statusLabel:SetText("|cffff4444导入失败|r")
|
||
end
|
||
end)
|
||
|
||
local cancelBtn = CreateFrame("Button", "SFramesProfileImportCancel", f, "UIPanelButtonTemplate")
|
||
cancelBtn:SetWidth(100)
|
||
cancelBtn:SetHeight(26)
|
||
cancelBtn:SetPoint("LEFT", importBtn, "RIGHT", 8, 0)
|
||
cancelBtn:SetText("取消")
|
||
StyleButton(cancelBtn)
|
||
cancelBtn:SetScript("OnClick", function() f:Hide() end)
|
||
|
||
profileImportFrame = f
|
||
end
|
||
|
||
profileImportFrame.edit:SetText("")
|
||
profileImportFrame.statusLabel:SetText("")
|
||
profileImportFrame:Show()
|
||
profileImportFrame:Raise()
|
||
profileImportFrame.edit:SetFocus()
|
||
end
|
||
|
||
--------------------------------------------------------------------------------
|
||
-- Profile page: partial apply dialog
|
||
--------------------------------------------------------------------------------
|
||
|
||
local APPLY_CATEGORIES = {
|
||
{ key = "ui", label = "界面功能开关", keys = {"enableMerchant","enableQuestUI","enableQuestLogSkin","enableTrainer","enableSpellBook","enableTradeSkill","enableSocial","enableInspect","enableFlightMap","enablePetStable","enableMail","enableUnitFrames","enablePlayerFrame","enableTargetFrame","enablePartyFrame","enableChat","showLevel","classColorHealth","smoothBars","fontKey","frameStylePreset"} },
|
||
{ key = "player", label = "玩家框体", keys = {"playerFrameScale","playerFrameWidth","playerPortraitWidth","playerHealthHeight","playerPowerHeight","playerPowerWidth","playerPowerOffsetX","playerPowerOffsetY","playerPowerOnTop","playerPortraitBgAlpha","playerShowClass","playerShowClassIcon","playerShowPortrait","playerFrameAlpha","playerBgAlpha","playerNameFontSize","playerValueFontSize","playerHealthFontSize","playerPowerFontSize","playerHealthTexture","playerPowerTexture","playerNameFontKey","playerHealthFontKey","playerPowerFontKey","castbarStandalone","castbarRainbow","powerRainbow","petHealthTexture","petPowerTexture"} },
|
||
{ key = "target", label = "目标框体", keys = {"targetFrameScale","targetFrameWidth","targetPortraitWidth","targetHealthHeight","targetPowerHeight","targetPowerWidth","targetPowerOffsetX","targetPowerOffsetY","targetPowerOnTop","targetPortraitBgAlpha","targetShowClass","targetShowClassIcon","targetShowPortrait","targetFrameAlpha","targetBgAlpha","targetNameFontSize","targetValueFontSize","targetHealthFontSize","targetPowerFontSize","targetHealthTexture","targetPowerTexture","targetNameFontKey","targetHealthFontKey","targetPowerFontKey","targetDistanceEnabled","targetDistanceOnFrame","targetDistanceScale","targetDistanceFontSize","targetDistanceFontKey","totHealthTexture"} },
|
||
{ key = "focus", label = "焦点框体", keys = {"focusEnabled","focusFrameScale","focusFrameWidth","focusPortraitWidth","focusHealthHeight","focusPowerHeight","focusPowerWidth","focusPowerOffsetX","focusPowerOffsetY","focusPowerOnTop","focusPortraitBgAlpha","focusShowPortrait","focusShowCastBar","focusShowAuras","focusNameFontSize","focusValueFontSize","focusHealthFontSize","focusPowerFontSize","focusCastFontSize","focusDistanceFontSize","focusHealthTexture","focusPowerTexture","focusNameFontKey","focusHealthFontKey","focusPowerFontKey","focusCastFontKey","focusDistanceFontKey","focusBgAlpha"} },
|
||
{ key = "party", label = "小队框架", keys = {"partyLayout","partyFrameScale","partyFrameWidth","partyFrameHeight","partyPortraitWidth","partyHealthHeight","partyPowerHeight","partyPowerWidth","partyPowerOffsetX","partyPowerOffsetY","partyPowerOnTop","partyPortraitBgAlpha","partyHorizontalGap","partyVerticalGap","partyNameFontSize","partyValueFontSize","partyHealthFontSize","partyPowerFontSize","partyHealthTexture","partyPowerTexture","partyNameFontKey","partyHealthFontKey","partyPowerFontKey","partyShowBuffs","partyShowDebuffs","partyBgAlpha","partyPortrait3D"} },
|
||
{ key = "raid", label = "团队框架", keys = {"raidLayout","raidFrameScale","raidFrameWidth","raidFrameHeight","raidHealthHeight","raidPowerWidth","raidPowerHeight","raidPowerOffsetX","raidPowerOffsetY","raidPowerOnTop","raidHorizontalGap","raidVerticalGap","raidGroupGap","raidNameFontSize","raidValueFontSize","raidHealthFontSize","raidPowerFontSize","raidHealthTexture","raidPowerTexture","raidNameFontKey","raidHealthFontKey","raidPowerFontKey","raidShowPower","enableRaidFrames","raidHealthFormat","raidShowGroupLabel","raidBgAlpha"} },
|
||
{ key = "bags", label = "背包设置", table_key = "Bags" },
|
||
{ key = "actionbar",label = "动作条设置", table_key = "ActionBars" },
|
||
{ key = "chat_general", label = "聊天-通用",
|
||
chat_keys = {"enable","showBorder","borderClassColor","showPlayerLevel","chatMonitorEnabled","layoutVersion"} },
|
||
{ key = "chat_window", label = "聊天-窗口",
|
||
chat_keys = {"width","height","scale","fontSize","bgAlpha","hoverTransparent","sidePadding","topPadding","bottomPadding","editBoxPosition","editBoxX","editBoxY"} },
|
||
{ key = "chat_tabs", label = "聊天-标签/过滤",
|
||
chat_keys = {"tabs","activeTab","nextTabId"} },
|
||
{ key = "chat_translate",label = "聊天-AI翻译",
|
||
chat_keys = {"translateEnabled"}, chat_tab_keys = {"translateFilters","channelTranslateFilters"} },
|
||
{ key = "chat_hardcore", label = "聊天-硬核设置",
|
||
chat_keys = {"hcGlobalDisable","hcDeathDisable","hcDeathLevelMin"} },
|
||
{ key = "theme", label = "主题设置", table_key = "Theme" },
|
||
{ key = "minimap", label = "地图设置", table_key = "Minimap" },
|
||
{ key = "buff", label = "Buff 栏设置", table_key = "MinimapBuffs" },
|
||
{ key = "tweaks", label = "微调 / 个性化", table_key = "Tweaks" },
|
||
{ key = "positions",label = "界面位置", table_key = "Positions" },
|
||
{ key = "qt", label = "Nanami-QT", ext_db = "_qtDB", ext_global = "QuickToolboxDB" },
|
||
{ key = "qtshared", label = "QT 全局配置", ext_db = "_qtSharedDB", ext_global = "QuickToolboxSharedDB" },
|
||
{ key = "plates", label = "Nanami-Plates", ext_db = "_platesDB", ext_global = "NanamiPlatesDB" },
|
||
{ key = "dps", label = "Nanami-DPS", ext_db = "_dpsDB", ext_global = "NanamiDPS_DB" },
|
||
}
|
||
|
||
local profileApplyFrame
|
||
|
||
local function ShowPartialApplyDialog(profileData, profileName)
|
||
if not profileApplyFrame then
|
||
local font = SFrames:GetFont()
|
||
local f = CreateFrame("Frame", "SFramesProfileApply", UIParent)
|
||
f:SetWidth(400)
|
||
f:SetHeight(540)
|
||
f:SetPoint("CENTER", UIParent, "CENTER", 0, 0)
|
||
f:SetMovable(true)
|
||
f:EnableMouse(true)
|
||
f:SetClampedToScreen(true)
|
||
f:SetToplevel(true)
|
||
f:SetFrameStrata("TOOLTIP")
|
||
f:SetFrameLevel(200)
|
||
f:RegisterForDrag("LeftButton")
|
||
f:SetScript("OnDragStart", function() this:StartMoving() end)
|
||
f:SetScript("OnDragStop", function() this:StopMovingOrSizing() end)
|
||
f: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 },
|
||
})
|
||
f:SetBackdropColor(0.05, 0.05, 0.08, 0.97)
|
||
f:SetBackdropBorderColor(0.4, 0.3, 0.5, 0.95)
|
||
tinsert(UISpecialFrames, "SFramesProfileApply")
|
||
|
||
local title = f:CreateFontString(nil, "OVERLAY")
|
||
title:SetFont(font, 12, "OUTLINE")
|
||
title:SetPoint("TOP", f, "TOP", 0, -12)
|
||
title:SetText("|cffffcc00选择要应用的配置项|r")
|
||
f.title = title
|
||
|
||
local checks = {}
|
||
local startY = -36
|
||
for idx, cat in ipairs(APPLY_CATEGORIES) do
|
||
local catKey = cat.key
|
||
local catLabel = cat.label
|
||
local cb = CreateFrame("CheckButton", NextWidgetName("ApplyCat"), f, "UICheckButtonTemplate")
|
||
cb:SetWidth(18)
|
||
cb:SetHeight(18)
|
||
local col = (idx <= 10) and 0 or 1
|
||
local row = (idx <= 10) and (idx - 1) or (idx - 11)
|
||
cb:SetPoint("TOPLEFT", f, "TOPLEFT", 16 + col * 180, startY - row * 24)
|
||
cb:SetChecked(1)
|
||
cb.sfChecked = true
|
||
cb.catKey = catKey
|
||
|
||
local text = _G[cb:GetName() .. "Text"]
|
||
if text then
|
||
text:SetText(catLabel)
|
||
text:SetWidth(150)
|
||
end
|
||
StyleCheckBox(cb)
|
||
if cb.sfBox then cb.sfBox:EnableMouse(false) end
|
||
if cb.sfApplyState then cb.sfApplyState(true) end
|
||
|
||
cb:SetScript("OnClick", function()
|
||
local v = this:GetChecked()
|
||
local isChecked = (v == 1 or v == true)
|
||
this.sfChecked = isChecked
|
||
if this.sfApplyState then this.sfApplyState(isChecked) end
|
||
end)
|
||
|
||
table.insert(checks, cb)
|
||
end
|
||
f.checks = checks
|
||
|
||
local selectAll = CreateFrame("Button", NextWidgetName("Btn"), f, "UIPanelButtonTemplate")
|
||
selectAll:SetWidth(80)
|
||
selectAll:SetHeight(22)
|
||
selectAll:SetPoint("TOPLEFT", f, "TOPLEFT", 16, -440)
|
||
selectAll:SetText("全选")
|
||
StyleButton(selectAll)
|
||
selectAll:SetScript("OnClick", function()
|
||
local cks = profileApplyFrame and profileApplyFrame.checks
|
||
if cks then
|
||
for i = 1, table.getn(cks) do
|
||
cks[i]:SetChecked(1)
|
||
cks[i].sfChecked = true
|
||
if cks[i].sfApplyState then cks[i].sfApplyState(true) end
|
||
end
|
||
end
|
||
end)
|
||
|
||
local deselectAll = CreateFrame("Button", NextWidgetName("Btn"), f, "UIPanelButtonTemplate")
|
||
deselectAll:SetWidth(80)
|
||
deselectAll:SetHeight(22)
|
||
deselectAll:SetPoint("LEFT", selectAll, "RIGHT", 4, 0)
|
||
deselectAll:SetText("取消全选")
|
||
StyleButton(deselectAll)
|
||
deselectAll:SetScript("OnClick", function()
|
||
local cks = profileApplyFrame and profileApplyFrame.checks
|
||
if cks then
|
||
for i = 1, table.getn(cks) do
|
||
cks[i]:SetChecked(0)
|
||
cks[i].sfChecked = false
|
||
if cks[i].sfApplyState then cks[i].sfApplyState(false) end
|
||
end
|
||
end
|
||
end)
|
||
|
||
local statusLabel = f:CreateFontString(nil, "OVERLAY")
|
||
statusLabel:SetFont(font, 10, "OUTLINE")
|
||
statusLabel:SetPoint("BOTTOMLEFT", f, "BOTTOMLEFT", 16, 42)
|
||
statusLabel:SetWidth(340)
|
||
statusLabel:SetJustifyH("LEFT")
|
||
statusLabel:SetText("")
|
||
f.statusLabel = statusLabel
|
||
|
||
local applyBtn = CreateFrame("Button", NextWidgetName("Btn"), f, "UIPanelButtonTemplate")
|
||
applyBtn:SetWidth(120)
|
||
applyBtn:SetHeight(26)
|
||
applyBtn:SetPoint("BOTTOMLEFT", f, "BOTTOMLEFT", 80, 12)
|
||
applyBtn:SetText("应用选中项")
|
||
StyleButton(applyBtn)
|
||
applyBtn:SetScript("OnClick", function()
|
||
if not f.profileData then return end
|
||
local applied = 0
|
||
for _, cb in ipairs(f.checks) do
|
||
if cb:GetChecked() == 1 or cb:GetChecked() == true then
|
||
local catKey = cb.catKey
|
||
for _, cat in ipairs(APPLY_CATEGORIES) do
|
||
if cat.key == catKey then
|
||
if cat.ext_db then
|
||
-- External addon DB (QT/Plates/DPS)
|
||
local srcData = f.profileData[cat.ext_db]
|
||
if srcData and type(srcData) == "table" then
|
||
local globalName = cat.ext_global
|
||
local target = getglobal(globalName)
|
||
if not target then
|
||
setglobal(globalName, {})
|
||
target = getglobal(globalName)
|
||
end
|
||
for k, v in pairs(srcData) do
|
||
target[k] = v
|
||
end
|
||
applied = applied + 1
|
||
end
|
||
elseif cat.chat_keys then
|
||
-- Chat sub-category: read from profileData.Chat
|
||
local srcChat = f.profileData.Chat or (f.profileData.db and f.profileData.db.Chat)
|
||
if srcChat and type(srcChat) == "table" then
|
||
if not SFramesDB.Chat then SFramesDB.Chat = {} end
|
||
for _, k in ipairs(cat.chat_keys) do
|
||
if srcChat[k] ~= nil then
|
||
SFramesDB.Chat[k] = DeepCopy(srcChat[k])
|
||
applied = applied + 1
|
||
end
|
||
end
|
||
-- chat_tab_keys: copy per-tab sub-fields (e.g. translateFilters)
|
||
if cat.chat_tab_keys and srcChat.tabs and SFramesDB.Chat.tabs then
|
||
for ti = 1, table.getn(srcChat.tabs) do
|
||
local srcTab = srcChat.tabs[ti]
|
||
local dstTab = SFramesDB.Chat.tabs[ti]
|
||
if srcTab and dstTab then
|
||
for _, tk in ipairs(cat.chat_tab_keys) do
|
||
if srcTab[tk] ~= nil then
|
||
dstTab[tk] = DeepCopy(srcTab[tk])
|
||
end
|
||
end
|
||
end
|
||
end
|
||
applied = applied + 1
|
||
end
|
||
end
|
||
elseif cat.table_key then
|
||
if f.profileData[cat.table_key] then
|
||
SFramesDB[cat.table_key] = f.profileData[cat.table_key]
|
||
applied = applied + 1
|
||
end
|
||
elseif cat.keys then
|
||
for _, k in ipairs(cat.keys) do
|
||
if f.profileData[k] ~= nil then
|
||
SFramesDB[k] = f.profileData[k]
|
||
applied = applied + 1
|
||
end
|
||
end
|
||
end
|
||
break
|
||
end
|
||
end
|
||
end
|
||
end
|
||
f.statusLabel:SetText("|cff44ff44已应用 " .. applied .. " 项设置,/reload 生效|r")
|
||
SFrames:Print("已部分应用配置(" .. applied .. " 项),需要 /reload 生效")
|
||
end)
|
||
|
||
local cancelBtn = CreateFrame("Button", NextWidgetName("Btn"), f, "UIPanelButtonTemplate")
|
||
cancelBtn:SetWidth(100)
|
||
cancelBtn:SetHeight(26)
|
||
cancelBtn:SetPoint("LEFT", applyBtn, "RIGHT", 8, 0)
|
||
cancelBtn:SetText("取消")
|
||
StyleButton(cancelBtn)
|
||
cancelBtn:SetScript("OnClick", function() f:Hide() end)
|
||
|
||
profileApplyFrame = f
|
||
end
|
||
|
||
profileApplyFrame.profileData = profileData
|
||
profileApplyFrame.title:SetText("|cffffcc00应用配置: " .. (profileName or "未知") .. "|r")
|
||
profileApplyFrame.statusLabel:SetText("")
|
||
for _, cb in ipairs(profileApplyFrame.checks) do cb:SetChecked(1) end
|
||
profileApplyFrame:Show()
|
||
profileApplyFrame:Raise()
|
||
end
|
||
|
||
--------------------------------------------------------------------------------
|
||
-- Profile page: Build
|
||
--------------------------------------------------------------------------------
|
||
|
||
function SFrames.ConfigUI:BuildProfilePage()
|
||
local font = SFrames:GetFont()
|
||
local page = self.profilePage
|
||
|
||
local profileScroll = CreateScrollArea(page, 4, -4, 548, 458, 1800)
|
||
local root = profileScroll.child
|
||
self.profileScroll = profileScroll
|
||
|
||
-- ── 角色配置管理 ──────────────────────────────────────
|
||
local charSection = CreateSection(root, "角色配置管理", 8, -8, 520, 280, font)
|
||
|
||
CreateDesc(charSection,
|
||
"每个角色的配置会自动保存。你可以查看同账号下其他角色的配置,并选择应用到当前角色。",
|
||
14, -30, font, 490)
|
||
|
||
local charListHolder = CreateFrame("Frame", NextWidgetName("CharList"), charSection)
|
||
charListHolder:SetWidth(492)
|
||
charListHolder:SetHeight(190)
|
||
charListHolder:SetPoint("TOPLEFT", charSection, "TOPLEFT", 14, -50)
|
||
|
||
self._charListHolder = charListHolder
|
||
self._charListButtons = {}
|
||
|
||
local applyBtn = CreateFrame("Button", NextWidgetName("Btn"), charSection, "UIPanelButtonTemplate")
|
||
applyBtn:SetWidth(120)
|
||
applyBtn:SetHeight(24)
|
||
applyBtn:SetPoint("TOPLEFT", charSection, "TOPLEFT", 14, -250)
|
||
applyBtn:SetText("应用此配置")
|
||
StyleButton(applyBtn)
|
||
AddBtnIcon(applyBtn, "save")
|
||
applyBtn:SetScript("OnClick", function()
|
||
local sel = SFrames.ConfigUI._selectedCharProfile
|
||
if not sel then
|
||
SFrames:Print("请先选择一个角色配置")
|
||
return
|
||
end
|
||
ShowPartialApplyDialog(sel.data, sel.name .. " (" .. sel.realm .. ")")
|
||
end)
|
||
|
||
local deleteBtn = CreateFrame("Button", NextWidgetName("Btn"), charSection, "UIPanelButtonTemplate")
|
||
deleteBtn:SetWidth(120)
|
||
deleteBtn:SetHeight(24)
|
||
deleteBtn:SetPoint("LEFT", applyBtn, "RIGHT", 8, 0)
|
||
deleteBtn:SetText("删除此配置")
|
||
StyleButton(deleteBtn)
|
||
AddBtnIcon(deleteBtn, "close")
|
||
deleteBtn:SetScript("OnClick", function()
|
||
local sel = SFrames.ConfigUI._selectedCharProfile
|
||
if not sel then
|
||
SFrames:Print("请先选择一个角色配置")
|
||
return
|
||
end
|
||
SFrames:DeleteCharProfile(sel.realm, sel.name)
|
||
SFrames:Print("已删除 " .. sel.name .. " (" .. sel.realm .. ") 的配置记录")
|
||
SFrames.ConfigUI._selectedCharProfile = nil
|
||
SFrames.ConfigUI:RefreshCharList()
|
||
end)
|
||
|
||
local exportSelBtn = CreateFrame("Button", NextWidgetName("Btn"), charSection, "UIPanelButtonTemplate")
|
||
exportSelBtn:SetWidth(120)
|
||
exportSelBtn:SetHeight(24)
|
||
exportSelBtn:SetPoint("LEFT", deleteBtn, "RIGHT", 8, 0)
|
||
exportSelBtn:SetText("导出此配置")
|
||
StyleButton(exportSelBtn)
|
||
AddBtnIcon(exportSelBtn, "scroll")
|
||
exportSelBtn:SetScript("OnClick", function()
|
||
local sel = SFrames.ConfigUI._selectedCharProfile
|
||
if sel and sel.data then
|
||
local cleanDB = {}
|
||
local data = { charName = sel.name, version = "profile" }
|
||
for k, v in pairs(sel.data) do
|
||
if k == "_qtDB" then
|
||
data.qtDB = v
|
||
elseif k == "_qtSharedDB" then
|
||
data.qtSharedDB = v
|
||
elseif k == "_platesDB" then
|
||
data.platesDB = v
|
||
elseif k == "_dpsDB" then
|
||
data.dpsDB = v
|
||
else
|
||
cleanDB[k] = v
|
||
end
|
||
end
|
||
data.db = cleanDB
|
||
local raw = SerializeValue(data, 0)
|
||
local text = PROFILE_PREFIX .. P64Encode(raw)
|
||
if profileExportFrame and profileExportFrame.edit then
|
||
profileExportFrame.edit:SetText(text)
|
||
profileExportFrame:Show()
|
||
profileExportFrame:Raise()
|
||
profileExportFrame.edit:HighlightText()
|
||
profileExportFrame.edit:SetFocus()
|
||
else
|
||
ShowProfileExportDialog()
|
||
end
|
||
else
|
||
ShowProfileExportDialog()
|
||
end
|
||
end)
|
||
|
||
-- ── 导入配置 ──────────────────────────────────────────
|
||
local importSection = CreateSection(root, "导入配置字符串", 8, -298, 520, 80, font)
|
||
|
||
CreateDesc(importSection,
|
||
"从其他玩家获取配置字符串后粘贴导入。导入后需要 /reload 生效。",
|
||
14, -30, font, 350)
|
||
|
||
local importBtn = CreateFrame("Button", NextWidgetName("Btn"), importSection, "UIPanelButtonTemplate")
|
||
importBtn:SetWidth(120)
|
||
importBtn:SetHeight(24)
|
||
importBtn:SetPoint("TOPLEFT", importSection, "TOPLEFT", 14, -54)
|
||
importBtn:SetText("粘贴导入")
|
||
StyleButton(importBtn)
|
||
AddBtnIcon(importBtn, "scroll")
|
||
importBtn:SetScript("OnClick", function() ShowProfileImportDialog() end)
|
||
|
||
local exportCurBtn = CreateFrame("Button", NextWidgetName("Btn"), importSection, "UIPanelButtonTemplate")
|
||
exportCurBtn:SetWidth(140)
|
||
exportCurBtn:SetHeight(24)
|
||
exportCurBtn:SetPoint("LEFT", importBtn, "RIGHT", 8, 0)
|
||
exportCurBtn:SetText("导出当前配置")
|
||
StyleButton(exportCurBtn)
|
||
AddBtnIcon(exportCurBtn, "save")
|
||
exportCurBtn:SetScript("OnClick", function() ShowProfileExportDialog() end)
|
||
|
||
-- ── 配置概览 ──────────────────────────────────────────
|
||
local summarySection = CreateSection(root, "当前配置概览", 8, -388, 520, 1140, font)
|
||
|
||
local summaryFS = summarySection:CreateFontString(nil, "OVERLAY")
|
||
summaryFS:SetFont(font, 10, "OUTLINE")
|
||
summaryFS:SetPoint("TOPLEFT", summarySection, "TOPLEFT", 14, -34)
|
||
summaryFS:SetWidth(490)
|
||
summaryFS:SetJustifyH("LEFT")
|
||
summaryFS:SetSpacing(3)
|
||
summaryFS:SetText(BuildSummaryText())
|
||
self._profileSummaryFS = summaryFS
|
||
|
||
self:RefreshCharList()
|
||
end
|
||
|
||
function SFrames.ConfigUI:RefreshCharList()
|
||
if not self._charListHolder then return end
|
||
local holder = self._charListHolder
|
||
|
||
if self._charListButtons then
|
||
for _, btn in ipairs(self._charListButtons) do
|
||
btn:Hide()
|
||
end
|
||
end
|
||
self._charListButtons = {}
|
||
self._selectedCharProfile = nil
|
||
|
||
local profiles = SFrames:GetAllCharProfiles()
|
||
local font = SFrames:GetFont()
|
||
local curRealm, curName = SFrames:GetCharKey()
|
||
local rowH = 22
|
||
local y = 0
|
||
|
||
table.sort(profiles, function(a, b)
|
||
if a.realm ~= b.realm then return a.realm < b.realm end
|
||
return a.name < b.name
|
||
end)
|
||
|
||
for idx, prof in ipairs(profiles) do
|
||
local pRealm = prof.realm
|
||
local pName = prof.name
|
||
local pClass = prof.class
|
||
local pLevel = prof.level
|
||
local pData = prof.data
|
||
local isCurrent = (pRealm == curRealm and pName == curName)
|
||
|
||
local btn = CreateFrame("Button", NextWidgetName("CharProf"), holder)
|
||
btn:SetWidth(490)
|
||
btn:SetHeight(rowH)
|
||
btn:SetPoint("TOPLEFT", holder, "TOPLEFT", 0, -y)
|
||
|
||
btn:SetBackdrop({
|
||
bgFile = "Interface\\Buttons\\WHITE8X8",
|
||
edgeFile = "Interface\\Buttons\\WHITE8X8",
|
||
tile = false, edgeSize = 1,
|
||
insets = { left = 1, right = 1, top = 1, bottom = 1 },
|
||
})
|
||
|
||
if isCurrent then
|
||
btn:SetBackdropColor(0.15, 0.25, 0.15, 0.7)
|
||
btn:SetBackdropBorderColor(0.3, 0.5, 0.3, 0.8)
|
||
else
|
||
btn:SetBackdropColor(0.08, 0.08, 0.10, 0.5)
|
||
btn:SetBackdropBorderColor(0.2, 0.2, 0.25, 0.6)
|
||
end
|
||
|
||
local classColors = {
|
||
WARRIOR = {0.78, 0.61, 0.43}, PALADIN = {0.96, 0.55, 0.73}, HUNTER = {0.67, 0.83, 0.45},
|
||
ROGUE = {1, 0.96, 0.41}, PRIEST = {1, 1, 1}, SHAMAN = {0, 0.44, 0.87},
|
||
MAGE = {0.41, 0.80, 0.94}, WARLOCK = {0.58, 0.51, 0.79}, DRUID = {1, 0.49, 0.04},
|
||
}
|
||
local cc = classColors[pClass] or {0.8, 0.8, 0.8}
|
||
|
||
local nameFS = btn:CreateFontString(nil, "OVERLAY")
|
||
nameFS:SetFont(font, 10, "OUTLINE")
|
||
nameFS:SetPoint("LEFT", btn, "LEFT", 6, 0)
|
||
nameFS:SetText(pName)
|
||
nameFS:SetTextColor(cc[1], cc[2], cc[3])
|
||
|
||
local infoFS = btn:CreateFontString(nil, "OVERLAY")
|
||
infoFS:SetFont(font, 9, "OUTLINE")
|
||
infoFS:SetPoint("LEFT", nameFS, "RIGHT", 8, 0)
|
||
local infoText = "Lv" .. pLevel .. " " .. pRealm
|
||
if isCurrent then infoText = infoText .. " |cff44ff44(当前角色)|r" end
|
||
infoFS:SetText(infoText)
|
||
infoFS:SetTextColor(0.6, 0.6, 0.6)
|
||
|
||
btn.profileData = { realm = pRealm, name = pName, class = pClass, level = pLevel, data = pData }
|
||
|
||
btn:SetScript("OnClick", function()
|
||
SFrames.ConfigUI._selectedCharProfile = this.profileData
|
||
for _, b in ipairs(SFrames.ConfigUI._charListButtons) do
|
||
b:SetBackdropBorderColor(0.2, 0.2, 0.25, 0.6)
|
||
end
|
||
this:SetBackdropBorderColor(1, 0.85, 0.5, 1)
|
||
end)
|
||
btn:SetScript("OnEnter", function()
|
||
this:SetBackdropColor(0.15, 0.15, 0.18, 0.8)
|
||
end)
|
||
btn:SetScript("OnLeave", function()
|
||
local sel = SFrames.ConfigUI._selectedCharProfile
|
||
if sel and sel.realm == this.profileData.realm and sel.name == this.profileData.name then
|
||
this:SetBackdropColor(0.18, 0.18, 0.22, 0.8)
|
||
elseif this.profileData.realm == curRealm and this.profileData.name == curName then
|
||
this:SetBackdropColor(0.15, 0.25, 0.15, 0.7)
|
||
else
|
||
this:SetBackdropColor(0.08, 0.08, 0.10, 0.5)
|
||
end
|
||
end)
|
||
|
||
table.insert(self._charListButtons, btn)
|
||
y = y + rowH + 2
|
||
end
|
||
|
||
if table.getn(profiles) == 0 then
|
||
local emptyFS = holder:CreateFontString(nil, "OVERLAY")
|
||
emptyFS:SetFont(font, 10, "OUTLINE")
|
||
emptyFS:SetPoint("TOPLEFT", holder, "TOPLEFT", 4, -4)
|
||
emptyFS:SetText("暂无其他角色配置。登录其他角色后自动保存。")
|
||
emptyFS:SetTextColor(0.5, 0.5, 0.5)
|
||
end
|
||
end
|
||
|
||
function SFrames.ConfigUI:RefreshProfileSummary()
|
||
if self._profileSummaryFS then
|
||
self._profileSummaryFS:SetText(BuildSummaryText())
|
||
end
|
||
if self._charListHolder then
|
||
self:RefreshCharList()
|
||
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 == "frames" then return
|
||
elseif mode == "player" then self:BuildPlayerPage()
|
||
elseif mode == "target" then self:BuildTargetPage()
|
||
elseif mode == "focus" then self:BuildFocusPage()
|
||
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 == "keybinds" then self:BuildKeybindsPage()
|
||
elseif mode == "minimap" then self:BuildMinimapPage()
|
||
elseif mode == "buff" then self:BuildBuffPage()
|
||
elseif mode == "personalize" then self:BuildPersonalizePage()
|
||
elseif mode == "theme" then self:BuildThemePage()
|
||
elseif mode == "profile" then self:BuildProfilePage()
|
||
end
|
||
end
|
||
|
||
function SFrames.ConfigUI:Build(mode)
|
||
EnsureDB()
|
||
self:EnsureFrame()
|
||
|
||
local requested = string.lower(mode or "")
|
||
if requested == "" or requested == "ui" then
|
||
requested = ((SFramesDB and SFramesDB.configLastPage) or "ui")
|
||
end
|
||
|
||
local page = string.lower(requested)
|
||
local validPages = { ui = true, frames = true, player = true, target = true, focus = true, bags = true, char = true, party = true, raid = true, actionbar = true, keybinds = true, minimap = true, buff = true, personalize = true, theme = true, profile = true }
|
||
if not validPages[page] then page = "ui" end
|
||
|
||
local activeBucket = page
|
||
if page == "player" or page == "target" or page == "focus" or page == "party" or page == "raid" then
|
||
activeBucket = "frames"
|
||
end
|
||
|
||
if self.frame:IsShown() and self.activePage == activeBucket and (activeBucket ~= "frames" or self.activeFrameSubPage == page or page == "frames") 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
|