修复拾取界面点击无效问题 修复宠物训练界面不显示训练点问题 新增天赋分享到聊天界面 修复飞行界面无法关闭问题 修复术士宠物的显示问题 为天赋界面添加默认数据库支持 框架现在也可自主选择是否启用 玩家框架和目标框架可取消显示3D头像以及透明度修改 背包和银行也添加透明度自定义支持 优化tooltip性能和背包物品显示方式 修复布局模式在ui缩放后不能正常定位的问题 添加硬核模式危险和死亡的工会通报 添加拾取和已拾取的框体 等等
4024 lines
169 KiB
Lua
4024 lines
169 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 SOFT_THEME = SFrames.ActiveTheme
|
||
|
||
local function EnsureSoftBackdrop(frame)
|
||
if not frame then return end
|
||
if frame.sfSoftBackdrop then return end
|
||
frame:SetBackdrop({
|
||
bgFile = "Interface\\Tooltips\\UI-Tooltip-Background",
|
||
edgeFile = "Interface\\Tooltips\\UI-Tooltip-Border",
|
||
tile = true, tileSize = 16, edgeSize = 14,
|
||
insets = { left = 3, right = 3, top = 3, bottom = 3 },
|
||
})
|
||
frame.sfSoftBackdrop = true
|
||
end
|
||
|
||
-- Thin 1px border backdrop, suitable for small frames like checkbox boxes
|
||
local function EnsureThinBackdrop(frame)
|
||
if not frame then return end
|
||
if frame.sfSoftBackdrop then return end
|
||
if SFrames and SFrames.CreateBackdrop then
|
||
SFrames:CreateBackdrop(frame)
|
||
elseif frame.SetBackdrop then
|
||
frame:SetBackdrop({
|
||
bgFile = "Interface\\Buttons\\WHITE8X8",
|
||
edgeFile = "Interface\\Buttons\\WHITE8X8",
|
||
tile = false, edgeSize = 1,
|
||
insets = { left = 1, right = 1, top = 1, bottom = 1 },
|
||
})
|
||
end
|
||
frame.sfSoftBackdrop = true
|
||
end
|
||
|
||
local function HideTexture(tex)
|
||
if not tex then return end
|
||
if tex.SetTexture then tex:SetTexture(nil) end
|
||
tex:Hide()
|
||
end
|
||
|
||
local function StyleButton(btn)
|
||
if not btn or btn.sfSoftStyled then return btn end
|
||
btn.sfSoftStyled = true
|
||
|
||
-- Apply ESC menu style round backdrop
|
||
btn:SetBackdrop({
|
||
bgFile = "Interface\\Tooltips\\UI-Tooltip-Background",
|
||
edgeFile = "Interface\\Tooltips\\UI-Tooltip-Border",
|
||
tile = true, tileSize = 16, edgeSize = 14,
|
||
insets = { left = 3, right = 3, top = 3, bottom = 3 },
|
||
})
|
||
HideTexture(btn.GetNormalTexture and btn:GetNormalTexture())
|
||
HideTexture(btn.GetPushedTexture and btn:GetPushedTexture())
|
||
HideTexture(btn.GetHighlightTexture and btn:GetHighlightTexture())
|
||
HideTexture(btn.GetDisabledTexture and btn:GetDisabledTexture())
|
||
|
||
local name = btn:GetName() or ""
|
||
for _, suffix in ipairs({ "Left", "Right", "Middle" }) do
|
||
local tex = _G[name .. suffix]
|
||
if tex then tex:SetAlpha(0) tex:Hide() end
|
||
end
|
||
|
||
local function SetVisual(state)
|
||
local bg = SOFT_THEME.buttonBg
|
||
local border = SOFT_THEME.buttonBorder
|
||
local text = SOFT_THEME.buttonText
|
||
|
||
if btn.sfSoftActive then
|
||
bg = SOFT_THEME.buttonActiveBg
|
||
border = SOFT_THEME.buttonActiveBorder
|
||
text = SOFT_THEME.buttonActiveText
|
||
elseif state == "hover" then
|
||
bg = SOFT_THEME.buttonHoverBg
|
||
border = (SFrames.ActiveTheme and SFrames.ActiveTheme.btnHoverBd) or { 0.80, 0.48, 0.64, 0.98 }
|
||
text = SOFT_THEME.buttonActiveText
|
||
elseif state == "down" then
|
||
bg = SOFT_THEME.buttonDownBg
|
||
elseif state == "disabled" then
|
||
bg = SOFT_THEME.buttonDisabledBg
|
||
text = SOFT_THEME.buttonDisabledText
|
||
end
|
||
|
||
if btn.SetBackdropColor then
|
||
btn:SetBackdropColor(bg[1], bg[2], bg[3], bg[4])
|
||
end
|
||
if btn.SetBackdropBorderColor then
|
||
btn:SetBackdropBorderColor(border[1], border[2], border[3], border[4])
|
||
end
|
||
local fs = btn.GetFontString and btn:GetFontString()
|
||
if fs then
|
||
fs:SetTextColor(text[1], text[2], text[3])
|
||
end
|
||
end
|
||
|
||
btn.RefreshVisual = function()
|
||
if btn.sfSoftActive then
|
||
SetVisual("active")
|
||
elseif btn.IsEnabled and not btn:IsEnabled() then
|
||
SetVisual("disabled")
|
||
else
|
||
SetVisual("normal")
|
||
end
|
||
end
|
||
|
||
local oldEnter = btn:GetScript("OnEnter")
|
||
local oldLeave = btn:GetScript("OnLeave")
|
||
local oldDown = btn:GetScript("OnMouseDown")
|
||
local oldUp = btn:GetScript("OnMouseUp")
|
||
|
||
btn:SetScript("OnEnter", function()
|
||
if oldEnter then oldEnter() end
|
||
if this.IsEnabled and this:IsEnabled() and not this.sfSoftActive then
|
||
SetVisual("hover")
|
||
end
|
||
end)
|
||
btn:SetScript("OnLeave", function()
|
||
if oldLeave then oldLeave() end
|
||
if this.IsEnabled and this:IsEnabled() and not this.sfSoftActive then
|
||
SetVisual("normal")
|
||
end
|
||
end)
|
||
btn:SetScript("OnMouseDown", function()
|
||
if oldDown then oldDown() end
|
||
if this.IsEnabled and this:IsEnabled() and not this.sfSoftActive then
|
||
SetVisual("down")
|
||
end
|
||
end)
|
||
btn:SetScript("OnMouseUp", function()
|
||
if oldUp then oldUp() end
|
||
if this.IsEnabled and this:IsEnabled() and not this.sfSoftActive then
|
||
SetVisual("hover")
|
||
end
|
||
end)
|
||
|
||
btn:RefreshVisual()
|
||
return btn
|
||
end
|
||
|
||
local function AddBtnIcon(btn, iconKey, size, align)
|
||
if not (SFrames and SFrames.CreateIcon) then return end
|
||
local sz = size or 12
|
||
local gap = 3
|
||
local ico = SFrames:CreateIcon(btn, iconKey, sz)
|
||
ico:SetDrawLayer("OVERLAY")
|
||
btn.nanamiIcon = ico
|
||
local fs = btn.GetFontString and btn:GetFontString()
|
||
if fs then
|
||
fs:ClearAllPoints()
|
||
if align == "left" then
|
||
ico:SetPoint("LEFT", btn, "LEFT", 8, 0)
|
||
fs:SetPoint("LEFT", ico, "RIGHT", gap, 0)
|
||
fs:SetPoint("RIGHT", btn, "RIGHT", -4, 0)
|
||
fs:SetJustifyH("LEFT")
|
||
else
|
||
fs:SetPoint("CENTER", btn, "CENTER", (sz + gap) / 2, 0)
|
||
ico:SetPoint("RIGHT", fs, "LEFT", -gap, 0)
|
||
end
|
||
else
|
||
ico:SetPoint("CENTER", btn, "CENTER", 0, 0)
|
||
end
|
||
end
|
||
|
||
local function StyleCheckBox(cb)
|
||
if not cb or cb.sfSoftStyled then return cb end
|
||
cb.sfSoftStyled = true
|
||
|
||
-- Hide ALL default CheckButton textures including the checked texture
|
||
HideTexture(cb.GetNormalTexture and cb:GetNormalTexture())
|
||
HideTexture(cb.GetPushedTexture and cb:GetPushedTexture())
|
||
HideTexture(cb.GetHighlightTexture and cb:GetHighlightTexture())
|
||
HideTexture(cb.GetDisabledTexture and cb:GetDisabledTexture())
|
||
if cb.SetCheckedTexture then cb:SetCheckedTexture("") end
|
||
if cb.SetDisabledCheckedTexture then cb:SetDisabledCheckedTexture("") end
|
||
|
||
-- Round-corner box (tooltip border = soft rounded edges)
|
||
local box = CreateFrame("Frame", nil, cb)
|
||
box:SetPoint("TOPLEFT", cb, "TOPLEFT", 0, 0)
|
||
box:SetPoint("BOTTOMRIGHT", cb, "BOTTOMRIGHT", 0, 0)
|
||
box:SetFrameLevel(cb:GetFrameLevel() + 1)
|
||
box:SetBackdrop({
|
||
bgFile = "Interface\\Tooltips\\UI-Tooltip-Background",
|
||
edgeFile = "Interface\\Tooltips\\UI-Tooltip-Border",
|
||
tile = true, tileSize = 16, edgeSize = 10,
|
||
insets = { left = 2, right = 2, top = 2, bottom = 2 },
|
||
})
|
||
cb.sfBox = box
|
||
|
||
local function ApplyState(isChecked)
|
||
if isChecked then
|
||
box:SetBackdropColor(
|
||
SOFT_THEME.checkFill[1], SOFT_THEME.checkFill[2],
|
||
SOFT_THEME.checkFill[3], 0.88)
|
||
box:SetBackdropBorderColor(
|
||
SOFT_THEME.checkFill[1], SOFT_THEME.checkFill[2],
|
||
SOFT_THEME.checkFill[3], 1)
|
||
else
|
||
box:SetBackdropColor(
|
||
SOFT_THEME.checkBg[1], SOFT_THEME.checkBg[2],
|
||
SOFT_THEME.checkBg[3], SOFT_THEME.checkBg[4])
|
||
box:SetBackdropBorderColor(
|
||
SOFT_THEME.checkBorder[1], SOFT_THEME.checkBorder[2],
|
||
SOFT_THEME.checkBorder[3], SOFT_THEME.checkBorder[4])
|
||
end
|
||
end
|
||
cb.sfApplyState = ApplyState
|
||
|
||
-- Text label: push it away from the box with a left margin
|
||
local label = _G[cb:GetName() .. "Text"]
|
||
if label then
|
||
label:ClearAllPoints()
|
||
label:SetPoint("LEFT", cb, "RIGHT", 4, 0)
|
||
label:SetFont(SFrames:GetFont(), 11, "OUTLINE")
|
||
label:SetJustifyH("LEFT")
|
||
label:SetTextColor(SOFT_THEME.text[1], SOFT_THEME.text[2], SOFT_THEME.text[3])
|
||
end
|
||
|
||
-- Hover: brighten border when unchecked
|
||
cb:SetScript("OnEnter", function()
|
||
if not this.sfChecked then
|
||
if this.sfBox then
|
||
this.sfBox:SetBackdropBorderColor(
|
||
SOFT_THEME.checkHoverBorder[1], SOFT_THEME.checkHoverBorder[2],
|
||
SOFT_THEME.checkHoverBorder[3], SOFT_THEME.checkHoverBorder[4])
|
||
end
|
||
end
|
||
end)
|
||
cb:SetScript("OnLeave", function()
|
||
if not this.sfChecked then
|
||
if this.sfBox then
|
||
this.sfBox:SetBackdropBorderColor(
|
||
SOFT_THEME.checkBorder[1], SOFT_THEME.checkBorder[2],
|
||
SOFT_THEME.checkBorder[3], SOFT_THEME.checkBorder[4])
|
||
end
|
||
end
|
||
end)
|
||
|
||
ApplyState(false)
|
||
return cb
|
||
end
|
||
|
||
local function StyleSlider(slider, low, high, text)
|
||
if not slider or slider.sfSoftStyled then return slider end
|
||
slider.sfSoftStyled = true
|
||
|
||
local regions = { slider:GetRegions() }
|
||
for i = 1, table.getn(regions) do
|
||
local region = regions[i]
|
||
if region and region.GetObjectType and region:GetObjectType() == "Texture" then
|
||
region:SetTexture(nil)
|
||
end
|
||
end
|
||
|
||
local track = slider:CreateTexture(nil, "BACKGROUND")
|
||
track:SetTexture("Interface\\Buttons\\WHITE8X8")
|
||
track:SetPoint("LEFT", slider, "LEFT", 0, 0)
|
||
track:SetPoint("RIGHT", slider, "RIGHT", 0, 0)
|
||
track:SetHeight(4)
|
||
track:SetVertexColor(SOFT_THEME.sliderTrack[1], SOFT_THEME.sliderTrack[2], SOFT_THEME.sliderTrack[3], SOFT_THEME.sliderTrack[4])
|
||
slider.sfTrack = track
|
||
|
||
local fill = slider:CreateTexture(nil, "ARTWORK")
|
||
fill:SetTexture("Interface\\Buttons\\WHITE8X8")
|
||
fill:SetPoint("LEFT", track, "LEFT", 0, 0)
|
||
fill:SetPoint("TOP", track, "TOP", 0, 0)
|
||
fill:SetPoint("BOTTOM", track, "BOTTOM", 0, 0)
|
||
fill:SetWidth(1)
|
||
fill:SetVertexColor(SOFT_THEME.sliderFill[1], SOFT_THEME.sliderFill[2], SOFT_THEME.sliderFill[3], SOFT_THEME.sliderFill[4])
|
||
slider.sfFill = fill
|
||
|
||
if slider.SetThumbTexture then
|
||
slider:SetThumbTexture("Interface\\Buttons\\WHITE8X8")
|
||
end
|
||
local thumb = slider.GetThumbTexture and slider:GetThumbTexture()
|
||
if thumb then
|
||
thumb:SetWidth(8)
|
||
thumb:SetHeight(16)
|
||
thumb:SetVertexColor(SOFT_THEME.sliderThumb[1], SOFT_THEME.sliderThumb[2], SOFT_THEME.sliderThumb[3], SOFT_THEME.sliderThumb[4])
|
||
end
|
||
|
||
local function UpdateFill()
|
||
local minValue, maxValue = slider:GetMinMaxValues()
|
||
local value = slider:GetValue() or minValue
|
||
local pct = 0
|
||
if maxValue > minValue then
|
||
pct = (value - minValue) / (maxValue - minValue)
|
||
end
|
||
pct = Clamp(pct, 0, 1)
|
||
local width = math.floor((slider:GetWidth() or 1) * pct + 0.5)
|
||
if width < 1 then width = 1 end
|
||
slider.sfFill:SetWidth(width)
|
||
end
|
||
|
||
local oldChanged = slider:GetScript("OnValueChanged")
|
||
slider:SetScript("OnValueChanged", function()
|
||
if oldChanged then oldChanged() end
|
||
UpdateFill()
|
||
end)
|
||
UpdateFill()
|
||
|
||
if low then
|
||
low:SetTextColor(SOFT_THEME.dimText[1], SOFT_THEME.dimText[2], SOFT_THEME.dimText[3])
|
||
low:ClearAllPoints()
|
||
low:SetPoint("TOPLEFT", slider, "BOTTOMLEFT", 0, 0)
|
||
end
|
||
if high then
|
||
high:SetTextColor(SOFT_THEME.dimText[1], SOFT_THEME.dimText[2], SOFT_THEME.dimText[3])
|
||
high:ClearAllPoints()
|
||
high:SetPoint("TOPRIGHT", slider, "BOTTOMRIGHT", 0, 0)
|
||
end
|
||
if text then
|
||
text:SetTextColor(SOFT_THEME.text[1], SOFT_THEME.text[2], SOFT_THEME.text[3])
|
||
text:ClearAllPoints()
|
||
text:SetPoint("BOTTOM", slider, "TOP", 0, 2)
|
||
end
|
||
return slider
|
||
end
|
||
|
||
local function StyleScrollBar(slider, sliderName)
|
||
if not slider or slider.sfSoftStyled then return slider end
|
||
slider.sfSoftStyled = true
|
||
|
||
slider:SetWidth(12)
|
||
local regions = { slider:GetRegions() }
|
||
for i = 1, table.getn(regions) do
|
||
local region = regions[i]
|
||
if region and region.GetObjectType and region:GetObjectType() == "Texture" then
|
||
region:SetTexture(nil)
|
||
end
|
||
end
|
||
|
||
local track = slider:CreateTexture(nil, "BACKGROUND")
|
||
track:SetTexture("Interface\\Buttons\\WHITE8X8")
|
||
track:SetPoint("TOPLEFT", slider, "TOPLEFT", 3, 0)
|
||
track:SetPoint("BOTTOMRIGHT", slider, "BOTTOMRIGHT", -3, 0)
|
||
track:SetVertexColor(0.18, 0.19, 0.22, 0.9)
|
||
|
||
if slider.SetThumbTexture then
|
||
slider:SetThumbTexture("Interface\\Buttons\\WHITE8X8")
|
||
end
|
||
local thumb = slider.GetThumbTexture and slider:GetThumbTexture()
|
||
if thumb then
|
||
thumb:SetWidth(8)
|
||
thumb:SetHeight(20)
|
||
thumb:SetVertexColor(SOFT_THEME.scrollThumb[1], SOFT_THEME.scrollThumb[2], SOFT_THEME.scrollThumb[3], SOFT_THEME.scrollThumb[4] or 0.95)
|
||
end
|
||
|
||
local upBtn = _G[sliderName .. "ScrollUpButton"]
|
||
local downBtn = _G[sliderName .. "ScrollDownButton"]
|
||
if upBtn then upBtn:Hide() end
|
||
if downBtn then downBtn:Hide() end
|
||
return slider
|
||
end
|
||
|
||
local function EnsureDB()
|
||
if not SFramesDB then SFramesDB = {} end
|
||
|
||
if SFramesDB.showLevel == nil then SFramesDB.showLevel = true end
|
||
if SFramesDB.classColorHealth == nil then SFramesDB.classColorHealth = true end
|
||
if SFramesDB.enableMerchant == nil then SFramesDB.enableMerchant = true end
|
||
if SFramesDB.enableQuestUI == nil then SFramesDB.enableQuestUI = true end
|
||
if SFramesDB.enableQuestLogSkin == nil then SFramesDB.enableQuestLogSkin = true end
|
||
if SFramesDB.enableTrainer == nil then SFramesDB.enableTrainer = true end
|
||
if SFramesDB.enableSpellBook == nil then SFramesDB.enableSpellBook = true end
|
||
if SFramesDB.enableTradeSkill == nil then SFramesDB.enableTradeSkill = true end
|
||
if SFramesDB.spellBookHighestOnly == nil then SFramesDB.spellBookHighestOnly = false end
|
||
if SFramesDB.spellBookAutoReplace == nil then SFramesDB.spellBookAutoReplace = false end
|
||
if SFramesDB.enableSocial == nil then SFramesDB.enableSocial = true end
|
||
if SFramesDB.enableInspect == nil then SFramesDB.enableInspect = true end
|
||
if SFramesDB.enableFlightMap == nil then SFramesDB.enableFlightMap = true end
|
||
if SFramesDB.enablePetStable == nil then SFramesDB.enablePetStable = true end
|
||
if SFramesDB.enableMail == nil then SFramesDB.enableMail = true end
|
||
if type(SFramesDB.spellBookScale) ~= "number" then SFramesDB.spellBookScale = 1 end
|
||
if type(SFramesDB.socialScale) ~= "number" then SFramesDB.socialScale = 1 end
|
||
|
||
if type(SFramesDB.playerFrameScale) ~= "number" then SFramesDB.playerFrameScale = 1 end
|
||
if type(SFramesDB.playerFrameWidth) ~= "number" then SFramesDB.playerFrameWidth = 220 end
|
||
if type(SFramesDB.playerPortraitWidth) ~= "number" then SFramesDB.playerPortraitWidth = 50 end
|
||
if type(SFramesDB.playerHealthHeight) ~= "number" then SFramesDB.playerHealthHeight = 38 end
|
||
if type(SFramesDB.playerPowerHeight) ~= "number" then SFramesDB.playerPowerHeight = 9 end
|
||
if SFramesDB.playerShowClass == nil then SFramesDB.playerShowClass = true end
|
||
if SFramesDB.playerShowClassIcon == nil then SFramesDB.playerShowClassIcon = true end
|
||
if SFramesDB.playerShowPortrait == nil then SFramesDB.playerShowPortrait = true end
|
||
if type(SFramesDB.playerFrameAlpha) ~= "number" then SFramesDB.playerFrameAlpha = 1 end
|
||
if type(SFramesDB.playerNameFontSize) ~= "number" then SFramesDB.playerNameFontSize = 10 end
|
||
if type(SFramesDB.playerValueFontSize) ~= "number" then SFramesDB.playerValueFontSize = 10 end
|
||
|
||
if type(SFramesDB.targetFrameScale) ~= "number" then SFramesDB.targetFrameScale = 1 end
|
||
if type(SFramesDB.targetFrameWidth) ~= "number" then SFramesDB.targetFrameWidth = 220 end
|
||
if type(SFramesDB.targetPortraitWidth) ~= "number" then SFramesDB.targetPortraitWidth = 50 end
|
||
if type(SFramesDB.targetHealthHeight) ~= "number" then SFramesDB.targetHealthHeight = 38 end
|
||
if type(SFramesDB.targetPowerHeight) ~= "number" then SFramesDB.targetPowerHeight = 9 end
|
||
if SFramesDB.targetShowClass == nil then SFramesDB.targetShowClass = true end
|
||
if SFramesDB.targetShowClassIcon == nil then SFramesDB.targetShowClassIcon = true end
|
||
if SFramesDB.targetShowPortrait == nil then SFramesDB.targetShowPortrait = true end
|
||
if type(SFramesDB.targetFrameAlpha) ~= "number" then SFramesDB.targetFrameAlpha = 1 end
|
||
if type(SFramesDB.targetNameFontSize) ~= "number" then SFramesDB.targetNameFontSize = 10 end
|
||
if type(SFramesDB.targetValueFontSize) ~= "number" then SFramesDB.targetValueFontSize = 10 end
|
||
if SFramesDB.targetDistanceEnabled == nil then SFramesDB.targetDistanceEnabled = true end
|
||
if type(SFramesDB.targetDistanceScale) ~= "number" then SFramesDB.targetDistanceScale = 1 end
|
||
|
||
if SFramesDB.showPetFrame == nil then SFramesDB.showPetFrame = true end
|
||
if type(SFramesDB.petFrameScale) ~= "number" then SFramesDB.petFrameScale = 1 end
|
||
|
||
if SFramesDB.partyLayout ~= "horizontal" and SFramesDB.partyLayout ~= "vertical" then
|
||
SFramesDB.partyLayout = "vertical"
|
||
end
|
||
if type(SFramesDB.partyFrameScale) ~= "number" then SFramesDB.partyFrameScale = 1 end
|
||
if type(SFramesDB.partyFrameWidth) ~= "number" then SFramesDB.partyFrameWidth = 150 end
|
||
if type(SFramesDB.partyFrameHeight) ~= "number" then SFramesDB.partyFrameHeight = 35 end
|
||
if type(SFramesDB.partyPortraitWidth) ~= "number" then SFramesDB.partyPortraitWidth = 33 end
|
||
if type(SFramesDB.partyHealthHeight) ~= "number" then SFramesDB.partyHealthHeight = 22 end
|
||
if type(SFramesDB.partyPowerHeight) ~= "number" then SFramesDB.partyPowerHeight = 10 end
|
||
if type(SFramesDB.partyHorizontalGap) ~= "number" then SFramesDB.partyHorizontalGap = 8 end
|
||
if type(SFramesDB.partyVerticalGap) ~= "number" then SFramesDB.partyVerticalGap = 30 end
|
||
if type(SFramesDB.partyNameFontSize) ~= "number" then SFramesDB.partyNameFontSize = 10 end
|
||
if type(SFramesDB.partyValueFontSize) ~= "number" then SFramesDB.partyValueFontSize = 10 end
|
||
if SFramesDB.partyShowBuffs == nil then SFramesDB.partyShowBuffs = true end
|
||
if SFramesDB.partyShowDebuffs == nil then SFramesDB.partyShowDebuffs = true end
|
||
|
||
if SFramesDB.raidLayout ~= "horizontal" and SFramesDB.raidLayout ~= "vertical" then
|
||
SFramesDB.raidLayout = "horizontal"
|
||
end
|
||
if type(SFramesDB.raidFrameScale) ~= "number" then SFramesDB.raidFrameScale = 1 end
|
||
if type(SFramesDB.raidFrameWidth) ~= "number" then SFramesDB.raidFrameWidth = 60 end
|
||
if type(SFramesDB.raidFrameHeight) ~= "number" then SFramesDB.raidFrameHeight = 40 end
|
||
if type(SFramesDB.raidHealthHeight) ~= "number" then SFramesDB.raidHealthHeight = 31 end
|
||
if type(SFramesDB.raidHorizontalGap) ~= "number" then SFramesDB.raidHorizontalGap = 2 end
|
||
if type(SFramesDB.raidVerticalGap) ~= "number" then SFramesDB.raidVerticalGap = 2 end
|
||
if type(SFramesDB.raidGroupGap) ~= "number" then SFramesDB.raidGroupGap = 8 end
|
||
if type(SFramesDB.raidNameFontSize) ~= "number" then SFramesDB.raidNameFontSize = 10 end
|
||
if type(SFramesDB.raidValueFontSize) ~= "number" then SFramesDB.raidValueFontSize = 9 end
|
||
if SFramesDB.raidShowPower == nil then SFramesDB.raidShowPower = true end
|
||
if SFramesDB.enableRaidFrames == nil then SFramesDB.enableRaidFrames = true end
|
||
if SFramesDB.raidHealthFormat == nil then SFramesDB.raidHealthFormat = "compact" end
|
||
if SFramesDB.raidShowGroupLabel == nil then SFramesDB.raidShowGroupLabel = true end
|
||
|
||
if SFramesDB.charPanelEnable == nil then SFramesDB.charPanelEnable = true end
|
||
|
||
if SFramesDB.afkEnabled == nil then SFramesDB.afkEnabled = true end
|
||
if type(SFramesDB.afkDelay) ~= "number" then SFramesDB.afkDelay = 5 end
|
||
if SFramesDB.afkOutsideRest == nil then SFramesDB.afkOutsideRest = false end
|
||
|
||
if SFramesDB.trainerReminder == nil then SFramesDB.trainerReminder = true end
|
||
if SFramesDB.trainerCache == nil then SFramesDB.trainerCache = {} end
|
||
|
||
if SFramesDB.smoothBars == nil then SFramesDB.smoothBars = true end
|
||
if SFramesDB.mobRealHealth == nil then SFramesDB.mobRealHealth = true end
|
||
if SFramesDB.showItemLevel == nil then SFramesDB.showItemLevel = true end
|
||
if SFramesDB.showTooltipIDs == nil then SFramesDB.showTooltipIDs = true end
|
||
if SFramesDB.itemCompare == nil then SFramesDB.itemCompare = true end
|
||
if SFramesDB.gearScore == nil then SFramesDB.gearScore = true end
|
||
|
||
if type(SFramesDB.Bags) ~= "table" then SFramesDB.Bags = {} end
|
||
local defaults = BAG_DEFAULTS
|
||
if SFrames and SFrames.Config and type(SFrames.Config.Bags) == "table" then defaults = SFrames.Config.Bags end
|
||
for key, value in pairs(defaults) do
|
||
if SFramesDB.Bags[key] == nil then SFramesDB.Bags[key] = value end
|
||
end
|
||
if type(SFramesDB.Bags.alpha) ~= "number" then SFramesDB.Bags.alpha = 1 end
|
||
if type(SFramesDB.Bags.bankAlpha) ~= "number" then SFramesDB.Bags.bankAlpha = 1 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.autoDismount == nil then SFramesDB.Tweaks.autoDismount = true end
|
||
if SFramesDB.Tweaks.superWoW == nil then SFramesDB.Tweaks.superWoW = true end
|
||
if SFramesDB.Tweaks.turtleCompat == nil then SFramesDB.Tweaks.turtleCompat = true end
|
||
if SFramesDB.Tweaks.cooldownNumbers == nil then SFramesDB.Tweaks.cooldownNumbers = true end
|
||
if SFramesDB.Tweaks.darkUI == nil then SFramesDB.Tweaks.darkUI = false end
|
||
if SFramesDB.Tweaks.worldMapWindow == nil then SFramesDB.Tweaks.worldMapWindow = false end
|
||
|
||
if type(SFramesDB.WorldMap) ~= "table" then SFramesDB.WorldMap = {} end
|
||
if SFramesDB.WorldMap.enabled == nil then SFramesDB.WorldMap.enabled = true end
|
||
if type(SFramesDB.WorldMap.scale) ~= "number" then SFramesDB.WorldMap.scale = 0.85 end
|
||
if type(SFramesDB.WorldMap.nav) ~= "table" then
|
||
SFramesDB.WorldMap.nav = { enabled = false, width = 350, alpha = 0.70, locked = false }
|
||
end
|
||
|
||
if type(SFramesDB.MinimapBuffs) ~= "table" then SFramesDB.MinimapBuffs = {} end
|
||
if SFramesDB.MinimapBuffs.enabled == nil then SFramesDB.MinimapBuffs.enabled = true end
|
||
if type(SFramesDB.MinimapBuffs.iconSize) ~= "number" then SFramesDB.MinimapBuffs.iconSize = 30 end
|
||
if type(SFramesDB.MinimapBuffs.iconsPerRow) ~= "number" then SFramesDB.MinimapBuffs.iconsPerRow = 8 end
|
||
if type(SFramesDB.MinimapBuffs.spacing) ~= "number" then SFramesDB.MinimapBuffs.spacing = 2 end
|
||
if SFramesDB.MinimapBuffs.growDirection ~= "LEFT" and SFramesDB.MinimapBuffs.growDirection ~= "RIGHT" then
|
||
SFramesDB.MinimapBuffs.growDirection = "LEFT"
|
||
end
|
||
if SFramesDB.MinimapBuffs.position ~= "TOPRIGHT" and SFramesDB.MinimapBuffs.position ~= "TOPLEFT"
|
||
and SFramesDB.MinimapBuffs.position ~= "BOTTOMRIGHT" and SFramesDB.MinimapBuffs.position ~= "BOTTOMLEFT" then
|
||
SFramesDB.MinimapBuffs.position = "TOPRIGHT"
|
||
end
|
||
if type(SFramesDB.MinimapBuffs.offsetX) ~= "number" then SFramesDB.MinimapBuffs.offsetX = 0 end
|
||
if type(SFramesDB.MinimapBuffs.offsetY) ~= "number" then SFramesDB.MinimapBuffs.offsetY = 0 end
|
||
if SFramesDB.MinimapBuffs.showTimer == nil then SFramesDB.MinimapBuffs.showTimer = true end
|
||
if SFramesDB.MinimapBuffs.showDebuffs == nil then SFramesDB.MinimapBuffs.showDebuffs = true end
|
||
if type(SFramesDB.MinimapBuffs.debuffIconSize) ~= "number" then SFramesDB.MinimapBuffs.debuffIconSize = 30 end
|
||
|
||
if type(SFramesDB.ActionBars) ~= "table" then SFramesDB.ActionBars = {} end
|
||
local abDef = {
|
||
enable = true, buttonSize = 36, buttonGap = 2, smallBarSize = 27,
|
||
scale = 1.0, barCount = 3, showHotkey = true, showMacroName = false,
|
||
rangeColoring = true, showPetBar = true, showStanceBar = true,
|
||
showRightBars = true, buttonRounded = false, buttonInnerShadow = false,
|
||
alpha = 1.0,
|
||
}
|
||
for k, v in pairs(abDef) do
|
||
if SFramesDB.ActionBars[k] == nil then SFramesDB.ActionBars[k] = v end
|
||
end
|
||
|
||
if SFramesDB.enableUnitFrames == nil then SFramesDB.enableUnitFrames = true end
|
||
if SFramesDB.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.Theme) ~= "table" then SFramesDB.Theme = {} end
|
||
if SFramesDB.Theme.preset == nil then SFramesDB.Theme.preset = "pink" end
|
||
if SFramesDB.Theme.useClassTheme == nil then SFramesDB.Theme.useClassTheme = false end
|
||
if SFramesDB.Theme.iconSet == nil or SFramesDB.Theme.iconSet == "default" then SFramesDB.Theme.iconSet = "icon" end
|
||
end
|
||
|
||
local function CreateSection(parent, title, x, y, width, height, font)
|
||
local section = CreateFrame("Frame", NextWidgetName("Section"), parent)
|
||
section:SetWidth(width)
|
||
section:SetHeight(height)
|
||
section:SetPoint("TOPLEFT", parent, "TOPLEFT", x, y)
|
||
|
||
EnsureSoftBackdrop(section)
|
||
if section.SetBackdropColor then
|
||
section:SetBackdropColor(SOFT_THEME.sectionBg[1], SOFT_THEME.sectionBg[2], SOFT_THEME.sectionBg[3], SOFT_THEME.sectionBg[4])
|
||
end
|
||
if section.SetBackdropBorderColor then
|
||
section:SetBackdropBorderColor(SOFT_THEME.sectionBorder[1], SOFT_THEME.sectionBorder[2], SOFT_THEME.sectionBorder[3], SOFT_THEME.sectionBorder[4])
|
||
end
|
||
|
||
local titleFS = section:CreateFontString(nil, "OVERLAY")
|
||
titleFS:SetFont(font, 11, "OUTLINE")
|
||
titleFS:SetPoint("TOPLEFT", section, "TOPLEFT", 10, -9)
|
||
titleFS:SetText(title)
|
||
titleFS:SetTextColor(SOFT_THEME.title[1], SOFT_THEME.title[2], SOFT_THEME.title[3])
|
||
|
||
-- Divider line under title
|
||
local div = section:CreateTexture(nil, "ARTWORK")
|
||
div:SetTexture("Interface\\Buttons\\WHITE8X8")
|
||
div:SetHeight(1)
|
||
div:SetPoint("TOPLEFT", section, "TOPLEFT", 8, -24)
|
||
div:SetPoint("TOPRIGHT", section, "TOPRIGHT", -8, -24)
|
||
div:SetVertexColor(SOFT_THEME.sectionBorder[1], SOFT_THEME.sectionBorder[2], SOFT_THEME.sectionBorder[3], 0.6)
|
||
|
||
return section
|
||
end
|
||
|
||
local function CreateLabel(parent, text, x, y, font, size, r, g, b)
|
||
local fs = parent:CreateFontString(nil, "OVERLAY")
|
||
fs:SetFont(font, size or 11, "OUTLINE")
|
||
fs:SetPoint("TOPLEFT", parent, "TOPLEFT", x, y)
|
||
fs:SetText(text)
|
||
fs:SetTextColor(r or 1, g or 1, b or 1)
|
||
return fs
|
||
end
|
||
|
||
local function CreateDesc(parent, text, x, y, font, maxWidth)
|
||
local fs = parent:CreateFontString(nil, "OVERLAY")
|
||
fs:SetFont(font, 9, "OUTLINE")
|
||
fs:SetPoint("TOPLEFT", parent, "TOPLEFT", x, y)
|
||
if maxWidth then
|
||
fs:SetWidth(maxWidth)
|
||
fs:SetJustifyH("LEFT")
|
||
end
|
||
fs:SetText(text)
|
||
fs:SetTextColor(0.65, 0.58, 0.62)
|
||
return fs
|
||
end
|
||
|
||
local function CreateCheckBox(parent, label, x, y, getter, setter, onValueChanged)
|
||
local cb = CreateFrame("CheckButton", NextWidgetName("Check"), parent, "UICheckButtonTemplate")
|
||
cb:SetWidth(18)
|
||
cb:SetHeight(18)
|
||
cb:SetPoint("TOPLEFT", parent, "TOPLEFT", x, y)
|
||
StyleCheckBox(cb)
|
||
|
||
local text = _G[cb:GetName() .. "Text"]
|
||
if text then
|
||
text:SetText(label)
|
||
local parentWidth = parent:GetWidth() or 520
|
||
local textWidth = parentWidth - x - 24
|
||
if textWidth > 240 then textWidth = 240 end
|
||
text:SetWidth(textWidth)
|
||
end
|
||
|
||
local internalRefresh = false
|
||
|
||
cb:SetScript("OnClick", function()
|
||
if internalRefresh then return end
|
||
local v = this:GetChecked()
|
||
local checkedBool = (v == 1 or v == true)
|
||
this.sfChecked = checkedBool
|
||
if this.sfApplyState then this.sfApplyState(checkedBool) end
|
||
setter(checkedBool)
|
||
if onValueChanged then onValueChanged(checkedBool) end
|
||
PlaySound(checkedBool and "igMainMenuOptionCheckBoxOn" or "igMainMenuOptionCheckBoxOff")
|
||
end)
|
||
|
||
cb.Refresh = function()
|
||
internalRefresh = true
|
||
local isChecked = getter() and true or false
|
||
cb:SetChecked(isChecked and 1 or 0)
|
||
cb.sfChecked = isChecked
|
||
if cb.sfApplyState then cb.sfApplyState(isChecked) end
|
||
internalRefresh = false
|
||
end
|
||
|
||
cb:Refresh()
|
||
return cb
|
||
end
|
||
|
||
local function CreateSlider(parent, label, x, y, width, minValue, maxValue, step, getter, setter, formatter, onValueChanged)
|
||
local sliderName = NextWidgetName("Slider")
|
||
local slider = CreateFrame("Slider", sliderName, parent, "OptionsSliderTemplate")
|
||
slider:SetWidth(width)
|
||
slider:SetHeight(26)
|
||
slider:SetPoint("TOPLEFT", parent, "TOPLEFT", x, y)
|
||
slider:SetMinMaxValues(minValue, maxValue)
|
||
slider:SetValueStep(step)
|
||
if slider.SetObeyStepOnDrag then slider:SetObeyStepOnDrag(true) end
|
||
|
||
local low = _G[sliderName .. "Low"]
|
||
local high = _G[sliderName .. "High"]
|
||
local text = _G[sliderName .. "Text"]
|
||
if low then low:SetText(tostring(minValue)) end
|
||
if high then high:SetText(tostring(maxValue)) end
|
||
|
||
local internalRefresh = false
|
||
local function UpdateLabel(value)
|
||
local display = formatter and formatter(value) or value
|
||
if text then text:SetText(label .. ": " .. tostring(display)) end
|
||
end
|
||
|
||
slider:SetScript("OnValueChanged", function()
|
||
if internalRefresh then return end
|
||
local raw = this:GetValue() or minValue or 0
|
||
local safeStep = step or 1
|
||
local value
|
||
if safeStep >= 1 then
|
||
value = math.floor(raw + 0.5)
|
||
else
|
||
if safeStep == 0 then safeStep = 1 end
|
||
local scaled = raw / safeStep
|
||
value = math.floor(scaled + 0.5) * safeStep
|
||
end
|
||
value = Clamp(value, minValue, maxValue)
|
||
UpdateLabel(value)
|
||
setter(value)
|
||
if onValueChanged then onValueChanged(value) end
|
||
end)
|
||
|
||
slider.Refresh = function()
|
||
local value = tonumber(getter()) or minValue
|
||
value = Clamp(value, minValue, maxValue)
|
||
internalRefresh = true
|
||
slider:SetValue(value)
|
||
internalRefresh = false
|
||
UpdateLabel(value)
|
||
end
|
||
|
||
StyleSlider(slider, low, high, text)
|
||
slider:Refresh()
|
||
return slider
|
||
end
|
||
|
||
local function CreateButton(parent, text, x, y, width, height, onClick)
|
||
local btn = CreateFrame("Button", NextWidgetName("Button"), parent, "UIPanelButtonTemplate")
|
||
btn:SetWidth(width)
|
||
btn:SetHeight(height)
|
||
btn:SetPoint("TOPLEFT", parent, "TOPLEFT", x, y)
|
||
btn:SetText(text)
|
||
btn:SetScript("OnClick", onClick)
|
||
StyleButton(btn)
|
||
return btn
|
||
end
|
||
local function CreateScrollArea(parent, x, y, width, height, childHeight)
|
||
local holder = CreateFrame("Frame", NextWidgetName("ScrollHolder"), parent)
|
||
holder:SetWidth(width)
|
||
holder:SetHeight(height)
|
||
holder:SetPoint("TOPLEFT", parent, "TOPLEFT", x, y)
|
||
|
||
local scroll = CreateFrame("ScrollFrame", NextWidgetName("ScrollFrame"), holder)
|
||
scroll:SetPoint("TOPLEFT", holder, "TOPLEFT", 0, 0)
|
||
scroll:SetPoint("BOTTOMRIGHT", holder, "BOTTOMRIGHT", -18, 0)
|
||
|
||
local child = CreateFrame("Frame", NextWidgetName("ScrollChild"), scroll)
|
||
child:SetWidth(width - 22)
|
||
child:SetHeight(childHeight)
|
||
scroll:SetScrollChild(child)
|
||
|
||
local sliderName = NextWidgetName("ScrollBar")
|
||
local slider = CreateFrame("Slider", sliderName, holder, "UIPanelScrollBarTemplate")
|
||
slider:SetPoint("TOPRIGHT", holder, "TOPRIGHT", 0, -16)
|
||
slider:SetPoint("BOTTOMRIGHT", holder, "BOTTOMRIGHT", 0, 16)
|
||
slider:SetScript("OnValueChanged", function()
|
||
if scroll and scroll.SetVerticalScroll then
|
||
scroll:SetVerticalScroll(this:GetValue())
|
||
end
|
||
end)
|
||
slider:SetMinMaxValues(0, 0)
|
||
slider:SetValueStep(20)
|
||
slider:SetValue(0)
|
||
|
||
local upBtn = _G[sliderName .. "ScrollUpButton"]
|
||
if upBtn then
|
||
upBtn:SetScript("OnClick", function()
|
||
local value = slider:GetValue() - 24
|
||
if value < 0 then value = 0 end
|
||
slider:SetValue(value)
|
||
end)
|
||
end
|
||
|
||
local downBtn = _G[sliderName .. "ScrollDownButton"]
|
||
if downBtn then
|
||
downBtn:SetScript("OnClick", function()
|
||
local _, maxValue = slider:GetMinMaxValues()
|
||
local value = slider:GetValue() + 24
|
||
if value > maxValue then value = maxValue end
|
||
slider:SetValue(value)
|
||
end)
|
||
end
|
||
StyleScrollBar(slider, sliderName)
|
||
|
||
local function UpdateRange()
|
||
local maxScroll = child:GetHeight() - scroll:GetHeight()
|
||
if maxScroll < 0 then maxScroll = 0 end
|
||
|
||
slider:SetMinMaxValues(0, maxScroll)
|
||
slider:SetValueStep(20)
|
||
|
||
local current = slider:GetValue()
|
||
if current > maxScroll then
|
||
current = maxScroll
|
||
slider:SetValue(current)
|
||
end
|
||
if scroll and scroll.SetVerticalScroll then
|
||
scroll:SetVerticalScroll(current)
|
||
end
|
||
|
||
if maxScroll <= 0 then slider:Hide() else slider:Show() end
|
||
end
|
||
|
||
local function ScrollBy(delta)
|
||
local _, maxValue = slider:GetMinMaxValues()
|
||
local value = slider:GetValue() - delta * 28
|
||
if value < 0 then value = 0 end
|
||
if value > maxValue then value = maxValue end
|
||
slider:SetValue(value)
|
||
end
|
||
|
||
if scroll.EnableMouseWheel then
|
||
scroll:EnableMouseWheel(true)
|
||
scroll:SetScript("OnMouseWheel", function() ScrollBy(arg1) end)
|
||
end
|
||
|
||
if child.EnableMouseWheel then
|
||
child:EnableMouseWheel(true)
|
||
child:SetScript("OnMouseWheel", function() ScrollBy(arg1) end)
|
||
end
|
||
|
||
return {
|
||
holder = holder,
|
||
scroll = scroll,
|
||
child = child,
|
||
slider = slider,
|
||
UpdateRange = UpdateRange,
|
||
Reset = function()
|
||
slider:SetValue(0)
|
||
if scroll and scroll.SetVerticalScroll then
|
||
scroll:SetVerticalScroll(0)
|
||
end
|
||
UpdateRange()
|
||
end,
|
||
}
|
||
end
|
||
|
||
function SFrames.ConfigUI:RefreshControls(controlList)
|
||
if not controlList then return end
|
||
for _, control in ipairs(controlList) do
|
||
if control and control.Refresh then control:Refresh() end
|
||
end
|
||
end
|
||
|
||
function SFrames.ConfigUI:BuildUIPage()
|
||
local font = SFrames:GetFont()
|
||
local page = self.uiPage
|
||
local controls = {}
|
||
|
||
local function RefreshPlayer()
|
||
if SFrames.Player and SFrames.Player.ApplyConfig then SFrames.Player:ApplyConfig() return end
|
||
if SFrames.Player and SFrames.Player.UpdateAll then SFrames.Player:UpdateAll() end
|
||
end
|
||
local function RefreshTarget()
|
||
if SFrames.Target and SFrames.Target.ApplyConfig then SFrames.Target:ApplyConfig() return end
|
||
if SFrames.Target and SFrames.Target.UpdateAll then SFrames.Target:UpdateAll() end
|
||
end
|
||
local function RefreshParty()
|
||
if SFrames.Party and SFrames.Party.ApplyConfig then SFrames.Party:ApplyConfig() return end
|
||
if SFrames.Party and SFrames.Party.UpdateAll then SFrames.Party:UpdateAll() end
|
||
end
|
||
|
||
-- Section 内容从 y=-32 开始(标题24 + 分隔线 + 8px 间距)
|
||
-- 每个选项行占 26px,描述文字占 16px
|
||
local uiScroll = CreateScrollArea(page, 4, -4, 548, 458, 990)
|
||
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, 214, font)
|
||
|
||
table.insert(controls, CreateCheckBox(tweaksSection,
|
||
"自动切换姿态/形态", 14, -34,
|
||
function() return SFramesDB.Tweaks.autoStance ~= false end,
|
||
function(checked) SFramesDB.Tweaks.autoStance = checked end
|
||
))
|
||
CreateDesc(tweaksSection, "施法需要特定姿态时自动切换(如人形按熊掌→自动变熊)", 36, -50, font, 218)
|
||
|
||
table.insert(controls, CreateCheckBox(tweaksSection,
|
||
"自动取消变形/下马", 270, -34,
|
||
function() return SFramesDB.Tweaks.autoDismount ~= false end,
|
||
function(checked) SFramesDB.Tweaks.autoDismount = checked end
|
||
))
|
||
CreateDesc(tweaksSection, "变形/骑马时施法自动取消(如熊形按治疗→自动回人形)", 292, -50, font, 218)
|
||
|
||
table.insert(controls, CreateCheckBox(tweaksSection,
|
||
"乌龟服兼容修改", 14, -80,
|
||
function() return SFramesDB.Tweaks.turtleCompat ~= false end,
|
||
function(checked) SFramesDB.Tweaks.turtleCompat = checked end
|
||
))
|
||
CreateDesc(tweaksSection, "隐藏乌龟服自带目标血量文字,修复地图窗口标题偏移", 36, -96, font, 218)
|
||
|
||
table.insert(controls, CreateCheckBox(tweaksSection,
|
||
"SuperWoW 客户端兼容", 270, -80,
|
||
function() return SFramesDB.Tweaks.superWoW ~= false end,
|
||
function(checked) SFramesDB.Tweaks.superWoW = checked end
|
||
))
|
||
CreateDesc(tweaksSection, "为 SuperWoW 提供 GUID 施法数据支持(需安装 SuperWoW)", 292, -96, font, 218)
|
||
|
||
table.insert(controls, CreateCheckBox(tweaksSection,
|
||
"冷却倒计时", 14, -126,
|
||
function() return SFramesDB.Tweaks.cooldownNumbers ~= false end,
|
||
function(checked) SFramesDB.Tweaks.cooldownNumbers = checked end
|
||
))
|
||
CreateDesc(tweaksSection, "在技能/物品冷却图标上显示剩余时间文字(>2秒)", 36, -142, font, 218)
|
||
|
||
table.insert(controls, CreateCheckBox(tweaksSection,
|
||
"暗色界面风格", 270, -126,
|
||
function() return SFramesDB.Tweaks.darkUI == true end,
|
||
function(checked) SFramesDB.Tweaks.darkUI = checked end
|
||
))
|
||
CreateDesc(tweaksSection, "将整个游戏界面调暗为深色主题(默认关闭)", 292, -142, font, 218)
|
||
|
||
CreateLabel(tweaksSection, "提示:以上所有选项修改后需要 /reload 才能生效。", 14, -172, font, 10, 0.6, 0.6, 0.65)
|
||
|
||
uiScroll:UpdateRange()
|
||
self.uiControls = controls
|
||
self.uiScroll = uiScroll
|
||
end
|
||
|
||
|
||
function SFrames.ConfigUI:BuildPlayerPage()
|
||
local font = SFrames:GetFont()
|
||
local page = self.playerPage
|
||
local controls = {}
|
||
|
||
local function RefreshPlayer()
|
||
if SFrames.Player and SFrames.Player.ApplyConfig then
|
||
SFrames.Player:ApplyConfig()
|
||
return
|
||
end
|
||
if SFrames.Player and SFrames.Player.UpdateAll then SFrames.Player:UpdateAll() end
|
||
end
|
||
|
||
local playerSection = CreateSection(page, "玩家框体", 8, -8, 520, 342, 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, 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
|
||
))
|
||
|
||
CreateLabel(playerSection,
|
||
"提示:关闭头像后职业图标移到框体最左侧,框体仅显示血条。",
|
||
14, -299, font, 10, 0.9, 0.9, 0.9)
|
||
|
||
-- ── 宠物框体 ──────────────────────────────────────────────────
|
||
local petSection = CreateSection(page, "宠物框体", 8, -358, 520, 98, font)
|
||
|
||
table.insert(controls, CreateCheckBox(petSection,
|
||
"显示宠物框体", 14, -34,
|
||
function() return SFramesDB.showPetFrame ~= false end,
|
||
function(checked) SFramesDB.showPetFrame = checked end,
|
||
function() if SFrames.Pet and SFrames.Pet.UpdateAll then SFrames.Pet:UpdateAll() end end
|
||
))
|
||
CreateDesc(petSection, "在玩家框体附近显示当前宠物的血量条", 36, -50, font)
|
||
|
||
table.insert(controls, CreateSlider(petSection, "缩放", 270, -52, 200, 0.7, 1.8, 0.05,
|
||
function() return SFramesDB.petFrameScale or 1 end,
|
||
function(value) SFramesDB.petFrameScale = value end,
|
||
function(v) return string.format("%.2f", v) end,
|
||
function(value) if SFrames.Pet and SFrames.Pet.frame then SFrames.Pet.frame:SetScale(value) end end
|
||
))
|
||
|
||
self.playerControls = controls
|
||
end
|
||
|
||
function SFrames.ConfigUI:BuildTargetPage()
|
||
local font = SFrames:GetFont()
|
||
local page = self.targetPage
|
||
local controls = {}
|
||
|
||
local function RefreshTarget()
|
||
if SFrames.Target and SFrames.Target.ApplyConfig then
|
||
SFrames.Target:ApplyConfig()
|
||
return
|
||
end
|
||
if SFrames.Target and SFrames.Target.UpdateAll then SFrames.Target:UpdateAll() end
|
||
end
|
||
|
||
local targetSection = CreateSection(page, "目标框体", 8, -8, 520, 382, 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, 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, CreateCheckBox(targetSection,
|
||
"启用目标距离文本", 326, -266,
|
||
function() return SFramesDB.targetDistanceEnabled ~= false end,
|
||
function(checked) SFramesDB.targetDistanceEnabled = checked end,
|
||
function(checked)
|
||
if SFrames.Target and SFrames.Target.distanceFrame then
|
||
if checked and UnitExists("target") then
|
||
SFrames.Target.distanceFrame:Show()
|
||
elseif not checked then
|
||
SFrames.Target.distanceFrame:Hide()
|
||
end
|
||
end
|
||
end
|
||
))
|
||
|
||
table.insert(controls, CreateSlider(targetSection, "距离文本缩放", 14, -318, 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
|
||
))
|
||
|
||
self.targetControls = controls
|
||
end
|
||
|
||
function SFrames.ConfigUI:BuildPartyPage()
|
||
local font = SFrames:GetFont()
|
||
local page = self.partyPage
|
||
local controls = {}
|
||
|
||
local function RefreshParty()
|
||
if SFrames.Party and SFrames.Party.ApplyConfig then
|
||
SFrames.Party:ApplyConfig()
|
||
end
|
||
if SFrames.Party and SFrames.Party.UpdateAll then SFrames.Party:UpdateAll() end
|
||
end
|
||
|
||
local uiScroll = CreateScrollArea(page, 4, -4, 548, 458, 320)
|
||
local root = uiScroll.child
|
||
|
||
local partySection = CreateSection(root, "小队", 8, -8, 520, 382, font)
|
||
|
||
table.insert(controls, CreateCheckBox(partySection,
|
||
"启用 Nanami 小队框体(需 /reload)", 12, -28,
|
||
function() return SFramesDB.enablePartyFrame ~= false end,
|
||
function(checked) SFramesDB.enablePartyFrame = checked end
|
||
))
|
||
|
||
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, CreateCheckBox(partySection,
|
||
"显示小队增益", 170, -318,
|
||
function() return SFramesDB.partyShowBuffs ~= false end,
|
||
function(checked) SFramesDB.partyShowBuffs = checked end,
|
||
function() RefreshParty() end
|
||
))
|
||
|
||
table.insert(controls, CreateCheckBox(partySection,
|
||
"显示小队减益", 326, -318,
|
||
function() return SFramesDB.partyShowDebuffs ~= false end,
|
||
function(checked) SFramesDB.partyShowDebuffs = checked end,
|
||
function() RefreshParty() end
|
||
))
|
||
|
||
CreateButton(partySection, "小队测试模式", 326, -350, 130, 22, function()
|
||
if SFrames.Party and SFrames.Party.TestMode then SFrames.Party:TestMode() end
|
||
end)
|
||
uiScroll:UpdateRange()
|
||
self.partyControls = controls
|
||
self.partyScroll = uiScroll
|
||
end
|
||
|
||
function SFrames.ConfigUI:BuildBagPage()
|
||
local font = SFrames:GetFont()
|
||
local page = self.bagPage
|
||
local controls = {}
|
||
|
||
local bagScroll = CreateScrollArea(page, 4, -4, 548, 458, 630)
|
||
local root = bagScroll.child
|
||
|
||
local bagSection = CreateSection(root, "背包", 8, -8, 502, 246, 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
|
||
))
|
||
|
||
CreateButton(bagSection, "打开背包", 16, -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, -260, 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()
|
||
local n = 0
|
||
if NanamiSellPriceDB then
|
||
for _ in pairs(NanamiSellPriceDB) do n = n + 1 end
|
||
end
|
||
return n
|
||
end
|
||
|
||
local dbCount = CountDBEntries()
|
||
local statusLabel = CreateLabel(priceSection,
|
||
string.format("内置: %d 条 | 已学习: %d 条", dbCount, CountCacheEntries()),
|
||
14, -30, font, 10, 0.8, 0.8, 0.7)
|
||
|
||
local scanResultLabel = CreateLabel(priceSection, "", 14, -104, font, 10, 0.45, 0.9, 0.45)
|
||
|
||
CreateButton(priceSection, "扫描全部物品售价", 16, -48, 228, 24, function()
|
||
if not SFramesGlobalDB then SFramesGlobalDB = {} end
|
||
if not SFramesGlobalDB.sellPriceCache then SFramesGlobalDB.sellPriceCache = {} end
|
||
local cache = SFramesGlobalDB.sellPriceCache
|
||
local scanned, learned = 0, 0
|
||
|
||
local function TryScan(link)
|
||
if not link then return end
|
||
local _, _, sid = string.find(link, "item:(%d+)")
|
||
if not sid then return end
|
||
local id = tonumber(sid)
|
||
if not id then return end
|
||
scanned = scanned + 1
|
||
if cache[id] and cache[id] > 0 then return end
|
||
|
||
local price
|
||
if NanamiSellPriceDB and NanamiSellPriceDB[id] and NanamiSellPriceDB[id] > 0 then
|
||
price = NanamiSellPriceDB[id]
|
||
elseif ShaguTweaks and ShaguTweaks.SellValueDB and ShaguTweaks.SellValueDB[id] and ShaguTweaks.SellValueDB[id] > 0 then
|
||
price = ShaguTweaks.SellValueDB[id]
|
||
else
|
||
local _,_,_,_,_,_,_,_,_,_,sp = GetItemInfo(link)
|
||
if sp and type(sp) == "number" and sp > 0 then price = sp end
|
||
end
|
||
if price then
|
||
cache[id] = price
|
||
learned = learned + 1
|
||
end
|
||
end
|
||
|
||
for bag = 0, 4 do
|
||
for slot = 1, (GetContainerNumSlots(bag) or 0) do
|
||
TryScan(GetContainerItemLink(bag, slot))
|
||
end
|
||
end
|
||
for bag = 5, 10 do
|
||
pcall(function()
|
||
for slot = 1, (GetContainerNumSlots(bag) or 0) do
|
||
TryScan(GetContainerItemLink(bag, slot))
|
||
end
|
||
end)
|
||
end
|
||
pcall(function()
|
||
for slot = 1, (GetContainerNumSlots(-1) or 0) do
|
||
TryScan(GetContainerItemLink(-1, slot))
|
||
end
|
||
end)
|
||
for slot = 1, 19 do
|
||
TryScan(GetInventoryItemLink("player", slot))
|
||
end
|
||
|
||
statusLabel:SetText(string.format("内置: %d 条 | 已学习: %d 条", dbCount, CountCacheEntries()))
|
||
scanResultLabel:SetText(string.format("完成! 检查 %d 件, 新增 %d 条售价", scanned, learned))
|
||
end)
|
||
|
||
CreateButton(priceSection, "清空学习缓存", 258, -48, 228, 24, function()
|
||
if SFramesGlobalDB then SFramesGlobalDB.sellPriceCache = {} end
|
||
statusLabel:SetText(string.format("内置: %d 条 | 已学习: 0 条", dbCount))
|
||
scanResultLabel:SetText("学习缓存已清空")
|
||
end)
|
||
|
||
CreateDesc(priceSection, "扫描背包/银行/装备中所有物品, 缓存售价到离线数据库 (随存档保存)", 14, -76, font, 480)
|
||
CreateDesc(priceSection, "多次游玩后数据自动丰富, 其他角色也能共享离线数据", 14, -90, font, 480)
|
||
|
||
bagScroll:UpdateRange()
|
||
self.bagScroll = bagScroll
|
||
self.bagControls = controls
|
||
end
|
||
|
||
function SFrames.ConfigUI:BuildRaidPage()
|
||
local font = SFrames:GetFont()
|
||
local page = self.raidPage
|
||
local controls = {}
|
||
|
||
local function RefreshRaid()
|
||
if SFrames.Raid and SFrames.Raid.ApplyLayout then
|
||
if not SFrames.Raid._framesBuilt then return end
|
||
SFrames.Raid:ApplyLayout()
|
||
SFrames.Raid:UpdateAll()
|
||
for i = 1, 40 do
|
||
local f = SFrames.Raid.frames[i] and SFrames.Raid.frames[i].frame
|
||
if f then SFrames.Raid:ApplyFrameStyle(f, SFrames.Raid:GetMetrics()) end
|
||
end
|
||
end
|
||
end
|
||
|
||
local uiScroll = CreateScrollArea(page, 4, -4, 548, 458, 600)
|
||
local root = uiScroll.child
|
||
|
||
local raidSection = CreateSection(root, "团队框架设置", 8, -8, 520, 570, font)
|
||
|
||
table.insert(controls, CreateCheckBox(raidSection,
|
||
"启用 Nanami 团队框架(默认启用,需 /reload)", 12, -28,
|
||
function() return SFramesDB.enableRaidFrames ~= false end,
|
||
function(checked) SFramesDB.enableRaidFrames = checked end
|
||
))
|
||
|
||
CreateButton(raidSection, "解锁团队框架", 14, -56, 130, 22, function()
|
||
if SFrames and SFrames.UnlockFrames then SFrames:UnlockFrames() end
|
||
end)
|
||
|
||
CreateButton(raidSection, "锁定团队框架", 154, -56, 130, 22, function()
|
||
if SFrames and SFrames.LockFrames then SFrames:LockFrames() end
|
||
end)
|
||
|
||
table.insert(controls, CreateCheckBox(raidSection,
|
||
"横向小队排列(关闭为竖向叠放)", 12, -88,
|
||
function() return SFramesDB.raidLayout == "horizontal" end,
|
||
function(checked)
|
||
SFramesDB.raidLayout = checked and "horizontal" or "vertical"
|
||
end,
|
||
function() RefreshRaid() end
|
||
))
|
||
|
||
table.insert(controls, CreateSlider(raidSection, "缩放", 14, -150, 150, 0.5, 2.0, 0.05,
|
||
function() return SFramesDB.raidFrameScale end,
|
||
function(value) SFramesDB.raidFrameScale = value end,
|
||
function(v) return string.format("%.2f", v) end,
|
||
function(val)
|
||
if SFrames.Raid and SFrames.Raid.parent then
|
||
SFrames.Raid.parent:SetScale(val)
|
||
end
|
||
end
|
||
))
|
||
|
||
table.insert(controls, CreateSlider(raidSection, "框体宽度", 170, -150, 150, 30, 150, 1,
|
||
function() return SFramesDB.raidFrameWidth end,
|
||
function(value) SFramesDB.raidFrameWidth = value end,
|
||
function(v) return tostring(math.floor(v + 0.5)) end,
|
||
function() RefreshRaid() end
|
||
))
|
||
|
||
table.insert(controls, CreateSlider(raidSection, "框体高度", 326, -150, 130, 20, 80, 1,
|
||
function() return SFramesDB.raidFrameHeight end,
|
||
function(value) SFramesDB.raidFrameHeight = value end,
|
||
function(v) return tostring(math.floor(v + 0.5)) end,
|
||
function() RefreshRaid() end
|
||
))
|
||
|
||
table.insert(controls, CreateSlider(raidSection, "生命条高度", 14, -212, 150, 10, 78, 1,
|
||
function() return SFramesDB.raidHealthHeight end,
|
||
function(value) SFramesDB.raidHealthHeight = value end,
|
||
function(v) return tostring(math.floor(v + 0.5)) end,
|
||
function() RefreshRaid() end
|
||
))
|
||
|
||
table.insert(controls, CreateCheckBox(raidSection,
|
||
"显示能量条", 170, -210,
|
||
function() return SFramesDB.raidShowPower ~= false end,
|
||
function(checked) SFramesDB.raidShowPower = checked end,
|
||
function() RefreshRaid() end
|
||
))
|
||
|
||
table.insert(controls, CreateCheckBox(raidSection,
|
||
"显示小队标题(横向=顶部,竖向=左侧)", 14, -244,
|
||
function() return SFramesDB.raidShowGroupLabel ~= false end,
|
||
function(checked) SFramesDB.raidShowGroupLabel = checked end,
|
||
function() RefreshRaid() end
|
||
))
|
||
|
||
table.insert(controls, CreateSlider(raidSection, "横向间距", 14, -274, 150, 0, 40, 1,
|
||
function() return SFramesDB.raidHorizontalGap end,
|
||
function(value) SFramesDB.raidHorizontalGap = value end,
|
||
function(v) return tostring(math.floor(v + 0.5)) end,
|
||
function() RefreshRaid() end
|
||
))
|
||
|
||
table.insert(controls, CreateSlider(raidSection, "纵向间距", 170, -274, 150, 0, 40, 1,
|
||
function() return SFramesDB.raidVerticalGap end,
|
||
function(value) SFramesDB.raidVerticalGap = value end,
|
||
function(v) return tostring(math.floor(v + 0.5)) end,
|
||
function() RefreshRaid() end
|
||
))
|
||
|
||
table.insert(controls, CreateSlider(raidSection, "小队间隔", 326, -274, 130, 0, 60, 1,
|
||
function() return SFramesDB.raidGroupGap end,
|
||
function(value) SFramesDB.raidGroupGap = value end,
|
||
function(v) return tostring(math.floor(v + 0.5)) end,
|
||
function() RefreshRaid() end
|
||
))
|
||
|
||
table.insert(controls, CreateSlider(raidSection, "姓名字号", 14, -336, 150, 8, 18, 1,
|
||
function() return SFramesDB.raidNameFontSize end,
|
||
function(value) SFramesDB.raidNameFontSize = value end,
|
||
function(v) return tostring(math.floor(v + 0.5)) end,
|
||
function() RefreshRaid() end
|
||
))
|
||
|
||
table.insert(controls, CreateSlider(raidSection, "数值字号", 170, -336, 150, 8, 18, 1,
|
||
function() return SFramesDB.raidValueFontSize end,
|
||
function(value) SFramesDB.raidValueFontSize = value end,
|
||
function(v) return tostring(math.floor(v + 0.5)) end,
|
||
function() RefreshRaid() end
|
||
))
|
||
|
||
table.insert(controls, CreateCheckBox(raidSection,
|
||
"血量百分比模式 (关=紧凑数值/-缺口)", 14, -390,
|
||
function() return SFramesDB.raidHealthFormat == "percent" end,
|
||
function(checked) SFramesDB.raidHealthFormat = checked and "percent" or "compact" end,
|
||
function() RefreshRaid() end
|
||
))
|
||
|
||
CreateButton(raidSection, "测试模式(假团队)", 14, -450, 150, 24, function()
|
||
if SFrames.Raid then
|
||
if SFrames.Raid.testing then
|
||
SFrames.Raid.testing = false
|
||
SFrames.Raid:UpdateAll()
|
||
else
|
||
SFrames.Raid.testing = true
|
||
SFrames.Raid:EnsureFrames()
|
||
SFrames.Raid.parent:Show()
|
||
for i = 1, 40 do
|
||
local f = SFrames.Raid.frames[i].frame
|
||
f:Show()
|
||
f.nameText:SetText("玩家"..i)
|
||
f.healthText:SetText("100%")
|
||
f.health:SetMinMaxValues(0, 100)
|
||
f.health:SetValue(100)
|
||
f.health:SetStatusBarColor(0, 1, 0)
|
||
end
|
||
end
|
||
end
|
||
end)
|
||
|
||
uiScroll:UpdateRange()
|
||
self.raidControls = controls
|
||
self.raidScroll = uiScroll
|
||
end
|
||
|
||
function SFrames.ConfigUI:BuildCharPage()
|
||
local font = SFrames:GetFont()
|
||
local page = self.charPage
|
||
local controls = {}
|
||
|
||
local uiScroll = CreateScrollArea(page, 4, -4, 552, PANEL_HEIGHT - 110, 720)
|
||
local root = uiScroll.child
|
||
|
||
local charSection = CreateSection(root, "人物面板", 8, -8, 524, 430, font)
|
||
|
||
table.insert(controls, CreateCheckBox(charSection,
|
||
"启用 Nanami 人物面板(需 /reload)", 12, -34,
|
||
function() return SFramesDB.charPanelEnable ~= false end,
|
||
function(checked) SFramesDB.charPanelEnable = checked end
|
||
))
|
||
CreateDesc(charSection, "关闭后按 C 键将打开原版人物面板", 36, -50, font)
|
||
|
||
table.insert(controls, CreateSlider(charSection,
|
||
"面板缩放", 14, -72, 240, 0.6, 1.5, 0.05,
|
||
function() return SFramesDB.charPanelScale or 1.0 end,
|
||
function(v) SFramesDB.charPanelScale = v end,
|
||
function(v) return string.format("%.0f%%", v * 100) end,
|
||
function()
|
||
if SFramesCharacterPanel then
|
||
SFramesCharacterPanel:SetScale(SFramesDB.charPanelScale or 1.0)
|
||
end
|
||
end
|
||
))
|
||
|
||
table.insert(controls, CreateCheckBox(charSection,
|
||
"显示PVP称号", 12, -118,
|
||
function() return SFramesDB.charShowTitle ~= false end,
|
||
function(checked) SFramesDB.charShowTitle = checked end,
|
||
function()
|
||
if SFrames.CharacterPanel and SFrames.CharacterPanel.UpdateCurrentTab then
|
||
SFrames.CharacterPanel:UpdateCurrentTab()
|
||
end
|
||
end
|
||
))
|
||
|
||
table.insert(controls, CreateCheckBox(charSection,
|
||
"显示公会名", 12, -146,
|
||
function() return SFramesDB.charShowGuild ~= false end,
|
||
function(checked) SFramesDB.charShowGuild = checked end,
|
||
function()
|
||
if SFrames.CharacterPanel and SFrames.CharacterPanel.UpdateCurrentTab then
|
||
SFrames.CharacterPanel:UpdateCurrentTab()
|
||
end
|
||
end
|
||
))
|
||
|
||
table.insert(controls, CreateCheckBox(charSection,
|
||
"显示装备品质光晕", 12, -174,
|
||
function() return SFramesDB.charShowQualityGlow ~= false end,
|
||
function(checked) SFramesDB.charShowQualityGlow = checked end,
|
||
function()
|
||
if SFrames.CharacterPanel and SFrames.CharacterPanel.UpdateCurrentTab then
|
||
SFrames.CharacterPanel:UpdateCurrentTab()
|
||
end
|
||
end
|
||
))
|
||
|
||
table.insert(controls, CreateCheckBox(charSection,
|
||
"显示3D角色模型", 12, -202,
|
||
function() return SFramesDB.charShowModel ~= false end,
|
||
function(checked) SFramesDB.charShowModel = checked end,
|
||
function()
|
||
if SFrames.CharacterPanel and SFrames.CharacterPanel.UpdateCurrentTab then
|
||
SFrames.CharacterPanel:UpdateCurrentTab()
|
||
end
|
||
end
|
||
))
|
||
|
||
table.insert(controls, CreateCheckBox(charSection,
|
||
"启用 Nanami 观察面板(需 /reload)", 12, -230,
|
||
function() return SFramesDB.enableInspect ~= false end,
|
||
function(checked) SFramesDB.enableInspect = checked end
|
||
))
|
||
CreateDesc(charSection, "关闭后观察他人将使用原版界面", 36, -246, font)
|
||
|
||
CreateLabel(charSection, "提示: 面板使用 C 键打开/关闭,按 ESC 关闭。", 14, -274, font, 10, 0.6, 0.6, 0.65)
|
||
CreateLabel(charSection, "装备页下方可滚动查看全部角色属性。", 14, -292, font, 10, 0.6, 0.6, 0.65)
|
||
|
||
-- Spell Book section
|
||
local sbSection = CreateSection(root, "法术书", 8, -446, 524, 200, font)
|
||
|
||
table.insert(controls, CreateSlider(sbSection,
|
||
"法术书缩放", 14, -34, 240, 0.6, 1.5, 0.05,
|
||
function() return SFramesDB.spellBookScale or 1.0 end,
|
||
function(v) SFramesDB.spellBookScale = v end,
|
||
function(v) return string.format("%.0f%%", v * 100) end,
|
||
function()
|
||
if SFramesSpellBookUI then
|
||
SFramesSpellBookUI:SetScale(SFramesDB.spellBookScale or 1.0)
|
||
end
|
||
end
|
||
))
|
||
|
||
table.insert(controls, CreateCheckBox(sbSection,
|
||
"只显示最高等级法术", 14, -80,
|
||
function() return SFramesDB.spellBookHighestOnly == true end,
|
||
function(checked) SFramesDB.spellBookHighestOnly = checked end
|
||
))
|
||
CreateDesc(sbSection, "隐藏同名法术的低等级版本,只保留最高等级", 36, -96, font)
|
||
|
||
table.insert(controls, CreateCheckBox(sbSection,
|
||
"学习新等级后自动替换动作条", 14, -116,
|
||
function() return SFramesDB.spellBookAutoReplace == true end,
|
||
function(checked) SFramesDB.spellBookAutoReplace = checked end
|
||
))
|
||
CreateDesc(sbSection, "学习高等级法术时,自动替换动作条上的低等级版本", 36, -132, font)
|
||
|
||
CreateLabel(sbSection, "提示: 法术书使用 P 键打开/关闭,支持鼠标滚轮翻页。", 14, -160, font, 10, 0.6, 0.6, 0.65)
|
||
|
||
-- Social section
|
||
local socialSection = CreateSection(root, "社交面板", 8, -660, 524, 120, font)
|
||
|
||
table.insert(controls, CreateSlider(socialSection,
|
||
"社交面板缩放", 14, -34, 240, 0.6, 1.5, 0.05,
|
||
function() return SFramesDB.socialScale or 1.0 end,
|
||
function(v) SFramesDB.socialScale = v end,
|
||
function(v) return string.format("%.0f%%", v * 100) end,
|
||
function()
|
||
if SFramesSocialUI then
|
||
SFramesSocialUI:SetScale(SFramesDB.socialScale or 1.0)
|
||
end
|
||
end
|
||
))
|
||
|
||
CreateLabel(socialSection, "提示: 社交面板使用 O 键打开/关闭。", 14, -80, font, 10, 0.6, 0.6, 0.65)
|
||
|
||
uiScroll:UpdateRange()
|
||
self.charControls = controls
|
||
self.charScroll = uiScroll
|
||
end
|
||
|
||
function SFrames.ConfigUI:BuildActionBarPage()
|
||
local font = SFrames:GetFont()
|
||
local page = self.actionBarPage
|
||
local controls = {}
|
||
|
||
local function RefreshAB()
|
||
if SFrames.ActionBars and SFrames.ActionBars.ApplyConfig then
|
||
SFrames.ActionBars:ApplyConfig()
|
||
end
|
||
end
|
||
|
||
local uiScroll = CreateScrollArea(page, 4, -4, 548, 458, 1040)
|
||
local root = uiScroll.child
|
||
|
||
local abSection = CreateSection(root, "动作条", 8, -8, 520, 840, 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.rightHolder then SFrames.ActionBars.rightHolder: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, 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, CreateCheckBox(abSection,
|
||
"始终显示动作条(空格子也显示背景框)", 12, -412,
|
||
function() return SFramesDB.ActionBars.alwaysShowGrid == true end,
|
||
function(checked) SFramesDB.ActionBars.alwaysShowGrid = checked end,
|
||
function() RefreshAB() end
|
||
))
|
||
|
||
table.insert(controls, CreateCheckBox(abSection,
|
||
"按钮圆角", 12, -440,
|
||
function() return SFramesDB.ActionBars.buttonRounded == true end,
|
||
function(checked) SFramesDB.ActionBars.buttonRounded = checked end,
|
||
function() RefreshAB() end
|
||
))
|
||
|
||
table.insert(controls, CreateCheckBox(abSection,
|
||
"按钮内阴影", 200, -440,
|
||
function() return SFramesDB.ActionBars.buttonInnerShadow == true end,
|
||
function(checked) SFramesDB.ActionBars.buttonInnerShadow = checked end,
|
||
function() RefreshAB() end
|
||
))
|
||
|
||
table.insert(controls, CreateCheckBox(abSection,
|
||
"显示动作条狮鹫(在底部动作条两侧显示装饰狮鹫)", 12, -468,
|
||
function() return SFramesDB.ActionBars.hideGryphon == false end,
|
||
function(checked) SFramesDB.ActionBars.hideGryphon = not checked end,
|
||
function() RefreshAB() end
|
||
))
|
||
|
||
table.insert(controls, CreateCheckBox(abSection,
|
||
"狮鹫置于动作条之上(否则在动作条之下)", 12, -496,
|
||
function() return SFramesDB.ActionBars.gryphonOnTop == true end,
|
||
function(checked) SFramesDB.ActionBars.gryphonOnTop = checked end,
|
||
function() RefreshAB() end
|
||
))
|
||
|
||
table.insert(controls, CreateSlider(abSection, "狮鹫宽度", 14, -550, 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, -550, 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, -612, font, 480)
|
||
|
||
-- 狮鹫样式选择器(带图例预览)
|
||
CreateLabel(abSection, "狮鹫样式:", 14, -664, 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 = -684
|
||
|
||
for idx, style in ipairs(GRYPHON_STYLES_UI) do
|
||
local xOff = styleStartX + (idx - 1) * 125
|
||
|
||
-- 预览框
|
||
local preview = CreateFrame("Frame", nil, abSection)
|
||
preview:SetWidth(64)
|
||
preview:SetHeight(64)
|
||
preview:SetPoint("TOPLEFT", abSection, "TOPLEFT", xOff, styleY)
|
||
|
||
-- 预览背景
|
||
local bg = preview:CreateTexture(nil, "BACKGROUND")
|
||
bg:SetAllPoints()
|
||
bg:SetTexture(0, 0, 0, 0.4)
|
||
|
||
-- 纹理预览
|
||
local tex = preview:CreateTexture(nil, "ARTWORK")
|
||
tex:SetAllPoints()
|
||
tex:SetTexture(style.tex)
|
||
|
||
-- 选中边框
|
||
local border = CreateFrame("Frame", nil, preview)
|
||
border:SetPoint("TOPLEFT", preview, "TOPLEFT", -2, 2)
|
||
border:SetPoint("BOTTOMRIGHT", preview, "BOTTOMRIGHT", 2, -2)
|
||
border:SetBackdrop({
|
||
edgeFile = "Interface\\Buttons\\WHITE8X8",
|
||
edgeSize = 2,
|
||
})
|
||
border:Hide()
|
||
styleBorders[idx] = border
|
||
|
||
-- 样式名标签
|
||
local label = preview:CreateFontString(nil, "OVERLAY")
|
||
label:SetFont(font, 10, "OUTLINE")
|
||
label:SetPoint("TOP", preview, "BOTTOM", 0, -4)
|
||
label:SetText(style.label)
|
||
label:SetTextColor(0.7, 0.65, 0.7)
|
||
|
||
-- 点击选择
|
||
preview:EnableMouse(true)
|
||
preview._sfStyleKey = style.key
|
||
preview._sfIdx = idx
|
||
preview:SetScript("OnMouseUp", function()
|
||
SFramesDB.ActionBars.gryphonStyle = this._sfStyleKey
|
||
for i, bd in ipairs(styleBorders) do
|
||
if i == this._sfIdx then
|
||
bd:Show()
|
||
bd:SetBackdropBorderColor(1, 0.78, 0.2, 1)
|
||
else
|
||
bd:Hide()
|
||
end
|
||
end
|
||
RefreshAB()
|
||
end)
|
||
|
||
-- 悬停高亮
|
||
preview:SetScript("OnEnter", function()
|
||
local bd = styleBorders[this._sfIdx]
|
||
if bd and not bd:IsShown() then
|
||
bd:Show()
|
||
bd:SetBackdropBorderColor(SOFT_THEME.panelBorder[1], SOFT_THEME.panelBorder[2], SOFT_THEME.panelBorder[3], 0.8)
|
||
end
|
||
end)
|
||
preview:SetScript("OnLeave", function()
|
||
local bd = styleBorders[this._sfIdx]
|
||
local current = SFramesDB.ActionBars.gryphonStyle or "dragonflight"
|
||
if bd and this._sfStyleKey ~= current then
|
||
bd:Hide()
|
||
end
|
||
end)
|
||
end
|
||
|
||
-- 初始化选中边框
|
||
local currentStyle = SFramesDB.ActionBars.gryphonStyle or "dragonflight"
|
||
for idx, style in ipairs(GRYPHON_STYLES_UI) do
|
||
if style.key == currentStyle then
|
||
styleBorders[idx]:Show()
|
||
styleBorders[idx]:SetBackdropBorderColor(1, 0.78, 0.2, 1)
|
||
end
|
||
end
|
||
|
||
CreateLabel(abSection, "动作条位置:", 14, -778, font, 11, 0.85, 0.75, 0.80)
|
||
CreateDesc(abSection, "使用 /nui layout 或右键聊天框 Nanami 标题进入布局模式调整动作条位置", 14, -798, font, 480)
|
||
|
||
CreateLabel(abSection,
|
||
"提示:启用/禁用动作条接管需要 /reload 才能生效。",
|
||
14, -818, font, 10, 1, 0.92, 0.38)
|
||
|
||
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, 100, 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 mapexport | /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, 520)
|
||
local root = uiScroll.child
|
||
|
||
local buffSection = CreateSection(root, "Buff / Debuff 栏", 8, -8, 520, 420, font)
|
||
|
||
-- Row 1: enable + show timer
|
||
table.insert(controls, CreateCheckBox(buffSection,
|
||
"启用 Buff / Debuff 栏", 14, -36,
|
||
function() return SFramesDB.MinimapBuffs.enabled ~= false end,
|
||
function(checked) SFramesDB.MinimapBuffs.enabled = checked end
|
||
))
|
||
CreateDesc(buffSection, "替代暴雪默认 Buff 栏显示(需 /reload 生效)", 36, -52, font)
|
||
|
||
table.insert(controls, CreateCheckBox(buffSection,
|
||
"显示时间文本", 270, -36,
|
||
function() return SFramesDB.MinimapBuffs.showTimer ~= false end,
|
||
function(checked)
|
||
SFramesDB.MinimapBuffs.showTimer = checked
|
||
RefreshBuffs()
|
||
end
|
||
))
|
||
CreateDesc(buffSection, "图标下方显示剩余时间,永久 Buff 显示 N/A", 292, -52, font)
|
||
|
||
-- Row 2: show debuffs
|
||
table.insert(controls, CreateCheckBox(buffSection,
|
||
"显示 Debuff", 14, -72,
|
||
function() return SFramesDB.MinimapBuffs.showDebuffs ~= false end,
|
||
function(checked)
|
||
SFramesDB.MinimapBuffs.showDebuffs = checked
|
||
RefreshBuffs()
|
||
end
|
||
))
|
||
CreateDesc(buffSection, "在 Buff 栏下方显示 Debuff(边框按类型着色)", 36, -88, font)
|
||
|
||
-- Row 3: sliders - icon sizes
|
||
table.insert(controls, CreateSlider(buffSection, "Buff 图标大小", 14, -114, 220, 20, 50, 1,
|
||
function() return SFramesDB.MinimapBuffs.iconSize or 30 end,
|
||
function(value) SFramesDB.MinimapBuffs.iconSize = value end,
|
||
function(v) return tostring(math.floor(v + 0.5)) end,
|
||
function() RefreshBuffs() end
|
||
))
|
||
|
||
table.insert(controls, CreateSlider(buffSection, "Debuff 图标大小", 270, -114, 220, 20, 50, 1,
|
||
function() return SFramesDB.MinimapBuffs.debuffIconSize or 30 end,
|
||
function(value) SFramesDB.MinimapBuffs.debuffIconSize = value end,
|
||
function(v) return tostring(math.floor(v + 0.5)) end,
|
||
function() RefreshBuffs() end
|
||
))
|
||
|
||
-- Row 4: per-row + spacing
|
||
table.insert(controls, CreateSlider(buffSection, "每行图标数", 14, -176, 220, 4, 16, 1,
|
||
function() return SFramesDB.MinimapBuffs.iconsPerRow or 8 end,
|
||
function(value) SFramesDB.MinimapBuffs.iconsPerRow = value end,
|
||
function(v) return tostring(math.floor(v + 0.5)) end,
|
||
function() RefreshBuffs() end
|
||
))
|
||
|
||
table.insert(controls, CreateSlider(buffSection, "间距", 270, -176, 220, 0, 8, 1,
|
||
function() return SFramesDB.MinimapBuffs.spacing or 2 end,
|
||
function(value) SFramesDB.MinimapBuffs.spacing = value end,
|
||
function(v) return tostring(math.floor(v + 0.5)) end,
|
||
function() RefreshBuffs() end
|
||
))
|
||
|
||
-- Row 5: grow direction + anchor
|
||
CreateLabel(buffSection, "增长方向:", 14, -240, font, 11, 0.85, 0.75, 0.80)
|
||
CreateButton(buffSection, "向左", 100, -240, 70, 22, function()
|
||
SFramesDB.MinimapBuffs.growDirection = "LEFT"
|
||
RefreshBuffs()
|
||
end)
|
||
CreateButton(buffSection, "向右", 176, -240, 70, 22, function()
|
||
SFramesDB.MinimapBuffs.growDirection = "RIGHT"
|
||
RefreshBuffs()
|
||
end)
|
||
|
||
CreateLabel(buffSection, "锚点位置:", 270, -240, font, 11, 0.85, 0.75, 0.80)
|
||
local posLabels = { TOPRIGHT = "右上", TOPLEFT = "左上", BOTTOMRIGHT = "右下", BOTTOMLEFT = "左下" }
|
||
local posKeys = { "TOPRIGHT", "TOPLEFT", "BOTTOMRIGHT", "BOTTOMLEFT" }
|
||
local posStartX = 356
|
||
for _, key in ipairs(posKeys) do
|
||
CreateButton(buffSection, posLabels[key], posStartX, -240, 40, 22, function()
|
||
SFramesDB.MinimapBuffs.position = key
|
||
RefreshBuffs()
|
||
end)
|
||
posStartX = posStartX + 42
|
||
end
|
||
|
||
-- Row 6: position hint
|
||
CreateDesc(buffSection, "使用 /nui layout 进入布局模式调整 Buff 栏位置", 14, -286, font, 480)
|
||
|
||
-- Row 7: action buttons
|
||
CreateButton(buffSection, "重置默认位置", 14, -310, 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, -310, 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, -344, font, 9, 0.65, 0.58, 0.62)
|
||
CreateLabel(buffSection, "Debuff 边框颜色: |cff3399ff魔法|r |cff9900ff诅咒|r |cff996600疾病|r |cff009900毒药|r |cffcc0000物理|r", 14, -360, font, 9, 0.65, 0.58, 0.62)
|
||
|
||
CreateLabel(buffSection, "提示:启用/禁用 Buff 栏需要 /reload 才能生效。其他调整实时生效。", 14, -384, font, 10, 0.6, 0.6, 0.65)
|
||
|
||
uiScroll:UpdateRange()
|
||
self.buffControls = controls
|
||
self.buffScroll = uiScroll
|
||
end
|
||
|
||
function SFrames.ConfigUI:ShowPage(mode)
|
||
self.activePage = mode
|
||
|
||
self.uiPage:Hide()
|
||
self.playerPage:Hide()
|
||
self.targetPage:Hide()
|
||
self.bagPage:Hide()
|
||
self.raidPage:Hide()
|
||
self.partyPage:Hide()
|
||
self.charPage:Hide()
|
||
self.actionBarPage:Hide()
|
||
self.keybindsPage:Hide()
|
||
self.minimapPage:Hide()
|
||
self.buffPage:Hide()
|
||
self.personalizePage:Hide()
|
||
self.themePage:Hide()
|
||
|
||
local allTabs = { self.uiTab, self.playerTab, self.targetTab, self.partyTab, self.raidTab, self.bagTab, self.charTab, self.actionBarTab, self.keybindsTab, self.minimapTab, self.buffTab, self.personalizeTab, self.themeTab }
|
||
for _, tab in ipairs(allTabs) do
|
||
tab.sfSoftActive = false
|
||
tab:Enable()
|
||
tab:RefreshVisual()
|
||
end
|
||
|
||
self:EnsurePage(mode or "ui")
|
||
|
||
if mode == "player" then
|
||
self.playerPage:Show()
|
||
self.playerTab.sfSoftActive = true
|
||
self.playerTab:Disable()
|
||
self.playerTab:RefreshVisual()
|
||
self.title:SetText("Nanami-UI 设置 - 玩家框体")
|
||
self:RefreshControls(self.playerControls)
|
||
elseif mode == "target" then
|
||
self.targetPage:Show()
|
||
self.targetTab.sfSoftActive = true
|
||
self.targetTab:Disable()
|
||
self.targetTab:RefreshVisual()
|
||
self.title:SetText("Nanami-UI 设置 - 目标框体")
|
||
self:RefreshControls(self.targetControls)
|
||
elseif mode == "bags" then
|
||
self.bagPage:Show()
|
||
self.bagTab.sfSoftActive = true
|
||
self.bagTab:Disable()
|
||
self.bagTab:RefreshVisual()
|
||
self.title:SetText("Nanami-UI 设置 - 背包")
|
||
self:RefreshControls(self.bagControls)
|
||
if self.bagScroll and self.bagScroll.Reset then self.bagScroll:Reset() end
|
||
elseif mode == "raid" then
|
||
self.raidPage:Show()
|
||
self.raidTab.sfSoftActive = true
|
||
self.raidTab:Disable()
|
||
self.raidTab:RefreshVisual()
|
||
self.title:SetText("Nanami-UI 设置 - 团队")
|
||
self:RefreshControls(self.raidControls)
|
||
if self.raidScroll and self.raidScroll.Reset then self.raidScroll:Reset() end
|
||
elseif mode == "party" then
|
||
self.partyPage:Show()
|
||
self.partyTab.sfSoftActive = true
|
||
self.partyTab:Disable()
|
||
self.partyTab:RefreshVisual()
|
||
self.title:SetText("Nanami-UI 设置 - 小队")
|
||
self:RefreshControls(self.partyControls)
|
||
if self.partyScroll and self.partyScroll.Reset then self.partyScroll:Reset() end
|
||
elseif mode == "char" then
|
||
self.charPage:Show()
|
||
self.charTab.sfSoftActive = true
|
||
self.charTab:Disable()
|
||
self.charTab:RefreshVisual()
|
||
self.title:SetText("Nanami-UI 设置 - 人物面板")
|
||
self:RefreshControls(self.charControls)
|
||
if self.charScroll and self.charScroll.Reset then self.charScroll:Reset() end
|
||
elseif mode == "actionbar" then
|
||
self.actionBarPage:Show()
|
||
self.actionBarTab.sfSoftActive = true
|
||
self.actionBarTab:Disable()
|
||
self.actionBarTab:RefreshVisual()
|
||
self.title:SetText("Nanami-UI 设置 - 动作条")
|
||
self:RefreshControls(self.actionBarControls)
|
||
if self.actionBarScroll and self.actionBarScroll.Reset then self.actionBarScroll:Reset() end
|
||
elseif mode == "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
|
||
else
|
||
self.uiPage:Show()
|
||
self.uiTab.sfSoftActive = true
|
||
self.uiTab:Disable()
|
||
self.uiTab:RefreshVisual()
|
||
self.title:SetText("Nanami-UI 设置 - 界面")
|
||
self:RefreshControls(self.uiControls)
|
||
if self.uiScroll and self.uiScroll.Reset then self.uiScroll:Reset() end
|
||
end
|
||
end
|
||
|
||
function SFrames.ConfigUI:EnsureFrame()
|
||
if self.frame then return end
|
||
|
||
local font = SFrames:GetFont()
|
||
|
||
local panel = CreateFrame("Frame", "SFramesConfigPanel", UIParent)
|
||
panel:SetWidth(PANEL_WIDTH)
|
||
panel:SetHeight(PANEL_HEIGHT)
|
||
panel:SetPoint("CENTER", UIParent, "CENTER", 0, 0)
|
||
panel:SetMovable(true)
|
||
panel:EnableMouse(true)
|
||
panel:SetClampedToScreen(true)
|
||
panel:SetToplevel(true)
|
||
panel:SetFrameStrata("TOOLTIP")
|
||
panel:SetFrameLevel(100)
|
||
panel:RegisterForDrag("LeftButton")
|
||
panel:SetScript("OnDragStart", function() this:StartMoving() end)
|
||
panel:SetScript("OnDragStop", function() this:StopMovingOrSizing() end)
|
||
tinsert(UISpecialFrames, "SFramesConfigPanel")
|
||
|
||
-- Apply the rounded "Roll UI" textured backdrop to the main settings panel
|
||
if SFrames and SFrames.CreateRoundBackdrop then
|
||
SFrames:CreateRoundBackdrop(panel)
|
||
panel.sfSoftBackdrop = true
|
||
else
|
||
EnsureSoftBackdrop(panel)
|
||
end
|
||
|
||
if panel.SetBackdropColor then
|
||
panel:SetBackdropColor(SOFT_THEME.panelBg[1], SOFT_THEME.panelBg[2], SOFT_THEME.panelBg[3], SOFT_THEME.panelBg[4])
|
||
end
|
||
if panel.SetBackdropBorderColor then
|
||
panel:SetBackdropBorderColor(SOFT_THEME.panelBorder[1], SOFT_THEME.panelBorder[2], SOFT_THEME.panelBorder[3], SOFT_THEME.panelBorder[4])
|
||
end
|
||
|
||
local titleIco = SFrames:CreateIcon(panel, "logo", 18)
|
||
titleIco:SetDrawLayer("OVERLAY")
|
||
titleIco:SetPoint("TOP", panel, "TOP", -52, -14)
|
||
titleIco:SetVertexColor(SOFT_THEME.title[1], SOFT_THEME.title[2], SOFT_THEME.title[3])
|
||
|
||
local title = panel:CreateFontString(nil, "OVERLAY")
|
||
title:SetFont(font, 14, "OUTLINE")
|
||
title:SetPoint("LEFT", titleIco, "RIGHT", 5, 0)
|
||
title:SetTextColor(SOFT_THEME.title[1], SOFT_THEME.title[2], SOFT_THEME.title[3])
|
||
title:SetText("Nanami-UI 设置")
|
||
|
||
local divider = panel:CreateTexture(nil, "ARTWORK")
|
||
divider:SetWidth(1)
|
||
divider:SetPoint("TOPLEFT", panel, "TOPLEFT", 120, -40)
|
||
divider:SetPoint("BOTTOMLEFT", panel, "BOTTOMLEFT", 120, 10)
|
||
divider:SetTexture(0.44, 0.42, 0.5, 0.9)
|
||
|
||
local closeBtn = CreateFrame("Button", "SFramesConfigCloseBtn", panel)
|
||
closeBtn:SetWidth(20)
|
||
closeBtn:SetHeight(20)
|
||
closeBtn:SetPoint("TOPRIGHT", panel, "TOPRIGHT", -8, -8)
|
||
closeBtn:SetBackdrop({
|
||
bgFile = "Interface\\Tooltips\\UI-Tooltip-Background",
|
||
edgeFile = "Interface\\Tooltips\\UI-Tooltip-Border",
|
||
tile = true, tileSize = 16, edgeSize = 14,
|
||
insets = { left = 3, right = 3, top = 3, bottom = 3 },
|
||
})
|
||
local _cA = SOFT_THEME
|
||
closeBtn:SetBackdropColor(_cA.buttonDownBg[1], _cA.buttonDownBg[2], _cA.buttonDownBg[3], _cA.buttonDownBg[4] or 0.85)
|
||
closeBtn:SetBackdropBorderColor(_cA.btnBorder[1], _cA.btnBorder[2], _cA.btnBorder[3], _cA.btnBorder[4] or 0.7)
|
||
local closeIco = SFrames:CreateIcon(closeBtn, "close", 14)
|
||
closeIco:SetDrawLayer("OVERLAY")
|
||
closeIco:SetPoint("CENTER", closeBtn, "CENTER", 0, 0)
|
||
closeIco:SetVertexColor(1, 0.7, 0.7)
|
||
closeBtn:SetScript("OnClick", function() SFrames.ConfigUI.frame:Hide() end)
|
||
closeBtn:SetScript("OnEnter", function()
|
||
this:SetBackdropColor(_cA.btnHoverBg[1], _cA.btnHoverBg[2], _cA.btnHoverBg[3], _cA.btnHoverBg[4] or 0.95)
|
||
this:SetBackdropBorderColor(_cA.btnHoverBd[1], _cA.btnHoverBd[2], _cA.btnHoverBd[3], _cA.btnHoverBd[4] or 0.9)
|
||
end)
|
||
closeBtn:SetScript("OnLeave", function()
|
||
this:SetBackdropColor(_cA.buttonDownBg[1], _cA.buttonDownBg[2], _cA.buttonDownBg[3], _cA.buttonDownBg[4] or 0.85)
|
||
this:SetBackdropBorderColor(_cA.btnBorder[1], _cA.btnBorder[2], _cA.btnBorder[3], _cA.btnBorder[4] or 0.7)
|
||
end)
|
||
|
||
local tabUI = CreateFrame("Button", "SFramesConfigTabUI", panel, "UIPanelButtonTemplate")
|
||
tabUI:SetWidth(100)
|
||
tabUI:SetHeight(28)
|
||
tabUI:SetPoint("TOPLEFT", panel, "TOPLEFT", 10, -50)
|
||
tabUI:SetText("界面设置")
|
||
tabUI:SetScript("OnClick", function() SFrames.ConfigUI:ShowPage("ui") end)
|
||
StyleButton(tabUI)
|
||
AddBtnIcon(tabUI, "settings", nil, "left")
|
||
|
||
local tabPlayer = CreateFrame("Button", "SFramesConfigTabPlayer", panel, "UIPanelButtonTemplate")
|
||
tabPlayer:SetWidth(100)
|
||
tabPlayer:SetHeight(28)
|
||
tabPlayer:SetPoint("TOP", tabUI, "BOTTOM", 0, -4)
|
||
tabPlayer:SetText("玩家框体")
|
||
tabPlayer:SetScript("OnClick", function() SFrames.ConfigUI:ShowPage("player") end)
|
||
StyleButton(tabPlayer)
|
||
AddBtnIcon(tabPlayer, "character", nil, "left")
|
||
|
||
local tabTarget = CreateFrame("Button", "SFramesConfigTabTarget", panel, "UIPanelButtonTemplate")
|
||
tabTarget:SetWidth(100)
|
||
tabTarget:SetHeight(28)
|
||
tabTarget:SetPoint("TOP", tabPlayer, "BOTTOM", 0, -4)
|
||
tabTarget:SetText("目标框体")
|
||
tabTarget:SetScript("OnClick", function() SFrames.ConfigUI:ShowPage("target") end)
|
||
StyleButton(tabTarget)
|
||
AddBtnIcon(tabTarget, "search", nil, "left")
|
||
|
||
local tabParty = CreateFrame("Button", "SFramesConfigTabParty", panel, "UIPanelButtonTemplate")
|
||
tabParty:SetWidth(100)
|
||
tabParty:SetHeight(28)
|
||
tabParty:SetPoint("TOP", tabTarget, "BOTTOM", 0, -4)
|
||
tabParty:SetText("小队框架")
|
||
tabParty:SetScript("OnClick", function() SFrames.ConfigUI:ShowPage("party") end)
|
||
StyleButton(tabParty)
|
||
AddBtnIcon(tabParty, "friends", nil, "left")
|
||
|
||
local tabRaid = CreateFrame("Button", "SFramesConfigTabRaid", panel, "UIPanelButtonTemplate")
|
||
tabRaid:SetWidth(100)
|
||
tabRaid:SetHeight(28)
|
||
tabRaid:SetPoint("TOP", tabParty, "BOTTOM", 0, -4)
|
||
tabRaid:SetText("团队框架")
|
||
tabRaid:SetScript("OnClick", function() SFrames.ConfigUI:ShowPage("raid") end)
|
||
StyleButton(tabRaid)
|
||
AddBtnIcon(tabRaid, "party", nil, "left")
|
||
|
||
local tabBags = CreateFrame("Button", "SFramesConfigTabBags", panel, "UIPanelButtonTemplate")
|
||
tabBags:SetWidth(100)
|
||
tabBags:SetHeight(28)
|
||
tabBags:SetPoint("TOP", tabRaid, "BOTTOM", 0, -4)
|
||
tabBags:SetText("背包设置")
|
||
tabBags:SetScript("OnClick", function() SFrames.ConfigUI:ShowPage("bags") end)
|
||
StyleButton(tabBags)
|
||
AddBtnIcon(tabBags, "backpack", nil, "left")
|
||
|
||
local tabChar = CreateFrame("Button", "SFramesConfigTabChar", panel, "UIPanelButtonTemplate")
|
||
tabChar:SetWidth(100)
|
||
tabChar:SetHeight(28)
|
||
tabChar:SetPoint("TOP", tabBags, "BOTTOM", 0, -4)
|
||
tabChar:SetText("人物面板")
|
||
tabChar:SetScript("OnClick", function() SFrames.ConfigUI:ShowPage("char") end)
|
||
StyleButton(tabChar)
|
||
AddBtnIcon(tabChar, "charsheet", nil, "left")
|
||
|
||
local tabActionBar = CreateFrame("Button", "SFramesConfigTabActionBar", panel, "UIPanelButtonTemplate")
|
||
tabActionBar:SetWidth(100)
|
||
tabActionBar:SetHeight(28)
|
||
tabActionBar:SetPoint("TOP", tabChar, "BOTTOM", 0, -4)
|
||
tabActionBar:SetText("动作条")
|
||
tabActionBar:SetScript("OnClick", function() SFrames.ConfigUI:ShowPage("actionbar") end)
|
||
StyleButton(tabActionBar)
|
||
AddBtnIcon(tabActionBar, "attack", nil, "left")
|
||
|
||
local 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 content = CreateFrame("Frame", "SFramesConfigContentMain", panel)
|
||
content:SetWidth(PANEL_WIDTH - 140)
|
||
content:SetHeight(PANEL_HEIGHT - 60)
|
||
content:SetPoint("TOPLEFT", panel, "TOPLEFT", 130, -50)
|
||
|
||
local uiPage = CreateFrame("Frame", "SFramesConfigUIPage", content)
|
||
uiPage:SetAllPoints(content)
|
||
|
||
local playerPage = CreateFrame("Frame", "SFramesConfigPlayerPage", content)
|
||
playerPage:SetAllPoints(content)
|
||
|
||
local targetPage = CreateFrame("Frame", "SFramesConfigTargetPage", content)
|
||
targetPage:SetAllPoints(content)
|
||
|
||
local bagPage = CreateFrame("Frame", "SFramesConfigBagPage", content)
|
||
bagPage:SetAllPoints(content)
|
||
|
||
local raidPage = CreateFrame("Frame", "SFramesConfigRaidPage", content)
|
||
raidPage:SetAllPoints(content)
|
||
|
||
local partyPage = CreateFrame("Frame", "SFramesConfigPartyPage", content)
|
||
partyPage:SetAllPoints(content)
|
||
|
||
local charPage = CreateFrame("Frame", "SFramesConfigCharPage", content)
|
||
charPage:SetAllPoints(content)
|
||
|
||
local actionBarPage = CreateFrame("Frame", "SFramesConfigActionBarPage", content)
|
||
actionBarPage:SetAllPoints(content)
|
||
|
||
local 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)
|
||
|
||
self.frame = panel
|
||
self.title = title
|
||
self.uiTab = tabUI
|
||
self.playerTab = tabPlayer
|
||
self.targetTab = tabTarget
|
||
self.bagTab = tabBags
|
||
self.raidTab = tabRaid
|
||
self.partyTab = tabParty
|
||
self.charTab = tabChar
|
||
self.actionBarTab = tabActionBar
|
||
self.keybindsTab = tabKeybinds
|
||
self.minimapTab = tabMinimap
|
||
self.buffTab = tabBuff
|
||
self.personalizeTab = tabPersonalize
|
||
self.themeTab = tabTheme
|
||
self.content = content
|
||
self.uiPage = uiPage
|
||
self.playerPage = playerPage
|
||
self.targetPage = targetPage
|
||
self.bagPage = bagPage
|
||
self.raidPage = raidPage
|
||
self.partyPage = partyPage
|
||
self.charPage = charPage
|
||
self.actionBarPage = actionBarPage
|
||
self.keybindsPage = keybindsPage
|
||
self.minimapPage = minimapPage
|
||
self.buffPage = buffPage
|
||
self.personalizePage = personalizePage
|
||
self.themePage = themePage
|
||
|
||
local btnSaveReload = CreateFrame("Button", "SFramesConfigSaveReloadBtn", panel, "UIPanelButtonTemplate")
|
||
btnSaveReload:SetWidth(100)
|
||
btnSaveReload:SetHeight(26)
|
||
btnSaveReload:SetPoint("BOTTOMRIGHT", panel, "BOTTOMRIGHT", -15, 12)
|
||
btnSaveReload:SetText("保存并重载")
|
||
btnSaveReload:SetScript("OnClick", function() ReloadUI() end)
|
||
StyleButton(btnSaveReload)
|
||
AddBtnIcon(btnSaveReload, "save")
|
||
|
||
local btnConfirm = CreateFrame("Button", "SFramesConfigConfirmBtn", panel, "UIPanelButtonTemplate")
|
||
btnConfirm:SetWidth(80)
|
||
btnConfirm:SetHeight(26)
|
||
btnConfirm:SetPoint("RIGHT", btnSaveReload, "LEFT", -6, 0)
|
||
btnConfirm:SetText("确认")
|
||
btnConfirm:SetScript("OnClick", function() SFrames.ConfigUI.frame:Hide() end)
|
||
StyleButton(btnConfirm)
|
||
AddBtnIcon(btnConfirm, "close")
|
||
|
||
self._pageBuilt = {}
|
||
end
|
||
|
||
function SFrames.ConfigUI:BuildPersonalizePage()
|
||
local font = SFrames:GetFont()
|
||
local page = self.personalizePage
|
||
local controls = {}
|
||
|
||
local personalizeScroll = CreateScrollArea(page, 4, -4, 548, 458, 620)
|
||
local root = personalizeScroll.child
|
||
|
||
-- ── 升级训练师提醒 ──────────────────────────────────────────
|
||
local trainerSection = CreateSection(root, "升级技能提醒", 8, -8, 520, 100, font)
|
||
|
||
table.insert(controls, CreateCheckBox(trainerSection,
|
||
"升级时提醒可学习的新技能", 14, -34,
|
||
function() return SFramesDB.trainerReminder ~= false end,
|
||
function(checked) SFramesDB.trainerReminder = checked end
|
||
))
|
||
CreateDesc(trainerSection, "升级后如果有新的职业技能可学习,将在屏幕和聊天窗口显示技能列表提醒", 36, -50, font, 340)
|
||
|
||
CreateButton(trainerSection, "模拟提醒", 370, -34, 120, 22, function()
|
||
if SFrames.Player and SFrames.Player.ShowTrainerReminder then
|
||
local testLevel = UnitLevel("player")
|
||
if mod(testLevel, 2) ~= 0 then testLevel = testLevel + 1 end
|
||
local _, classEn = UnitClass("player")
|
||
local cachedSkills = SFramesDB and SFramesDB.trainerCache and SFramesDB.trainerCache[classEn]
|
||
local staticSkills = SFrames.ClassSkillData and SFrames.ClassSkillData[classEn]
|
||
local function hasData(lv)
|
||
if cachedSkills then
|
||
if cachedSkills[lv] then return true end
|
||
if lv > 1 and cachedSkills[lv - 1] then return true end
|
||
end
|
||
if staticSkills and staticSkills[lv] then return true end
|
||
return false
|
||
end
|
||
if not hasData(testLevel) then
|
||
while testLevel <= 60 and not hasData(testLevel) do
|
||
testLevel = testLevel + 2
|
||
end
|
||
if testLevel > 60 then testLevel = 60 end
|
||
end
|
||
SFrames.Player:ShowTrainerReminder(testLevel)
|
||
end
|
||
end)
|
||
CreateDesc(trainerSection, "点击按钮预览升级提醒效果", 392, -55, font, 100)
|
||
|
||
-- ── 鼠标提示框 ────────────────────────────────────────────────
|
||
local tooltipSection = CreateSection(root, "鼠标提示框", 8, -118, 520, 160, 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, -100, 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 afkSection = CreateSection(root, "AFK 待机动画", 8, -288, 520, 146, font)
|
||
|
||
table.insert(controls, CreateCheckBox(afkSection,
|
||
"启用 AFK 待机画面", 14, -34,
|
||
function() return SFramesDB.afkEnabled ~= false end,
|
||
function(checked) SFramesDB.afkEnabled = checked end
|
||
))
|
||
CreateDesc(afkSection, "进入暂离状态后显示全屏待机画面(角色跳舞 + 信息面板)", 36, -50, font)
|
||
|
||
table.insert(controls, CreateCheckBox(afkSection,
|
||
"在非休息区也启用", 14, -70,
|
||
function() return SFramesDB.afkOutsideRest == true end,
|
||
function(checked) SFramesDB.afkOutsideRest = checked end
|
||
))
|
||
CreateDesc(afkSection, "默认仅在旅店/主城等休息区触发,勾选后在任何区域都会触发", 36, -86, font)
|
||
|
||
table.insert(controls, CreateSlider(afkSection,
|
||
"AFK 延迟(分钟)", 14, -116, 200, 0, 15, 1,
|
||
function() return SFramesDB.afkDelay or 5 end,
|
||
function(v) SFramesDB.afkDelay = v end,
|
||
function(v) if v == 0 then return "立即" end return v .. " 分钟" end
|
||
))
|
||
CreateDesc(afkSection, "进入暂离后等待多久才显示待机画面", 240, -116, font)
|
||
|
||
CreateButton(afkSection, "预览待机画面", 370, -34, 120, 22, function()
|
||
if SFrames.AFKScreen and SFrames.AFKScreen.Toggle then
|
||
SFrames.AFKScreen._manualTrigger = true
|
||
SFrames.AFKScreen:Show()
|
||
end
|
||
end)
|
||
|
||
personalizeScroll:UpdateRange()
|
||
self.personalizeControls = controls
|
||
self.personalizeScroll = personalizeScroll
|
||
end
|
||
|
||
function SFrames.ConfigUI:BuildThemePage()
|
||
local font = SFrames:GetFont()
|
||
local page = self.themePage
|
||
local controls = {}
|
||
|
||
local themeScroll = CreateScrollArea(page, 4, -4, 548, 458, 680)
|
||
local root = themeScroll.child
|
||
|
||
---------------------------------------------------------------------------
|
||
-- Section 1: Theme color selection (regular + class presets)
|
||
---------------------------------------------------------------------------
|
||
local sec1 = CreateSection(root, "主题色", 8, -8, 520, 340, font)
|
||
|
||
CreateDesc(sec1, "选择主题色后界面全局配色将跟随变化,重载 UI 后完全生效。",
|
||
14, -34, font, 480)
|
||
|
||
local SWATCH_SIZE = 32
|
||
local SWATCH_GAP = 8
|
||
local SWATCH_ROW = SWATCH_SIZE + SWATCH_GAP + 14
|
||
local presets = SFrames.Theme.Presets
|
||
local order = SFrames.Theme.PresetOrder
|
||
local swatches = {}
|
||
|
||
local function CreateSwatchRow(parent, presetList, startY)
|
||
for idx = 1, table.getn(presetList) do
|
||
local key = presetList[idx]
|
||
local preset = presets[key]
|
||
if preset then
|
||
local col = (idx - 1) - math.floor((idx - 1) / 5) * 5
|
||
local row = math.floor((idx - 1) / 5)
|
||
local sx = 14 + col * (SWATCH_SIZE + SWATCH_GAP)
|
||
local sy = startY + row * -SWATCH_ROW
|
||
|
||
local swatch = CreateFrame("Button", NextWidgetName("Swatch"), parent)
|
||
swatch:SetWidth(SWATCH_SIZE)
|
||
swatch:SetHeight(SWATCH_SIZE)
|
||
swatch:SetPoint("TOPLEFT", parent, "TOPLEFT", sx, sy)
|
||
|
||
swatch:SetBackdrop({
|
||
bgFile = "Interface\\Buttons\\WHITE8X8",
|
||
edgeFile = "Interface\\Buttons\\WHITE8X8",
|
||
tile = false, edgeSize = 2,
|
||
insets = { left = 2, right = 2, top = 2, bottom = 2 },
|
||
})
|
||
|
||
local r, g, b
|
||
if preset.swatchRGB then
|
||
r, g, b = preset.swatchRGB[1], preset.swatchRGB[2], preset.swatchRGB[3]
|
||
else
|
||
r, g, b = SFrames.Theme.HSVtoRGB(preset.hue, 0.50 * (preset.satMul or 1), 0.75)
|
||
end
|
||
swatch:SetBackdropColor(r, g, b, 1)
|
||
swatch:SetBackdropBorderColor(0.25, 0.25, 0.25, 1)
|
||
|
||
local nameFs = swatch:CreateFontString(nil, "OVERLAY")
|
||
nameFs:SetFont(font, 8, "OUTLINE")
|
||
nameFs:SetPoint("TOP", swatch, "BOTTOM", 0, -1)
|
||
nameFs:SetText(preset.name)
|
||
nameFs:SetTextColor(0.75, 0.75, 0.75)
|
||
|
||
swatch.presetKey = key
|
||
swatch.selectFrame = CreateFrame("Frame", nil, swatch)
|
||
swatch.selectFrame:SetPoint("TOPLEFT", swatch, "TOPLEFT", -4, 4)
|
||
swatch.selectFrame:SetPoint("BOTTOMRIGHT", swatch, "BOTTOMRIGHT", 4, -4)
|
||
swatch.selectFrame:SetBackdrop({
|
||
edgeFile = "Interface\\Buttons\\WHITE8X8",
|
||
edgeSize = 2,
|
||
})
|
||
swatch.selectFrame:SetBackdropBorderColor(1, 0.85, 0, 1)
|
||
swatch.selectFrame:Hide()
|
||
|
||
swatch.selectGlow = swatch.selectFrame:CreateTexture(nil, "BACKGROUND")
|
||
swatch.selectGlow:SetTexture("Interface\\Buttons\\WHITE8X8")
|
||
swatch.selectGlow:SetAllPoints(swatch.selectFrame)
|
||
swatch.selectGlow:SetVertexColor(1, 0.85, 0, 0.15)
|
||
|
||
swatch:SetScript("OnClick", function()
|
||
EnsureDB()
|
||
SFramesDB.Theme.preset = this.presetKey
|
||
SFramesDB.Theme.useClassTheme = false
|
||
SFrames.Theme:Apply(this.presetKey)
|
||
SFrames.ConfigUI:RefreshThemeSwatches()
|
||
SFrames.ConfigUI:RefreshThemePreview()
|
||
PlaySound("igMainMenuOptionCheckBoxOn")
|
||
end)
|
||
swatch:SetScript("OnEnter", function()
|
||
this:SetBackdropBorderColor(0.8, 0.8, 0.8, 1)
|
||
end)
|
||
swatch:SetScript("OnLeave", function()
|
||
local active = SFramesDB and SFramesDB.Theme and SFramesDB.Theme.preset
|
||
local useClass = SFramesDB and SFramesDB.Theme and SFramesDB.Theme.useClassTheme
|
||
if useClass then active = SFrames.Theme:GetCurrentPreset() end
|
||
if this.presetKey == active then
|
||
this:SetBackdropBorderColor(1, 0.85, 0, 1)
|
||
else
|
||
this:SetBackdropBorderColor(0.25, 0.25, 0.25, 1)
|
||
end
|
||
end)
|
||
|
||
table.insert(swatches, swatch)
|
||
end
|
||
end
|
||
end
|
||
|
||
CreateSwatchRow(sec1, order, -52)
|
||
|
||
local classLabel = sec1:CreateFontString(nil, "OVERLAY")
|
||
classLabel:SetFont(font, 10, "OUTLINE")
|
||
classLabel:SetPoint("TOPLEFT", sec1, "TOPLEFT", 14, -164)
|
||
classLabel:SetText("职业色")
|
||
classLabel:SetTextColor(0.85, 0.85, 0.85)
|
||
|
||
local divLine = sec1:CreateTexture(nil, "ARTWORK")
|
||
divLine:SetTexture("Interface\\Buttons\\WHITE8X8")
|
||
divLine:SetHeight(1)
|
||
divLine:SetPoint("TOPLEFT", classLabel, "BOTTOMLEFT", 0, -3)
|
||
divLine:SetPoint("RIGHT", sec1, "RIGHT", -14, 0)
|
||
divLine:SetVertexColor(0.35, 0.35, 0.40, 0.6)
|
||
|
||
CreateSwatchRow(sec1, SFrames.Theme.ClassPresetOrder, -182)
|
||
|
||
local classCheck = CreateCheckBox(sec1,
|
||
"跟随当前职业自动切换", 14, -296,
|
||
function() return SFramesDB and SFramesDB.Theme and SFramesDB.Theme.useClassTheme end,
|
||
function(checked)
|
||
EnsureDB()
|
||
SFramesDB.Theme.useClassTheme = checked
|
||
if checked then
|
||
SFrames.Theme:Apply(SFrames.Theme:GetCurrentPreset())
|
||
else
|
||
SFrames.Theme:Apply(SFramesDB.Theme.preset or "pink")
|
||
end
|
||
SFrames.ConfigUI:RefreshThemeSwatches()
|
||
SFrames.ConfigUI:RefreshThemePreview()
|
||
end
|
||
)
|
||
table.insert(controls, classCheck)
|
||
|
||
self._themeSwatches = swatches
|
||
|
||
---------------------------------------------------------------------------
|
||
-- Section 3: Icon set picker
|
||
---------------------------------------------------------------------------
|
||
local sec3 = CreateSection(root, "图标风格", 8, -356, 520, 110, font)
|
||
|
||
CreateDesc(sec3, "选择图标风格后需重载 UI 以完全生效。",
|
||
14, -34, font, 480)
|
||
|
||
local ICON_SETS = { "icon", "icon2", "icon3", "icon4", "icon5", "icon6", "icon7", "icon8" }
|
||
local ICON_SET_SIZE = 48
|
||
local ICON_SET_GAP = 10
|
||
local faction = UnitFactionGroup and UnitFactionGroup("player") or "Alliance"
|
||
local factionKey = (faction == "Horde") and "horde" or "alliance"
|
||
local factionCoords = SFrames.ICON_TCOORDS and SFrames.ICON_TCOORDS[factionKey]
|
||
if not factionCoords then
|
||
factionCoords = (factionKey == "horde")
|
||
and { 0.75, 0.875, 0, 0.125 }
|
||
or { 0.625, 0.75, 0, 0.125 }
|
||
end
|
||
|
||
local iconSetBtns = {}
|
||
for idx = 1, table.getn(ICON_SETS) do
|
||
local setKey = ICON_SETS[idx]
|
||
local texPath = "Interface\\AddOns\\Nanami-UI\\img\\" .. setKey
|
||
local sx = 14 + (idx - 1) * (ICON_SET_SIZE + ICON_SET_GAP)
|
||
|
||
local btn = CreateFrame("Button", NextWidgetName("IcoSet"), sec3)
|
||
btn:SetWidth(ICON_SET_SIZE)
|
||
btn:SetHeight(ICON_SET_SIZE)
|
||
btn:SetPoint("TOPLEFT", sec3, "TOPLEFT", sx, -50)
|
||
|
||
btn:SetBackdrop({
|
||
bgFile = "Interface\\Tooltips\\UI-Tooltip-Background",
|
||
edgeFile = "Interface\\Tooltips\\UI-Tooltip-Border",
|
||
tile = true, tileSize = 16, edgeSize = 10,
|
||
insets = { left = 2, right = 2, top = 2, bottom = 2 },
|
||
})
|
||
btn:SetBackdropColor(0.08, 0.08, 0.1, 0.85)
|
||
btn:SetBackdropBorderColor(0.3, 0.3, 0.35, 1)
|
||
|
||
local preview = btn:CreateTexture(nil, "ARTWORK")
|
||
preview:SetTexture(texPath)
|
||
preview:SetTexCoord(factionCoords[1], factionCoords[2], factionCoords[3], factionCoords[4])
|
||
preview:SetPoint("CENTER", btn, "CENTER", 0, 0)
|
||
preview:SetWidth(ICON_SET_SIZE - 8)
|
||
preview:SetHeight(ICON_SET_SIZE - 8)
|
||
|
||
btn.setKey = setKey
|
||
btn.selectBorder = CreateFrame("Frame", nil, btn)
|
||
btn.selectBorder:SetPoint("TOPLEFT", btn, "TOPLEFT", -2, 2)
|
||
btn.selectBorder:SetPoint("BOTTOMRIGHT", btn, "BOTTOMRIGHT", 2, -2)
|
||
btn.selectBorder:SetBackdrop({ edgeFile = "Interface\\Buttons\\WHITE8X8", edgeSize = 2 })
|
||
btn.selectBorder:SetBackdropBorderColor(1, 1, 1, 1)
|
||
btn.selectBorder:Hide()
|
||
|
||
btn:SetScript("OnClick", function()
|
||
EnsureDB()
|
||
SFramesDB.Theme.iconSet = this.setKey
|
||
SFrames.ConfigUI:RefreshIconSetButtons()
|
||
if SFrames.MinimapButton and SFrames.MinimapButton.Refresh then
|
||
SFrames.MinimapButton:Refresh()
|
||
end
|
||
PlaySound("igMainMenuOptionCheckBoxOn")
|
||
end)
|
||
btn:SetScript("OnEnter", function()
|
||
this:SetBackdropBorderColor(0.7, 0.7, 0.7, 1)
|
||
GameTooltip:SetOwner(this, "ANCHOR_NONE")
|
||
GameTooltip:ClearAllPoints()
|
||
GameTooltip:SetPoint("BOTTOM", this, "TOP", 0, 6)
|
||
local num = this.setKey == "icon" and "1" or string.sub(this.setKey, 5)
|
||
GameTooltip:SetText("图标风格 " .. num .. (this.setKey == "icon" and " (默认)" or ""))
|
||
GameTooltip:AddLine("点击选择,重载 UI 后生效", 0.7, 0.7, 0.7)
|
||
GameTooltip:Show()
|
||
GameTooltip:SetFrameStrata("TOOLTIP")
|
||
GameTooltip:Raise()
|
||
end)
|
||
btn:SetScript("OnLeave", function()
|
||
local cur = SFramesDB and SFramesDB.Theme and SFramesDB.Theme.iconSet or "icon"
|
||
if this.setKey == cur then
|
||
this:SetBackdropBorderColor(1, 0.85, 0.6, 1)
|
||
else
|
||
this:SetBackdropBorderColor(0.3, 0.3, 0.35, 1)
|
||
end
|
||
GameTooltip:Hide()
|
||
end)
|
||
|
||
table.insert(iconSetBtns, btn)
|
||
end
|
||
self._iconSetBtns = iconSetBtns
|
||
self:RefreshIconSetButtons()
|
||
|
||
---------------------------------------------------------------------------
|
||
-- Section 4: Preview
|
||
---------------------------------------------------------------------------
|
||
local sec4 = CreateSection(root, "主题预览", 8, -478, 520, 180, font)
|
||
|
||
local previewPanel = CreateFrame("Frame", NextWidgetName("Preview"), sec4)
|
||
previewPanel:SetWidth(490)
|
||
previewPanel:SetHeight(50)
|
||
previewPanel:SetPoint("TOPLEFT", sec4, "TOPLEFT", 14, -34)
|
||
EnsureSoftBackdrop(previewPanel)
|
||
|
||
local previewTitle = previewPanel:CreateFontString(nil, "OVERLAY")
|
||
previewTitle:SetFont(font, 12, "OUTLINE")
|
||
previewTitle:SetPoint("TOPLEFT", previewPanel, "TOPLEFT", 10, -8)
|
||
previewTitle:SetText("面板标题示例")
|
||
|
||
local previewText = previewPanel:CreateFontString(nil, "OVERLAY")
|
||
previewText:SetFont(font, 10, "OUTLINE")
|
||
previewText:SetPoint("TOPLEFT", previewPanel, "TOPLEFT", 10, -26)
|
||
previewText:SetText("正文文本 / Body text")
|
||
|
||
local previewDim = previewPanel:CreateFontString(nil, "OVERLAY")
|
||
previewDim:SetFont(font, 9, "OUTLINE")
|
||
previewDim:SetPoint("TOPLEFT", previewPanel, "TOPLEFT", 10, -40)
|
||
previewDim:SetText("辅助文字 / Dim text")
|
||
|
||
local previewBtn = CreateFrame("Button", NextWidgetName("PreviewBtn"), sec4, "UIPanelButtonTemplate")
|
||
previewBtn:SetWidth(90)
|
||
previewBtn:SetHeight(24)
|
||
previewBtn:SetPoint("TOPLEFT", sec4, "TOPLEFT", 14, -92)
|
||
previewBtn:SetText("按钮示例")
|
||
StyleButton(previewBtn)
|
||
|
||
local previewAccent = sec4:CreateTexture(nil, "ARTWORK")
|
||
previewAccent:SetTexture("Interface\\Buttons\\WHITE8X8")
|
||
previewAccent:SetWidth(490)
|
||
previewAccent:SetHeight(4)
|
||
previewAccent:SetPoint("TOPLEFT", sec4, "TOPLEFT", 14, -122)
|
||
|
||
local previewSlot = CreateFrame("Frame", NextWidgetName("PreviewSlot"), sec4)
|
||
previewSlot:SetWidth(80)
|
||
previewSlot:SetHeight(24)
|
||
previewSlot:SetPoint("TOPLEFT", sec4, "TOPLEFT", 14, -134)
|
||
previewSlot:SetBackdrop({
|
||
bgFile = "Interface\\Buttons\\WHITE8X8",
|
||
edgeFile = "Interface\\Buttons\\WHITE8X8",
|
||
tile = false, edgeSize = 1,
|
||
insets = { left = 1, right = 1, top = 1, bottom = 1 },
|
||
})
|
||
|
||
local previewSlotLabel = previewSlot:CreateFontString(nil, "OVERLAY")
|
||
previewSlotLabel:SetFont(font, 9, "OUTLINE")
|
||
previewSlotLabel:SetPoint("CENTER", previewSlot, "CENTER", 0, 0)
|
||
previewSlotLabel:SetText("色块")
|
||
|
||
self._themePreview = {
|
||
panel = previewPanel,
|
||
title = previewTitle,
|
||
text = previewText,
|
||
dim = previewDim,
|
||
btn = previewBtn,
|
||
accent = previewAccent,
|
||
slot = previewSlot,
|
||
slotLabel = previewSlotLabel,
|
||
}
|
||
|
||
---------------------------------------------------------------------------
|
||
-- Hint
|
||
---------------------------------------------------------------------------
|
||
CreateDesc(root, "提示: 更换主题后建议点击右下方 [保存并重载] 以确保所有界面生效。",
|
||
14, -650, font, 500)
|
||
|
||
---------------------------------------------------------------------------
|
||
-- Finalize
|
||
---------------------------------------------------------------------------
|
||
themeScroll:UpdateRange()
|
||
self.themeControls = controls
|
||
self.themeScroll = themeScroll
|
||
|
||
self:RefreshThemeSwatches()
|
||
self:RefreshThemePreview()
|
||
end
|
||
|
||
function SFrames.ConfigUI:RefreshThemeSwatches()
|
||
if not self._themeSwatches then return end
|
||
local active = SFramesDB and SFramesDB.Theme and SFramesDB.Theme.preset or "pink"
|
||
local useClass = SFramesDB and SFramesDB.Theme and SFramesDB.Theme.useClassTheme
|
||
if useClass then
|
||
active = SFrames.Theme:GetCurrentPreset()
|
||
end
|
||
for _, sw in ipairs(self._themeSwatches) do
|
||
if sw.presetKey == active then
|
||
sw:SetBackdropBorderColor(1, 0.85, 0, 1)
|
||
if sw.selectFrame then sw.selectFrame:Show() end
|
||
else
|
||
sw:SetBackdropBorderColor(0.25, 0.25, 0.25, 1)
|
||
if sw.selectFrame then sw.selectFrame:Hide() end
|
||
end
|
||
end
|
||
end
|
||
|
||
function SFrames.ConfigUI:RefreshThemePreview()
|
||
local p = self._themePreview
|
||
if not p then return end
|
||
local A = SFrames.ActiveTheme
|
||
|
||
if p.panel and p.panel.SetBackdropColor then
|
||
p.panel:SetBackdropColor(A.panelBg[1], A.panelBg[2], A.panelBg[3], A.panelBg[4] or 0.95)
|
||
p.panel:SetBackdropBorderColor(A.panelBorder[1], A.panelBorder[2], A.panelBorder[3], A.panelBorder[4] or 0.9)
|
||
end
|
||
if p.title then p.title:SetTextColor(A.title[1], A.title[2], A.title[3]) end
|
||
if p.text then p.text:SetTextColor(A.text[1], A.text[2], A.text[3]) end
|
||
if p.dim then p.dim:SetTextColor(A.dimText[1], A.dimText[2], A.dimText[3]) end
|
||
if p.accent then p.accent:SetVertexColor(A.accent[1], A.accent[2], A.accent[3], A.accent[4] or 1) end
|
||
if p.slot and p.slot.SetBackdropColor then
|
||
p.slot:SetBackdropColor(A.slotBg[1], A.slotBg[2], A.slotBg[3], A.slotBg[4] or 0.9)
|
||
p.slot:SetBackdropBorderColor(A.slotSelected[1], A.slotSelected[2], A.slotSelected[3], A.slotSelected[4] or 1)
|
||
end
|
||
if p.slotLabel then p.slotLabel:SetTextColor(A.nameText[1], A.nameText[2], A.nameText[3]) end
|
||
end
|
||
|
||
function SFrames.ConfigUI:RefreshIconSetButtons()
|
||
if not self._iconSetBtns then return end
|
||
local cur = SFramesDB and SFramesDB.Theme and SFramesDB.Theme.iconSet or "icon"
|
||
for _, btn in ipairs(self._iconSetBtns) do
|
||
if btn.setKey == cur then
|
||
btn:SetBackdropBorderColor(1, 0.85, 0.6, 1)
|
||
if btn.selectBorder then btn.selectBorder:Show() end
|
||
else
|
||
btn:SetBackdropBorderColor(0.3, 0.3, 0.35, 1)
|
||
if btn.selectBorder then btn.selectBorder:Hide() end
|
||
end
|
||
end
|
||
end
|
||
|
||
function SFrames.ConfigUI:EnsurePage(mode)
|
||
if self._pageBuilt[mode] then return end
|
||
self._pageBuilt[mode] = true
|
||
if mode == "ui" then self:BuildUIPage()
|
||
elseif mode == "player" then self:BuildPlayerPage()
|
||
elseif mode == "target" then self:BuildTargetPage()
|
||
elseif mode == "party" then self:BuildPartyPage()
|
||
elseif mode == "bags" then self:BuildBagPage()
|
||
elseif mode == "raid" then self:BuildRaidPage()
|
||
elseif mode == "char" then self:BuildCharPage()
|
||
elseif mode == "actionbar" then self:BuildActionBarPage()
|
||
elseif mode == "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()
|
||
end
|
||
end
|
||
|
||
function SFrames.ConfigUI:Build(mode)
|
||
EnsureDB()
|
||
self:EnsureFrame()
|
||
|
||
local page = string.lower(mode or "ui")
|
||
local validPages = { ui = true, player = true, target = true, bags = true, char = true, party = true, raid = true, actionbar = true, keybinds = true, minimap = true, buff = true, personalize = true, theme = true }
|
||
if not validPages[page] then page = "ui" end
|
||
|
||
if self.frame:IsShown() and self.activePage == page then
|
||
self.frame:Hide()
|
||
return
|
||
end
|
||
|
||
self:ShowPage(page)
|
||
self.frame:Show()
|
||
self.frame:Raise()
|
||
end
|
||
|
||
function SFrames.ConfigUI:OpenUI()
|
||
self:Build("ui")
|
||
end
|
||
|
||
function SFrames.ConfigUI:OpenBags()
|
||
self:Build("bags")
|
||
end
|